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 的預設值之外,自動組態還提供下列功能

  • 包含 ContentNegotiatingViewResolverBeanNameViewResolver beans。

  • 支援提供靜態資源,包括支援 WebJars(稍後在本文檔中涵蓋)。

  • 自動註冊 ConverterGenericConverterFormatter beans。

  • 支援 HttpMessageConverters(稍後在本文檔中涵蓋)。

  • 自動註冊 MessageCodesResolver(稍後在本文檔中涵蓋)。

  • 靜態 index.html 支援。

  • 自動使用 ConfigurableWebBindingInitializer bean(稍後在本文檔中涵蓋)。

如果您想要保留這些 Spring Boot MVC 自訂設定,並進行更多MVC 自訂設定(攔截器、格式器、視圖控制器和其他功能),您可以新增自己的 @Configuration 類別,類型為 WebMvcConfigurer,但不使用 @EnableWebMvc

如果您想要提供 RequestMappingHandlerMappingRequestMappingHandlerAdapterExceptionHandlerExceptionResolver 的自訂實例,並且仍然保留 Spring Boot MVC 自訂設定,您可以宣告類型為 WebMvcRegistrations 的 bean,並使用它來提供這些組件的自訂實例。自訂實例將受到 Spring MVC 的進一步初始化和組態。若要參與(如果需要,覆寫)後續處理,應使用 WebMvcConfigurer

如果您不想使用自動組態,並且想要完全控制 Spring MVC,請新增您自己的 @Configuration 並使用 @EnableWebMvc 註解。或者,依照 @EnableWebMvc API 文件中的說明,新增您自己的使用 @Configuration 註解的 DelegatingWebMvcConfiguration

Spring MVC 轉換服務

Spring MVC 使用不同的 ConversionService,與用於從您的 application.propertiesapplication.yaml 檔案轉換值的 ConversionService 不同。這表示 PeriodDurationDataSize 轉換器不可用,並且 @DurationUnit@DataSizeUnit 註解將被忽略。

如果您想要自訂 Spring MVC 使用的 ConversionService,您可以提供具有 addFormatters 方法的 WebMvcConfigurer bean。從這個方法中,您可以註冊任何您喜歡的轉換器,或者您可以委派給 ApplicationConversionService 上可用的靜態方法。

也可以使用 spring.mvc.format.* 組態屬性自訂轉換。當未組態時,將使用下列預設值

屬性 DateTimeFormatter 格式

spring.mvc.format.date

ofLocalizedDate(FormatStyle.SHORT)

java.util.Datejava.time.LocalDate

spring.mvc.format.time

ofLocalizedTime(FormatStyle.SHORT)

java.time 的 LocalTimeOffsetTime

spring.mvc.format.date-time

ofLocalizedDateTime(FormatStyle.SHORT)

java.time 的 LocalDateTimeOffsetDateTimeZonedDateTime

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_CODEPOSTFIX_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 Framework 的參考文件中詳細說明。

歡迎頁面

Spring Boot 支援靜態和範本化的歡迎頁面。它首先在組態的靜態內容位置中尋找 index.html 檔案。如果找不到,則會尋找 index 範本。如果找到任一項,它會自動用作應用程式的歡迎頁面。

這僅作為應用程式定義的實際索引路由的後備機制。排序由 HandlerMapping beans 的順序定義,依預設順序如下

RouterFunctionMapping

使用 RouterFunction beans 宣告的端點

RequestMappingHandlerMapping

@Controller beans 中宣告的端點

WelcomePageHandlerMapping

歡迎頁面支援

自訂 Favicon

與其他靜態資源一樣,Spring Boot 會在組態的靜態內容位置中檢查 favicon.ico。如果存在此類檔案,則會自動用作應用程式的 favicon。

路徑比對和內容協商

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 格式呈現相同的資料(若要自訂它,請新增解析為 errorView)。

如果您想要自訂預設錯誤處理行為,可以設定許多 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 支援

跨來源資源共享 (CORS) 是一種 W3C 規範,由 大多數瀏覽器 實作,可讓您以彈性的方式指定授權哪些類型的跨網域請求,而不是使用安全性較低且功能較弱的方法,例如 IFRAME 或 JSONP。

從 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。JerseyApache CXF 在開箱即用的情況下運作良好。CXF 需要您將其 ServletFilter 註冊為應用程式內容中的 @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 會設定為名為 jerseyServletRegistrationServletRegistrationBean 類型的 @Bean 中的 Servlet。依預設,Servlet 會延遲初始化,但您可以透過設定 spring.jersey.servlet.load-on-startup 來自訂該行為。您可以透過建立一個同名的 bean 來停用或覆寫該 bean。您也可以透過設定 spring.jersey.type=filter 來使用篩選器來取代 Servlet(在這種情況下,要取代或覆寫的 @BeanjerseyFilterRegistration)。篩選器具有 @Order,您可以使用 spring.jersey.filter.order 進行設定。當使用 Jersey 作為篩選器時,必須存在一個 Servlet 來處理任何未被 Jersey 攔截的請求。如果您的應用程式不包含此類 Servlet,您可能需要透過將 server.servlet.register-default-servlet 設定為 true 來啟用預設 Servlet。Servlet 和篩選器註冊都可以使用 spring.jersey.init.* 來指定屬性對應,從而給定初始化參數。

嵌入式 Servlet 容器支援

對於 Servlet 應用程式,Spring Boot 包含對嵌入式 TomcatJettyUndertow 伺服器的支援。大多數開發人員使用適當的 Starter 來取得完整組態的執行個體。依預設,嵌入式伺服器會接聽連接埠 8080 上的 HTTP 請求。

Servlet、篩選器和接聽器

當使用嵌入式 Servlet 容器時,您可以透過使用 Spring bean 或掃描 Servlet 元件來註冊 Servlet 規範中的 Servlet、篩選器和所有接聽器(例如 HttpSessionListener)。

將 Servlet、篩選器和接聽器註冊為 Spring Bean

任何作為 Spring bean 的 ServletFilter 或 Servlet *Listener 執行個體都會在嵌入式容器中註冊。如果您想要在組態期間參考 application.properties 中的值,這會特別方便。

依預設,如果內容只包含單一 Servlet,則會對應到 /。在多個 Servlet bean 的情況下,bean 名稱會用作路徑前置詞。篩選器會對應到 /*

如果基於慣例的對應不夠彈性,您可以使用 ServletRegistrationBeanFilterRegistrationBeanServletListenerRegistrationBean 類別來進行完整控制。

通常可以安全地保持篩選器 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 的配接器。

掃描 Servlet、篩選器和接聽器

當使用嵌入式容器時,可以使用 @ServletComponentScan 啟用自動註冊使用 @WebServlet@WebFilter@WebListener 註解的類別。

@ServletComponentScan 在獨立容器中無效,在獨立容器中,會改用容器的內建探索機制。

ServletWebServerApplicationContext

在幕後,Spring Boot 為嵌入式 Servlet 容器支援使用不同類型的 ApplicationContextServletWebServerApplicationContext 是一種特殊類型的 WebApplicationContext,它透過搜尋單一 ServletWebServerFactory bean 來引導自身。通常,TomcatServletWebServerFactoryJettyServletWebServerFactoryUndertowServletWebServerFactory 已自動組態。

您通常不需要知道這些實作類別。大多數應用程式都是自動組態的,並且會代表您建立適當的 ApplicationContextServletWebServerFactory

在嵌入式容器設定中,ServletContext 會設定為伺服器啟動的一部分,這發生在應用程式內容初始化期間。因此,ApplicationContext 中的 bean 無法使用 ServletContext 可靠地初始化。解決此問題的一種方法是將 ApplicationContext 作為 bean 的相依性注入,並且僅在需要時存取 ServletContext。另一種方法是在伺服器啟動後使用回呼。這可以使用接聽 ApplicationStartedEventApplicationListener 來完成,如下所示

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.propertiesapplication.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) 等。

  • SSL

  • HTTP 壓縮

Spring Boot 盡可能公開常見設定,但這並非總是可行。對於這些情況,專用的命名空間提供伺服器特定的自訂 (請參閱 server.tomcatserver.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.propertiesapplication.yaml 檔案

  • 屬性

  • YAML

server.servlet.session.cookie.same-site=none
server:
  servlet:
    session:
      cookie:
        same-site: "none"

如果您想要變更新增至 HttpServletResponse 的其他 Cookie 的 SameSite 屬性,可以使用 CookieSameSiteSupplierCookieSameSiteSupplier 會傳遞 Cookie,並且可能會傳回 SameSite 值或 null

有許多便利的 Factory 和篩選器方法,您可以使用它們來快速比對特定的 Cookie。例如,新增下列 bean 將自動為名稱符合正則表達式 myapp.* 的所有 Cookie 套用 LaxSameSite

  • 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)
	}

}

TomcatServletWebServerFactoryJettyServletWebServerFactoryUndertowServletWebServerFactoryConfigurableServletWebServerFactory 的專用變體,它們分別具有用於 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 上,因此請謹慎使用該選項。

JSP 限制

當執行使用嵌入式 Servlet 容器 (並封裝為可執行封存檔) 的 Spring Boot 應用程式時,JSP 支援存在一些限制。

  • 使用 Jetty 和 Tomcat 時,如果您使用 WAR 封裝,它應該可以運作。使用 java -jar 啟動可執行 WAR 將會運作,並且也可以部署到任何標準容器。使用可執行 JAR 時,不支援 JSP。

  • Undertow 不支援 JSP。

  • 建立自訂 error.jsp 頁面不會覆寫 錯誤處理 的預設檢視。應改用自訂錯誤頁面