Servlet Web 應用程式
如果您想要建置基於 servlet 的 web 應用程式,您可以利用 Spring Boot 針對 Spring MVC 或 Jersey 的自動組態。
「Spring Web MVC Framework」
Spring Web MVC framework(通常稱為「Spring MVC」)是一個豐富的「模型視圖控制器」web framework。Spring MVC 讓您可以建立特殊的 @Controller
或 @RestController
beans 來處理傳入的 HTTP 請求。您控制器中的方法透過使用 @RequestMapping
註解對應到 HTTP。
下列程式碼顯示一個典型的 @RestController
,用於提供 JSON 資料
-
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 beans,以模組化路由器定義。如果您需要應用優先順序,可以對 beans 進行排序。 |
Spring MVC 自動組態
Spring Boot 為 Spring MVC 提供自動組態,這對於大多數應用程式都非常適用。它取代了 @EnableWebMvc
的需求,並且兩者不能一起使用。除了 Spring MVC 的預設值之外,自動組態還提供下列功能
如果您想要保留這些 Spring Boot MVC 自訂設定,並進行更多MVC 自訂設定(攔截器、格式器、視圖控制器和其他功能),您可以新增自己的 @Configuration
類別,類型為 WebMvcConfigurer
,但不使用 @EnableWebMvc
。
如果您想要提供 RequestMappingHandlerMapping
、RequestMappingHandlerAdapter
或 ExceptionHandlerExceptionResolver
的自訂實例,並且仍然保留 Spring Boot MVC 自訂設定,您可以宣告類型為 WebMvcRegistrations
的 bean,並使用它來提供這些組件的自訂實例。自訂實例將受到 Spring MVC 的進一步初始化和組態。若要參與(如果需要,覆寫)後續處理,應使用 WebMvcConfigurer
。
如果您不想使用自動組態,並且想要完全控制 Spring MVC,請新增您自己的 @Configuration
並使用 @EnableWebMvc
註解。或者,依照 @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.*
組態屬性自訂轉換。當未組態時,將使用下列預設值
屬性 | DateTimeFormatter |
格式 |
---|---|---|
|
|
|
|
|
java.time 的 |
|
|
java.time 的 |
HttpMessageConverters
Spring MVC 使用 HttpMessageConverter
介面來轉換 HTTP 請求和回應。開箱即用包含合理的預設值。例如,物件可以自動轉換為 JSON(透過使用 Jackson 程式庫)或 XML(透過使用 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 從類別路徑中名為 /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/**
可以如下實現
-
屬性
-
YAML
spring.mvc.static-path-pattern=/resources/**
spring:
mvc:
static-path-pattern: "/resources/**"
您也可以使用 spring.web.resources.static-locations
屬性自訂靜態資源位置(將預設值替換為目錄位置清單)。根 servlet 內容路徑 "/"
也會自動新增為位置。
除了先前提及的「標準」靜態資源位置之外,Webjars 內容還有一個特殊情況。依預設,如果任何路徑在 /webjars/**
中的資源以 Webjars 格式封裝在 jar 檔案中,則會從 jar 檔案提供這些資源。路徑可以使用 spring.mvc.webjars-path-pattern
屬性自訂。
如果您的應用程式封裝為 jar,請勿使用 src/main/webapp 目錄。雖然此目錄是常見標準,但它僅適用於 war 封裝,如果您產生 jar,則大多數建置工具會靜默忽略它。 |
Spring Boot 也支援 Spring MVC 提供的進階資源處理功能,允許使用案例,例如快取失效靜態資源或為 Webjars 使用版本無關的 URL。
若要為 Webjars 使用版本無關的 URL,請新增 webjars-locator-core
依賴。然後宣告您的 Webjar。以 jQuery 為例,新增 "/webjars/jquery/jquery.min.js"
會產生 "/webjars/jquery/x.y.z/jquery.min.js"
,其中 x.y.z
是 Webjar 版本。
如果您使用 JBoss,則需要宣告 webjars-locator-jboss-vfs 依賴,而不是 webjars-locator-core 。否則,所有 Webjars 都會解析為 404 。 |
若要使用快取失效,下列組態會為所有靜態資源組態快取失效解決方案,有效地新增內容雜湊,例如 <link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>
在 URL 中
-
屬性
-
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 模組載入器等動態載入資源時,重新命名檔案不是一個選項。這就是為什麼也支援其他策略並且可以組合的原因。「固定」策略在 URL 中新增靜態版本字串,而不變更檔案名稱,如下列範例所示
-
屬性
-
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
beans 的順序定義,依預設順序如下
|
使用 |
|
在 |
|
歡迎頁面支援 |
路徑比對和內容協商
Spring MVC 可以透過查看請求路徑並將其與應用程式中定義的對應(例如,Controller 方法上的 @GetMapping
註解)進行比對,將傳入的 HTTP 請求對應到處理常式。
Spring Boot 選擇依預設停用後綴模式比對,這表示像 "GET /projects/spring-boot.json"
這樣的請求將不會與 @GetMapping("/projects/spring-boot")
對應比對。這被認為是 Spring MVC 應用程式的最佳實務。此功能在過去主要適用於未傳送正確 "Accept" 請求標頭的 HTTP 客戶端;我們需要確保將正確的內容類型傳送給客戶端。如今,內容協商更加可靠。
還有其他方法可以處理不一致地傳送正確 "Accept" 請求標頭的 HTTP 客戶端。我們可以使用查詢參數,而不是使用後綴比對,以確保像 "GET /projects/spring-boot?format=json"
這樣的請求將對應到 @GetMapping("/projects/spring-boot")
-
屬性
-
YAML
spring.mvc.contentnegotiation.favor-parameter=true
spring:
mvc:
contentnegotiation:
favor-parameter: true
或者,如果您偏好使用不同的參數名稱
-
屬性
-
YAML
spring.mvc.contentnegotiation.favor-parameter=true
spring.mvc.contentnegotiation.parameter-name=myparam
spring:
mvc:
contentnegotiation:
favor-parameter: true
parameter-name: "myparam"
大多數標準媒體類型都是開箱即用支援的,但您也可以定義新的媒體類型
-
屬性
-
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
組態屬性組態策略,如下列範例所示
-
屬性
-
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 可能會以不同的方式排序類別路徑。從其 main 方法在 IDE 中執行應用程式會導致與使用 Maven 或 Gradle 執行應用程式或從其封裝的 jar 執行應用程式不同的排序。這可能會導致 Spring Boot 無法找到預期的範本。如果您遇到此問題,您可以在 IDE 中重新排序類別路徑,以將模組的類別和資源放在最前面。 |
錯誤處理
依預設,Spring Boot 提供 /error
對應,以合理的方式處理所有錯誤,並且它在 servlet 容器中註冊為「全域」錯誤頁面。對於機器客戶端,它產生一個 JSON 回應,其中包含錯誤詳細資訊、HTTP 狀態和例外訊息。對於瀏覽器客戶端,有一個「白標籤」錯誤視圖,以 HTML 格式呈現相同的資料(若要自訂它,請新增解析為 error
的 View
)。
如果您想要自訂預設錯誤處理行為,可以設定許多 server.error
屬性。請參閱附錄的伺服器屬性章節。
若要完全取代預設行為,您可以實作 ErrorController
並註冊該類型的 bean 定義,或新增 ErrorAttributes
類型的 bean 以使用現有的機制,但取代內容。
BasicErrorController 可以用作自訂 ErrorController 的基底類別。如果您想要為新的內容類型新增處理常式,這特別有用(預設是專門處理 text/html 並為其他所有內容提供後備機制)。若要執行此操作,請擴充 BasicErrorController ,新增具有 @RequestMapping 的公用方法,該方法具有 produces 屬性,並建立您的新類型的 bean。 |
從 Spring Framework 6.0 開始,支援 RFC 9457 問題詳細資訊。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
介面直接註冊 ErrorPages
。即使您沒有 Spring MVC DispatcherServlet
,此抽象概念也能直接與底層的嵌入式 Servlet 容器協同運作。
-
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 處理(這在某些非 Spring Web 框架(如 Jersey 和 Wicket)中很常見),則必須將 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 應用程式中使用 控制器方法 CORS 組態 和 @CrossOrigin
註解,不需要任何特定的組態。全域 CORS 組態 可以透過註冊具有自訂 addCorsMappings(CorsRegistry)
方法的 WebMvcConfigurer
bean 來定義,如下列範例所示
-
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 中為其提供自動組態支援,以及一個 Starter。
若要開始使用 Jersey,請包含 spring-boot-starter-jersey
作為相依性,然後您需要一個 ResourceConfig
類型的 @Bean
,您可以在其中註冊所有端點,如下列範例所示
import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.stereotype.Component;
@Component
public class MyJerseyConfig extends ResourceConfig {
public MyJerseyConfig() {
register(MyEndpoint.class);
}
}
Jersey 對於掃描可執行封存檔的支援相當有限。例如,它無法掃描 完全可執行的 jar 檔案 或執行可執行 WAR 檔案時的 WEB-INF/classes 中找到的套件中的端點。為了避免此限制,不應使用 packages 方法,而應使用 register 方法個別註冊端點,如前面的範例所示。 |
對於更進階的自訂,您也可以註冊任意數量的 bean 來實作 ResourceConfigCustomizer
。
所有註冊的端點都應該是具有 HTTP 資源註解 (@GET
和其他) 的 @Components
,如下列範例所示
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 會註冊並對應到 /*
。您可以透過將 @ApplicationPath
新增至您的 ResourceConfig
來變更對應。
依預設,Jersey 會設定為名為 jerseyServletRegistration
的 ServletRegistrationBean
類型的 @Bean
中的 Servlet。依預設,Servlet 會延遲初始化,但您可以透過設定 spring.jersey.servlet.load-on-startup
來自訂該行為。您可以透過建立一個同名的 bean 來停用或覆寫該 bean。您也可以透過設定 spring.jersey.type=filter
來使用篩選器來取代 Servlet(在這種情況下,要取代或覆寫的 @Bean
是 jerseyFilterRegistration
)。篩選器具有 @Order
,您可以使用 spring.jersey.filter.order
進行設定。當使用 Jersey 作為篩選器時,必須存在一個 Servlet 來處理任何未被 Jersey 攔截的請求。如果您的應用程式不包含此類 Servlet,您可能需要透過將 server.servlet.register-default-servlet
設定為 true
來啟用預設 Servlet。Servlet 和篩選器註冊都可以使用 spring.jersey.init.*
來指定屬性對應,從而給定初始化參數。
嵌入式 Servlet 容器支援
對於 Servlet 應用程式,Spring Boot 包含對嵌入式 Tomcat、Jetty 和 Undertow 伺服器的支援。大多數開發人員使用適當的 Starter 來取得完整組態的執行個體。依預設,嵌入式伺服器會接聽連接埠 8080
上的 HTTP 請求。
Servlet、篩選器和接聽器
當使用嵌入式 Servlet 容器時,您可以透過使用 Spring bean 或掃描 Servlet 元件來註冊 Servlet 規範中的 Servlet、篩選器和所有接聽器(例如 HttpSessionListener
)。
將 Servlet、篩選器和接聽器註冊為 Spring Bean
任何作為 Spring bean 的 Servlet
、Filter
或 Servlet *Listener
執行個體都會在嵌入式容器中註冊。如果您想要在組態期間參考 application.properties
中的值,這會特別方便。
依預設,如果內容只包含單一 Servlet,則會對應到 /
。在多個 Servlet bean 的情況下,bean 名稱會用作路徑前置詞。篩選器會對應到 /*
。
如果基於慣例的對應不夠彈性,您可以使用 ServletRegistrationBean
、FilterRegistrationBean
和 ServletListenerRegistrationBean
類別來進行完整控制。
通常可以安全地保持篩選器 bean 處於未排序狀態。如果需要特定的順序,您應該使用 @Order
註解篩選器,或使其實作 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 容器不會直接執行 jakarta.servlet.ServletContainerInitializer
介面或 Spring 的 org.springframework.web.WebApplicationInitializer
介面。這是一個刻意的設計決策,旨在降低設計為在 WAR 內執行的第三方程式庫可能會中斷 Spring Boot 應用程式的風險。
如果您需要在 Spring Boot 應用程式中執行 Servlet 內容初始化,則應註冊實作 org.springframework.boot.web.servlet.ServletContextInitializer
介面的 bean。單一 onStartup
方法提供對 ServletContext
的存取權,並且如果需要,可以輕鬆地用作現有 WebApplicationInitializer
的配接器。
ServletWebServerApplicationContext
在幕後,Spring Boot 為嵌入式 Servlet 容器支援使用不同類型的 ApplicationContext
。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 容器
常見的 Servlet 容器設定可以使用 Spring Environment
屬性來組態。通常,您會在 application.properties
或 application.yaml
檔案中定義屬性。
常見的伺服器設定包括
-
網路設定:用於傳入 HTTP 請求的接聽連接埠 (
server.port
)、要繫結的介面位址 (server.address
) 等。 -
工作階段設定:工作階段是否為持續性 (
server.servlet.session.persistent
)、工作階段逾時 (server.servlet.session.timeout
)、工作階段資料的位置 (server.servlet.session.store-dir
) 和工作階段 Cookie 組態 (server.servlet.session.cookie.*
)。 -
錯誤管理:錯誤頁面的位置 (
server.error.path
) 等。
Spring Boot 盡可能公開常見設定,但這並非總是可行。對於這些情況,專用的命名空間提供伺服器特定的自訂 (請參閱 server.tomcat
和 server.undertow
)。例如,可以使用嵌入式 Servlet 容器的特定功能來組態 存取記錄。
請參閱 ServerProperties 類別以取得完整清單。 |
SameSite Cookie
Web 瀏覽器可以使用 SameSite
Cookie 屬性來控制是否以及如何在跨網站請求中提交 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
檔案
-
屬性
-
YAML
server.servlet.session.cookie.same-site=none
server:
servlet:
session:
cookie:
same-site: "none"
如果您想要變更新增至 HttpServletResponse
的其他 Cookie 的 SameSite
屬性,可以使用 CookieSameSiteSupplier
。CookieSameSiteSupplier
會傳遞 Cookie
,並且可能會傳回 SameSite
值或 null
。
有許多便利的 Factory 和篩選器方法,您可以使用它們來快速比對特定的 Cookie。例如,新增下列 bean 將自動為名稱符合正則表達式 myapp.*
的所有 Cookie 套用 Lax
的 SameSite
。
-
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
組態屬性來自訂對應,如下列範例所示
-
屬性
-
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 文件以取得詳細資訊。
自動組態的自訂工具仍然會套用在您的自訂 Factory 上,因此請謹慎使用該選項。 |