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。
以下代码显示了一个典型的提供 JSON 数据的 @RestController
-
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 自定义(拦截器、格式化程序、视图控制器和其他功能),您可以添加您自己的@Configuration
类型的类WebMvcConfigurer
,但**不要**使用@EnableWebMvc
。
如果您想提供RequestMappingHandlerMapping
、RequestMappingHandlerAdapter
或ExceptionHandlerExceptionResolver
的自定义实例,并且仍然保留 Spring Boot MVC 自定义项,您可以声明一个WebMvcRegistrations
类型的bean并使用它来提供这些组件的自定义实例。自定义实例将受到 Spring MVC 的进一步初始化和配置。为了参与,并如果需要,覆盖后续处理,应该使用WebMvcConfigurer
。
如果您不想使用自动配置并想完全控制 Spring MVC,请添加您自己的用@EnableWebMvc
注释的@Configuration
。或者,添加您自己的@Configuration
注释的DelegatingWebMvcConfiguration
,如@EnableWebMvc
API 文档中所述。
Spring MVC 类型转换服务
Spring MVC 使用的ConversionService
与用于从您的application.properties
或application.yaml
文件转换值的ConversionService
不同。这意味着Period
、Duration
和DataSize
转换器不可用,并且@DurationUnit
和@DataSizeUnit
注解将被忽略。
如果您想自定义 Spring MVC 使用的ConversionService
,您可以提供一个带有addFormatters
方法的WebMvcConfigurer
bean。通过此方法,您可以注册任何您喜欢的转换器,或者您可以委托给ApplicationConversionService
上可用的静态方法。
也可以使用spring.mvc.format.*
配置属性自定义转换。未配置时,将使用以下默认值
属性 | DateTimeFormatter |
格式 |
---|---|---|
|
|
|
|
|
java.time 的 |
|
|
java.time 的 |
HttpMessageConverters
Spring MVC 使用HttpMessageConverter
接口转换 HTTP 请求和响应。开箱即用地包含合理的默认值。例如,对象可以自动转换为 JSON(使用 Jackson 库)或 XML(如果可用,则使用 Jackson XML 扩展,或者如果 Jackson XML 扩展不可用,则使用 JAXB)。默认情况下,字符串以UTF-8
编码。
上下文中存在的任何HttpMessageConverter
bean 都将添加到转换器列表中。您也可以以相同的方式覆盖默认转换器。
如果您需要添加或自定义转换器,您可以使用 Spring Boot 的HttpMessageConverters
类,如下所示
-
Java
-
Kotlin
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
@Configuration(proxyBeanMethods = false)
public class MyHttpMessageConvertersConfiguration {
@Bean
public HttpMessageConverters customConverters() {
HttpMessageConverter<?> additional = new AdditionalHttpMessageConverter();
HttpMessageConverter<?> another = new AnotherHttpMessageConverter();
return new HttpMessageConverters(additional, another);
}
}
import org.springframework.boot.autoconfigure.http.HttpMessageConverters
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.converter.HttpMessageConverter
@Configuration(proxyBeanMethods = false)
class MyHttpMessageConvertersConfiguration {
@Bean
fun customConverters(): HttpMessageConverters {
val additional: HttpMessageConverter<*> = AdditionalHttpMessageConverter()
val another: HttpMessageConverter<*> = AnotherHttpMessageConverter()
return HttpMessageConverters(additional, another)
}
}
为了进一步控制,您还可以对HttpMessageConverters
进行子类化并覆盖其postProcessConverters
和/或postProcessPartConverters
方法。当您想要重新排序或删除 Spring MVC 默认配置的一些转换器时,这很有用。
MessageCodesResolver
Spring MVC 具有一个策略,用于为从绑定错误呈现错误消息生成错误代码:MessageCodesResolver
。如果您设置spring.mvc.message-codes-resolver-format
属性PREFIX_ERROR_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/**
中的任何资源都将从 jar 文件提供服务,前提是它们以 Webjars 格式打包。可以使用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 Boot 支持静态和模板欢迎页面。它首先在配置的静态内容位置中查找index.html
文件。如果未找到,则查找index
模板。如果找到任一文件,则会自动将其用作应用程序的欢迎页面。
这仅作为应用程序定义的实际索引路由的后备。排序由HandlerMapping
bean 的顺序定义,默认情况下如下所示
|
使用 |
|
在 |
|
欢迎页面支持 |
路径匹配和内容协商
Spring MVC 可以通过查看请求路径并将其与应用程序中定义的映射(例如,@GetMapping
在 Controller 方法上的注解)进行匹配来将传入的 HTTP 请求映射到处理程序。
Spring Boot 选择默认情况下禁用后缀模式匹配,这意味着像"GET /projects/spring-boot.json"
这样的请求将不会与@GetMapping("/projects/spring-boot")
映射匹配。这被认为是Spring MVC 应用程序的最佳实践。此功能过去主要用于不发送正确的“Accept”请求标头的 HTTP 客户端;我们需要确保向客户端发送正确的内容类型。如今,内容协商更加可靠。
还有其他方法可以处理不一致地发送正确的“Accept”请求标头的 HTTP 客户端。我们可以使用查询参数来确保像"GET /projects/spring-boot?format=json"
这样的请求将映射到@GetMapping("/projects/spring-boot")
,而不是使用后缀匹配。
-
属性
-
YAML
spring.mvc.contentnegotiation.favor-parameter=true
spring:
mvc:
contentnegotiation:
favor-parameter: true
或者如果您更喜欢使用不同的参数名称
-
属性
-
YAML
spring.mvc.contentnegotiation.favor-parameter=true
spring.mvc.contentnegotiation.parameter-name=myparam
spring:
mvc:
contentnegotiation:
favor-parameter: true
parameter-name: "myparam"
大多数标准媒体类型都开箱即用地受支持,但您也可以定义新的媒体类型。
-
属性
-
YAML
spring.mvc.contentnegotiation.media-types.markdown=text/markdown
spring:
mvc:
contentnegotiation:
media-types:
markdown: "text/markdown"
从 Spring Framework 5.3 开始,Spring MVC 支持两种将请求路径与控制器匹配的策略。默认情况下,Spring Boot 使用PathPatternParser
策略。PathPatternParser
是一个优化的实现,但与AntPathMatcher
策略相比,它有一些限制。PathPatternParser
限制了某些路径模式变体的使用。它也与使用路径前缀 (spring.mvc.servlet.path
) 配置DispatcherServlet
不兼容。
可以使用spring.mvc.pathmatch.matching-strategy
配置属性配置策略,如下例所示
-
属性
-
YAML
spring.mvc.pathmatch.matching-strategy=ant-path-matcher
spring:
mvc:
pathmatch:
matching-strategy: "ant-path-matcher"
如果找不到请求的处理程序,Spring MVC 将抛出NoHandlerFoundException
异常。请注意,默认情况下,静态内容的服务映射到/**
,因此将为所有请求提供处理程序。如果没有可用的静态内容,ResourceHttpRequestHandler
将抛出NoResourceFoundException
异常。要抛出NoHandlerFoundException
异常,请将spring.mvc.static-path-pattern
设置为更具体的值,例如/resources/**
,或将spring.web.resources.add-mappings
设置为false
以完全禁用静态内容的服务。
ConfigurableWebBindingInitializer
Spring MVC 使用WebBindingInitializer
为特定请求初始化WebDataBinder
。如果创建自己的ConfigurableWebBindingInitializer
@Bean
,Spring Boot 将自动配置 Spring MVC 以使用它。
模板引擎
除了 REST web 服务之外,还可以使用 Spring MVC 来服务动态 HTML 内容。Spring MVC 支持多种模板技术,包括 Thymeleaf、FreeMarker 和 JSP。此外,许多其他模板引擎都包含其自身的 Spring MVC 集成。
Spring Boot 包含对以下模板引擎的自动配置支持:
如果可能,应避免使用 JSP。在与嵌入式 servlet 容器一起使用时,存在一些已知的限制。 |
当使用默认配置使用这些模板引擎之一时,系统会自动从src/main/resources/templates
中获取模板。
根据应用程序的运行方式,IDE 可能以不同的顺序排列类路径。从其主方法在 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 9457 问题详细信息。Spring MVC 可以使用application/problem+json
媒体类型生成自定义错误消息,例如:
{
"type": "https://example.org/problems/unknown-project",
"title": "Unknown project",
"status": 404,
"detail": "No project found for id 'spring-unknown'",
"instance": "/projects/spring-unknown"
}
可以通过将spring.mvc.problemdetails.enabled
设置为true
来启用此支持。
还可以定义一个用@ControllerAdvice
注释的类来自定义要为特定控制器和/或异常类型返回的 JSON 文档,如下例所示:
-
Java
-
Kotlin
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
@ControllerAdvice(basePackageClasses = SomeController.class)
public class MyControllerAdvice extends ResponseEntityExceptionHandler {
@ResponseBody
@ExceptionHandler(MyException.class)
public ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {
HttpStatus status = getStatus(request);
return new ResponseEntity<>(new MyErrorBody(status.value(), ex.getMessage()), status);
}
private HttpStatus getStatus(HttpServletRequest request) {
Integer code = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
HttpStatus status = HttpStatus.resolve(code);
return (status != null) ? status : HttpStatus.INTERNAL_SERVER_ERROR;
}
}
import jakarta.servlet.RequestDispatcher
import jakarta.servlet.http.HttpServletRequest
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.ResponseBody
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler
@ControllerAdvice(basePackageClasses = [SomeController::class])
class MyControllerAdvice : ResponseEntityExceptionHandler() {
@ResponseBody
@ExceptionHandler(MyException::class)
fun handleControllerException(request: HttpServletRequest, ex: Throwable): ResponseEntity<*> {
val status = getStatus(request)
return ResponseEntity(MyErrorBody(status.value(), ex.message), status)
}
private fun getStatus(request: HttpServletRequest): HttpStatus {
val code = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE) as Int
val status = HttpStatus.resolve(code)
return status ?: HttpStatus.INTERNAL_SERVER_ERROR
}
}
在前面的示例中,如果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 的 service 方法成功完成时提交响应。应通过将com.ibm.ws.webcontainer.invokeFlushAfterService
设置为false
来禁用此行为。
CORS 支持
从 4.2 版本开始,Spring MVC支持 CORS。在 Spring Boot 应用程序中使用控制器方法 CORS 配置和@CrossOrigin
注释不需要任何特定配置。全局 CORS 配置可以通过注册具有自定义addCorsMappings(CorsRegistry)
方法的WebMvcConfigurer
bean 来定义,如下例所示:
-
Java
-
Kotlin
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration(proxyBeanMethods = false)
public class MyCorsConfiguration {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**");
}
};
}
}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.servlet.config.annotation.CorsRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
@Configuration(proxyBeanMethods = false)
class MyCorsConfiguration {
@Bean
fun corsConfigurer(): WebMvcConfigurer {
return object : WebMvcConfigurer {
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/api/**")
}
}
}
}
JAX-RS 和 Jersey
如果您更喜欢用于 REST 端点的 JAX-RS 编程模型,可以使用可用的实现之一来代替 Spring MVC。Jersey和Apache CXF开箱即用效果很好。CXF 需要您在其应用程序上下文中将Servlet
或Filter
注册为@Bean
。Jersey 有一些原生 Spring 支持,因此我们还在 Spring Boot 中为其提供了自动配置支持以及启动器。
要开始使用 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 作为过滤器时,必须存在一个 servlet 来处理 Jersey 未拦截的任何请求。如果您的应用程序不包含这样的 servlet,则可能需要通过将 server.servlet.register-default-servlet
设置为 true
来启用默认 servlet。servlet 和过滤器注册都可以通过使用 spring.jersey.init.*
指定属性映射来赋予初始化参数。
嵌入式 Servlet 容器支持
对于 servlet 应用程序,Spring Boot 包括对嵌入式 Tomcat、Jetty 和 Undertow 服务器的支持。大多数开发人员使用相应的启动器来获取完全配置的实例。默认情况下,嵌入式服务器在端口 8080
上侦听 HTTP 请求。
Servlets、过滤器和监听器
使用嵌入式 servlet 容器时,您可以注册 servlet、过滤器和所有 servlet 规范中的监听器(例如 HttpSessionListener
),方法是使用 Spring bean 或扫描 servlet 组件。
将 Servlets、过滤器和监听器注册为 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
的适配器。
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
文件中定义属性。
常见的服务器设置包括
Spring Boot 尽可能多地公开常见设置,但这并非总是可能的。对于这些情况,专用命名空间提供了特定于服务器的自定义(请参阅 server.tomcat
和 server.undertow
)。例如,访问日志 可以使用嵌入式 servlet 容器的特定功能进行配置。
有关完整列表,请参阅 ServerProperties 类。 |
SameSite Cookie
SameSite
cookie 属性可供 Web 浏览器使用,以控制是否以及如何在跨站点请求中提交 cookie。对于开始更改使用缺失属性时的默认值的现代 Web 浏览器,此属性尤其重要。
如果要更改会话 cookie 的 SameSite
属性,可以使用 server.servlet.session.cookie.same-site
属性。此属性受自动配置的 Tomcat、Jetty 和 Undertow 服务器支持。它还用于配置基于 Spring Session servlet 的 SessionRepository
bean。
例如,如果希望会话 cookie 具有 SameSite
属性 None
,可以将以下内容添加到 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 应用 SameSite
为 Lax
。
-
Java
-
Kotlin
import org.springframework.boot.web.servlet.server.CookieSameSiteSupplier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MySameSiteConfiguration {
@Bean
public CookieSameSiteSupplier applicationCookieSameSiteSupplier() {
return CookieSameSiteSupplier.ofLax().whenHasNameMatching("myapp.*");
}
}
import org.springframework.boot.web.servlet.server.CookieSameSiteSupplier
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration(proxyBeanMethods = false)
class MySameSiteConfiguration {
@Bean
fun applicationCookieSameSiteSupplier(): CookieSameSiteSupplier {
return CookieSameSiteSupplier.ofLax().whenHasNameMatching("myapp.*")
}
}
字符编码
可以使用 server.servlet.encoding.*
配置属性配置嵌入式 servlet 容器的请求和响应处理的字符编码行为。
当请求的 Accept-Language
头指示请求的区域设置时,它将由 servlet 容器自动映射到字符集。每个容器都提供默认的区域设置到字符集的映射,您应该验证它们是否满足应用程序的需求。如果不满足,请使用 server.servlet.encoding.mapping
配置属性来自定义映射,如下例所示
-
属性
-
YAML
server.servlet.encoding.mapping.ko=UTF-8
server:
servlet:
encoding:
mapping:
ko: "UTF-8"
在前面的示例中,ko
(韩语)区域设置已映射到 UTF-8
。这等效于传统 war 部署的 web.xml
文件中的 <locale-encoding-mapping-list>
条目。
编程自定义
如果您需要以编程方式配置嵌入式 servlet 容器,可以注册一个实现 WebServerFactoryCustomizer
接口的 Spring bean。WebServerFactoryCustomizer
提供对 ConfigurableServletWebServerFactory
的访问,其中包括许多自定义 setter 方法。以下示例显示了以编程方式设置端口
-
Java
-
Kotlin
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.stereotype.Component;
@Component
public class MyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory server) {
server.setPort(9000);
}
}
import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory
import org.springframework.stereotype.Component
@Component
class MyWebServerFactoryCustomizer : WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
override fun customize(server: ConfigurableServletWebServerFactory) {
server.setPort(9000)
}
}
TomcatServletWebServerFactory
、JettyServletWebServerFactory
和 UndertowServletWebServerFactory
是 ConfigurableServletWebServerFactory
的专用变体,它们分别具有 Tomcat、Jetty 和 Undertow 的其他自定义 setter 方法。以下示例显示了如何自定义 TomcatServletWebServerFactory
,它提供了对 Tomcat 特定配置选项的访问
-
Java
-
Kotlin
import java.time.Duration;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;
@Component
public class MyTomcatWebServerFactoryCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
@Override
public void customize(TomcatServletWebServerFactory server) {
server.addConnectorCustomizers((connector) -> connector.setAsyncTimeout(Duration.ofSeconds(20).toMillis()));
}
}
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory
import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.stereotype.Component
import java.time.Duration
@Component
class MyTomcatWebServerFactoryCustomizer : WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
override fun customize(server: TomcatServletWebServerFactory) {
server.addConnectorCustomizers({ connector -> connector.asyncTimeout = Duration.ofSeconds(20).toMillis() })
}
}
直接自定义 ConfigurableServletWebServerFactory
对于需要从 ServletWebServerFactory
扩展的更高级用例,您可以自己公开此类 bean。
许多配置选项都提供了 Setter 方法。如果您需要执行更复杂的配置,还提供了一些受保护的“hook”方法。详情请参阅ConfigurableServletWebServerFactory
API 文档。
自动配置的自定义程序仍然会应用于您的自定义工厂,因此请谨慎使用此选项。 |