Servlet Web 應用程式
如果你想構建基於 servlet 的 Web 應用程式,你可以利用 Spring Boot 對 Spring MVC 或 Jersey 的自動配置。
“Spring Web MVC 框架”
Spring Web MVC 框架(通常稱為 “Spring MVC”)是一個功能豐富的“模型-檢視-控制器” Web 框架。Spring MVC 允許你建立特殊的 @Controller
或 @RestController
bean 來處理傳入的 HTTP 請求。控制器中的方法透過使用 @RequestMapping
註解對映到 HTTP。
以下程式碼展示了一個典型的用於提供 JSON 資料的 @RestController
:
-
Java
-
Kotlin
import java.util.List;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/users")
public class MyRestController {
private final UserRepository userRepository;
private final CustomerRepository customerRepository;
public MyRestController(UserRepository userRepository, CustomerRepository customerRepository) {
this.userRepository = userRepository;
this.customerRepository = customerRepository;
}
@GetMapping("/{userId}")
public User getUser(@PathVariable Long userId) {
return this.userRepository.findById(userId).get();
}
@GetMapping("/{userId}/customers")
public List<Customer> getUserCustomers(@PathVariable Long userId) {
return this.userRepository.findById(userId).map(this.customerRepository::findByUser).get();
}
@DeleteMapping("/{userId}")
public void deleteUser(@PathVariable Long userId) {
this.userRepository.deleteById(userId);
}
}
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/users")
class MyRestController(private val userRepository: UserRepository, private val customerRepository: CustomerRepository) {
@GetMapping("/{userId}")
fun getUser(@PathVariable userId: Long): User {
return userRepository.findById(userId).get()
}
@GetMapping("/{userId}/customers")
fun getUserCustomers(@PathVariable userId: Long): List<Customer> {
return userRepository.findById(userId).map(customerRepository::findByUser).get()
}
@DeleteMapping("/{userId}")
fun deleteUser(@PathVariable userId: Long) {
userRepository.deleteById(userId)
}
}
“WebMvc.fn” 是其函式式變體,它將路由配置與請求的實際處理分離,如下例所示:
-
Java
-
Kotlin
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.function.RequestPredicate;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.ServerResponse;
import static org.springframework.web.servlet.function.RequestPredicates.accept;
import static org.springframework.web.servlet.function.RouterFunctions.route;
@Configuration(proxyBeanMethods = false)
public class MyRoutingConfiguration {
private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON);
@Bean
public RouterFunction<ServerResponse> routerFunction(MyUserHandler userHandler) {
return route()
.GET("/{user}", ACCEPT_JSON, userHandler::getUser)
.GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
.DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
.build();
}
}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.MediaType
import org.springframework.web.servlet.function.RequestPredicates.accept
import org.springframework.web.servlet.function.RouterFunction
import org.springframework.web.servlet.function.RouterFunctions
import org.springframework.web.servlet.function.ServerResponse
@Configuration(proxyBeanMethods = false)
class MyRoutingConfiguration {
@Bean
fun routerFunction(userHandler: MyUserHandler): RouterFunction<ServerResponse> {
return RouterFunctions.route()
.GET("/{user}", ACCEPT_JSON, userHandler::getUser)
.GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
.DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
.build()
}
companion object {
private val ACCEPT_JSON = accept(MediaType.APPLICATION_JSON)
}
}
-
Java
-
Kotlin
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;
@Component
public class MyUserHandler {
public ServerResponse getUser(ServerRequest request) {
...
}
public ServerResponse getUserCustomers(ServerRequest request) {
...
}
public ServerResponse deleteUser(ServerRequest request) {
...
}
}
import org.springframework.stereotype.Component
import org.springframework.web.servlet.function.ServerRequest
import org.springframework.web.servlet.function.ServerResponse
@Component
class MyUserHandler {
fun getUser(request: ServerRequest?): ServerResponse {
...
}
fun getUserCustomers(request: ServerRequest?): ServerResponse {
...
}
fun deleteUser(request: ServerRequest?): ServerResponse {
...
}
}
Spring MVC 是核心 Spring Framework 的一部分,詳細資訊可在參考文件中找到。spring.io/guides 上也有幾篇涵蓋 Spring MVC 的指南。
你可以定義任意數量的 RouterFunction bean 來模組化路由的定義。如果需要應用優先順序,可以對 bean 進行排序。 |
Spring MVC 自動配置
Spring Boot 為 Spring MVC 提供了自動配置,這適用於大多數應用程式。它取代了對 @EnableWebMvc
的需求,兩者不能一起使用。除了 Spring MVC 的預設設定外,自動配置還提供了以下特性:
-
包含
ContentNegotiatingViewResolver
和BeanNameViewResolver
bean。 -
支援提供靜態資源,包括對 WebJars 的支援(本文件後續會介紹)。
-
自動註冊
Converter
、GenericConverter
和Formatter
bean。 -
支援
HttpMessageConverters
(本文件後續會介紹)。 -
自動註冊
MessageCodesResolver
(本文件後續會介紹)。 -
靜態
index.html
支援。 -
自動使用
ConfigurableWebBindingInitializer
bean(本文件後續會介紹)。
如果你想保留這些 Spring Boot MVC 自定義設定並進行更多 MVC 自定義(攔截器、格式化器、檢視控制器和其他特性),你可以新增自己的 @Configuration
類,型別為 WebMvcConfigurer
,但不包含 @EnableWebMvc
。
如果你想提供 RequestMappingHandlerMapping
、RequestMappingHandlerAdapter
或 ExceptionHandlerExceptionResolver
的自定義例項,並且仍然保留 Spring Boot MVC 的自定義設定,你可以宣告一個 WebMvcRegistrations
型別的 bean,並使用它來提供這些元件的自定義例項。自定義例項將由 Spring MVC 進行進一步初始化和配置。要參與或在需要時覆蓋後續處理,應使用 WebMvcConfigurer
。
如果你不想使用自動配置並希望完全控制 Spring MVC,請新增帶有 @EnableWebMvc
註解的 @Configuration
類。或者,如 @EnableWebMvc
API 文件中所述,新增帶有 @Configuration
註解的 DelegatingWebMvcConfiguration
。
Spring MVC 轉換服務
Spring MVC 使用的 ConversionService
與用於轉換 application.properties
或 application.yaml
檔案中值的 ConversionService
不同。這意味著 Period
、Duration
和 DataSize
轉換器不可用,並且 @DurationUnit
和 @DataSizeUnit
註解將被忽略。
如果你想自定義 Spring MVC 使用的 ConversionService
,可以提供一個帶有 addFormatters
方法的 WebMvcConfigurer
bean。透過此方法,你可以註冊所需的任何轉換器,或者委託給 ApplicationConversionService
上可用的靜態方法。
還可以使用 spring.mvc.format.*
配置屬性來自定義轉換。未配置時,使用以下預設值:
屬性 | 日期時間格式化器 |
格式 |
---|---|---|
|
|
|
|
|
java.time 的 |
|
|
java.time 的 |
HttpMessageConverters
Spring MVC 使用 HttpMessageConverter
介面來轉換 HTTP 請求和響應。開箱即用地包含了合理的預設設定。例如,物件可以自動轉換為 JSON(使用 Jackson 庫)或 XML(如果 Jackson XML 擴充套件可用,則使用它;如果不可用,則使用 JAXB)。預設情況下,字串使用 UTF-8
編碼。
上下文中存在的任何 HttpMessageConverter
bean 都會被新增到轉換器列表中。你也可以以同樣的方式覆蓋預設轉換器。
如果需要新增或自定義轉換器,可以使用 Spring Boot 的 HttpMessageConverters
類,如下所示:
-
Java
-
Kotlin
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
@Configuration(proxyBeanMethods = false)
public class MyHttpMessageConvertersConfiguration {
@Bean
public HttpMessageConverters customConverters() {
HttpMessageConverter<?> additional = new AdditionalHttpMessageConverter();
HttpMessageConverter<?> another = new AnotherHttpMessageConverter();
return new HttpMessageConverters(additional, another);
}
}
import org.springframework.boot.autoconfigure.http.HttpMessageConverters
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.converter.HttpMessageConverter
@Configuration(proxyBeanMethods = false)
class MyHttpMessageConvertersConfiguration {
@Bean
fun customConverters(): HttpMessageConverters {
val additional: HttpMessageConverter<*> = AdditionalHttpMessageConverter()
val another: HttpMessageConverter<*> = AnotherHttpMessageConverter()
return HttpMessageConverters(additional, another)
}
}
為了進一步控制,你還可以繼承 HttpMessageConverters
類並覆蓋其 postProcessConverters
和/或 postProcessPartConverters
方法。當你想重新排序或移除 Spring MVC 預設配置的一些轉換器時,這非常有用。
MessageCodesResolver
Spring MVC 有一套從繫結錯誤生成用於渲染錯誤訊息的錯誤碼的策略:MessageCodesResolver
。如果你設定了 spring.mvc.message-codes-resolver-format
屬性為 PREFIX_ERROR_CODE
或 POSTFIX_ERROR_CODE
,Spring Boot 會為你建立一個(參見 DefaultMessageCodesResolver.Format
中的列舉)。
靜態內容
預設情況下,Spring Boot 從 classpath 中名為 /static
(或 /public
、/resources
或 /META-INF/resources
)的目錄或從 ServletContext
的根目錄提供靜態內容。它使用 Spring MVC 的 ResourceHttpRequestHandler
,因此你可以透過新增自己的 WebMvcConfigurer
並覆蓋 addResourceHandlers
方法來修改該行為。
在獨立 Web 應用程式中,容器的預設 servlet 未啟用。可以使用 server.servlet.register-default-servlet
屬性來啟用它。
預設 servlet 作為備用,如果 Spring 決定不處理內容,則從 ServletContext
的根目錄提供內容。大多數情況下,這不會發生(除非你修改了預設的 MVC 配置),因為 Spring 總是可以透過 DispatcherServlet
處理請求。
預設情況下,資源對映到 /**
,但你可以使用 spring.mvc.static-path-pattern
屬性進行調整。例如,可以將所有資源重定位到 /resources/**
,如下所示:
-
Properties
-
YAML
spring.mvc.static-path-pattern=/resources/**
spring:
mvc:
static-path-pattern: "/resources/**"
你還可以使用 spring.web.resources.static-locations
屬性來自定義靜態資源位置(用目錄位置列表替換預設值)。根 servlet 上下文路徑 "/"
也會自動新增為位置。
除了前面提到的“標準”靜態資源位置之外,對 Webjars 內容也做了特殊處理。預設情況下,路徑在 /webjars/**
中的任何資源,如果以 Webjars 格式打包,都會從 jar 檔案中提供。路徑可以使用 spring.mvc.webjars-path-pattern
屬性進行自定義。
如果你的應用程式打包為 jar,請勿使用 src/main/webapp 目錄。儘管此目錄是一個常見標準,但它僅適用於 war 打包,並且如果你生成 jar,大多數構建工具會默默忽略它。 |
Spring Boot 也支援 Spring MVC 提供的高階資源處理特性,例如允許使用快取清除靜態資源或使用與版本無關的 URL 來訪問 Webjars。
要為 Webjars 使用與版本無關的 URL,請新增 org.webjars:webjars-locator-lite
依賴項。然後宣告你的 Webjar。以 jQuery 為例,新增 "/webjars/jquery/jquery.min.js"
將生成 "/webjars/jquery/x.y.z/jquery.min.js"
,其中 x.y.z
是 Webjar 版本。
要使用快取清除,以下配置為所有靜態資源配置了一個快取清除解決方案,有效地在 URL 中添加了內容雜湊,例如 <link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>
:
-
Properties
-
YAML
spring.web.resources.chain.strategy.content.enabled=true
spring.web.resources.chain.strategy.content.paths=/**
spring:
web:
resources:
chain:
strategy:
content:
enabled: true
paths: "/**"
藉助為 Thymeleaf 和 FreeMarker 自動配置的 ResourceUrlEncodingFilter ,模板中的資源連結會在執行時重寫。使用 JSP 時,應手動宣告此過濾器。其他模板引擎目前不支援自動配置,但可以透過自定義模板宏/助手和使用 ResourceUrlProvider 來實現。 |
例如,當使用 JavaScript 模組載入器動態載入資源時,重新命名檔案是不可行的。這就是為什麼也支援其他策略並且可以組合使用的原因。“fixed” 策略在 URL 中新增一個靜態版本字串,而不改變檔名,如下例所示:
-
Properties
-
YAML
spring.web.resources.chain.strategy.content.enabled=true
spring.web.resources.chain.strategy.content.paths=/**
spring.web.resources.chain.strategy.fixed.enabled=true
spring.web.resources.chain.strategy.fixed.paths=/js/lib/
spring.web.resources.chain.strategy.fixed.version=v12
spring:
web:
resources:
chain:
strategy:
content:
enabled: true
paths: "/**"
fixed:
enabled: true
paths: "/js/lib/"
version: "v12"
使用此配置,位於 "/js/lib/"
下的 JavaScript 模組使用固定的版本策略("/v12/js/lib/mymodule.js"
),而其他資源仍使用內容雜湊版本(<link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>
)。
有關更多支援的選項,請參閱 WebProperties.Resources
。
歡迎頁面
Spring Boot 支援靜態和模板歡迎頁面。它首先在配置的靜態內容位置查詢 index.html
檔案。如果未找到,則查詢 index
模板。如果找到其中之一,它會自動用作應用程式的歡迎頁面。
這僅作為應用程式定義的實際索引路由的備用。順序由 HandlerMapping
bean 的順序定義,預設情況下如下:
|
使用 |
|
在 |
|
歡迎頁面支援 |
路徑匹配與內容協商
Spring MVC 可以透過檢視請求路徑並將其與應用程式中定義的對映(例如,Controller 方法上的 @GetMapping
註解)匹配,來將傳入的 HTTP 請求對映到處理程式。
Spring Boot 預設選擇停用字尾模式匹配,這意味著 "GET /projects/spring-boot.json"
等請求不會匹配到 @GetMapping("/projects/spring-boot")
對映。這被認為是 Spring MVC 應用程式的最佳實踐。此功能在過去主要用於不傳送正確 "Accept" 請求頭的 HTTP 客戶端;我們需要確保向客戶端傳送正確的 Content Type。如今,內容協商更加可靠。
還有其他方法來處理不持續傳送正確 "Accept" 請求頭的 HTTP 客戶端。我們可以使用查詢引數代替字尾匹配,以確保 "GET /projects/spring-boot?format=json"
等請求將被對映到 @GetMapping("/projects/spring-boot")
:
-
Properties
-
YAML
spring.mvc.contentnegotiation.favor-parameter=true
spring:
mvc:
contentnegotiation:
favor-parameter: true
或者如果你喜歡使用不同的引數名稱:
-
Properties
-
YAML
spring.mvc.contentnegotiation.favor-parameter=true
spring.mvc.contentnegotiation.parameter-name=myparam
spring:
mvc:
contentnegotiation:
favor-parameter: true
parameter-name: "myparam"
大多數標準媒體型別開箱即用地受到支援,但你也可以定義新的型別:
-
Properties
-
YAML
spring.mvc.contentnegotiation.media-types.markdown=text/markdown
spring:
mvc:
contentnegotiation:
media-types:
markdown: "text/markdown"
從 Spring Framework 5.3 開始,Spring MVC 支援兩種將請求路徑匹配到控制器的策略。預設情況下,Spring Boot 使用 PathPatternParser
策略。PathPatternParser
是一個最佳化實現,但與 AntPathMatcher
策略相比存在一些限制。PathPatternParser
限制了某些路徑模式變體的使用。它也與配置帶有路徑字首(spring.mvc.servlet.path
)的 DispatcherServlet
不相容。
該策略可以使用 spring.mvc.pathmatch.matching-strategy
配置屬性進行配置,如下例所示:
-
Properties
-
YAML
spring.mvc.pathmatch.matching-strategy=ant-path-matcher
spring:
mvc:
pathmatch:
matching-strategy: "ant-path-matcher"
如果找不到請求的處理程式,Spring MVC 將丟擲 NoHandlerFoundException
。請注意,預設情況下,靜態內容服務對映到 /**
,因此將為所有請求提供處理程式。如果沒有可用的靜態內容,ResourceHttpRequestHandler
將丟擲 NoResourceFoundException
。要丟擲 NoHandlerFoundException
,請將 spring.mvc.static-path-pattern
設定為更具體的值(例如 /resources/**
),或將 spring.web.resources.add-mappings
設定為 false
以完全停用靜態內容服務。
ConfigurableWebBindingInitializer
Spring MVC 使用 WebBindingInitializer
為特定請求初始化 WebDataBinder
。如果你建立自己的 ConfigurableWebBindingInitializer
@Bean
,Spring Boot 會自動配置 Spring MVC 使用它。
模板引擎
除了 REST web 服務外,你還可以使用 Spring MVC 提供動態 HTML 內容。Spring MVC 支援各種模板技術,包括 Thymeleaf、FreeMarker 和 JSP。此外,許多其他模板引擎也包含自己的 Spring MVC 整合。
Spring Boot 包含對以下模板引擎的自動配置支援:
如果可能,應避免使用 JSP。當與嵌入式 servlet 容器一起使用時,存在一些已知的限制。 |
當你使用這些模板引擎之一併採用預設配置時,你的模板會自動從 src/main/resources/templates
中載入。
根據你執行應用程式的方式,你的 IDE 可能會以不同的順序排列 classpath。在 IDE 中從 main 方法執行應用程式的順序與使用 Maven 或 Gradle 或從打包的 jar 執行時不同。這可能導致 Spring Boot 找不到預期的模板。如果遇到此問題,可以在 IDE 中重新排列 classpath,將模組的類和資源放在前面。 |
錯誤處理
預設情況下,Spring Boot 提供一個 /error
對映,以合理的方式處理所有錯誤,並將其註冊為 servlet 容器中的“全域性”錯誤頁面。對於機器客戶端,它會生成一個包含錯誤詳情、HTTP 狀態和異常訊息的 JSON 響應。對於瀏覽器客戶端,有一個“白標”錯誤檢視,以 HTML 格式呈現相同的資料(要自定義它,請新增一個解析為 error
的 View
)。
如果你想自定義預設的錯誤處理行為,可以設定一些 server.error
屬性。請參見附錄的Server Properties 部分。
要完全替換預設行為,你可以實現 ErrorController
並註冊該型別的 bean 定義,或者新增一個 ErrorAttributes
型別的 bean 來使用現有機制但替換內容。
BasicErrorController 可以用作自定義 ErrorController 的基類。如果您想為新的內容型別新增處理程式(預設是專門處理 text/html 併為其他所有內容提供備用),這尤其有用。為此,請繼承 BasicErrorController ,新增一個帶有 produces 屬性的 @RequestMapping 公共方法,並建立您的新型別 bean。 |
從 Spring Framework 6.0 開始,支援 RFC 9457 Problem Details。Spring MVC 可以生成使用 application/problem+json
媒體型別的自定義錯誤訊息,例如
{
"type": "https://example.org/problems/unknown-project",
"title": "Unknown project",
"status": 404,
"detail": "No project found for id 'spring-unknown'",
"instance": "/projects/spring-unknown"
}
可以透過將 spring.mvc.problemdetails.enabled
設定為 true
來啟用此支援。
您還可以定義一個使用 @ControllerAdvice
註解的類,以自定義針對特定控制器和/或異常型別返回的 JSON 文件,如下例所示
-
Java
-
Kotlin
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
@ControllerAdvice(basePackageClasses = SomeController.class)
public class MyControllerAdvice extends ResponseEntityExceptionHandler {
@ResponseBody
@ExceptionHandler(MyException.class)
public ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {
HttpStatus status = getStatus(request);
return new ResponseEntity<>(new MyErrorBody(status.value(), ex.getMessage()), status);
}
private HttpStatus getStatus(HttpServletRequest request) {
Integer code = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
HttpStatus status = HttpStatus.resolve(code);
return (status != null) ? status : HttpStatus.INTERNAL_SERVER_ERROR;
}
}
import jakarta.servlet.RequestDispatcher
import jakarta.servlet.http.HttpServletRequest
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.ResponseBody
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler
@ControllerAdvice(basePackageClasses = [SomeController::class])
class MyControllerAdvice : ResponseEntityExceptionHandler() {
@ResponseBody
@ExceptionHandler(MyException::class)
fun handleControllerException(request: HttpServletRequest, ex: Throwable): ResponseEntity<*> {
val status = getStatus(request)
return ResponseEntity(MyErrorBody(status.value(), ex.message), status)
}
private fun getStatus(request: HttpServletRequest): HttpStatus {
val code = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE) as Int
val status = HttpStatus.resolve(code)
return status ?: HttpStatus.INTERNAL_SERVER_ERROR
}
}
在前面的示例中,如果由與 SomeController
在同一包中定義的控制器丟擲 MyException
,則使用 MyErrorBody
POJO 的 JSON 表示形式,而不是 ErrorAttributes
表示形式。
在某些情況下,在控制器級別處理的錯誤不會被 Web 觀測或 度量基礎設施 記錄。應用程式可以透過在觀測上下文上設定已處理的異常來確保此類異常被觀測記錄下來。
自定義錯誤頁面
如果您想為給定的狀態碼顯示自定義 HTML 錯誤頁面,可以將檔案新增到 /error
目錄。錯誤頁面可以是靜態 HTML(即,新增到任何靜態資源目錄下)或使用模板構建。檔名稱應為精確的狀態碼或系列掩碼。
例如,要將 404
對映到靜態 HTML 檔案,您的目錄結構將如下所示
src/
+- main/
+- java/
| + <source code>
+- resources/
+- public/
+- error/
| +- 404.html
+- <other public assets>
要使用 FreeMarker 模板對映所有 5xx
錯誤,您的目錄結構將如下所示
src/
+- main/
+- java/
| + <source code>
+- resources/
+- templates/
+- error/
| +- 5xx.ftlh
+- <other templates>
對於更復雜的對映,您還可以新增實現 ErrorViewResolver
介面的 bean,如下例所示
-
Java
-
Kotlin
import java.util.Map;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.ModelAndView;
public class MyErrorViewResolver implements ErrorViewResolver {
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
// Use the request or status to optionally return a ModelAndView
if (status == HttpStatus.INSUFFICIENT_STORAGE) {
// We could add custom model values here
new ModelAndView("myview");
}
return null;
}
}
import jakarta.servlet.http.HttpServletRequest
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver
import org.springframework.http.HttpStatus
import org.springframework.web.servlet.ModelAndView
class MyErrorViewResolver : ErrorViewResolver {
override fun resolveErrorView(request: HttpServletRequest, status: HttpStatus,
model: Map<String, Any>): ModelAndView? {
// Use the request or status to optionally return a ModelAndView
if (status == HttpStatus.INSUFFICIENT_STORAGE) {
// We could add custom model values here
return ModelAndView("myview")
}
return null
}
}
您還可以使用常規的 Spring MVC 功能,例如 @ExceptionHandler
方法和 @ControllerAdvice
。ErrorController
隨後會捕獲任何未處理的異常。
在 Spring MVC 之外對映錯誤頁面
對於不使用 Spring MVC 的應用程式,可以使用 ErrorPageRegistrar
介面直接註冊 ErrorPage
例項。此抽象直接與底層的嵌入式 Servlet 容器一起工作,即使您沒有 Spring MVC DispatcherServlet
也能工作。
-
Java
-
Kotlin
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.ErrorPageRegistrar;
import org.springframework.boot.web.server.ErrorPageRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
@Configuration(proxyBeanMethods = false)
public class MyErrorPagesConfiguration {
@Bean
public ErrorPageRegistrar errorPageRegistrar() {
return this::registerErrorPages;
}
private void registerErrorPages(ErrorPageRegistry registry) {
registry.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/400"));
}
}
import org.springframework.boot.web.server.ErrorPage
import org.springframework.boot.web.server.ErrorPageRegistrar
import org.springframework.boot.web.server.ErrorPageRegistry
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpStatus
@Configuration(proxyBeanMethods = false)
class MyErrorPagesConfiguration {
@Bean
fun errorPageRegistrar(): ErrorPageRegistrar {
return ErrorPageRegistrar { registry: ErrorPageRegistry -> registerErrorPages(registry) }
}
private fun registerErrorPages(registry: ErrorPageRegistry) {
registry.addErrorPages(ErrorPage(HttpStatus.BAD_REQUEST, "/400"))
}
}
如果您註冊一個 ErrorPage ,其路徑最終由 Filter 處理(這在 Jersey 和 Wicket 等一些非 Spring Web 框架中很常見),則必須將該 Filter 明確註冊為 ERROR 分發器,如下例所示 |
-
Java
-
Kotlin
import java.util.EnumSet;
import jakarta.servlet.DispatcherType;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MyFilterConfiguration {
@Bean
public FilterRegistrationBean<MyFilter> myFilter() {
FilterRegistrationBean<MyFilter> registration = new FilterRegistrationBean<>(new MyFilter());
// ...
registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
return registration;
}
}
import jakarta.servlet.DispatcherType
import org.springframework.boot.web.servlet.FilterRegistrationBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.util.EnumSet
@Configuration(proxyBeanMethods = false)
class MyFilterConfiguration {
@Bean
fun myFilter(): FilterRegistrationBean<MyFilter> {
val registration = FilterRegistrationBean(MyFilter())
// ...
registration.setDispatcherTypes(EnumSet.allOf(DispatcherType::class.java))
return registration
}
}
請注意,預設的 FilterRegistrationBean
不包含 ERROR
分發器型別。
WAR 部署中的錯誤處理
當部署到 Servlet 容器時,Spring Boot 使用其錯誤頁面過濾器將帶有錯誤狀態的請求轉發到相應的錯誤頁面。這是必要的,因為 Servlet 規範不提供註冊錯誤頁面的 API。根據您部署 war 檔案的容器以及應用程式使用的技術,可能需要一些額外的配置。
錯誤頁面過濾器只有在響應尚未提交時才能將請求轉發到正確的錯誤頁面。預設情況下,WebSphere Application Server 8.0 及更高版本在 Servlet 的 service 方法成功完成時提交響應。您應透過將 com.ibm.ws.webcontainer.invokeFlushAfterService
設定為 false
來停用此行為。
CORS 支援
從 4.2 版本開始,Spring MVC 支援 CORS。在 Spring Boot 應用程式中使用帶有 @CrossOrigin
註解的控制器方法 CORS 配置不需要任何特定配置。可以透過註冊一個帶有定製的 addCorsMappings(CorsRegistry)
方法的 WebMvcConfigurer
bean 來定義全域性 CORS 配置,如下例所示
-
Java
-
Kotlin
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration(proxyBeanMethods = false)
public class MyCorsConfiguration {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**");
}
};
}
}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.servlet.config.annotation.CorsRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
@Configuration(proxyBeanMethods = false)
class MyCorsConfiguration {
@Bean
fun corsConfigurer(): WebMvcConfigurer {
return object : WebMvcConfigurer {
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/api/**")
}
}
}
}
JAX-RS 和 Jersey
如果您更喜歡用於 REST 端點的 JAX-RS 程式設計模型,可以使用可用的實現之一代替 Spring MVC。Jersey
和 Apache CXF
開箱即用效果很好。CXF 需要您將其 Servlet
或 Filter
在應用程式上下文中註冊為 @Bean
。Jersey 具有一些原生的 Spring 支援,因此我們在 Spring Boot 中也為其提供了自動配置支援,以及一個啟動器。
要開始使用 Jersey,請將 spring-boot-starter-jersey
作為依賴項包含在內,然後您需要一個 @Bean
型別為 ResourceConfig
,並在其中註冊所有端點,如下例所示
import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.stereotype.Component;
@Component
public class MyJerseyConfig extends ResourceConfig {
public MyJerseyConfig() {
register(MyEndpoint.class);
}
}
Jersey 對掃描可執行歸檔的支援相當有限。例如,在執行可執行 war 檔案時,它無法掃描 完全可執行 jar 檔案或 WEB-INF/classes 中包裡的端點。為避免此限制,不應使用 packages 方法,而應使用 register 方法單獨註冊端點,如前面的示例所示。 |
對於更高階的定製,您還可以註冊任意數量實現 ResourceConfigCustomizer
的 bean。
所有註冊的端點都應是帶有 HTTP 資源註解(@GET
等)的 @Component
,如下例所示
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.springframework.stereotype.Component;
@Component
@Path("/hello")
public class MyEndpoint {
@GET
public String message() {
return "Hello";
}
}
由於 @Endpoint
是一個 Spring @Component
,其生命週期由 Spring 管理,您可以使用 @Autowired
註解注入依賴項,並使用 @Value
註解注入外部配置。預設情況下,Jersey servlet 已註冊並對映到 /*
。您可以透過向 ResourceConfig
新增 @ApplicationPath
來更改對映。
預設情況下,Jersey 在一個名為 jerseyServletRegistration
的 @Bean
中作為型別為 ServletRegistrationBean
的 servlet 進行設定。預設情況下,servlet 是延遲初始化的,但您可以透過設定 spring.jersey.servlet.load-on-startup
來自定義該行為。您可以透過建立同名的 bean 來停用或覆蓋該 bean。您還可以透過設定 spring.jersey.type=filter
來使用過濾器代替 servlet(在這種情況下,要替換或覆蓋的 @Bean
是 jerseyFilterRegistration
)。該過濾器有一個 @Order
,您可以透過 spring.jersey.filter.order
進行設定。當使用 Jersey 作為過濾器時,必須存在一個用於處理 Jersey 未攔截的任何請求的 servlet。如果您的應用程式不包含這樣的 servlet,您可能希望透過將 server.servlet.register-default-servlet
設定為 true
來啟用預設 servlet。servlet 和過濾器註冊都可以透過使用 spring.jersey.init.*
指定屬性對映來設定初始化引數。
嵌入式 Servlet 容器支援
對於 Servlet 應用程式,Spring Boot 包含對嵌入式 Tomcat
、Jetty
和 Undertow
伺服器的支援。大多數開發人員使用相應的啟動器來獲取完全配置的例項。預設情況下,嵌入式伺服器監聽埠 8080
上的 HTTP 請求。
Servlets、Filters 和 Listeners
使用嵌入式 Servlet 容器時,您可以透過使用 Spring bean 或掃描 Servlet 元件來註冊 Servlet 規範中的 servlets、filters 和所有 listeners(例如 HttpSessionListener
)。
將 Servlets、Filters 和 Listeners 註冊為 Spring Bean
任何作為 Spring bean 的 Servlet
、Filter
或 servlet *Listener
例項都將註冊到嵌入式容器。如果您想在配置期間引用 application.properties
中的值,這會特別方便。
預設情況下,如果上下文中只包含一個 Servlet,它將被對映到 /
。如果有多個 servlet bean,則 bean 名稱用作路徑字首。Filters 對映到 /*
。
如果基於約定的對映不夠靈活,您可以使用 ServletRegistrationBean
、FilterRegistrationBean
和 ServletListenerRegistrationBean
類進行完全控制。
通常可以安全地讓過濾器 bean 無序。如果需要特定順序,應使用 @Order
註解 Filter
或使其實現 Ordered
。您無法透過使用 @Order
註解其 bean 方法來配置 Filter
的順序。如果您無法更改 Filter
類以新增 @Order
或實現 Ordered
,則必須為該 Filter
定義一個 FilterRegistrationBean
,並使用 setOrder(int)
方法設定註冊 bean 的順序。避免在 Ordered.HIGHEST_PRECEDENCE
配置讀取請求體的過濾器,因為它可能與應用程式的字元編碼配置衝突。如果 servlet 過濾器包裝了請求,則應將其配置為順序小於或等於 OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER
。
要檢視應用程式中每個 Filter 的順序,請為 web 日誌記錄組 啟用除錯級別日誌記錄(logging.level.web=debug )。然後,註冊過濾器的詳細資訊,包括其順序和 URL 模式,將在啟動時記錄。 |
註冊 Filter bean 時要小心,因為它們在應用程式生命週期中初始化得非常早。如果您需要註冊一個與其它 bean 互動的 Filter ,請考慮改用 DelegatingFilterProxyRegistrationBean 。 |
Servlet 上下文初始化
嵌入式 Servlet 容器不會直接執行 ServletContainerInitializer
介面或 Spring 的 WebApplicationInitializer
介面。這是一個有意的設計決策,旨在降低設計用於在 war 中執行的第三方庫可能破壞 Spring Boot 應用程式的風險。
如果您需要在 Spring Boot 應用程式中執行 Servlet 上下文初始化,則應註冊一個實現 ServletContextInitializer
介面的 bean。其唯一的 onStartup
方法提供對 ServletContext
的訪問,並且在必要時可以輕鬆地用作現有 WebApplicationInitializer
的介面卡。
掃描 Servlets、Filters 和 listeners
使用嵌入式容器時,可以透過使用 @ServletComponentScan
啟用自動註冊使用 @WebServlet
、@WebFilter
和 @WebListener
註解的類。
在獨立容器中,@ServletComponentScan 沒有效果,因為那裡使用的是容器內建的發現機制。 |
ServletWebServerApplicationContext
在底層,Spring Boot 使用不同型別的 ApplicationContext
來支援嵌入式 Servlet 容器。ServletWebServerApplicationContext
是一種特殊型別的 WebApplicationContext
,它透過搜尋單個 ServletWebServerFactory
bean 來引導自身。通常,一個 TomcatServletWebServerFactory
、JettyServletWebServerFactory
或 UndertowServletWebServerFactory
已經被自動配置。
您通常不需要了解這些實現類。大多數應用程式都是自動配置的,並且會為您建立適當的 ApplicationContext 和 ServletWebServerFactory 。 |
在嵌入式容器設定中,ServletContext
是在伺服器啟動時設定的,這發生在應用程式上下文初始化期間。因此,ApplicationContext
中的 bean 無法可靠地使用 ServletContext
進行初始化。解決這個問題的一種方法是將 ApplicationContext
作為 bean 的依賴項注入,並在需要時才訪問 ServletContext
。另一種方法是在伺服器啟動後使用回撥。這可以透過使用監聽 ApplicationStartedEvent
的 ApplicationListener
來實現,如下所示
import jakarta.servlet.ServletContext;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.web.context.WebApplicationContext;
public class MyDemoBean implements ApplicationListener<ApplicationStartedEvent> {
private ServletContext servletContext;
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
ApplicationContext applicationContext = event.getApplicationContext();
this.servletContext = ((WebApplicationContext) applicationContext).getServletContext();
}
}
定製嵌入式 Servlet 容器
可以使用 Spring Environment
屬性配置常見的 Servlet 容器設定。通常,您會在 application.properties
或 application.yaml
檔案中定義屬性。
常見的伺服器設定包括
Spring Boot 儘可能嘗試暴露通用設定,但這並非總是可行。對於這些情況,專用名稱空間提供伺服器特定的定製(請參閱 server.tomcat
和 server.undertow
)。例如,訪問日誌 可以根據嵌入式 Servlet 容器的特定功能進行配置。
有關完整列表,請參閱 ServerProperties 類。 |
SameSite Cookie
SameSite
cookie 屬性可被 Web 瀏覽器用來控制跨站點請求中是否以及如何提交 cookie。該屬性對於已經開始更改屬性缺失時使用的預設值的現代 Web 瀏覽器尤為重要。
如果您想更改會話 cookie 的 SameSite
屬性,可以使用 server.servlet.session.cookie.same-site
屬性。此屬性受自動配置的 Tomcat、Jetty 和 Undertow 伺服器支援。它也用於配置基於 Spring Session servlet 的 SessionRepository
bean。
例如,如果您希望會話 cookie 的 SameSite
屬性為 None
,可以將以下內容新增到 application.properties
或 application.yaml
檔案中
-
Properties
-
YAML
server.servlet.session.cookie.same-site=none
server:
servlet:
session:
cookie:
same-site: "none"
如果您想更改新增到 HttpServletResponse
中的其他 cookie 的 SameSite
屬性,可以使用 CookieSameSiteSupplier
。CookieSameSiteSupplier
會傳遞一個 Cookie
,並可能返回一個 SameSite
值或 null
。
有許多便利的工廠方法和過濾方法可用於快速匹配特定的 cookie。例如,新增以下 bean 將自動為名稱匹配正則表示式 myapp.*
的所有 cookie 應用 SameSite
屬性 Lax
。
-
Java
-
Kotlin
import org.springframework.boot.web.servlet.server.CookieSameSiteSupplier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MySameSiteConfiguration {
@Bean
public CookieSameSiteSupplier applicationCookieSameSiteSupplier() {
return CookieSameSiteSupplier.ofLax().whenHasNameMatching("myapp.*");
}
}
import org.springframework.boot.web.servlet.server.CookieSameSiteSupplier
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration(proxyBeanMethods = false)
class MySameSiteConfiguration {
@Bean
fun applicationCookieSameSiteSupplier(): CookieSameSiteSupplier {
return CookieSameSiteSupplier.ofLax().whenHasNameMatching("myapp.*")
}
}
字元編碼
可以使用 server.servlet.encoding.*
配置屬性配置嵌入式 Servlet 容器在請求和響應處理中的字元編碼行為。
當請求的 Accept-Language
頭指示請求的區域設定時,Servlet 容器會自動將其對映到字元集。每個容器都提供預設的區域設定到字元集對映,您應該驗證它們是否滿足應用程式的需求。如果不滿足,請使用 server.servlet.encoding.mapping
配置屬性來自定義對映,如下例所示
-
Properties
-
YAML
server.servlet.encoding.mapping.ko=UTF-8
server:
servlet:
encoding:
mapping:
ko: "UTF-8"
在前面的示例中,ko
(韓語)區域設定已對映到 UTF-8
。這等同於傳統 war 部署的 web.xml
檔案中的 <locale-encoding-mapping-list>
條目。
程式化定製
如果您需要以程式設計方式配置嵌入式 Servlet 容器,可以註冊一個實現 WebServerFactoryCustomizer
介面的 Spring bean。WebServerFactoryCustomizer
提供對 ConfigurableServletWebServerFactory
的訪問,後者包含許多定製 setter 方法。以下示例顯示了以程式設計方式設定埠
-
Java
-
Kotlin
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.stereotype.Component;
@Component
public class MyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory server) {
server.setPort(9000);
}
}
import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory
import org.springframework.stereotype.Component
@Component
class MyWebServerFactoryCustomizer : WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
override fun customize(server: ConfigurableServletWebServerFactory) {
server.setPort(9000)
}
}
TomcatServletWebServerFactory
、JettyServletWebServerFactory
和 UndertowServletWebServerFactory
是 ConfigurableServletWebServerFactory
的專用變體,分別包含針對 Tomcat、Jetty 和 Undertow 的額外定製 setter 方法。以下示例展示瞭如何定製 TomcatServletWebServerFactory
,它提供了訪問特定於 Tomcat 的配置選項的能力
-
Java
-
Kotlin
import java.time.Duration;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;
@Component
public class MyTomcatWebServerFactoryCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
@Override
public void customize(TomcatServletWebServerFactory server) {
server.addConnectorCustomizers((connector) -> connector.setAsyncTimeout(Duration.ofSeconds(20).toMillis()));
}
}
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory
import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.stereotype.Component
import java.time.Duration
@Component
class MyTomcatWebServerFactoryCustomizer : WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
override fun customize(server: TomcatServletWebServerFactory) {
server.addConnectorCustomizers({ connector -> connector.asyncTimeout = Duration.ofSeconds(20).toMillis() })
}
}
直接定製 ConfigurableServletWebServerFactory
對於需要您從 ServletWebServerFactory
繼承的更高階用例,您可以自己暴露一個此類 bean。
提供了許多配置選項的 setter 方法。如果您需要做更獨特的事情,還提供了幾個受保護的方法“鉤子”。詳細資訊請參閱 ConfigurableServletWebServerFactory
API 文件。
自動配置的定製器仍然會應用於您的自定義工廠,因此請謹慎使用該選項。 |