Servlet Web 应用程序
如果你想构建基于 servlet 的 Web 应用程序,可以利用 Spring Boot 对 Spring MVC 或 Jersey 的自动配置。
“Spring Web MVC 框架”
Spring Web MVC 框架(通常简称为 “Spring MVC”)是一个功能丰富的 “模型-视图-控制器”(Model-View-Controller)Web 框架。Spring MVC 允许你创建特殊的 @Controller 或 @RestController Bean 来处理传入的 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 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 自定义(例如拦截器、格式化器、视图控制器以及其他功能),你可以添加一个类型为 WebMvcConfigurer 的 @Configuration 类,但 不要 使用 @EnableWebMvc。
如果你想提供自定义的 RequestMappingHandlerMapping、RequestMappingHandlerAdapter 或 ExceptionHandlerExceptionResolver 实例,同时仍保留 Spring Boot 的 MVC 自定义配置,你可以声明一个类型为 WebMvcRegistrations 的 Bean,并使用它来提供这些组件的自定义实例。这些自定义实例将由 Spring MVC 进行进一步的初始化和配置。若要参与(并在需要时覆盖)后续的处理过程,应使用 WebMvcConfigurer。
如果你不想使用自动配置,并希望完全控制 Spring MVC,可以添加你自己的 @Configuration 类,并用 @EnableWebMvc 进行注解。或者,也可以按照 @EnableWebMvc API 文档中的描述,添加你自己的带有 @Configuration 注解的 DelegatingWebMvcConfiguration。
Spring MVC Conversion Service
Spring MVC 使用的 ConversionService 与用于转换 application.properties 或 application.yaml 文件中值的 ConversionService 不同。这意味着 Period、Duration 和 DataSize 转换器不可用,且 @DurationUnit 与 @DataSizeUnit 注解将被忽略。
如果你想自定义 Spring MVC 所使用的 ConversionService,你可以提供一个带有 addFormatters 方法的 WebMvcConfigurer bean。在该方法中,你可以注册任意你喜欢的转换器,也可以委托给 ApplicationConversionService 上提供的静态方法。
也可以使用 spring.mvc.format.* 配置属性来自定义转换。当未进行配置时,将使用以下默认值:
| 属性 | DateTimeFormatter | 格式化类型 |
|---|---|---|
spring.mvc.format.date | ofLocalizedDate(FormatStyle.SHORT) | java.util.Date 和 LocalDate |
spring.mvc.format.time | ofLocalizedTime(FormatStyle.SHORT) | java.time 的 LocalTime 和 OffsetTime |
spring.mvc.format.date-time | ofLocalizedDateTime(FormatStyle.SHORT) | java.time 的 LocalDateTime、OffsetDateTime 和 ZonedDateTime |
HttpMessageConverters
Spring MVC 使用 HttpMessageConverter 接口来转换 HTTP 请求和响应。框架开箱即用提供了合理的默认配置。例如,对象可以自动转换为 JSON(通过使用 Jackson 库)或 XML(如果可用,则使用 Jackson XML 扩展;否则使用 JAXB)。默认情况下,字符串以 UTF-8 编码。
任何存在于上下文中的 HttpMessageConverter Bean 都会被添加到转换器列表中。你也可以通过同样的方式覆盖默认的转换器。
如果需要添加或自定义转换器,你可以将一个或多个 ClientHttpMessageConvertersCustomizer 或 ServerHttpMessageConvertersCustomizer 声明为 Bean。在其中,你可以选择是将转换器实例添加到默认转换器之前(addCustomConverter),还是覆盖某个特定的默认转换器(例如 withJsonConverter)。
参见以下列表中的示例:
- Java
- Kotlin
import java.text.SimpleDateFormat;
import tools.jackson.databind.json.JsonMapper;
import org.springframework.boot.http.converter.autoconfigure.ClientHttpMessageConvertersCustomizer;
import org.springframework.boot.http.converter.autoconfigure.ServerHttpMessageConvertersCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverters.ClientBuilder;
import org.springframework.http.converter.HttpMessageConverters.ServerBuilder;
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
@Configuration(proxyBeanMethods = false)
public class MyHttpMessageConvertersConfiguration {
@Bean
public ClientHttpMessageConvertersCustomizer myClientConvertersCustomizer() {
return (clientBuilder) -> clientBuilder.addCustomConverter(new AdditionalHttpMessageConverter())
.addCustomConverter(new AnotherHttpMessageConverter());
}
@Bean
public JacksonConverterCustomizer jacksonConverterCustomizer() {
JsonMapper jsonMapper = JsonMapper.builder().defaultDateFormat(new SimpleDateFormat("yyyy-MM")).build();
return new JacksonConverterCustomizer(jsonMapper);
}
// contribute a custom JSON converter to both client and server
static class JacksonConverterCustomizer
implements ClientHttpMessageConvertersCustomizer, ServerHttpMessageConvertersCustomizer {
private final JsonMapper jsonMapper;
JacksonConverterCustomizer(JsonMapper jsonMapper) {
this.jsonMapper = jsonMapper;
}
@Override
public void customize(ClientBuilder builder) {
builder.withJsonConverter(new JacksonJsonHttpMessageConverter(this.jsonMapper));
}
@Override
public void customize(ServerBuilder builder) {
builder.withJsonConverter(new JacksonJsonHttpMessageConverter(this.jsonMapper));
}
}
}
import org.springframework.boot.http.converter.autoconfigure.ClientHttpMessageConvertersCustomizer
import org.springframework.boot.http.converter.autoconfigure.ServerHttpMessageConvertersCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.converter.HttpMessageConverters
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter
import tools.jackson.databind.json.JsonMapper
import java.text.SimpleDateFormat
@Configuration(proxyBeanMethods = false)
class MyHttpMessageConvertersConfiguration {
@Bean
fun myClientConvertersCustomizer(): ClientHttpMessageConvertersCustomizer {
return ClientHttpMessageConvertersCustomizer { clientBuilder: HttpMessageConverters.ClientBuilder ->
clientBuilder
.addCustomConverter(AdditionalHttpMessageConverter())
.addCustomConverter(AnotherHttpMessageConverter())
}
}
@Bean
fun jacksonConverterCustomizer(): JacksonConverterCustomizer {
val jsonMapper = JsonMapper.builder()
.defaultDateFormat(SimpleDateFormat("yyyy-MM"))
.build()
return JacksonConverterCustomizer(jsonMapper)
}
// contribute a custom JSON converter to both client and server
class JacksonConverterCustomizer(private val jsonMapper: JsonMapper) :
ClientHttpMessageConvertersCustomizer, ServerHttpMessageConvertersCustomizer {
override fun customize(builder: HttpMessageConverters.ClientBuilder) {
builder.withJsonConverter(JacksonJsonHttpMessageConverter(this.jsonMapper))
}
override fun customize(builder: HttpMessageConverters.ServerBuilder) {
builder.withJsonConverter(JacksonJsonHttpMessageConverter(this.jsonMapper))
}
}
}
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 文件中,则会从这些 jar 文件中提供。该路径可以通过 spring.mvc.webjars-path-pattern 属性进行自定义。
如果你的应用程序被打包为 jar,请不要使用 src/main/webapp 目录。尽管该目录是一个常见的标准,但它仅适用于 war 打包方式;如果你生成的是 jar,大多数构建工具会默默地忽略该目录。
Spring Boot 还支持 Spring MVC 提供的高级资源处理功能,允许诸如缓存清除(cache-busting)静态资源或对 Webjars 使用与版本无关的 URL 等用例。
要使用与版本无关的 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 的版本号。
要使用缓存清除(cache busting),以下配置为所有静态资源设置了一个缓存清除方案,有效地在 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 时,你需要手动声明此过滤器。其他模板引擎目前不支持自动配置,但可以通过自定义模板宏(macros)/辅助方法(helpers)并结合使用 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 的顺序决定,默认顺序如下:
RouterFunctionMapping | 使用 RouterFunction Bean 声明的端点 |
|---|---|
RequestMappingHandlerMapping | 在 @Controller Bean 中声明的端点 |
WelcomePageHandlerMapping | 欢迎页支持 |
自定义 Favicon
与其他静态资源一样,Spring Boot 会在配置的静态内容位置中检查是否存在 favicon.ico 文件。如果存在该文件,它将被自动用作应用程序的 favicon。
路径匹配与内容协商
Spring MVC 可以通过查看请求路径并将其与应用程序中定义的映射(例如 Controller 方法上的 @GetMapping 注解)进行匹配,从而将传入的 HTTP 请求映射到相应的处理器。
Spring Boot 默认选择禁用后缀模式匹配(suffix pattern matching),这意味着像 "GET /projects/spring-boot.json" 这样的请求将不会匹配到 @GetMapping("/projects/spring-boot") 的映射。这被视为 Spring MVC 应用程序的最佳实践。该特性在过去主要用于那些未发送正确 "Accept" 请求头的 HTTP 客户端;我们需要确保向客户端发送正确的 Content Type。如今,内容协商(Content Negotiation)已经更加可靠。
还有其他方法可以处理那些未始终发送正确 “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 限制了某些路径模式变体的使用。此外,它还无法与为 DispatcherServlet 配置路径前缀(spring.mvc.servlet.path)一起使用。
该策略可以通过 spring.mvc.pathmatch.matching-strategy 配置属性进行配置,如下例所示:
- Properties
- YAML
spring.mvc.pathmatch.matching-strategy=ant-path-matcher
spring:
mvc:
pathmatch:
matching-strategy: "ant-path-matcher"
如果请求找不到对应的处理器(handler),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 中通过主方法运行应用程序时的 classpath 顺序,与使用 Maven 或 Gradle 运行,或从打包好的 jar 文件运行时的顺序不同。这可能导致 Spring Boot 无法找到预期的模板。如果你遇到此问题,可以在 IDE 中重新调整 classpath 的顺序,将模块的 classes 和 resources 放在最前面。
错误处理
默认情况下,Spring Boot 提供了一个 /error 映射,以合理的方式处理所有错误,并在 Servlet 容器中注册为“全局”错误页面。对于机器客户端,它会生成一个包含错误详情、HTTP 状态和异常消息的 JSON 响应。对于浏览器客户端,则提供一个“白标(whitelabel)”错误视图,以 HTML 格式渲染相同的数据(若要自定义该视图,可添加一个解析为 error 的 View)。
如果你想要自定义默认的错误处理行为,可以设置一些 spring.web.error 属性。参见附录中的 Web Properties 部分。
要完全替换默认行为,你可以实现 ErrorController 并注册该类型的 Bean 定义,或者添加一个类型为 ErrorAttributes 的 Bean,以使用现有机制但替换其内容。
BasicErrorController 可用作自定义 ErrorController 的基类。如果你希望为新的内容类型添加处理器(默认专门处理 text/html,并为其他所有类型提供回退机制),这将特别有用。为此,可继承 BasicErrorController,添加一个带有 @RequestMapping 注解的公共方法,并设置其 produces 属性,然后创建你新类型的 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
}
}
在前面的示例中,如果 MyException 是由与 SomeController 位于同一包中的控制器抛出的,则会使用 MyErrorBody POJO 的 JSON 表示形式,而不是 ErrorAttributes 的表示形式。
在某些情况下,控制器层面处理的错误不会被 Web Observations 或 指标基础设施 记录。应用程序可以通过在 Observation Context 上设置已处理的异常,确保这些异常被 Observations 记录下来。
自定义错误页面
如果你想为某个状态码显示自定义的 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.webmvc.autoconfigure.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.webmvc.autoconfigure.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.error.ErrorPage;
import org.springframework.boot.web.error.ErrorPageRegistrar;
import org.springframework.boot.web.error.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.error.ErrorPage
import org.springframework.boot.web.error.ErrorPageRegistrar
import org.springframework.boot.web.error.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"))
}
}
- 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 配置 时,无需任何特定配置。可以通过注册一个 WebMvcConfigurer Bean 并自定义其 addCorsMappings(CorsRegistry) 方法来定义 全局 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/**")
}
}
}
}
API 版本控制
Spring MVC 支持 API 版本控制,可用于随着时间的推移演进 HTTP API。同一个 @Controller 路径可以被多次映射,以支持不同版本的 API。
更多详情请参见 Spring Framework 的参考文档。
一旦添加了映射,你还需要额外配置 Spring MVC,使其能够使用随请求发送的任何版本信息。通常,版本信息会通过 HTTP 头、查询参数或作为路径的一部分进行传递。
要配置 Spring MVC,你可以使用一个 WebMvcConfigurer Bean 并重写 configureApiVersioning(…) 方法,或者也可以使用配置属性。
例如,以下代码将使用 X-Version HTTP 头来获取版本信息,并在未发送该头时默认使用 1.0.0。
- Properties
- YAML
spring.mvc.apiversion.default=1.0.0
spring.mvc.apiversion.use.header=X-Version
spring:
mvc:
apiversion:
default: 1.0.0
use:
header: X-Version
为了实现更完整的控制,你还可以定义 ApiVersionResolver、ApiVersionParser 和 ApiVersionDeprecationHandler 的 Bean,这些 Bean 将被注入到自动配置的 Spring MVC 配置中。
WebClient 和 RestClient 均支持 API 版本控制。详见 API Versioning。
JAX-RS 和 Jersey
如果你更喜欢使用 JAX-RS 编程模型来开发 REST 端点,可以选用其中一个可用的实现,而不是使用 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 方法单独注册各个端点。
对于更高级的自定义,你还可以注册任意数量的实现了 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](https://docs.spring.io/spring-boot/4.0.2/api/java/org/springframework/boot/actuate/endpoint/annotation/Endpoint.html) 是一个 Spring [@Component](https://docs.spring.io/spring-framework/docs/7.0.x/javadoc-api/org/springframework/stereotype/Component.html),其生命周期由 Spring 管理,你可以使用 [@Autowired](https://docs.spring.io/spring-framework/docs/7.0.x/javadoc-api/org/springframework/beans/factory/annotation/Autowired.html) 注解注入依赖项,并使用 [@Value](https://docs.spring.io/spring-framework/docs/7.0.x/javadoc-api/org/springframework/beans/factory/annotation/Value.html) 注解注入外部配置。默认情况下,Jersey servlet 会被注册并映射到 /*。你可以通过在你的 [ResourceConfig](https://javadoc.io/doc/org.glassfish.jersey.core/jersey-server/4.0.0/org/glassfish/jersey/server/ResourceConfig.html) 上添加 [@ApplicationPath](https://jakarta.ee/specifications/restful-ws/4.0/apidocs/jakarta/ws/rs/ApplicationPath.html) 来更改该映射。
默认情况下,Jersey 会以一个名为 jerseyServletRegistration 的 @Bean 形式配置为 servlet,该 Bean 的类型为 ServletRegistrationBean。默认情况下,该 servlet 是延迟初始化的,但你可以通过设置 spring.jersey.servlet.load-on-startup 来自定义此行为。你可以通过创建一个同名的 Bean 来禁用或覆盖该默认 Bean。
此外,你也可以通过设置 spring.jersey.type=filter 来使用过滤器(filter)替代 servlet(在这种情况下,需要替换或覆盖的 @Bean 名称为 jerseyFilterRegistration)。该过滤器具有一个 @Order,你可以通过 spring.jersey.filter.order 进行设置。
当 Jersey 作为过滤器使用时,必须存在一个 servlet 来处理所有未被 Jersey 拦截的请求。如果你的应用程序中没有这样的 servlet,你可能需要将 server.servlet.register-default-servlet 设置为 true 以启用默认 servlet。
无论是 servlet 还是 filter 的注册,都可以通过 spring.jersey.init.* 来指定一组属性,从而为其提供初始化参数。
内嵌 Servlet 容器支持
Servlets、Filters 和 Listeners
使用嵌入式 servlet 容器时,可以通过使用 Spring Bean 或扫描 servlet 组件的方式,注册 servlet、filter 以及 servlet 规范中的所有监听器(例如 HttpSessionListener)。
将 Servlet、Filter 和 Listener 注册为 Spring Bean
默认情况下,如果上下文中仅包含一个 Servlet,则将其映射到 /。在存在多个 Servlet Bean 的情况下,Bean 名称将用作路径前缀。过滤器则映射到 /*。
如果基于约定的映射不够灵活,你可以使用 ServletRegistrationBean、FilterRegistrationBean 和 ServletListenerRegistrationBean 类来实现完全控制。如果你更倾向于使用注解而不是 ServletRegistrationBean 和 FilterRegistrationBean,也可以使用 @ServletRegistration 和 @FilterRegistration 作为替代方案。
通常情况下,让 Filter Bean 保持无序是安全的。如果需要特定的顺序,你应该在 Filter 上使用 @Order 注解,或者让它实现 Ordered 接口。你不能通过在 Filter 的 Bean 方法上添加 @Order 注解来配置 Filter 的顺序。如果你无法修改 Filter 类以添加 @Order 注解或实现 Ordered 接口,则必须为该 Filter 定义一个 FilterRegistrationBean,并使用 setOrder(int) 方法设置注册 Bean 的顺序。或者,如果你更喜欢使用注解,也可以使用 @FilterRegistration 并设置其 order 属性。应避免将读取请求体的过滤器配置为 Ordered.HIGHEST_PRECEDENCE,因为这可能会与应用程序的字符编码配置冲突。如果某个 Servlet 过滤器包装了请求,则其顺序应配置为小于或等于 OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER。
注册 Filter Bean 时要小心,因为它们在应用程序生命周期的非常早期就被初始化。如果你需要注册一个与其他 Bean 交互的 Filter,请考虑改用 DelegatingFilterProxyRegistrationBean。
Servlet 上下文初始化
嵌入式 Servlet 容器不会直接执行 ServletContainerInitializer 接口或 Spring 的 WebApplicationInitializer 接口。这是一个有意的设计决策,旨在降低那些为在 WAR 内部运行而设计的第三方库破坏 Spring Boot 应用程序的风险。
如果你需要在 Spring Boot 应用程序中执行 Servlet 上下文初始化,你应该注册一个实现了 ServletContextInitializer 接口的 Bean。该接口唯一的 onStartup 方法提供了对 ServletContext 的访问,并且在必要时可以轻松地用作现有 WebApplicationInitializer 的适配器。
初始化参数
可以使用 server.servlet.context-parameters.* 属性在 ServletContext 上配置 Init 参数。例如,属性 server.servlet.context-parameters.com.example.parameter=example 将配置一个名为 com.example.parameter 的 ServletContext Init 参数,其值为 example。
扫描 Servlet、Filter 和监听器
使用嵌入式容器时,可以通过使用 @ServletComponentScan 来启用对带有 @WebServlet、@WebFilter 和 @WebListener 注解的类的自动注册。
@ServletComponentScan 在独立容器中无效,此时将使用容器内置的发现机制。
The ServletWebServerApplicationContext
在底层,Spring Boot 使用一种不同类型的 ApplicationContext 来支持内嵌的 servlet 容器。ServletWebServerApplicationContext 是一种特殊的 WebApplicationContext,它通过搜索一个单一的 ServletWebServerFactory bean 来引导自身。通常会自动配置一个 TomcatServletWebServerFactory 或 JettyServletWebServerFactory。
你通常不需要了解这些实现类。大多数应用程序都是自动配置的,相应的 ApplicationContext 和 ServletWebServerFactory 会自动为你创建。
在嵌入式容器设置中,ServletContext 是在服务器启动期间设置的,而服务器启动发生在应用上下文(ApplicationContext)初始化阶段。因此,在 ApplicationContext 中的 Bean 无法可靠地使用 ServletContext 进行初始化。一种解决方法是将 ApplicationContext 作为 Bean 的依赖项注入,并仅在需要时访问 ServletContext。另一种方法是在服务器启动完成后使用回调。这可以通过使用 ApplicationListener 监听 ApplicationStartedEvent 来实现,如下所示:
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 文件中定义这些属性。
常见的服务器设置包括:
Spring Boot 尽可能地暴露了通用设置,但并非总是可行。对于这些情况,专用的命名空间提供了针对特定服务器的自定义配置(参见 server.tomcat)。例如,访问日志 可以通过内嵌 Servlet 容器的特定功能进行配置。
完整列表请参见 ServerProperties 类。
SameSite Cookies
SameSite Cookie 属性可被 Web 浏览器用于控制在跨站请求中是否以及如何提交 Cookie。该属性对于现代 Web 浏览器尤其重要,因为这些浏览器已经开始更改在缺少该属性时所使用的默认值。
如果你想更改会话 Cookie 的 SameSite 属性,可以使用 server.servlet.session.cookie.same-site 属性。该属性被自动配置的 Tomcat 和 Jetty 服务器所支持。它也用于配置基于 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.server.servlet.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.server.servlet.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 头指明了该请求的区域设置(locale)时,servlet 容器会自动将其映射到一个字符集(charset)。每个容器都提供了默认的区域设置到字符集的映射关系,你应该验证这些默认映射是否满足应用程序的需求。如果不满足,可以使用 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.server.servlet.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.servlet.ConfigurableServletWebServerFactory
import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.stereotype.Component
@Component
class MyWebServerFactoryCustomizer : WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
override fun customize(server: ConfigurableServletWebServerFactory) {
server.setPort(9000)
}
}
TomcatServletWebServerFactory 和 JettyServletWebServerFactory 是 ConfigurableServletWebServerFactory 的专用变体,分别提供了针对 Tomcat 和 Jetty 的额外自定义 setter 方法。以下示例展示了如何自定义 TomcatServletWebServerFactory,以访问 Tomcat 特有的配置选项:
- Java
- Kotlin
import java.time.Duration;
import org.springframework.boot.tomcat.servlet.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.server.WebServerFactoryCustomizer
import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory
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 方法。如果你需要执行一些更特殊(exotic)的操作,还提供了一些受保护的“钩子”方法。详情请参见 ConfigurableServletWebServerFactory 的 API 文档。
自动配置的定制器仍会应用于你自定义的工厂,因此请谨慎使用该选项。
JSP 限制
在运行使用嵌入式 Servlet 容器(并打包为可执行归档文件)的 Spring Boot 应用程序时,JSP 支持存在一些限制。