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

	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 的默认设置外,自动配置还提供了以下功能

如果您希望保留这些 Spring Boot MVC 自定义设置并进行更多 MVC 自定义设置(拦截器、格式化器、视图控制器和其他功能),您可以添加自己的 @Configuration 类,类型为 WebMvcConfigurer,但不带 @EnableWebMvc

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

如果您不想使用自动配置并希望完全控制 Spring MVC,请添加自己的使用 @EnableWebMvc 注解的 @Configuration 类。或者,按照 @EnableWebMvc API 文档的描述,添加自己的 @Configuration 注解的 DelegatingWebMvcConfiguration

Spring MVC 转换服务

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

如果您想自定义 Spring MVC 使用的 ConversionService,您可以提供一个具有 addFormatters 方法的 WebMvcConfigurer Bean。通过此方法,您可以注册任何您喜欢的转换器,也可以委托给 ApplicationConversionService 提供的静态方法。

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

财产 DateTimeFormatter 格式

spring.mvc.format.date

ofLocalizedDate(FormatStyle.SHORT)

java.util.DateLocalDate

spring.mvc.format.time

ofLocalizedTime(FormatStyle.SHORT)

java.time 的 LocalTimeOffsetTime

spring.mvc.format.date-time

ofLocalizedDateTime(FormatStyle.SHORT)

java.time 的 LocalDateTimeOffsetDateTimeZonedDateTime

HttpMessageConverters

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

上下文中存在的任何 HttpMessageConverter bean 都会添加到转换器列表中。您也可以以相同的方式覆盖默认转换器。

如果需要添加或自定义转换器,您可以声明一个或多个 ClientHttpMessageConvertersCustomizerServerHttpMessageConvertersCustomizer 作为 Bean,如以下列表所示

  • Java

  • Kotlin

import org.springframework.boot.http.converter.autoconfigure.ClientHttpMessageConvertersCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyHttpMessageConvertersConfiguration {

	@Bean
	public ClientHttpMessageConvertersCustomizer myClientConvertersCustomizer() {
		return (clientBuilder) -> clientBuilder.addCustomConverter(new AdditionalHttpMessageConverter())
			.addCustomConverter(new AnotherHttpMessageConverter());
	}

}
import org.springframework.boot.http.converter.autoconfigure.ClientHttpMessageConvertersCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.converter.HttpMessageConverters

@Configuration(proxyBeanMethods = false)
class MyHttpMessageConvertersConfiguration {

	@Bean
	fun myClientConvertersCustomizer(): ClientHttpMessageConvertersCustomizer {
		return ClientHttpMessageConvertersCustomizer { clientBuilder: HttpMessageConverters.ClientBuilder ->
			clientBuilder
				.addCustomConverter(AdditionalHttpMessageConverter())
				.addCustomConverter(AnotherHttpMessageConverter())
		}
	}

}

MessageCodesResolver

Spring MVC 有一个策略来生成错误代码,用于从绑定错误中渲染错误消息:MessageCodesResolver。如果您将 spring.mvc.message-codes-resolver-format 属性设置为 PREFIX_ERROR_CODEPOSTFIX_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,请添加 org.webjars:webjars-locator-lite 依赖项。然后声明您的 Webjar。以 jQuery 为例,添加 "/webjars/jquery/jquery.min.js" 将导致 "/webjars/jquery/x.y.z/jquery.min.js",其中 x.y.z 是 Webjar 版本。

要使用缓存清除,以下配置为所有静态资源配置了缓存清除解决方案,有效地在 URL 中添加了内容哈希,例如 <link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>

  • 属性

  • 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 可以通过查看请求路径并将其与应用程序中定义的映射(例如,控制器方法上的 @GetMapping 注解)匹配来将传入的 HTTP 请求映射到处理程序。

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 将抛出 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 容器中注册为“全局”错误页面。对于机器客户端,它生成一个包含错误详细信息、HTTP 状态和异常消息的 JSON 响应。对于浏览器客户端,有一个“白标签”错误视图,以 HTML 格式呈现相同的数据(要自定义它,请添加一个解析为 errorView)。

如果您想自定义默认错误处理行为,可以设置一些 server.error 属性。请参阅附录的服务器属性部分。

要完全替换默认行为,您可以实现 ErrorController 并注册该类型的 bean 定义,或者添加一个类型为 ErrorAttributes 的 bean 来使用现有机制但替换内容。

BasicErrorController 可以用作自定义 ErrorController 的基类。这在您想要为新的内容类型添加处理程序时特别有用(默认是专门处理 text/html 并为所有其他内容提供回退)。为此,请扩展 BasicErrorController,添加一个带有 produces 属性的 @RequestMapping 公共方法,并创建您新类型的 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.webmvc.autoconfigure.error.ErrorViewResolver;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.ModelAndView;

public class MyErrorViewResolver implements ErrorViewResolver {

	@Override
	public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
		// Use the request or status to optionally return a ModelAndView
		if (status == HttpStatus.INSUFFICIENT_STORAGE) {
			// We could add custom model values here
			new ModelAndView("myview");
		}
		return null;
	}

}
import jakarta.servlet.http.HttpServletRequest
import org.springframework.boot.webmvc.autoconfigure.error.ErrorViewResolver
import org.springframework.http.HttpStatus
import org.springframework.web.servlet.ModelAndView

class MyErrorViewResolver : ErrorViewResolver {

	override fun resolveErrorView(request: HttpServletRequest, status: HttpStatus,
			model: Map<String, Any>): ModelAndView? {
		// Use the request or status to optionally return a ModelAndView
		if (status == HttpStatus.INSUFFICIENT_STORAGE) {
			// We could add custom model values here
			return ModelAndView("myview")
		}
		return null
	}

}

您还可以使用常规 Spring MVC 功能,例如 @ExceptionHandler 方法@ControllerAdvice。然后,ErrorController 会捕获任何未处理的异常。

在 Spring MVC 之外映射错误页面

对于不使用 Spring MVC 的应用程序,您可以使用 ErrorPageRegistrar 接口直接注册 ErrorPage 实例。此抽象直接与底层嵌入式 servlet 容器配合使用,即使您没有 Spring MVC DispatcherServlet 也能工作。

  • Java

  • Kotlin

import org.springframework.boot.web.error.ErrorPage;
import org.springframework.boot.web.error.ErrorPageRegistrar;
import org.springframework.boot.web.error.ErrorPageRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;

@Configuration(proxyBeanMethods = false)
public class MyErrorPagesConfiguration {

	@Bean
	public ErrorPageRegistrar errorPageRegistrar() {
		return this::registerErrorPages;
	}

	private void registerErrorPages(ErrorPageRegistry registry) {
		registry.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/400"));
	}

}
import org.springframework.boot.web.error.ErrorPage
import org.springframework.boot.web.error.ErrorPageRegistrar
import org.springframework.boot.web.error.ErrorPageRegistry
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpStatus

@Configuration(proxyBeanMethods = false)
class MyErrorPagesConfiguration {

	@Bean
	fun errorPageRegistrar(): ErrorPageRegistrar {
		return ErrorPageRegistrar { registry: ErrorPageRegistry -> registerErrorPages(registry) }
	}

	private fun registerErrorPages(registry: ErrorPageRegistry) {
		registry.addErrorPages(ErrorPage(HttpStatus.BAD_REQUEST, "/400"))
	}

}
如果您注册的 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 应用程序中使用带有 @CrossOrigin 注解的控制器方法 CORS 配置不需要任何特定配置。全局 CORS 配置可以通过注册一个 WebMvcConfigurer Bean 并使用自定义的 addCorsMappings(CorsRegistry) 方法来定义,如以下示例所示

  • Java

  • Kotlin

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration(proxyBeanMethods = false)
public class MyCorsConfiguration {

	@Bean
	public WebMvcConfigurer corsConfigurer() {
		return new WebMvcConfigurer() {

			@Override
			public void addCorsMappings(CorsRegistry registry) {
				registry.addMapping("/api/**");
			}

		};
	}

}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.servlet.config.annotation.CorsRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer

@Configuration(proxyBeanMethods = false)
class MyCorsConfiguration {

	@Bean
	fun corsConfigurer(): WebMvcConfigurer {
		return object : WebMvcConfigurer {
			override fun addCorsMappings(registry: CorsRegistry) {
				registry.addMapping("/api/**")
			}
		}
	}

}

API 版本控制

Spring MVC 支持 API 版本控制,可用于随着时间的推移演进 HTTP API。同一个 @Controller 路径可以多次映射以支持不同版本的 API。

有关更多详细信息,请参阅 Spring Framework 的参考文档

添加映射后,您还需要配置 Spring MVC,使其能够使用请求中发送的任何版本信息。通常,版本以 HTTP 标头、查询参数或路径的一部分发送。

要配置 Spring MVC,您可以选择使用 WebMvcConfigurer Bean 并覆盖 configureApiVersioning(…​) 方法,或者您可以使用属性。

例如,以下将使用 X-Version HTTP 标头获取版本信息,并且在未发送标头时默认为 1.0.0

  • 属性

  • YAML

spring.mvc.apiversion.default=1.0.0
spring.mvc.apiversion.use.header=X-Version
spring:
  mvc:
    apiversion:
      default: 1.0.0
      use:
        header: X-Version

为了更全面的控制,您还可以定义 ApiVersionResolverApiVersionParserApiVersionDeprecationHandler Bean,它们将注入到自动配置的 Spring MVC 配置中。

API 版本控制也支持 WebClientRestClient。有关详细信息,请参阅API 版本控制

JAX-RS 和 Jersey

如果您更喜欢 REST 端点的 JAX-RS 编程模型,可以使用其中一种可用的实现而不是 Spring MVC。JerseyApache CXF 开箱即用。CXF 要求您在应用程序上下文中将其 ServletFilter 注册为 @Bean。Jersey 对 Spring 有一些原生支持,因此我们还在 Spring Boot 中为它提供了自动配置支持,以及一个启动器。

要开始使用 Jersey,请将 spring-boot-starter-jersey 作为依赖项包含进来,然后您需要一个类型为 ResourceConfig@Bean,在该 Bean 中注册所有端点,如以下示例所示

import org.glassfish.jersey.server.ResourceConfig;

import org.springframework.stereotype.Component;

@Component
public class MyJerseyConfig extends ResourceConfig {

	public MyJerseyConfig() {
		register(MyEndpoint.class);
	}

}
Jersey 对可执行归档的扫描支持相当有限。例如,它无法在 完全可执行的 jar 文件 或运行可执行 war 文件时的 WEB-INF/classes 中找到端点。为避免此限制,不应使用 packages 方法,而应使用 register 方法单独注册端点,如上例所示。

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

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

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

import org.springframework.stereotype.Component;

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

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

}

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

默认情况下,Jersey 被设置为 @Bean 类型 ServletRegistrationBean,名为 jerseyServletRegistration 中的 servlet。默认情况下,servlet 是惰性初始化的,但您可以通过设置 spring.jersey.servlet.load-on-startup 来自定义该行为。您可以通过创建自己的同名 bean 来禁用或覆盖该 bean。您还可以通过设置 spring.jersey.type=filter 来使用过滤器而不是 servlet(在这种情况下,要替换或覆盖的 @BeanjerseyFilterRegistration)。过滤器具有一个 @Order,您可以使用 spring.jersey.filter.order 进行设置。当使用 Jersey 作为过滤器时,必须存在一个 servlet 来处理未被 Jersey 拦截的任何请求。如果您的应用程序不包含此类 servlet,您可能希望通过将 server.servlet.register-default-servlet 设置为 true 来启用默认 servlet。servlet 和过滤器注册都可以通过使用 spring.jersey.init.* 来指定属性映射来提供初始化参数。

嵌入式 Servlet 容器支持

对于 servlet 应用程序,Spring Boot 支持嵌入式 TomcatJetty 服务器。大多数开发人员使用适当的启动器来获取完全配置的实例。默认情况下,嵌入式服务器在端口 8080 监听 HTTP 请求。

Servlets、过滤器和监听器

当使用嵌入式 servlet 容器时,您可以通过使用 Spring Bean 或扫描 servlet 组件来注册 servlet、过滤器和所有监听器(例如 HttpSessionListener)。

将 Servlet、过滤器和监听器注册为 Spring Bean

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

默认情况下,如果上下文仅包含一个 Servlet,它将映射到 /。如果有多个 servlet bean,则 bean 名称用作路径前缀。过滤器映射到 /*

如果基于约定的映射不够灵活,您可以使用 ServletRegistrationBeanFilterRegistrationBeanServletListenerRegistrationBean 类来完全控制。如果您更喜欢注解而不是 ServletRegistrationBeanFilterRegistrationBean,您也可以使用 @ServletRegistration@FilterRegistration 作为替代。

通常可以安全地不对过滤器 Bean 进行排序。如果需要特定顺序,您应该使用 @Order 注解 Filter,或者使其实现 Ordered。您不能通过使用 @Order 注解其 bean 方法来配置 Filter 的顺序。如果您无法更改 Filter 类以添加 @Order 或实现 Ordered,则必须为 Filter 定义一个 FilterRegistrationBean,并使用 setOrder(int) 方法设置注册 bean 的顺序。或者,如果您更喜欢注解,您也可以使用 @FilterRegistration 并设置 order 属性。避免在 Ordered.HIGHEST_PRECEDENCE 配置读取请求体的过滤器,因为它可能与应用程序的字符编码配置冲突。如果 servlet 过滤器包装请求,它应该配置一个小于或等于 OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER 的顺序。

要查看应用程序中每个 Filter 的顺序,请启用 web 日志组logging.level.web=debug)的调试级别日志记录。然后,在启动时将记录注册过滤器的详细信息,包括其顺序和 URL 模式。
注册 Filter bean 时请注意,它们在应用程序生命周期中很早就会初始化。如果您需要注册一个与其他 bean 交互的 Filter,请考虑改用 DelegatingFilterProxyRegistrationBean

Servlet 上下文初始化

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

如果您需要在 Spring Boot 应用程序中执行 servlet 上下文初始化,则应注册一个实现 ServletContextInitializer 接口的 bean。单个 onStartup 方法提供对 ServletContext 的访问,如果需要,可以轻松地用作现有 WebApplicationInitializer 的适配器。

初始化参数

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

扫描 Servlets、过滤器和监听器

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

@ServletComponentScan 在独立容器中没有效果,因为在这种情况下会使用容器内置的发现机制。

ServletWebServerApplicationContext

在底层,Spring Boot 为嵌入式 servlet 容器支持使用不同类型的 ApplicationContextServletWebServerApplicationContext 是一种特殊类型的 WebApplicationContext,它通过查找单个 ServletWebServerFactory Bean 来引导自身。通常,TomcatServletWebServerFactoryJettyServletWebServerFactory 已自动配置。

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

在嵌入式容器设置中,ServletContext 在服务器启动期间(应用程序上下文初始化期间)设置。因此,ApplicationContext 中的 Bean 无法可靠地使用 ServletContext 进行初始化。解决此问题的一种方法是将 ApplicationContext 注入为 Bean 的依赖项,并且仅在需要时才访问 ServletContext。另一种方法是在服务器启动后使用回调。这可以通过使用监听 ApplicationStartedEventApplicationListener 来实现,如下所示

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.propertiesapplication.yaml 文件中定义这些属性。

常见的服务器设置包括

  • 网络设置:传入 HTTP 请求的监听端口 (server.port)、要绑定的接口地址 (server.address) 等。

  • 会话设置:会话是否持久化 (server.servlet.session.persistent)、会话超时 (server.servlet.session.timeout)、会话数据位置 (server.servlet.session.store-dir) 和会话 cookie 配置 (server.servlet.session.cookie.*)。

  • 错误管理:错误页面的位置 (spring.web.error.path) 等。

  • SSL

  • HTTP 压缩

Spring Boot 尽可能地公开通用设置,但这并非总是可行。对于这些情况,专用命名空间提供了服务器特定的自定义(参见 server.tomcat)。例如,访问日志可以通过嵌入式 servlet 容器的特定功能进行配置。

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

SameSite Cookie

SameSite cookie 属性可由 Web 浏览器用于控制在跨站点请求中是否以及如何提交 cookie。此属性对于现代 Web 浏览器尤为重要,它们已开始更改当属性缺失时使用的默认值。

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

例如,如果您希望会话 cookie 的 SameSite 属性为 None,您可以将以下内容添加到您的 application.propertiesapplication.yaml 文件中

  • 属性

  • YAML

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

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

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

  • Java

  • Kotlin

import org.springframework.boot.web.server.servlet.CookieSameSiteSupplier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MySameSiteConfiguration {

	@Bean
	public CookieSameSiteSupplier applicationCookieSameSiteSupplier() {
		return CookieSameSiteSupplier.ofLax().whenHasNameMatching("myapp.*");
	}

}
import org.springframework.boot.web.server.servlet.CookieSameSiteSupplier
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
class MySameSiteConfiguration {

	@Bean
	fun applicationCookieSameSiteSupplier(): CookieSameSiteSupplier {
		return CookieSameSiteSupplier.ofLax().whenHasNameMatching("myapp.*")
	}

}

字符编码

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

当请求的 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.server.servlet.ConfigurableServletWebServerFactory;
import org.springframework.stereotype.Component;

@Component
public class MyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {

	@Override
	public void customize(ConfigurableServletWebServerFactory server) {
		server.setPort(9000);
	}

}
import org.springframework.boot.web.server.servlet.ConfigurableServletWebServerFactory
import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.stereotype.Component

@Component
class MyWebServerFactoryCustomizer : WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {

	override fun customize(server: ConfigurableServletWebServerFactory) {
		server.setPort(9000)
	}

}

TomcatServletWebServerFactoryJettyServletWebServerFactoryConfigurableServletWebServerFactory 的专用变体,分别具有用于 Tomcat 和 Jetty 的附加自定义设置器方法。以下示例展示了如何自定义 TomcatServletWebServerFactory,它提供了对特定于 Tomcat 的配置选项的访问

  • Java

  • Kotlin

import java.time.Duration;

import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;

@Component
public class MyTomcatWebServerFactoryCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {

	@Override
	public void customize(TomcatServletWebServerFactory server) {
		server.addConnectorCustomizers((connector) -> connector.setAsyncTimeout(Duration.ofSeconds(20).toMillis()));
	}

}
import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory
import org.springframework.stereotype.Component
import java.time.Duration

@Component
class MyTomcatWebServerFactoryCustomizer : WebServerFactoryCustomizer<TomcatServletWebServerFactory> {

	override fun customize(server: TomcatServletWebServerFactory) {
		server.addConnectorCustomizers({ connector -> connector.asyncTimeout = Duration.ofSeconds(20).toMillis() })
	}

}

直接自定义 ConfigurableServletWebServerFactory

对于需要从 ServletWebServerFactory 扩展的更高级用例,您可以自行公开此类类型的 Bean。

提供了许多配置选项的设置器。如果您需要做更奇特的事情,还提供了几个受保护的方法“钩子”。有关详细信息,请参阅 ConfigurableServletWebServerFactory API 文档。

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

JSP 限制

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

  • 使用 Jetty 和 Tomcat,如果您使用 war 包,它应该可以工作。可执行 war 在使用 java -jar 启动时将正常工作,并且也可以部署到任何标准容器。使用可执行 jar 时不支持 JSP。

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

© . This site is unofficial and not affiliated with VMware.