Spring Boot 参考 Web Servlet Web 应用程序 Servlet Web 应用程序 如果您想构建基于 servlet 的 Web 应用程序,则可以利用 Spring Boot 对 Spring MVC 或 Jersey 的自动配置。 “Spring Web MVC 框架” Spring Web MVC 框架(通常称为“Spring MVC”)是一个丰富的“模型视图控制器”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) { /**/ return ServerResponse.ok().build(); } public ServerResponse getUserCustomers(ServerRequest request) { /**/ return ServerResponse.ok().build(); } public ServerResponse deleteUser(ServerRequest request) { /**/ return ServerResponse.ok().build(); } } 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 { return ServerResponse.ok().build() } fun getUserCustomers(request: ServerRequest?): ServerResponse { return ServerResponse.ok().build() } fun deleteUser(request: ServerRequest?): ServerResponse { return ServerResponse.ok().build() } } Spring MVC 是核心 Spring 框架的一部分,详细的信息可在 参考文档 中找到。还有几个涵盖 Spring MVC 的指南,可在 spring.io/guides 中找到。 您可以根据需要定义任意数量的 RouterFunction bean 来模块化路由的定义。如果您需要应用优先级,则可以对 bean 进行排序。 Spring MVC 自动配置 Spring Boot 为 Spring MVC 提供了自动配置,适用于大多数应用程序。它取代了对 @EnableWebMvc 的需求,并且两者不能同时使用。除了 Spring MVC 的默认值之外,自动配置还提供了以下功能 包含 ContentNegotiatingViewResolver 和 BeanNameViewResolver bean。 支持提供静态资源,包括对 WebJar 的支持(稍后在本文档中介绍 )。 自动注册 Converter、GenericConverter 和 Formatter bean。 支持 HttpMessageConverters(稍后在本文档中介绍 )。 自动注册 MessageCodesResolver(稍后在本文档中介绍 )。 静态 index.html 支持。 自动使用 ConfigurableWebBindingInitializer bean(稍后在本文档中介绍 )。 如果你想保留这些 Spring Boot MVC 自定义项并进行更多 MVC 自定义项(拦截器、格式化程序、视图控制器和其他功能),你可以添加你自己的 @Configuration 类,类型为 WebMvcConfigurer,但不使用 @EnableWebMvc。 如果你想提供 RequestMappingHandlerMapping、RequestMappingHandlerAdapter 或 ExceptionHandlerExceptionResolver 的自定义实例,同时保留 Spring Boot MVC 自定义项,你可以声明一个类型为 WebMvcRegistrations 的 bean,并使用它来提供这些组件的自定义实例。自定义实例将受到 Spring MVC 的进一步初始化和配置。为了参与,并在需要时覆盖后续处理,应该使用 WebMvcConfigurer。 如果你不想使用自动配置并希望完全控制 Spring MVC,请添加你自己的 @Configuration,并用 @EnableWebMvc 注释。或者,添加你自己的 @Configuration 注释的 DelegatingWebMvcConfiguration,如 @EnableWebMvc 的 Javadoc 中所述。 Spring MVC 转换服务 Spring MVC 使用与从 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) spring.mvc.format.time ofLocalizedTime(FormatStyle.SHORT) spring.mvc.format.date-time ofLocalizedDateTime(FormatStyle.SHORT) HttpMessageConverters Spring MVC 使用 HttpMessageConverter 接口来转换 HTTP 请求和响应。开箱即用地包含了明智的默认值。例如,可以使用 Jackson 库(使用 Jackson XML 扩展,如果可用,或者在 Jackson XML 扩展不可用时使用 JAXB)将对象自动转换为 JSON(通过使用 Jackson 库)或 XML。默认情况下,字符串会使用 UTF-8 编码。 上下文中存在的任何 HttpMessageConverter bean 都会被添加到转换器列表中。你还可以以相同的方式覆盖默认转换器。 如果你需要添加或自定义转换器,可以使用 Spring Boot 的 HttpMessageConverters 类,如下面的清单所示 Java Kotlin import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; @Configuration(proxyBeanMethods = false) public class MyHttpMessageConvertersConfiguration { @Bean public HttpMessageConverters customConverters() { HttpMessageConverter<?> additional = new AdditionalHttpMessageConverter(); HttpMessageConverter<?> another = new AnotherHttpMessageConverter(); return new HttpMessageConverters(additional, another); } } import org.springframework.boot.autoconfigure.http.HttpMessageConverters import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.http.converter.HttpMessageConverter @Configuration(proxyBeanMethods = false) class MyHttpMessageConvertersConfiguration { @Bean fun customConverters(): HttpMessageConverters { val additional: HttpMessageConverter<*> = AdditionalHttpMessageConverter() val another: HttpMessageConverter<*> = AnotherHttpMessageConverter() return HttpMessageConverters(additional, another) } } 为了进一步控制,你还可以对 HttpMessageConverters 进行子类化,并覆盖其 postProcessConverters 和/或 postProcessPartConverters 方法。当你想要重新排序或删除 Spring MVC 默认配置的某些转换器时,这会很有用。 MessageCodesResolver Spring MVC 有一种策略,用于生成错误代码以呈现绑定错误中的错误消息:MessageCodesResolver。如果你设置 spring.mvc.message-codes-resolver-format 属性 PREFIX_ERROR_CODE 或 POSTFIX_ERROR_CODE,Spring Boot 会为你创建一个(请参阅 DefaultMessageCodesResolver.Format 中的枚举)。 静态内容 默认情况下,Spring Boot 从类路径中的名为 /static(或 /public 或 /resources 或 /META-INF/resources)的目录或 ServletContext 的根目录提供静态内容。它使用 Spring MVC 中的 ResourceHttpRequestHandler,以便你可以通过添加自己的 WebMvcConfigurer 并覆盖 addResourceHandlers 方法来修改该行为。 在独立 Web 应用程序中,容器的默认 servlet 未启用。可以使用 server.servlet.register-default-servlet 属性来启用它。 默认 servlet 充当后备,如果 Spring 决定不处理内容,则从 ServletContext 的根目录提供内容。大多数情况下,这种情况不会发生(除非你修改默认 MVC 配置),因为 Spring 始终可以通过 DispatcherServlet 处理请求。 默认情况下,资源映射到 /**,但你可以使用 spring.mvc.static-path-pattern 属性对其进行调整。例如,可以按如下方式将所有资源重新定位到 /resources/** 属性 YAML spring.mvc.static-path-pattern=/resources/** spring: mvc: static-path-pattern: "/resources/**" 你还可以使用 spring.web.resources.static-locations 属性(用目录位置列表替换默认值)来自定义静态资源位置。根 servlet 上下文路径 "/" 也将自动添加为位置。 除了前面提到的“标准”静态资源位置之外,Webjars 内容是一个特例。默认情况下,如果以 Webjars 格式打包,则路径为 /webjars/** 的任何资源都将从 jar 文件中提供。可以使用 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 bean 的顺序定义,默认情况下如下所示 RouterFunctionMapping 使用 RouterFunction bean 声明的端点 RequestMappingHandlerMapping 在 @Controller bean 中声明的端点 WelcomePageHandlerMapping 欢迎页支持 自定义收藏夹图标 与其他静态资源一样,Spring Boot 会在已配置的静态内容位置中检查 favicon.ico。如果存在此类文件,它将自动用作应用程序的收藏夹图标。 路径匹配和内容协商 Spring MVC 可以通过查看请求路径并将其与应用程序中定义的映射进行匹配,将传入的 HTTP 请求映射到处理程序(例如,Controller 方法上的 @GetMapping 注释)。 Spring Boot 默认情况下选择禁用后缀模式匹配,这意味着诸如 "GET /projects/spring-boot.json" 的请求将不会与 @GetMapping("/projects/spring-boot") 映射匹配。这被认为是 Spring MVC 应用程序的最佳实践。此功能过去主要对未发送正确“Accept”请求标头的 HTTP 客户端有用;我们需要确保向客户端发送正确的 Content Type。如今,内容协商更加可靠。 还有其他方法可以处理不一致发送正确“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 将发送 404 未找到错误响应。若要改为抛出 NoHandlerFoundException,请将 configprop:spring.mvc.throw-exception-if-no-handler-found 设置为 true。请注意,默认情况下,静态内容的提供被映射到 /**,因此将为所有请求提供处理程序。若要抛出 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 包含对以下模板引擎的自动配置支持 FreeMarker Groovy Thymeleaf Mustache 如果可能,应避免使用 JSP。在将它们与嵌入式 Servlet 容器一起使用时,存在一些已知限制。 当您将其中一个模板引擎与默认配置一起使用时,您的模板将从 src/main/resources/templates 自动获取。 根据运行应用程序的方式,IDE 可能会以不同的方式对类路径进行排序。从其主方法在 IDE 中运行应用程序会产生与使用 Maven 或 Gradle 或从其打包的 jar 中运行应用程序时不同的排序。这会导致 Spring Boot 无法找到预期的模板。如果您遇到此问题,可以在 IDE 中重新排序类路径,以首先放置模块的类和资源。 错误处理 默认情况下,Spring Boot 提供了一个 /error 映射,以合理的方式处理所有错误,并且它在 servlet 容器中注册为“全局”错误页面。对于机器客户端,它会生成一个 JSON 响应,其中包含错误的详细信息、HTTP 状态和异常消息。对于浏览器客户端,有一个“白标签”错误视图,以 HTML 格式呈现相同的数据(要对其进行自定义,请添加一个解析为 error 的 View)。 如果您想自定义默认错误处理行为,则可以设置许多 server.error 属性。请参阅附录的 “服务器属性” 部分。 要完全替换默认行为,您可以实现 ErrorController 并注册该类型的 bean 定义,或添加类型为 ErrorAttributes 的 bean,以使用现有机制但替换内容。 BasicErrorController 可用作自定义 ErrorController 的基类。如果您想为新内容类型添加一个处理程序,这尤其有用(默认情况下,专门处理 text/html,并为其他所有内容提供后备)。为此,请扩展 BasicErrorController,添加一个具有 @RequestMapping 的公共方法,该方法具有 produces 属性,并创建您新类型的 bean。 从 Spring Framework 6.0 开始,RFC 7807 问题详细信息 得到支持。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 观察或 度量基础设施 记录。应用程序可以通过 在观察上下文中设置已处理的异常 来确保记录此类异常。 自定义错误页面 如果您想为给定的状态代码显示自定义 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。此抽象直接与底层嵌入式 Servlet 容器一起工作,即使您没有 Spring MVC DispatcherServlet 也可以工作。 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 的服务方法成功完成后提交响应。您应通过将 com.ibm.ws.webcontainer.invokeFlushAfterService 设置为 false 来禁用此行为。 CORS 支持 跨源资源共享 (CORS) 是 W3C 规范,由 大多数浏览器 实现,它允许您灵活地指定允许哪些类型的跨域请求,而不是使用 IFRAME 或 JSONP 等不太安全且功能较弱的方法。 从 4.2 版本开始,Spring MVC 支持 CORS。在 Spring Boot 应用程序中使用 控制器方法 CORS 配置和 @CrossOrigin 注释不需要任何特定配置。可以通过注册带有自定义 addCorsMappings(CorsRegistry) 方法的 WebMvcConfigurer bean 来定义 全局 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/**") } } } } JAX-RS 和 Jersey 如果您更喜欢 REST 端点的 JAX-RS 编程模型,则可以使用可用的实现之一来替代 Spring MVC。 Jersey 和 Apache CXF 开箱即用,效果很好。CXF 要求您在应用程序上下文中将它的 Servlet 或 Filter 注册为 @Bean。Jersey 有一些本机 Spring 支持,因此我们还为它提供了 Spring Boot 中的自动配置支持,以及一个 starter。 要开始使用 Jersey,请将 spring-boot-starter-jersey 作为依赖项包含进来,然后需要一个类型为 ResourceConfig 的 @Bean,在其中注册所有端点,如下例所示 import org.glassfish.jersey.server.ResourceConfig; import org.springframework.stereotype.Component; @Component public class MyJerseyConfig extends ResourceConfig { public MyJerseyConfig() { register(MyEndpoint.class); } } Jersey 对可执行归档的扫描支持相当有限。例如,它无法扫描在 完全可执行 jar 文件 中找到的包中的端点,或者在运行可执行 war 文件时无法扫描 WEB-INF/classes 中的端点。为避免此限制,不应使用 packages 方法,而应使用 register 方法单独注册端点,如前例所示。 对于更高级的自定义,您还可以注册任意数量实现 ResourceConfigCustomizer 的 bean。 所有已注册的端点都应为带有 HTTP 资源注释(@GET 和其他)的 @Components,如下例所示 import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import org.springframework.stereotype.Component; @Component @Path("/hello") public class MyEndpoint { @GET public String message() { return "Hello"; } } 由于 Endpoint 是 Spring @Component,因此其生命周期由 Spring 管理,您可以使用 @Autowired 注释来注入依赖项,并使用 @Value 注释来注入外部配置。默认情况下,Jersey servlet 已注册并映射到 /*。您可以通过将 @ApplicationPath 添加到 ResourceConfig 来更改映射。 默认情况下,Jersey 在名为 jerseyServletRegistration 的 ServletRegistrationBean 类型的 @Bean 中被设置为 servlet。默认情况下,servlet 会被延迟初始化,但你可以通过设置 spring.jersey.servlet.load-on-startup 来自定义该行为。你可以通过创建同名的 bean 来禁用或覆盖该 bean。你还可以通过设置 spring.jersey.type=filter 来使用过滤器代替 servlet(在这种情况下,要替换或覆盖的 @Bean 是 jerseyFilterRegistration)。该过滤器有一个 @Order,你可以使用 spring.jersey.filter.order 来设置它。在将 Jersey 用作过滤器时,必须存在一个将处理未被 Jersey 拦截的任何请求的 servlet。如果你的应用程序不包含这样的 servlet,你可能希望通过将 server.servlet.register-default-servlet 设置为 true 来启用默认 servlet。servlet 和过滤器注册都可以通过使用 spring.jersey.init.* 来指定属性映射来提供初始化参数。 嵌入式 Servlet 容器支持 对于 servlet 应用程序,Spring Boot 包括对嵌入式 Tomcat、Jetty 和 Undertow 服务器的支持。大多数开发人员使用合适的“Starter”来获取完全配置的实例。默认情况下,嵌入式服务器在端口 8080 上侦听 HTTP 请求。 Servlet、过滤器和侦听器 在使用嵌入式 servlet 容器时,你可以通过使用 Spring bean 或扫描 servlet 组件来注册 servlet、过滤器和所有侦听器(例如 HttpSessionListener)从 servlet 规范中注册 servlet、过滤器和侦听器。 将 Servlet、过滤器和侦听器注册为 Spring Bean 任何作为 Spring bean 的 Servlet、Filter 或 servlet *Listener 实例都会在嵌入式容器中注册。如果你希望在配置期间引用 application.properties 中的值,这可能特别方便。 默认情况下,如果上下文只包含一个 Servlet,它将映射到 /。在多个 servlet bean 的情况下,bean 名称用作路径前缀。过滤器映射到 /*。 如果基于约定的映射不够灵活,你可以使用 ServletRegistrationBean、FilterRegistrationBean 和 ServletListenerRegistrationBean 类来完全控制。 通常情况下,可以安全地将过滤器 bean 保留为无序状态。如果需要特定顺序,则应使用 @Order 为 Filter 添加注释,或使其实现 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 使用不同类型的 ApplicationContext 来支持嵌入式 servlet 容器。ServletWebServerApplicationContext 是一种特殊的 WebApplicationContext 类型,它通过搜索单个 ServletWebServerFactory bean 来引导自身。通常会自动配置 TomcatServletWebServerFactory、JettyServletWebServerFactory 或 UndertowServletWebServerFactory。 您通常不需要了解这些实现类。大多数应用程序都是自动配置的,并且会代表您创建适当的 ApplicationContext 和 ServletWebServerFactory。 在嵌入式容器设置中,ServletContext 会在应用程序上下文初始化期间发生的服务器启动过程中设置。由于此原因,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 容器 可以使用 Spring Environment 属性配置常见的 servlet 容器设置。通常,您会在 application.properties 或 application.yaml 文件中定义属性。 常见的服务器设置包括 网络设置:用于传入 HTTP 请求的侦听端口 (server.port)、要绑定的接口地址 (server.address) 等。 会话设置:会话是否持久 (server.servlet.session.persistent)、会话超时 (server.servlet.session.timeout)、会话数据的位置 (server.servlet.session.store-dir) 和会话 cookie 配置 (server.servlet.session.cookie.*)。 错误管理:错误页面的位置 (server.error.path) 等。 SSL HTTP 压缩 Spring Boot 尽可能多地公开常见设置,但这并不总是可行的。对于这些情况,专用命名空间提供特定于服务器的自定义项(请参阅 server.tomcat 和 server.undertow)。例如,访问日志可以使用嵌入式 servlet 容器的特定功能进行配置。 请参阅 ServerProperties 类以获取完整列表。 SameSite Cookie Web 浏览器可以使用 SameSite cookie 属性来控制在跨站点请求中是否以及如何提交 cookie。当属性缺失时,该属性与已开始更改所使用的默认值的现代 Web 浏览器尤其相关。 如果您想更改会话 Cookie 的 SameSite 属性,可以使用 server.servlet.session.cookie.same-site 属性。此属性受自动配置的 Tomcat、Jetty 和 Undertow 服务器支持。它还用于配置基于 Spring Session servlet 的 SessionRepository Bean。 例如,如果您希望会话 Cookie 具有 None 的 SameSite 属性,可以将以下内容添加到 application.properties 或 application.yaml 文件中 属性 YAML server.servlet.session.cookie.same-site=none server: servlet: session: cookie: same-site: "none" 如果您想更改添加到 HttpServletResponse 的其他 Cookie 上的 SameSite 属性,可以使用 CookieSameSiteSupplier。CookieSameSiteSupplier 会传递一个 Cookie,并且可以返回一个 SameSite 值或 null。 有一些方便的工厂和过滤器方法,您可以使用它们快速匹配特定 Cookie。例如,添加以下 Bean 将自动为所有名称与正则表达式 myapp.* 匹配的 Cookie 应用 Lax 的 SameSite。 Java Kotlin import org.springframework.boot.web.servlet.server.CookieSameSiteSupplier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration(proxyBeanMethods = false) public class MySameSiteConfiguration { @Bean public CookieSameSiteSupplier applicationCookieSameSiteSupplier() { return CookieSameSiteSupplier.ofLax().whenHasNameMatching("myapp.*"); } } import org.springframework.boot.web.servlet.server.CookieSameSiteSupplier import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @Configuration(proxyBeanMethods = false) class MySameSiteConfiguration { @Bean fun applicationCookieSameSiteSupplier(): CookieSameSiteSupplier { return CookieSameSiteSupplier.ofLax().whenHasNameMatching("myapp.*") } } 字符编码 可以使用 server.servlet.encoding.* 配置属性配置嵌入式 Servlet 容器的请求和响应处理的字符编码行为。 当请求的 Accept-Language 标头指示请求的语言环境时,Servlet 容器会自动将其映射到字符集。每个容器都提供默认语言环境到字符集的映射,您应该验证它们是否满足应用程序的需求。如果它们不满足,请使用 server.servlet.encoding.mapping 配置属性自定义映射,如下例所示 属性 YAML server.servlet.encoding.mapping.ko=UTF-8 server: servlet: encoding: mapping: ko: "UTF-8" 在前面的示例中,ko(韩语)语言环境已映射到 UTF-8。这等效于传统 war 部署的 web.xml 文件中的 <locale-encoding-mapping-list> 条目。 编程自定义 如果您需要以编程方式配置嵌入式 Servlet 容器,可以注册一个实现 WebServerFactoryCustomizer 接口的 Spring Bean。WebServerFactoryCustomizer 提供对 ConfigurableServletWebServerFactory 的访问,其中包括许多自定义设置器方法。以下示例显示了以编程方式设置端口 Java Kotlin import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory; import org.springframework.stereotype.Component; @Component public class MyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> { @Override public void customize(ConfigurableServletWebServerFactory server) { server.setPort(9000); } } import org.springframework.boot.web.server.WebServerFactoryCustomizer import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory import org.springframework.stereotype.Component @Component class MyWebServerFactoryCustomizer : WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> { override fun customize(server: ConfigurableServletWebServerFactory) { server.setPort(9000) } } TomcatServletWebServerFactory、JettyServletWebServerFactory 和 UndertowServletWebServerFactory 是 ConfigurableServletWebServerFactory 的专用变体,分别为 Tomcat、Jetty 和 Undertow 提供了额外的自定义设置器方法。以下示例显示了如何自定义 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。还提供了几个受保护的方法“挂钩”,以防您需要执行更奇特的操作。有关详细信息,请参阅 源代码文档。 自动配置的定制器仍然应用于您的自定义工厂,因此请谨慎使用该选项。 JSP 限制 在运行使用嵌入式 Servlet 容器(并打包为可执行存档)的 Spring Boot 应用程序时,JSP 支持存在一些限制。 对于 Jetty 和 Tomcat,如果您使用 war 打包,则应该可以正常工作。使用 java -jar 启动可执行 war 时,它将正常工作,并且还可以部署到任何标准容器。使用可执行 jar 时不支持 JSP。 Undertow 不支持 JSP。 创建自定义 error.jsp 页面不会覆盖 错误处理 的默认视图。应该改用 自定义错误页面。 Web 响应式 Web 应用程序