28、开发 Web 应用程序

Spring Boot 非常适合用于开发 web 应用程序。您可以使用嵌入式 Tomcat、Jetty 或者 Undertow 来创建一个独立(self-contained)的 HTTP 服务器。大多数 web 应用程序使用 spring-boot-starter-web 模块来快速搭建和运行,您也可以选择使用 spring-boot-starter-webflux 模块来构建响应式(reactive) web 应用程序。

如果您尚未开发过 Spring Boot web 应用程序,则可以按照入门章节中的“Hello World!”示例进行操作。

28.1、Spring Web MVC 框架

Spring Web MVC 框架(通常简称“Spring MVC”)是一个富模型-视图-控制器的 web 框架。Spring MVC 允许您创建 @Controller 或者 @RestController bean 来处理传入的 HTTP 请求。控制器中的方法通过 @RequestMapping 注解映射到 HTTP。

以下是一个使用了 @RestController 来响应 JSON 数据的典型示例:

  1. @RestController
  2. @RequestMapping(value="/users")
  3. public class MyRestController {
  4. @RequestMapping(value="/{user}", method=RequestMethod.GET)
  5. public User getUser(@PathVariable Long user) {
  6. // ...
  7. }
  8. @RequestMapping(value="/{user}/customers", method=RequestMethod.GET)
  9. List<Customer> getUserCustomers(@PathVariable Long user) {
  10. // ...
  11. }
  12. @RequestMapping(value="/{user}", method=RequestMethod.DELETE)
  13. public User deleteUser(@PathVariable Long user) {
  14. // ...
  15. }
  16. }

Spring MVC 是 Spring Framework 核心的一部分,详细介绍可参考其参考文档spring.io/guides 还提供了几个 Spring MVC 相关的指南。

28.1.1、Spring MVC 自动配置

Spring Boot 提供了适用于大多数 Spring MVC 应用的自动配置(auto-configuration)。

自动配置在 Spring 默认功能上添加了以下功能:

  • 引入 ContentNegotiatingViewResolverBeanNameViewResolver bean。
  • 支持服务静态资源,包括对 WebJar 的支持(见下文)。
  • 自动注册 ConverterGenericConverterFormatter bean。
  • 支持 HttpMessageConverter(见下文)。
  • 自动注册 MessageCodesResolver(见下文)。
  • 支持静态 index.html。
  • 支持自定义 Favicon (见下文)。
  • 自动使用 ConfigurableWebBindingInitializer bean(见下文)。

如果您想保留 Spring Boot MVC 的功能,并且需要添加其他 MVC 配置(interceptor、formatter 和视图控制器等),可以添加自己的 WebMvcConfigurerAdapter 类型的 @Configuration 类,但不能@EnableWebMvc 注解。如果您想自定义 RequestMappingHandlerMappingRequestMappingHandlerAdapter 或者 ExceptionHandlerExceptionResolver 实例,可以声明一个 WebMvcRegistrationsAdapter 实例来提供这些组件。

如果您想完全掌控 Spring MVC,可以添加自定义注解了 @EnableWebMvc 的 @Configuration 配置类。

28.1.2、HttpMessageConverters

Spring MVC 使用 HttpMessageConverter 接口来转换 HTTP 的请求和响应。开箱即用功能包含了合适的默认值,比如对象可以自动转换为 JSON(使用 Jackson 库)或者 XML(优先使用 Jackson XML 扩展,其次为 JAXB)。字符串默认使用 UTF-8 编码。

如果您需要添加或者自定义转换器(converter),可以使用 Spring Boot 的 HttpMessageConverters 类:

  1. import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
  2. import org.springframework.context.annotation.*;
  3. import org.springframework.http.converter.*;
  4. @Configuration
  5. public class MyConfiguration {
  6. @Bean
  7. public HttpMessageConverters customConverters() {
  8. HttpMessageConverter<?> additional = ...
  9. HttpMessageConverter<?> another = ...
  10. return new HttpMessageConverters(additional, another);
  11. }
  12. }

上下文中的所有 HttpMessageConverter bean 都将被添加到转换器列表中。您也可以用这种方式来覆盖默认转换器。

28.1.3、自定义 JSON Serializer 和 Deserializer

如果您使用 Jackson 序列化和反序列化 JSON 数据,可能需要自己编写 JsonSerializerJsonDeserializer 类。自定义序列化器(serializer)的做法通常是通过一个模块来注册 Jackson, 然而 Spring Boot 提供了一个备选的 @JsonComponent 注解,它可以更加容易地直接注册 Spring Bean。

您可以直接在 JsonSerializer 或者 JsonDeserializer 实现上使用 @JsonComponent 注解。您也可以在将序列化器/反序列化器(deserializer)作为内部类的类上使用。例如:

  1. import java.io.*;
  2. import com.fasterxml.jackson.core.*;
  3. import com.fasterxml.jackson.databind.*;
  4. import org.springframework.boot.jackson.*;
  5. @JsonComponent
  6. public class Example {
  7. public static class Serializer extends JsonSerializer<SomeObject> {
  8. // ...
  9. }
  10. public static class Deserializer extends JsonDeserializer<SomeObject> {
  11. // ...
  12. }
  13. }

ApplicationContext 中所有的 @JsonComponent bean 将被自动注册到 Jackson 中,由于 @JsonComponent 使用 @Component 注解标记,因此组件扫描(component-scanning)规则将对其生效。

Spring Boot 还提供了 JsonObjectSerializerJsonObjectDeserializer 基类,它们在序列化对象时为标准的 Jackson 版本提供了有用的替代方案。有关详细信息,请参阅 Javadoc 中的 JsonObjectSerializerJsonObjectDeserializer

28.1.4、MessageCodesResolver

Spring MVC 有一个从绑定错误中生成错误码的策略,用于渲染错误信息:MessageCodesResolver。如果您设置了 spring.mvc.message-codes-resolver.format 属性值为 PREFIX_ERROR_CODEPOSTFIX_ERROR_CODE,Spring Boot 将为你创建该策略(请参阅 DefaultMessageCodesResolver.Format 中的枚举)。

28.1.5、静态内容

默认情况下,Spring Boot 将在 classpath 或者 ServletContext 根目录下从名为 /static/public/resources/META-INF/resources)目录中服务静态内容。它使用了 Spring MVC 的 ResourceHttpRequestHandler,因此您可以通过添加自己的 WebMvcConfigurerAdapter 并重写 addResourceHandlers 方法来修改此行为。

在一个独立的(stand-alone) web 应用程序中,来自容器的默认 servlet 也是被启用的,并充当一个回退支援,Spring 决定不处理 ServletContext 根目录下的静态资源,容器的默认 servlet 也将会处理。大多情况下,这是不会发生的(除非您修改了默认的 MVC 配置),因为 Spring 始终能通过 DispatcherServlet 来处理请求。

默认情况下,资源被映射到 /**,但可以通过 spring.mvc.static-path-pattern 属性调整。比如,将所有资源重定位到 /resources/**

  1. spring.mvc.static-path-pattern=/resources/**

您还可以使用 spring.resources.static-locations 属性来自定义静态资源的位置(使用一个目录位置列表替换默认值)。根 Servlet context path / 自动作为一个 location 添加进来。

除了上述提到的标准静态资源位置之外,还有一种特殊情况是用于 Webjar 内容。如果以 Webjar 格式打包,则所有符合 /webjars/** 的资源都将从 jar 文件中服务。

提示

如果您的应用程序要包成 jar,请不要使用 src/main/webapp 目录。虽然此目录是一个通用标准,但它适用于 war 打包,如果生成的是一个 jar,它将被绝大多数的构建工具所忽略。

Spring Boot 还支持 Spring MVC 提供的高级资源处理功能,允许使用例如静态资源缓存清除(cache busting)或者 Webjar 版本无关 URL。

要使用 Webjar 版本无关 URL 功能,只需要添加 webjars-locator-core 依赖。然后声明您的 Webjar,以 jQuery 为例,添加的 "/webjars/jquery/dist/jquery.min.js" 将变成 "/webjars/jquery/x.y.z/dist/jquery.min.js",其中 x.y.z 是 Webjar 的版本。

注意

如果您使用 JBoss,则需要声明 webjars-locator-jboss-vfs 依赖,而不是 webjars-locator-core,否则所有 Webjar 将被解析成 404

要使用缓存清除功能,以下配置为所有静态资源配置了一个缓存清除方案,实际上是在 URL 上添加了一个内容哈希,例如 <link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>

  1. pring.resources.chain.strategy.content.enabled=true
  2. spring.resources.chain.strategy.content.paths=/**

注意

模板中的资源链接在运行时被重写,这得益于 ResourceUrlEncodingFilter 为 Thymeleaf 和 FreeMarker 自动配置。在使用 JSP 时,您应该手动声明此过滤器。其他模板引擎现在还不会自动支持,但可以与自定义模板宏(macro)/helper 和 ResourceUrlProvider 结合使用。

当使用例如 Javascript 模块加载器动态加载资源时,重命名文件是不可选的。这也是为什么支持其他策略并且可以组合使用的原因。fixed策略将在 URL 中添加一个静态版本字符串,而不是更改文件名:

  1. spring.resources.chain.strategy.content.enabled=true
  2. spring.resources.chain.strategy.content.paths=/**
  3. spring.resources.chain.strategy.fixed.enabled=true
  4. spring.resources.chain.strategy.fixed.paths=/js/lib/
  5. spring.resources.chain.strategy.fixed.version=v12

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

有关更多支持选项,请参阅 ResourceProperties

提示

该功能已经在一个专门的博客文章和 Spring 框架的参考文档中进行了详细描述。

28.1.6、欢迎页面

Spring Boot 支持静态和模板化的欢迎页面。它首先在配置的静态内容位置中查找 index.html 文件。如果找不到,则查找 index 模板。如果找到其中任何一个,它将自动用作应用程序的欢迎页面。

28.1.7、自定义 Favicon

Spring Boot 在配置的静态内容位置和根 classpath 中查找 favicon.ico(按顺序)。如果该文件存在,则将被自动用作应用程序的 favicon。

28.1.8、路径匹配与内容协商

Spring MVC 可以通过查看请求路径并将其与应用程序中定义的映射相匹配,将传入的 HTTP 请求映射到处理程序(例如 Controller 方法上的 @GetMapping 注解)。

Spring Boot 默认选择禁用后缀模式匹配,这意味着像 "GET /projects/spring-boot.json" 这样的请求将不会与 @GetMapping("/projects/spring-boot") 映射匹配。这被视为是 Spring MVC 应用程序的最佳实践。此功能在过去对于 HTTP 客户端没有发送正确的 Accept 请求头的情况还是很有用的,我们需要确保将正确的内容类型发送给客户端。如今,内容协商(Content Negotiation)更加可靠。

还有其他方法可以处理 HTTP 客户端发送不一致 Accept 请求头问题。我们可以使用查询参数来确保像 "GET /projects/spring-boot?format=json" 这样的请求映射到 @GetMapping("/projects/spring-boot"),而不是使用后缀匹配:

  1. spring.mvc.contentnegotiation.favor-parameter=true
  2. # We can change the parameter name, which is "format" by default:
  3. # spring.mvc.contentnegotiation.parameter-name=myparam
  4. # We can also register additional file extensions/media types with:
  5. spring.mvc.contentnegotiation.media-types.markdown=text/markdown

如果您了解相关注意事项并仍希望应用程序使用后缀模式匹配,则需要以下配置:

  1. spring.mvc.contentnegotiation.favor-path-extension=true
  2. spring.mvc.pathmatch.use-suffix-pattern=true

或者,不打开所有后缀模式,仅打开支持已注册的后缀模式更加安全:

  1. spring.mvc.contentnegotiation.favor-path-extension=true
  2. spring.mvc.pathmatch.use-registered-suffix-pattern=true
  3. # You can also register additional file extensions/media types with:
  4. # spring.mvc.contentnegotiation.media-types.adoc=text/asciidoc

28.1.9、ConfigurableWebBindingInitializer

Spring MVC 使用一个 WebBindingInitializer 为特定的请求初始化 WebDataBinder。如果您创建了自己的 ConfigurableWebBindingInitializer @Bean,Spring Boot 将自动配置 Spring MVC 使用它。

28.1.10、模板引擎

除了 REST web 服务之外,您还可以使用 Spring MVC 来服务动态 HTML 内容。Spring MVC 支持多种模板技术,包括 Thymeleaf、FreeMarker 和 JSP。当然,许多其他模板引擎也有自己的 Spring MVC 集成。

Spring Boot 包含了以下的模板引擎的自动配置支持:

提示

如果可以,请尽量避免使用 JSP,当使用了内嵌 servlet 容器,会有几个已知限制

当您使用这些模板引擎的其中一个并附带了默认配置时,您的模板将从 src/main/resources/templates 自动获取。

提示

IntelliJ IDEA 根据您运行应用程序的方式来对 classpath 进行不同的排序。在 IDE 中通过 main 方法来运行应用程序将导致与使用 Maven 或 Gradle 或来以 jar 包方式引用程序的排序有所不同,可能会导致 Spring Boot 找不到 classpath 中的模板。如果您碰到到此问题,可以重新排序 IDE 的 classpath 来放置模块的 classes 和 resources 到首位。或者,您可以配置模板前缀来搜索 classpath 中的每一个 templates 目录,比如:classpath*:/templates/

28.1.11、错误处理

默认情况下,Spring Boot 提供了一个使用了比较合理的方式来处理所有错误的 /error 映射,其在 servlet 容器中注册了一个全局错误页面。对于机器客户端而言,它将产生一个包含错误、HTTP 状态和异常消息的 JSON 响应。对于浏览器客户端而言,将以 HTML 格式呈现相同数据的 whitelabel 错误视图(可添加一个解析到 errorView 进行自定义)。要完全替换默认行为,您可以实现 ErrorController 并注册该类型的 bean,或者简单地添加一个类型为 ErrorAttributes 的 bean 来替换内容,但继续使用现用机制。

提示

BasicErrorController 可以作为自定义 ErrorController 的基类,这非常有用,尤其是在您想添加一个新的内容类型(默认专门处理 text/html,并为其他内容提供后备)处理器的情况下。要做到这点,您只需要继承 BasicErrorController 并添加一个带有 produces 属性的 @RequestMapping 注解的公共方法,之后创建一个新类型的 bean。

您还可以定义一个带有 @ControllerAdvice 注解的类来自定义为特定控制器或异常类型返回的 JSON 文档:

  1. @ControllerAdvice(basePackageClasses = AcmeController.class)
  2. public class AcmeControllerAdvice extends ResponseEntityExceptionHandler {
  3. @ExceptionHandler(YourException.class)
  4. @ResponseBody
  5. ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {
  6. HttpStatus status = getStatus(request);
  7. return new ResponseEntity<>(new CustomErrorType(status.value(), ex.getMessage()), status);
  8. }
  9. private HttpStatus getStatus(HttpServletRequest request) {
  10. Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
  11. if (statusCode == null) {
  12. return HttpStatus.INTERNAL_SERVER_ERROR;
  13. }
  14. return HttpStatus.valueOf(statusCode);
  15. }
  16. }

以上示例中,如果同包下定义的控制器 AcmeController 抛出了 YourException,则将使用 CustomerErrorType 类型的 POJO 来代替 ErrorAttributes 做 JSON 呈现。

28.1.11.1、自定义错误页面

如果您想在自定义的 HTML 错误页面上显示给定的状态码,请将文件添加到 /error 文件夹中。错误页面可以是静态 HTML(添加在任意静态资源文件夹下) 或者使用模板构建。文件的名称应该是确切的状态码或者一个序列掩码。

例如,要将 404 映射到一个静态 HTML 文件,文件夹结构可以如下:

  1. src/
  2. +- main/
  3. +- java/
  4. | + <source code>
  5. +- resources/
  6. +- public/
  7. +- error/
  8. | +- 404.html
  9. +- <other public assets>

使用 FreeMarker 模板来映射所有 5xx 错误,文件夹的结构如下:

  1. src/
  2. +- main/
  3. +- java/
  4. | + <source code>
  5. +- resources/
  6. +- templates/
  7. +- error/
  8. | +- 5xx.ftl
  9. +- <other templates>

对于更复杂的映射,您还通过可以添加实现了 ErrorViewResolver 接口的 bean 来处理:

  1. public class MyErrorViewResolver implements ErrorViewResolver {
  2. @Override
  3. public ModelAndView resolveErrorView(HttpServletRequest request,
  4. HttpStatus status, Map<String, Object> model) {
  5. // Use the request or status to optionally return a ModelAndView
  6. return ...
  7. }
  8. }

您还可以使用常规的 Spring MVC 功能,比如 @ExceptionHandler 方法@ControllerAdvice。之后,ErrorController 将能接收任何未处理的异常。

28.1.11.2、映射到 Spring MVC 之外的错误页面

对于不使用 Spring MVC 的应用程序,您可以使用 ErrorPageRegistrar 接口来直接注册 ErrorPages。抽象部分直接与底层的内嵌 servlet 容器一起工作,即使您没有 Spring MVC DispatcherServlet 也能使用。

  1. @Bean
  2. public ErrorPageRegistrar errorPageRegistrar(){
  3. return new MyErrorPageRegistrar();
  4. }
  5. // ...
  6. private static class MyErrorPageRegistrar implements ErrorPageRegistrar {
  7. @Override
  8. public void registerErrorPages(ErrorPageRegistry registry) {
  9. registry.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/400"));
  10. }
  11. }

注意

如果您注册了一个 ErrorPage,它的路径最终由一个 Filter(例如,像一些非 Spring web 框架一样,比如 Jersey 和 Wicket)处理,则必须将 Filter 显式注册为一个 ERROR dispatcher,如下示例:

  1. @Bean
  2. public FilterRegistrationBean myFilter() {
  3. FilterRegistrationBean registration = new FilterRegistrationBean();
  4. registration.setFilter(new MyFilter());
  5. ...
  6. registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
  7. return registration;
  8. }

请注意,默认的 FilterRegistrationBean 不包含 ERROR 调度器(dispatcher)类型。

当心:当部署到 servlet 容器时,Spring Boot 使用其错误页面过滤器会将有错误状态的请求转发到相应的错误页面。如果尚未提交响应,则只能将请求转发到正确的错误页面。默认情况下,WebSphere Application Server 8.0 及更高版本在成功完成 servlet 的 service 方法后提交响应。您应该将 com.ibm.ws.webcontainer.invokeFlushAfterService 设置为 false 来禁用此行为。

28.1.12、Spring HATEOAS

如果您想开发一个使用超媒体(hypermedia)的 RESTful API,Spring Boot 提供的 Spring HATEOAS 自动配置在大多数应用程序都工作得非常好。自动配置取代了 @EnableHypermediaSupport 的需要,并注册了一些 bean,以便能轻松构建基于超媒体的应用程序,其包括了一个 LinkDiscoverers (用于客户端支持)和一个用于配置将响应正确呈现的 ObjectMapperObjectMapper 可以通过设置 spring.jackson.* 属性或者 Jackson2ObjectMapperBuilder bean (如果存在)自定义。

您可以使用 @EnableHypermediaSupport 来控制 Spring HATEOAS 的配置。请注意,这使得上述的自定义 ObjectMapper 被禁用。

28.1.13、CORS 支持

跨域资源共享(Cross-origin resource sharing,CORS)是大多数浏览器实现的一个 W3C 规范,其可允许您以灵活的方式指定何种跨域请求可以被授权,而不是使用一些不太安全和不太强大的方式(比如 IFRAME 或者 JSONP)。

Spring MVC 从 4.2 版本起开始支持 CORS。您可在 Spring Boot 应用程序中使用 @CrossOrigin 注解配置控制器方法启用 CORS。还可以通过注册一个 WebMvcConfigurer bean 并自定义 addCorsMappings(CorsRegistry) 方法来定义全局 CORS 配置

  1. @Configuration
  2. public class MyConfiguration {
  3. @Bean
  4. public WebMvcConfigurer corsConfigurer() {
  5. return new WebMvcConfigurer() {
  6. @Override
  7. public void addCorsMappings(CorsRegistry registry) {
  8. registry.addMapping("/api/**");
  9. }
  10. };
  11. }
  12. }

28.2、Spring WebFlux 框架

Spring WebFlux 是 Spring Framework 5.0 中新引入的一个响应式 Web 框架。与 Spring MVC 不同,它不需要 Servlet API,完全异步且无阻塞,并通过 Reactor 项目实现响应式流(Reactive Streams)规范。

Spring WebFlux 有两个版本:函数式和基于注解。基于注解的方式非常接近 Spring MVC 模型,如下所示:

  1. @RestController
  2. @RequestMapping("/users")
  3. public class MyRestController {
  4. @GetMapping("/{user}")
  5. public Mono<User> getUser(@PathVariable Long user) {
  6. // ...
  7. }
  8. @GetMapping("/{user}/customers")
  9. public Flux<Customer> getUserCustomers(@PathVariable Long user) {
  10. // ...
  11. }
  12. @DeleteMapping("/{user}")
  13. public Mono<User> deleteUser(@PathVariable Long user) {
  14. // ...
  15. }
  16. }

WebFlux.fn 为函数式调用方式,它将路由配置与请求处理分开,如下所示:

  1. @Configuration
  2. public class RoutingConfiguration {
  3. @Bean
  4. public RouterFunction<ServerResponse> monoRouterFunction(UserHandler userHandler) {
  5. return route(GET("/{user}").and(accept(APPLICATION_JSON)), userHandler::getUser)
  6. .andRoute(GET("/{user}/customers").and(accept(APPLICATION_JSON)), userHandler::getUserCustomers)
  7. .andRoute(DELETE("/{user}").and(accept(APPLICATION_JSON)), userHandler::deleteUser);
  8. }
  9. }
  10. @Component
  11. public class UserHandler {
  12. public Mono<ServerResponse> getUser(ServerRequest request) {
  13. // ...
  14. }
  15. public Mono<ServerResponse> getUserCustomers(ServerRequest request) {
  16. // ...
  17. }
  18. public Mono<ServerResponse> deleteUser(ServerRequest request) {
  19. // ...
  20. }
  21. }

WebFlux 是 Spring Framework 的一部分,详细信息可查看其参考文档

提示

您可以根据需要定义尽可能多的 RouterFunction bean 来模块化路由定义。如果需要设定优先级,Bean 可以指定顺序。

首先,将 spring-boot-starter-webflux 模块添加到您的应用程序中。

注意

在应用程序中同时添加 spring-boot-starter-webspring-boot-starter-webflux 模块会导致Spring Boot 自动配置 Spring MVC,而不是使用 WebFlux。这样做的原因是因为许多 Spring 开发人员将 spring-boot-starter-webflux 添加到他们的 Spring MVC 应用程序中只是为了使用响应式 WebClient。 您仍然可以通过设置 SpringApplication.setWebApplicationType(WebApplicationType.REACTIVE) 来强制执行您选择的应用程序类型。

28.2.1、Spring WebFlux 自动配置

Spring Boot 为 Spring WebFlux 提供自动配置,适用于大多数应用程序。

自动配置在 Spring 的默认基础上添加了以下功能:

  • HttpMessageReaderHttpMessageWriter 实例配置编解码器(稍后将介绍)。
  • 支持提供静态资源,包括对 WebJars 的支持(后面将介绍)。

如果你要保留 Spring Boot WebFlux 功能并且想要添加其他 WebFlux 配置,可以添加自己的 @Configuration 类,类型为 WebFluxConfigurer,但不包含 @EnableWebFlux

如果您想完全控制 Spring WebFlux,可以将 @EnableWebFlux 注解到自己的 @Configuration。

28.2.2、使用 HttpMessageReader 和 HttpMessageWriter 作为 HTTP 编解码器

Spring WebFlux 使用 HttpMessageReaderHttpMessageWriter 接口来转换 HTTP 的请求和响应。它们通过检测 classpath 中可用的类库,配置了 CodecConfigurer 生成合适的默认值。

Spring Boot 通过使用 CodecCustomizer 实例加强定制。例如,spring.jackson.* 配置 key 应用于 Jackson 编解码器。

如果需要添加或自定义编解码器,您可以创建一个自定义的 CodecCustomizer 组件,如下所示:

  1. import org.springframework.boot.web.codec.CodecCustomizer;
  2. @Configuration
  3. public class MyConfiguration {
  4. @Bean
  5. public CodecCustomizer myCodecCustomizer() {
  6. return codecConfigurer -> {
  7. // ...
  8. }
  9. }
  10. }

您还可以利用 Boot 的自定义 JSON 序列化器和反序列化器

28.2.3、静态内容

默认情况下,Spring Boot 将在 classpath 或者 ServletContext 根目录下从名为 /static/public/resources/META-INF/resources)目录中服务静态内容。它使用了 Spring WebFlux 的 ResourceWebHandler,因此您可以通过添加自己的 WebFluxConfigurer 并重写 addResourceHandlers 方法来修改此行为。

默认情况下,资源被映射到 /**,但可以通过 spring.webflux.static-path-pattern 属性调整。比如,将所有资源重定位到 /resources/**

  1. spring.webflux.static-path-pattern=/resources/**

您还可以使用 spring.resources.static-locations 属性来自定义静态资源的位置(使用一个目录位置列表替换默认值),如果这样做,默认的欢迎页面检测会切换到您自定义的位置。因此,如果启动时有任何其中一个位置存在 index.html,那么它将是应用程序的主页。

除了上述提到的标准静态资源位置之外,还有一种特殊情况是用于 Webjar 内容。如果以 Webjar 格式打包,则所有符合 /webjars/** 的资源都将从 jar 文件中服务。

提示

Spring WebFlux 应用程序并不严格依赖于 Servlet API,因此它们不能作为 war 文件部署,也不能使用 src/main/webapp 目录。

28.2.4、模板引擎

除了 REST web 服务之外,您还可以使用 Spring WebFlux 来服务动态 HTML 内容。Spring WebFlux 支持多种模板技术,包括 Thymeleaf、FreeMarker 和 Mustache。

Spring Boot 包含了以下的模板引擎的自动配置支持:

当您使用这些模板引擎的其中一个并附带了默认配置时,您的模板将从 src/main/resources/templates 自动获取。

28.2.5、错误处理

Spring Boot 提供了一个 WebExceptionHandler,它以合理的方式处理所有错误。它在处理顺序中的位置紧接在 WebFlux 提供的处理程序之前,这些处理器排序是最后的。对于机器客户端,它会生成一个 JSON 响应,其中包含错误详情、HTTP 状态和异常消息。对于浏览器客户端,有一个 whitelabel 错误处理程序,它以 HTML 格式呈现同样的数据。您还可以提供自己的 HTML 模板来显示错误(请参阅下一节)。

自定义此功能的第一步通常会沿用现有机制,但替换或扩充了错误内容。为此,您可以添加 ErrorAttributes 类型的 bean。

想要更改错误处理行为,可以实现 ErrorWebExceptionHandler 并注册该类型的 bean。因为 WebExceptionHandler 是一个非常底层的异常处理器,所以 Spring Boot 还提供了一个方便的 AbstractErrorWebExceptionHandler 来让你以 WebFlux 的方式处理错误,如下所示:

  1. public class CustomErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
  2. // Define constructor here
  3. @Override
  4. protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
  5. return RouterFunctions
  6. .route(aPredicate, aHandler)
  7. .andRoute(anotherPredicate, anotherHandler);
  8. }
  9. }

要获得更完整的功能,您还可以直接继承 DefaultErrorWebExceptionHandler 并覆盖相关方法。

28.2.5.1、自定义错误页面

如果您想在自定义的 HTML 错误页面上显示给定的状态码,请将文件添加到 /error 文件夹中。错误页面可以是静态 HTML(添加在任意静态资源文件夹下) 或者使用模板构建。文件的名称应该是确切的状态码或者一个序列掩码。

例如,要将 404 映射到一个静态 HTML 文件,文件夹结构可以如下:

  1. src/
  2. +- main/
  3. +- java/
  4. | + <source code>
  5. +- resources/
  6. +- public/
  7. +- error/
  8. | +- 404.html
  9. +- <other public assets>

使用 Mustache 模板来映射所有 5xx 错误,文件夹的结构如下:

  1. src/
  2. +- main/
  3. +- java/
  4. | + <source code>
  5. +- resources/
  6. +- templates/
  7. +- error/
  8. | +- 5xx.mustache
  9. +- <other templates>

28.2.6、Web 过滤器

Spring WebFlux 提供了一个 WebFilter 接口,可以通过实现该接口来过滤 HTTP 请求/响应消息交换。在应用程序上下文中找到的 WebFilter bean 将自动用于过滤每个消息交换。

如果过滤器的执行顺序很重要,则可以实现 Ordered 接口或使用 @Order 注解来指定顺序。Spring Boot 自动配置可能为您配置了几个 Web 过滤器。执行此操作时,将使用下表中的顺序:

Web 过滤器 顺序
MetricsWebFilter Ordered.HIGHEST_PRECEDENCE + 1
WebFilterChainProxy(Spring Security) -100
HttpTraceWebFilter Ordered.LOWEST_PRECEDENCE - 10

28.3、JAX-RS 与 Jersey

如果您喜欢 JAX-RS 编程模型的 REST 端点,则可以使用一个实现来替代 Spring MVC。JerseyApache CXF 都能开箱即用。CXF 要求在应用程序上下文中以 @Bean 的方式将它注册为一个 Servlet 或者 Filter。Jersey 有部分原生 Spring 支持,所以我们也在 starter 中提供了与 Spring Boot 整合的自动配置支持。

要使用 Jersey,只需要将 spring-boot-starter-jersey 作为依赖引入,然后您需要一个 ResourceConfig 类型的 @Bean,您可以在其中注册所有端点:

  1. @Component
  2. public class JerseyConfig extends ResourceConfig {
  3. public JerseyConfig() {
  4. register(Endpoint.class);
  5. }
  6. }

警告

Jersey 对于扫描可执行归档文件的支持是相当有限的。例如,它无法扫描一个完整的可执行 jar 文件中的端点,同样,当运行一个可执行的 war 文件时,它也无法扫描包中 WEB-INF/classes 下的端点。为了避免该限制,您不应该使用 packages 方法,应该使用上述的 register 方法来单独注册每一个端点。

您可以注册任意数量实现了 ResourceConfigCustomizer 的 bean,以实现更高级的定制化。

所有注册的端点都应注解了 @Components 并具有 HTTP 资源注解( @GET 等),例如:

  1. @Component
  2. @Path("/hello")
  3. public class Endpoint {
  4. @GET
  5. public String message() {
  6. return "Hello";
  7. }
  8. }

由于 Endpoint 是一个 Spring @Component,它的生命周期由 Spring 管理,您可以使用 @Autowired 注入依赖并使用 @Value 注入外部配置。默认情况下,Jersey servlet 将被注册并映射到 /*。您可以通过将 @ApplicationPath 添加到 ResourceConfig 来改变此行为。

默认情况下,Jersey 在 ServletRegistrationBean 类型的 @Bean 中被设置为一个名为 jerseyServletRegistration 的 Servlet。默认情况下,该 servlet 将被延迟初始化,您可以使用 spring.jersey.servlet.load-on-startup 自定义。您可以禁用或通过创建一个自己的同名 bean 来覆盖该 bean。您还可以通过设置 spring.jersey.type=filter 使用过滤器替代 servlet(该情况下, 替代或覆盖 @Bean 的为jerseyFilterRegistration)。该过滤器有一个 @Order,您可以使用 spring.jersey.filter.order 设置。可以使用 spring.jersey.init.* 指定一个 map 类型的 property 以给定 servlet 和过滤器的初始化参数。

这里有一个 Jersey 示例,您可以解如何设置。

28.4、内嵌 Servlet 容器支持

Spring Boot 包含了对内嵌 TomcatJettyUndertow 服务器的支持。大部分开发人员只需简单地使用对应的 Starter 来获取完整的配置实例。默认情况下,内嵌服务器将监听 8080 上的 HTTP 请求。

警告

如果您选择在 CentOS 使用 Tomcat,请注意,默认情况下,临时目录用于储存编译后的 JSP、上传的文件等。当您的应用程序运行时发生了故障,该目录可能会被 tmpwatch 删除。为了避免出现该情况,您可能需要自定义 tmpwatch 配置,使 tomcat.*目录不被删除,或者配置 server.tomcat.basedir 让 Tomcat 使用其他位置。

28.4.1、Servlet、Filter 与 Listener

使用内嵌 servlet 容器时,您可以使用 Spring bean 或者扫描方式来注册 Servlet 规范中的 Servlet、Filter 和所有监听器(比如 HttpSessionListener)。

28.4.1.1、将 Servlet、Filter 和 Listener 注册为 Spring Bean

任何 ServletFilter*Listener 的 Spring bean 实例都将被注册到内嵌容器中。如果您想引用 application.properties 中的某个值,这可能会特别方便。

默认情况下,如果上下文只包含单个 Servlet,它将映射到 /。在多个 Servlet bean 的情况下,bean 的名称将用作路径的前缀。Filter 将映射到 /*

如果基于约定配置的映射不够灵活,您可以使用 ServletRegistrationBeanFilterRegistrationBeanServletListenerRegistrationBean 类来完全控制。

Spring Boot 附带了许多可以定义 Filter bean 的自动配置。以下是部分过滤器及其执行顺序的(顺序值越低,优先级越高):

Servlet Filter 顺序
OrderedCharacterEncodingFilter Ordered.HIGHEST_PRECEDENCE
WebMvcMetricsFilter Ordered.HIGHEST_PRECEDENCE + 1
ErrorPageFilter Ordered.HIGHEST_PRECEDENCE + 1
HttpTraceFilter Ordered.LOWEST_PRECEDENCE - 10

通常 Filter bean 无序放置也是安全的。

如果需要指定顺序,则应避免在 Ordered.HIGHEST_PRECEDENCE 顺序点配置读取请求体的过滤器,因为它的字符编码可能与应用程序的字符编码配置不一致。如果一个 Servlet 过滤器包装了请求,则应使用小于或等于 OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER的顺序点对其进行配置。

28.4.2、Servlet 上下文初始化

内嵌 servlet 容器不会直接执行 Servlet 3.0+ 的 javax.servlet.ServletContainerInitializer 接口或 Spring 的 org.springframework.web.WebApplicationInitializer 接口。这是一个有意的设计决策,旨在降低在 war 内运行时第三方类库产生的风险,防止破坏 Sring Boot 应用程序。

如果您需要在 Spring Boot 应用程序中执行 servlet 上下文初始化,则应注册一个实现了 org.springframework.boot.context.embedded.ServletContextInitializer 接口的 bean。onStartup 方法提供了针对 ServletContext 的访问入口,如果需要,它可以容易作为现有 WebApplicationInitializer 的适配器。

28.4.2.1、扫描 Servlet、Filter 和 Listener

使用内嵌容器时,可以使用 @ServletComponentScan 启用带 @WebServlet@WebFilter@WebListener 注解的类自动注册。

提示

@ServletComponentScan 在独立(standalone)容器中不起作用,因容器将使用内置发现机制来代替。

28.4.3、ServletWebServerApplicationContext

Spring Boot 底层使用了一个不同的 ApplicationContext 类型来支持内嵌 servlet。ServletWebServerApplicationContext 是一个特殊 WebApplicationContext 类型,它通过搜索单个 ServletWebServerFactory bean 来引导自身。通常,TomcatServletWebServerFactoryJettyServletWebServerFactory 或者 UndertowServletWebServerFactory 中的一个将被自动配置。

注意

通常,你不需要知道这些实现类。大部分应用程序会自动配置,并为您创建合适的 ApplicationContextServletWebServerFactory

28.4.4、自定义内嵌 Servlet 容器

可以使用 Spring Environment 属性来配置通用的 servlet 容器设置。通常,您可以在 application.properties 文件中定义这些属性。

通用的服务器设置包括:

  • 网络设置:监听 HTTP 请求的端口(server.port),绑定接口地址到 server.address 等。
  • 会话设置:是否持久会话(server.session.persistence)、session 超时(server.session.timeout)、会话数据存放位置(server.session.store-dir)和 session-cookie 配置(server.session.cookie.*)。
  • 错误管理:错误页面位置(server.error.path)等。
  • SSL
  • HTTP 压缩

Spring Boot 尽可能暴露通用的设置,但并不总是都可以。针对这些情况,专用的命名空间为特定的服务器提供了自定义功能(请参阅 server.tomcatserver.undertow)。例如,您可以使用内嵌 servlet 容器的特定功能来配置访问日志

提示

有关完整的内容列表,请参阅 ServerProperties 类。

28.4.4.1、以编程方式自定义

如果您需要以编程的方式配置内嵌 servlet 容器,可以注册一个是实现了 WebServerFactoryCustomizer 接口的 Spring bean。WebServerFactoryCustomizer 提供了对 ConfigurableServletWebServerFactory 的访问入口,其中包含了许多自定义 setter 方法。以下示例使用了编程方式来设置端口:

  1. import org.springframework.boot.web.server.WebServerFactoryCustomizer;
  2. import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
  3. import org.springframework.stereotype.Component;
  4. @Component
  5. public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
  6. @Override
  7. public void customize(ConfigurableServletWebServerFactory server) {
  8. server.setPort(9000);
  9. }
  10. }

注意

TomcatServletWebServerFactoryJettyServletWebServerFactoryUndertowServletWebServerFactory 是 ConfigurableServletWebServerFactory 的具体子类,它们分别为 Tomcat、Jetty 和 Undertow 提供了额外的自定义 setter 方法。

28.4.4.2、直接自定义 ConfigurableServletWebServerFactory

如果上述的自定义方式太局限,您可以自己注册 TomcatServletWebServerFactoryJettyServletWebServerFactoryUndertowServletWebServerFactory bean。

  1. @Bean
  2. public ConfigurableServletWebServerFactory webServerFactory() {
  3. TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
  4. factory.setPort(9000);
  5. factory.setSessionTimeout(10, TimeUnit.MINUTES);
  6. factory.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/notfound.html"));
  7. return factory;
  8. }

Setter 方法提供了许多配置选项。还有几个 hook 保护方法供您深入定制。有关详细信息,请参阅源码文档

28.4.5、JSP 局限

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

  • 如果您使用 war 打包,在 Jetty 和 Tomcat 中可以正常工作,使用 java -jar 启动时,可执行的 war 可正常使用,并且还可以部署到任何标准容器。使用可执行 jar 时不支持 JSP。
  • Undertow 不支持 JSP。
  • 创建自定义的 error.jsp 页面不会覆盖默认错误处理视图,应该使用自定义错误页面来代替。

这里有一个 JSP 示例,您可以了解到如何配置。

28.5、内嵌响应式服务器支持

Spring Boot 包括对以下内嵌响应式 Web 服务器的支持:Reactor Netty、Tomcat、Jetty 和 Undertow。大多数开发人员使用对应的 Starter 来获取一个完全配置的实例。默认情况下,内嵌服务器在 8080 端口上监听 HTTP 请求。

28.6、响应式服务器资源配置

在自动配置 Reactor Netty 或 Jetty 服务器时,Spring Boot 将创建特定的 bean 为服务器实例提供 HTTP 资源:ReactorResourceFactoryJettyResourceFactory

默认情况下,这些资源也将与 Reactor Netty 和 Jetty 客户端共享以获得最佳性能,具体如下:

  • 用于服务器和客户端的的相同技术
  • 客户端实例使用了 Spring Boot 自动配置的 WebClient.Builder bean 构建。

开发人员可以通过提供自定义的 ReactorResourceFactoryJettyResourceFactory bean 来重写 Jetty 和 Reactor Netty 的资源配置 —— 将应用于客户端和服务器。

您可以在 WebClient Runtime 章节中了解有关客户端资源配置的更多内容。