跳到主要内容
版本:3.5.10

Servlet Web 应用程序

QWen Max 中英对照 Servlet Web Applications

如果你想构建基于 servlet 的 Web 应用程序,可以利用 Spring Boot 对 Spring MVC 或 Jersey 的自动配置。

“Spring Web MVC 框架”

Spring Web MVC 框架(通常称为 “Spring MVC”)是一个功能丰富的 “模型-视图-控制器”(MVC)Web 框架。Spring MVC 允许你创建特殊的 @Controller@RestController Bean 来处理传入的 HTTP 请求。控制器中的方法通过使用 @RequestMapping 注解映射到 HTTP 请求。

以下代码展示了一个典型的 @RestController,用于提供 JSON 数据:

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

}

“WebMvc.fn” 是函数式变体,它将路由配置与请求的实际处理分离,如下例所示:

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.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) {
...
}

}

Spring MVC 是 Spring Framework 核心的一部分,详细信息可在参考文档中找到。此外,spring.io/guides 上也提供了多个涵盖 Spring MVC 的指南。

提示

你可以定义任意多个 RouterFunction Bean,以模块化的方式组织路由定义。如果需要应用优先级,可以对这些 Bean 进行排序。

Spring MVC 自动配置

Spring Boot 为 Spring MVC 提供了适用于大多数应用程序的自动配置。它取代了使用 @EnableWebMvc 的需求,且二者不能同时使用。除了 Spring MVC 的默认配置外,该自动配置还提供了以下特性:

如果你想保留这些 Spring Boot MVC 的自定义配置,并进一步进行更多 MVC 自定义(例如拦截器、格式化器、视图控制器以及其他功能),你可以添加一个类型为 WebMvcConfigurer 的自己的 @Configuration 类,但不要使用 @EnableWebMvc

如果你想提供自定义的 RequestMappingHandlerMappingRequestMappingHandlerAdapterExceptionHandlerExceptionResolver 实例,同时仍保留 Spring Boot 的 MVC 自定义配置,你可以声明一个类型为 WebMvcRegistrations 的 Bean,并使用它来提供这些组件的自定义实例。这些自定义实例将由 Spring MVC 进行进一步的初始化和配置。若要参与(并在需要时覆盖)该后续处理过程,应使用 WebMvcConfigurer

如果你不想使用自动配置,并希望完全控制 Spring MVC,可以添加你自己的 @Configuration 类,并用 @EnableWebMvc 进行注解。或者,如 @EnableWebMvc API 文档中所述,添加你自己的带有 @Configuration 注解的 DelegatingWebMvcConfiguration

Spring MVC Conversion Service

Spring MVC 使用的 ConversionService 与用于转换 application.propertiesapplication.yaml 文件中值的 ConversionService 不同。这意味着 PeriodDurationDataSize 的转换器不可用,且 @DurationUnit@DataSizeUnit 注解将被忽略。

如果你想自定义 Spring MVC 使用的 ConversionService,你可以提供一个带有 addFormatters 方法的 WebMvcConfigurer Bean。在该方法中,你可以注册任意你想要的转换器,也可以委托给 ApplicationConversionService 上提供的静态方法。

也可以使用 spring.mvc.format.* 配置属性来自定义转换。当未进行配置时,将使用以下默认值:

属性DateTimeFormatter格式化类型
spring.mvc.format.dateofLocalizedDate(FormatStyle.SHORT)java.util.DateLocalDate
spring.mvc.format.timeofLocalizedTime(FormatStyle.SHORT)java.time 的 LocalTimeOffsetTime
spring.mvc.format.date-timeofLocalizedDateTime(FormatStyle.SHORT)java.time 的 LocalDateTimeOffsetDateTimeZonedDateTime

HttpMessageConverters

Spring MVC 使用 HttpMessageConverter 接口来转换 HTTP 请求和响应。框架开箱即用地提供了合理的默认配置。例如,对象可以自动转换为 JSON(通过使用 Jackson 库)或 XML(如果可用,则使用 Jackson XML 扩展;否则使用 JAXB)。默认情况下,字符串以 UTF-8 编码。

任何存在于上下文中的 HttpMessageConverter Bean 都会被添加到转换器列表中。你也可以通过同样的方式覆盖默认的转换器。

如果你需要添加或自定义转换器,可以使用 Spring Boot 的 HttpMessageConverters 类,如下列代码所示:

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

}

为了进一步控制,你还可以继承 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 会从 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/** 可以按如下方式实现:

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"/>

spring.web.resources.chain.strategy.content.enabled=true
spring.web.resources.chain.strategy.content.paths=/**
备注

资源链接在模板中会在运行时被重写,这要归功于一个自动为 Thymeleaf 和 FreeMarker 配置的 ResourceUrlEncodingFilter。当你使用 JSP 时,需要手动声明此过滤器。其他模板引擎目前不支持自动配置,但可以通过自定义模板宏(macros)/辅助方法(helpers)并结合使用 ResourceUrlProvider 来实现。

当使用 JavaScript 模块加载器等工具动态加载资源时,重命名文件并不可行。因此,还支持其他策略,并且可以组合使用。一种 “fixed” 策略会在 URL 中添加一个静态的版本字符串,而不会更改文件名,如下例所示:

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

通过此配置,位于 "/js/lib/" 下的 JavaScript 模块使用固定版本策略("/v12/js/lib/mymodule.js"),而其他资源仍使用基于内容的版本策略(<link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>)。

更多支持的选项请参见 WebProperties.Resources

提示

此功能已在专门的 博客文章 和 Spring Framework 的参考文档 中进行了详尽描述。

欢迎页面

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")

spring.mvc.contentnegotiation.favor-parameter=true

或者,如果你更喜欢使用不同的参数名:

spring.mvc.contentnegotiation.favor-parameter=true
spring.mvc.contentnegotiation.parameter-name=myparam

大多数标准媒体类型都开箱即用,但你也可以定义新的媒体类型:

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 配置属性来配置该策略,如下例所示:

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 容器一起使用时,JSP 存在一些已知的限制

当你使用这些模板引擎之一并采用默认配置时,你的模板会自动从 src/main/resources/templates 目录下加载。

提示

根据你运行应用程序的方式,你的 IDE 可能会对 classpath 的顺序进行不同的排列。在 IDE 中通过主方法运行应用程序时的 classpath 顺序,与使用 Maven 或 Gradle 运行,或从打包好的 jar 文件运行时的顺序不同。这可能导致 Spring Boot 无法找到预期的模板。如果你遇到此问题,可以在 IDE 中调整 classpath 的顺序,将模块的 classes 和 resources 放在最前面。

错误处理

默认情况下,Spring Boot 提供了一个 /error 映射,以合理的方式处理所有错误,并在 Servlet 容器中注册为“全局”错误页面。对于机器客户端,它会生成一个 JSON 响应,其中包含错误详情、HTTP 状态和异常消息。对于浏览器客户端,则提供一个“白标”(whitelabel)错误视图,以 HTML 格式渲染相同的数据(若要自定义该视图,可添加一个解析为 errorView)。

如果你想要自定义默认的错误处理行为,可以设置多个 server.error 属性。参见附录中的 Server 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 文档,如下例所示:

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;
}

}

在前面的示例中,如果 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,如下例所示:

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;
}

}

你也可以使用常规的 Spring MVC 功能,例如 @ExceptionHandler 方法@ControllerAdvice。随后,ErrorController 会处理所有未被捕获的异常。

在 Spring MVC 之外映射错误页面

对于不使用 Spring MVC 的应用程序,你可以使用 ErrorPageRegistrar 接口直接注册 ErrorPage 实例。该抽象直接与底层的嵌入式 Servlet 容器协作,即使你没有 Spring MVC 的 DispatcherServlet,它也能正常工作。

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"));
}

}
备注

如果你注册了一个 ErrorPage,其路径最终由某个 Filter 处理(这在某些非 Spring 的 Web 框架中很常见,例如 Jersey 和 Wicket),那么该 Filter 必须显式注册为 ERROR 分发器,如下例所示:

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;
}

}

请注意,默认的 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 应用中使用 @CrossOrigin 注解进行 控制器方法级别的 CORS 配置 时,无需任何特殊配置。可以通过注册一个 WebMvcConfigurer bean 并自定义其 addCorsMappings(CorsRegistry) 方法来定义 全局 CORS 配置,如下例所示:

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/**");
}

};
}

}

JAX-RS 和 Jersey

如果你更倾向于使用 JAX-RS 编程模型来开发 REST 端点,可以选用现有的实现之一,而不使用 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 方法逐个注册端点。

对于更高级的自定义,你还可以注册任意数量的实现了 ResourceConfigCustomizer 的 Bean。

所有已注册的端点都应是带有 HTTP 资源注解(@GET 等)的 @Component,如下例所示:

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

import org.springframework.stereotype.Component;

@Component
@Path("/hello")
public class MyEndpoint {

@GET
public String message() {
return "Hello";
}

}

由于 @Endpoint 是一个 Spring @Component,其生命周期由 Spring 管理,你可以使用 @Autowired 注解注入依赖项,并使用 @Value 注解注入外部配置。默认情况下,Jersey servlet 会被注册并映射到 /*。你可以通过在 ResourceConfig 上添加 @ApplicationPath 来更改该映射。

默认情况下,Jersey 会以一个名为 jerseyServletRegistration、类型为 ServletRegistrationBean@Bean 形式配置为 servlet。默认情况下,该 servlet 是延迟初始化的,但你可以通过设置 spring.jersey.servlet.load-on-startup 来自定义该行为。你可以通过创建一个同名的 bean 来禁用或覆盖该默认 bean。你也可以通过设置 spring.jersey.type=filter 来使用过滤器(filter)代替 servlet(在这种情况下,需要替换或覆盖的 @BeanjerseyFilterRegistration)。该过滤器具有一个 @Order,你可以通过 spring.jersey.filter.order 进行设置。当 Jersey 作为过滤器使用时,必须存在一个 servlet 来处理所有未被 Jersey 拦截的请求。如果你的应用程序中没有这样的 servlet,你可能需要通过将 server.servlet.register-default-servlet 设置为 true 来启用默认 servlet。无论是 servlet 还是 filter 的注册,都可以通过使用 spring.jersey.init.* 来指定一个属性映射,从而传入初始化参数。

内嵌 Servlet 容器支持

对于 Servlet 应用,Spring Boot 内置支持嵌入式 TomcatJettyUndertow 服务器。大多数开发者使用相应的 starter 来获取一个完全配置好的实例。默认情况下,嵌入式服务器在端口 8080 上监听 HTTP 请求。

Servlets、Filters 和 Listeners

使用嵌入式 servlet 容器时,可以通过使用 Spring Bean 或扫描 servlet 组件的方式,注册 servlet、filter 以及 servlet 规范中的所有监听器(例如 HttpSessionListener)。

将 Servlet、Filter 和 Listener 注册为 Spring Bean

任何作为 Spring Bean 的 ServletFilter 或 servlet *Listener 实例都会被注册到内嵌容器中。如果你希望在配置期间引用 application.properties 中的值,这种方式会特别方便。

默认情况下,如果上下文中仅包含一个 Servlet,则将其映射到 /。在存在多个 Servlet Bean 的情况下,Bean 名称将用作路径前缀。过滤器则映射到 /*

如果基于约定的映射不够灵活,你可以使用 ServletRegistrationBeanFilterRegistrationBeanServletListenerRegistrationBean 类来实现完全控制。如果你更倾向于使用注解而非 ServletRegistrationBeanFilterRegistrationBean,也可以使用 @ServletRegistration@FilterRegistration 作为替代方案。

通常可以安全地让 Filter Bean 保持无序状态。如果需要特定的顺序,你应该在 Filter 上使用 @Order 注解,或者让它实现 Ordered 接口。你不能通过在 Filter 的 Bean 方法上添加 @Order 注解来配置该 Filter 的顺序。

如果你无法修改 Filter 类以添加 @Order 注解或实现 Ordered 接口,则必须为该 Filter 定义一个 FilterRegistrationBean,并使用 setOrder(int) 方法设置注册 Bean 的顺序。或者,如果你更喜欢使用注解,也可以使用 @FilterRegistration 并设置其 order 属性。

应避免将读取请求体的 Filter 配置为 Ordered.HIGHEST_PRECEDENCE,因为这可能会与应用程序的字符编码配置冲突。如果某个 Servlet Filter 对请求进行了包装(wraps the request),则应将其配置为顺序小于或等于 OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER

提示

要查看应用程序中每个 Filter 的顺序,请为 web 日志组 启用调试级别日志(logging.level.web=debug)。启动时将会记录已注册过滤器的详细信息,包括它们的顺序和 URL 模式。

注意

注册 Filter Bean 时需谨慎,因为它们在应用生命周期的非常早期就被初始化。如果你需要注册一个与其他 Bean 交互的 Filter,请考虑改用 DelegatingFilterProxyRegistrationBean

Servlet 上下文初始化

嵌入式 Servlet 容器不会直接执行 ServletContainerInitializer 接口或 Spring 的 WebApplicationInitializer 接口。这是一个有意为之的设计决策,旨在降低那些为在 WAR 内部运行而设计的第三方库破坏 Spring Boot 应用程序的风险。

如果你需要在 Spring Boot 应用程序中执行 Servlet 上下文初始化,你应该注册一个实现 ServletContextInitializer 接口的 Bean。其唯一的 onStartup 方法提供了对 ServletContext 的访问,并且在必要时可以轻松地用作现有 WebApplicationInitializer 的适配器。

初始化参数

可以使用 server.servlet.context-parameters.* 属性在 ServletContext 上配置初始化参数。例如,属性 server.servlet.context-parameters.com.example.parameter=example 将配置一个名为 com.example.parameterServletContext 初始化参数,其值为 example

扫描 Servlet、Filter 和监听器

使用嵌入式容器时,可以通过使用 @ServletComponentScan 启用对带有 @WebServlet@WebFilter@WebListener 注解的类的自动注册。

提示

@ServletComponentScan 在独立容器中无效,此时将使用容器内置的发现机制。

The ServletWebServerApplicationContext

在底层,Spring Boot 使用一种不同类型的 ApplicationContext 来支持内嵌的 Servlet 容器。ServletWebServerApplicationContext 是一种特殊的 WebApplicationContext,它通过搜索一个唯一的 ServletWebServerFactory Bean 来完成自身的引导。通常情况下,会自动配置一个 TomcatServletWebServerFactoryJettyServletWebServerFactoryUndertowServletWebServerFactory

备注

你通常不需要了解这些实现类。大多数应用程序都是自动配置的,相应的 ApplicationContextServletWebServerFactory 会自动为你创建。

在嵌入式容器设置中,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.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 Cookies

SameSite Cookie 属性可用于 Web 浏览器控制在跨站请求中是否以及如何提交 Cookie。该属性对于现代 Web 浏览器尤其重要,因为这些浏览器已经开始更改在缺少该属性时所使用的默认值。

如果你想更改会话 Cookie 的 SameSite 属性,可以使用 server.servlet.session.cookie.same-site 属性。该属性被自动配置的 Tomcat、Jetty 和 Undertow 服务器所支持。它也用于配置基于 Servlet 的 Spring Session SessionRepository Bean。

例如,如果你想让你的会话 Cookie 的 SameSite 属性为 None,可以在 application.propertiesapplication.yaml 文件中添加以下内容:

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

如果你想更改添加到 HttpServletResponse 中的其他 Cookie 的 SameSite 属性,可以使用 CookieSameSiteSupplierCookieSameSiteSupplier 会接收到一个 Cookie,并可返回一个 SameSite 值,或者返回 null

有许多便捷的工厂方法和过滤器方法可用于快速匹配特定的 Cookie。例如,添加以下 Bean 将自动为所有名称匹配正则表达式 myapp.* 的 Cookie 应用 SameSiteLax

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.*");
}

}

字符编码

可以使用 server.servlet.encoding.* 配置属性来配置嵌入式 Servlet 容器在处理请求和响应时的字符编码行为。

当请求的 Accept-Language 头部指明了请求的区域设置(locale)时,servlet 容器会自动将其映射到一个字符集(charset)。每个容器都提供了默认的区域设置到字符集的映射,你应该验证这些默认映射是否满足应用程序的需求。如果不满足,可以使用 server.servlet.encoding.mapping 配置属性来自定义这些映射,如下例所示:

server.servlet.encoding.mapping.ko=UTF-8

在前面的示例中,ko(韩语)区域设置已被映射为 UTF-8。这等同于在传统 war 部署的 web.xml 文件中的 <locale-encoding-mapping-list> 条目。

编程式自定义

如果你需要以编程方式配置嵌入式 servlet 容器,可以注册一个实现 WebServerFactoryCustomizer 接口的 Spring Bean。WebServerFactoryCustomizer 提供了对 ConfigurableServletWebServerFactory 的访问,该类包含许多用于自定义的 setter 方法。以下示例展示了如何以编程方式设置端口:

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

}

TomcatServletWebServerFactoryJettyServletWebServerFactoryUndertowServletWebServerFactoryConfigurableServletWebServerFactory 的专用变体,分别提供了额外的定制 setter 方法用于 Tomcat、Jetty 和 Undertow。以下示例展示了如何定制 TomcatServletWebServerFactory,以访问 Tomcat 特定的配置选项:

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()));
}

}

直接自定义 ConfigurableServletWebServerFactory

对于需要你扩展 ServletWebServerFactory 的更高级用例,你可以自行暴露一个该类型的 Bean。

为许多配置选项提供了 Setter 方法。如果你需要执行一些更特殊的操作,还提供了一些受保护的方法“钩子”。详情请参见 ConfigurableServletWebServerFactory 的 API 文档。

备注

自动配置的定制器仍会应用于你自定义的工厂,因此请谨慎使用该选项。

JSP 限制

在运行使用嵌入式 Servlet 容器(并打包为可执行归档文件)的 Spring Boot 应用程序时,JSP 支持存在一些限制。

  • 对于 Jetty 和 Tomcat,如果你使用 war 打包方式,应该可以正常工作。一个可执行的 war 文件在通过 java -jar 启动时能够运行,同时也可部署到任何标准容器中。但使用可执行 jar 时,JSP 不受支持。

  • Undertow 不支持 JSP。

  • 创建自定义的 error.jsp 页面不会覆盖 错误处理 的默认视图。应改用 自定义错误页面