Web

1. Servlet Web 应用程序

如果您想构建基于 servlet 的 Web 应用程序,您可以利用 Spring Boot 对 Spring MVC 或 Jersey 的自动配置。spring-doc.cadn.net.cn

1.1. “Spring Web MVC 框架”

Spring Web MVC 框架(通常称为“Spring MVC”)是一个丰富的“模型视图控制器”Web 框架。 Spring MVC 允许您创建特殊的@Controller@RestControllerbean 来处理传入的 HTTP 请求。 控制器中的方法通过使用@RequestMapping附注。spring-doc.cadn.net.cn

以下代码显示了一个典型的@RestController,它提供 JSON 数据:spring-doc.cadn.net.cn

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

}
Kotlin
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”是功能变体,它将路由配置与请求的实际处理分开,如以下示例所示:spring-doc.cadn.net.cn

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

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

}
Kotlin
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 Framework 的一部分,详细信息可在参考文档中找到。 spring.io/guides 上还提供了几个涵盖 Spring MVC 的指南。spring-doc.cadn.net.cn

您可以定义任意数量的RouterFunctionbean 来模块化路由器的定义。 如果需要应用优先级,则可以对 bean 进行排序。

1.1.1. Spring MVC 自动配置

Spring Boot 为 Spring MVC 提供了自动配置,适用于大多数应用程序。 它取代了对@EnableWebMvc并且两者不能一起使用。 除了 Spring MVC 的默认值之外,自动配置还提供以下功能:spring-doc.cadn.net.cn

如果你想保留这些 Spring Boot MVC 自定义并进行更多的 MVC 自定义(拦截器、格式化程序、视图控制器和其他功能),你可以添加自己的@Configurationtype 类WebMvcConfigurer但没有 @EnableWebMvc.spring-doc.cadn.net.cn

如果要提供RequestMappingHandlerMapping,RequestMappingHandlerAdapterExceptionHandlerExceptionResolver,并且仍然保留 Spring Boot MVC 自定义,则可以声明一个WebMvcRegistrations并使用它来提供这些组件的自定义实例。 自定义实例将受 Spring MVC 的进一步初始化和配置的约束。 要参与并在需要时覆盖该后续处理,需要一个WebMvcConfigurer应该使用。spring-doc.cadn.net.cn

如果您不想使用自动配置并希望完全控制 Spring MVC,请添加您自己的@Configuration注解@EnableWebMvc. 或者,添加您自己的@Configuration-注释DelegatingWebMvcConfiguration如 Javadoc 中所述@EnableWebMvc.spring-doc.cadn.net.cn

1.1.2. Spring MVC 转换服务

Spring MVC 使用不同的ConversionService转换为用于将值从application.propertiesapplication.yaml文件。 这意味着Period,DurationDataSize转换器不可用,并且@DurationUnit@DataSizeUnit注释将被忽略。spring-doc.cadn.net.cn

如果要自定义ConversionService由 Spring MVC 使用,您可以提供WebMvcConfigurer带有addFormatters方法。 通过此方法,您可以注册您喜欢的任何转换器,也可以委托给ApplicationConversionService.spring-doc.cadn.net.cn

转换也可以使用spring.mvc.format.*configuration 属性。 如果未配置,则使用以下默认值:spring-doc.cadn.net.cn

财产 DateTimeFormatter

spring.mvc.format.datespring-doc.cadn.net.cn

ofLocalizedDate(FormatStyle.SHORT)spring-doc.cadn.net.cn

spring.mvc.format.timespring-doc.cadn.net.cn

ofLocalizedTime(FormatStyle.SHORT)spring-doc.cadn.net.cn

spring.mvc.format.date-timespring-doc.cadn.net.cn

ofLocalizedDateTime(FormatStyle.SHORT)spring-doc.cadn.net.cn

1.1.3. HttpMessage转换器

Spring MVC 使用HttpMessageConverter接口来转换 HTTP 请求和响应。 合理的默认值是开箱即用的。 例如,对象可以自动转换为 JSON(通过使用 Jackson 库)或 XML(通过使用 Jackson XML 扩展,如果可用,或者通过使用 JAXB,如果 Jackson XML 扩展不可用)。 默认情况下,字符串以UTF-8.spring-doc.cadn.net.cn

任何HttpMessageConverter将 Bean 添加到转换器列表中。 您也可以以相同的方式覆盖默认转换器。spring-doc.cadn.net.cn

如果需要添加或自定义转换器,可以使用 Spring Boot 的HttpMessageConverters类,如下面的清单所示:spring-doc.cadn.net.cn

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

}
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)
class MyHttpMessageConvertersConfiguration {

    @Bean
    fun customConverters(): HttpMessageConverters {
        val additional: HttpMessageConverter<*> = AdditionalHttpMessageConverter()
        val another: HttpMessageConverter<*> = AnotherHttpMessageConverter()
        return HttpMessageConverters(additional, another)
    }

}

为了进一步控制,您还可以将 sub-classHttpMessageConverters并覆盖其postProcessConverters和/或postProcessPartConverters方法。 当您想要重新排序或删除 Spring MVC 默认配置的某些转换器时,这可能很有用。spring-doc.cadn.net.cn

1.1.4. MessageCodes解析器

Spring MVC 有一个生成错误代码的策略,用于呈现来自绑定错误的错误消息:MessageCodesResolver. 如果将spring.mvc.message-codes-resolver-format财产PREFIX_ERROR_CODEPOSTFIX_ERROR_CODE,Spring Boot 会为您创建一个(请参阅DefaultMessageCodesResolver.Format).spring-doc.cadn.net.cn

1.1.5. 静态内容

默认情况下, Spring Boot 从名为/static(或/public/resources/META-INF/resources) 或从ServletContext. 它使用ResourceHttpRequestHandler从 Spring MVC,以便您可以通过添加自己的WebMvcConfigurer并覆盖addResourceHandlers方法。spring-doc.cadn.net.cn

在独立的 Web 应用程序中,未启用容器中的默认 Servlet。 可以使用server.servlet.register-default-servlet财产。spring-doc.cadn.net.cn

默认 Servlet 充当后备,从ServletContext如果 Spring 决定不处理它。 大多数情况下,这不会发生(除非你修改了默认的 MVC 配置),因为 Spring 总是可以通过DispatcherServlet.spring-doc.cadn.net.cn

默认情况下,资源映射在 上,但您可以使用/**spring.mvc.static-path-pattern财产。 例如,将所有资源重新定位到/resources/**可以按如下方式实现:spring-doc.cadn.net.cn

性能
spring.mvc.static-path-pattern=/resources/**
Yaml
spring:
  mvc:
    static-path-pattern: "/resources/**"

您还可以使用spring.web.resources.static-locations属性(将默认值替换为目录位置列表)。 根 Servlet 上下文路径 也会自动添加为位置。"/"spring-doc.cadn.net.cn

除了前面提到的“标准”静态资源位置之外,Webjars 内容还有一个特殊情况。 默认情况下,路径为/webjars/**如果 jar 文件以 Webjars 格式打包,则从 jar 文件中提供。 可以使用spring.mvc.webjars-path-pattern财产。spring-doc.cadn.net.cn

请勿使用src/main/webapp目录(如果您的应用程序打包为 jar)。 虽然这个目录是一个通用标准,但它仅适用于 war 打包,如果你生成一个 jar,大多数构建工具都会默默地忽略它。

Spring Boot 还支持 Spring MVC 提供的高级资源处理功能,允许使用诸如缓存清除静态资源或对 Webjar 使用与版本无关的 URL 等用例。spring-doc.cadn.net.cn

要对 Webjar 使用与版本无关的 URL,请添加webjars-locator-coreDependency。 然后声明您的 Webjar。 以 jQuery 为例,添加"/webjars/jquery/jquery.min.js"结果"/webjars/jquery/x.y.z/jquery.min.js"哪里x.y.z是 Webjar 版本。spring-doc.cadn.net.cn

如果您使用 JBoss,则需要声明webjars-locator-jboss-vfsdependency 而不是webjars-locator-core. 否则,所有 Webjar 都会解析为404.

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

性能
spring.web.resources.chain.strategy.content.enabled=true
spring.web.resources.chain.strategy.content.paths=/**
Yaml
spring:
  web:
    resources:
      chain:
        strategy:
          content:
            enabled: true
            paths: "/**"
指向资源的链接在运行时在模板中重写,这要归功于ResourceUrlEncodingFilter这是为 Thymeleaf 和 FreeMarker 自动配置的。 使用 JSP 时,应手动声明此过滤器。 其他模板引擎目前不自动支持,但可以使用自定义模板宏/帮助程序并使用ResourceUrlProvider.

例如,使用 JavaScript 模块加载器动态加载资源时,重命名文件不是一个选项。 这就是为什么还支持其他策略并且可以组合的原因。 “fixed” 策略在 URL 中添加静态版本字符串,而不更改文件名,如以下示例所示:spring-doc.cadn.net.cn

性能
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
Yaml
spring:
  web:
    resources:
      chain:
        strategy:
          content:
            enabled: true
            paths: "/**"
          fixed:
            enabled: true
            paths: "/js/lib/"
            version: "v12"

使用此配置,位于"/js/lib/"使用固定版本控制策略 ("/v12/js/lib/mymodule.js"),而其他资源仍然使用内容 (<link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>).spring-doc.cadn.net.cn

WebProperties.Resources以获取更多支持的选项。spring-doc.cadn.net.cn

此功能已在专门的博客文章和 Spring Framework 的参考文档中进行了详细描述。spring-doc.cadn.net.cn

1.1.6. 欢迎页面

Spring Boot 支持静态和模板化欢迎页面。 它首先查找index.html文件。 如果未找到,则查找index模板。 如果找到任何一个,它将自动用作应用程序的欢迎页面。spring-doc.cadn.net.cn

1.1.7. 自定义网站图标

与其他静态资源一样, Spring Boot 会检查favicon.ico在配置的静态内容位置。 如果存在此类文件,则会自动将其用作应用程序的图标。spring-doc.cadn.net.cn

1.1.8. 路径匹配和内容协商

Spring MVC 可以通过查看请求路径并将其与应用程序中定义的 Map(例如,@GetMappingController 方法上的注释)。spring-doc.cadn.net.cn

Spring Boot 默认选择禁用后缀模式匹配,这意味着像"GET /projects/spring-boot.json"将不匹配到@GetMapping("/projects/spring-boot")映射。 这被认为是 Spring MVC 应用程序的最佳实践。 此功能在过去主要对没有发送正确的 “Accept” 请求标头的 HTTP 客户端有用;我们需要确保将正确的 Content Type 发送给客户端。 如今,Content Negotiation 更加可靠。spring-doc.cadn.net.cn

还有其他方法可以处理 HTTP 客户端,这些客户端不能始终如一地发送正确的 “Accept” 请求标头。 我们可以使用 query 参数来确保像"GET /projects/spring-boot?format=json"将映射到@GetMapping("/projects/spring-boot"):spring-doc.cadn.net.cn

性能
spring.mvc.contentnegotiation.favor-parameter=true
Yaml
spring:
  mvc:
    contentnegotiation:
      favor-parameter: true

或者,如果您更喜欢使用不同的参数名称:spring-doc.cadn.net.cn

性能
spring.mvc.contentnegotiation.favor-parameter=true
spring.mvc.contentnegotiation.parameter-name=myparam
Yaml
spring:
  mvc:
    contentnegotiation:
      favor-parameter: true
      parameter-name: "myparam"

大多数标准媒体类型都是开箱即用的,但您也可以定义新的媒体类型:spring-doc.cadn.net.cn

性能
spring.mvc.contentnegotiation.media-types.markdown=text/markdown
Yaml
spring:
  mvc:
    contentnegotiation:
      media-types:
        markdown: "text/markdown"

从 Spring Framework 5.3 开始, Spring MVC 支持两种将请求路径与控制器匹配的策略。 默认情况下,Spring Boot 使用PathPatternParser策略。PathPatternParser是一种优化的实现,但与AntPathMatcher策略。PathPatternParser限制某些路径模式变体的使用。 它也与配置DispatcherServlet使用路径前缀 (spring.mvc.servlet.path).spring-doc.cadn.net.cn

该策略可以使用spring.mvc.pathmatch.matching-strategyconfiguration 属性,如以下示例所示:spring-doc.cadn.net.cn

性能
spring.mvc.pathmatch.matching-strategy=ant-path-matcher
Yaml
spring:
  mvc:
    pathmatch:
      matching-strategy: "ant-path-matcher"

默认情况下,如果找不到请求的处理程序, Spring MVC 将发送 404 Not Found 错误响应。 要有一个NoHandlerFoundException而是将 configprop:spring.mvc.throw-exception-if-no-handler-found 设置为true. 请注意,默认情况下,静态内容的提供会映射到所有请求,因此会为所有请求提供处理程序。 对于/**NoHandlerFoundException要被抛出,您还必须设置spring.mvc.static-path-pattern设置为更具体的值,例如/resources/**或设置spring.web.resources.add-mappingsfalse以完全禁用静态内容的提供。spring-doc.cadn.net.cn

1.1.9. 可配置的 WebBindingInitializer

Spring MVC 使用WebBindingInitializer要初始化WebDataBinder对于特定请求。 如果您创建自己的ConfigurableWebBindingInitializer @Bean,Spring Boot 会自动配置 Spring MVC 以使用它。spring-doc.cadn.net.cn

1.1.10. 模板引擎

除了 REST Web 服务之外,您还可以使用 Spring MVC 来提供动态 HTML 内容。 Spring MVC 支持多种模板技术,包括 Thymeleaf、FreeMarker 和 JSP。 此外,许多其他模板引擎包括他们自己的 Spring MVC 集成。spring-doc.cadn.net.cn

Spring Boot 包括对以下模板引擎的自动配置支持:spring-doc.cadn.net.cn

如果可能,应避免使用 JSP。 将它们与嵌入式 servlet 容器一起使用时,存在几个已知的限制

当您使用具有默认配置的模板引擎之一时,您的模板会自动从src/main/resources/templates.spring-doc.cadn.net.cn

根据您运行应用程序的方式,IDE 可能会以不同的方式对 Classpath 进行排序。 在 IDE 中,从 main 方法运行应用程序时,与使用 Maven 或 Gradle 或从其打包的 jar 运行应用程序时的顺序不同。 这可能会导致 Spring Boot 无法找到预期的模板。 如果遇到此问题,可以在 IDE 中对 Classpath 重新排序,以便将模块的类和资源放在最前面。

1.1.11. 错误处理

默认情况下, Spring Boot 提供了一个/error映射,它以合理的方式处理所有错误,并在 servlet 容器中注册为“全局”错误页。 对于计算机客户端,它会生成一个 JSON 响应,其中包含错误、HTTP 状态和异常消息的详细信息。 对于浏览器客户端,有一个 “whitelabel” 错误视图,它以 HTML 格式呈现相同的数据(要对其进行自定义,请添加View解析为error).spring-doc.cadn.net.cn

有许多server.error属性,如果要自定义默认错误处理行为,则可以设置这些属性。 请参阅附录的 “Server Properties” 部分。spring-doc.cadn.net.cn

要完全替换默认行为,您可以实现ErrorController并注册该类型的 bean 定义或添加ErrorAttributes以使用现有机制,但替换内容。spring-doc.cadn.net.cn

BasicErrorController可用作自定义ErrorController. 如果要为新的内容类型添加处理程序(默认为 handletext/html具体来说,并为其他所有内容提供回退)。 为此,请扩展BasicErrorController中,添加一个带有@RequestMapping具有produces属性,并创建一个新类型的 bean。

从 Spring Framework 6.0 开始,支持 RFC 7807 问题详细信息。 Spring MVC 可以使用application/problem+jsonmedia 类型,例如:spring-doc.cadn.net.cn

{
  "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.enabledtrue.spring-doc.cadn.net.cn

您还可以定义一个带有@ControllerAdvice自定义 JSON 文档以返回特定控制器和/或异常类型,如以下示例所示:spring-doc.cadn.net.cn

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

}
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])
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 而不是ErrorAttributes表示法。spring-doc.cadn.net.cn

在某些情况下,指标基础设施不会记录在控制器级别处理的错误。 应用程序可以通过将 handled 异常设置为 request 属性来确保此类异常与请求指标一起记录:spring-doc.cadn.net.cn

Java
import jakarta.servlet.http.HttpServletRequest;

import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;

@Controller
public class MyController {

    @ExceptionHandler(CustomException.class)
    String handleCustomException(HttpServletRequest request, CustomException ex) {
        request.setAttribute(ErrorAttributes.ERROR_ATTRIBUTE, ex);
        return "errorView";
    }

}
Kotlin
import jakarta.servlet.http.HttpServletRequest
import org.springframework.boot.web.servlet.error.ErrorAttributes
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.ExceptionHandler

@Controller
class MyController {

    @ExceptionHandler(CustomException::class)
    fun handleCustomException(request: HttpServletRequest, ex: CustomException?): String {
        request.setAttribute(ErrorAttributes.ERROR_ATTRIBUTE, ex)
        return "errorView"
    }

}
自定义错误页面

如果要为给定状态代码显示自定义 HTML 错误页面,可以将文件添加到/error目录。 错误页面可以是静态 HTML(即,添加到任何静态资源目录下),也可以使用模板构建。 文件名应为确切的状态代码或系列掩码。spring-doc.cadn.net.cn

例如,要将404转换为静态 HTML 文件,则您的目录结构将如下所示:spring-doc.cadn.net.cn

src/
 +- main/
     +- java/
     |   + <source code>
     +- resources/
         +- public/
             +- error/
             |   +- 404.html
             +- <other public assets>

要映射所有5xx错误,则您的目录结构将如下所示:spring-doc.cadn.net.cn

src/
 +- main/
     +- java/
     |   + <source code>
     +- resources/
         +- templates/
             +- error/
             |   +- 5xx.ftlh
             +- <other templates>

对于更复杂的映射,您还可以添加实现ErrorViewResolver接口,如以下示例所示:spring-doc.cadn.net.cn

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

}
Kotlin
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-doc.cadn.net.cn

在 Spring MVC 之外映射错误页面

对于不使用 Spring MVC 的应用程序,您可以使用ErrorPageRegistrar直接注册的接口ErrorPages. 这种抽象直接与底层嵌入式 servlet 容器一起工作,即使你没有 Spring MVC,也可以工作DispatcherServlet.spring-doc.cadn.net.cn

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

}
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)
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必须显式注册为ERRORDispatcher,如以下示例所示:
Java
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;
    }

}
Kotlin
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不包括ERRORDispatcher 类型。spring-doc.cadn.net.cn

WAR 部署中的错误处理

当部署到 servlet 容器时, Spring Boot 使用其错误页面过滤器将具有错误状态的请求转发到相应的错误页面。 这是必需的,因为 servlet 规范没有提供用于注册错误页面的 API。 根据要将 war 文件部署到的容器和应用程序使用的技术,可能需要一些额外的配置。spring-doc.cadn.net.cn

如果尚未提交响应,则错误页面过滤器只能将请求转发到正确的错误页面。 默认情况下,WebSphere Application Server 8.0 及更高版本在成功完成 servlet 的服务方法后提交响应。 您应该通过设置com.ibm.ws.webcontainer.invokeFlushAfterServicefalse.spring-doc.cadn.net.cn

1.1.12. CORS 支持

跨域资源共享 (CORS) 是大多数浏览器实现的 W3C 规范,它允许您以灵活的方式指定授权的跨域请求类型,而不是使用一些安全性较低且功能较弱的方法,例如 IFRAME 或 JSONP。spring-doc.cadn.net.cn

从版本 4.2 开始,Spring MVC 支持 CORS。 使用控制器方法 CORS 配置@CrossOriginSpring Boot 应用程序中的注释不需要任何特定的配置。全局 CORS 配置可以通过注册WebMvcConfigurerbean 替换为自定义的addCorsMappings(CorsRegistry)方法,如以下示例所示:spring-doc.cadn.net.cn

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

        };
    }

}
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)
class MyCorsConfiguration {

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

}

1.2. JAX-RS 和Jersey

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

要开始使用 Jersey,请包括spring-boot-starter-jersey作为依赖项,然后您需要一个@Bean的类型ResourceConfig,您可以在其中注册所有终端节点,如以下示例所示:spring-doc.cadn.net.cn

import org.glassfish.jersey.server.ResourceConfig;

import org.springframework.stereotype.Component;

@Component
public class MyJerseyConfig extends ResourceConfig {

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

}
Jersey 对扫描可执行档案的支持相当有限。 例如,它无法扫描在完全可执行的 jar 文件WEB-INF/classes运行可执行 war 文件时。 为避免此限制,packages方法,并且应使用register方法,如前面的示例所示。

对于更高级的自定义,您还可以注册任意数量的 bean 来实现ResourceConfigCustomizer.spring-doc.cadn.net.cn

所有已注册的端点都应该是@Components使用 HTTP 资源注释 (@GET和其他),如以下示例所示:spring-doc.cadn.net.cn

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.spring-doc.cadn.net.cn

默认情况下,Jersey 在@Bean的类型ServletRegistrationBeanjerseyServletRegistration. 默认情况下,Servlet 是延迟初始化的,但您可以通过设置spring.jersey.servlet.load-on-startup. 您可以通过创建一个具有相同名称的 bean 来禁用或覆盖该 bean。 您还可以使用过滤器而不是 servlet,方法是将spring.jersey.type=filter(在这种情况下,@Bean替换或覆盖是jerseyFilterRegistration). 该过滤器具有@Order,您可以使用spring.jersey.filter.order. 当使用 Jersey 作为过滤器时,必须存在一个 Servlet,该 Servlet 将处理 Jersey 未拦截的任何请求。 如果您的应用程序不包含这样的 Servlet,则可能需要通过设置server.servlet.register-default-servlettrue. Servlet 和过滤器注册都可以通过使用spring.jersey.init.*以指定属性的映射。spring-doc.cadn.net.cn

1.3. 嵌入式 Servlet 容器支持

对于 servlet 应用程序,Spring Boot 包括对嵌入式 TomcatJettyUndertow 服务器的支持。 大多数开发人员使用适当的 “Starter” 来获取完全配置的实例。 默认情况下,嵌入式服务器在端口8080.spring-doc.cadn.net.cn

1.3.1. Servlet、过滤器和侦听器

使用嵌入式 servlet 容器时,您可以注册 servlet、过滤器和所有侦听器(例如HttpSessionListener),通过使用 Spring Bean 或扫描 servlet 组件。spring-doc.cadn.net.cn

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

任何Servlet,Filter或 Servlet*Listener实例,即 Spring Bean 注册到嵌入式容器中。 如果您想引用application.properties在配置期间。spring-doc.cadn.net.cn

默认情况下,如果上下文仅包含单个 Servlet,则会将其映射到 。 在多个 servlet bean 的情况下,bean 名称用作路径前缀。 筛选器映射到 。//*spring-doc.cadn.net.cn

如果基于约定的映射不够灵活,您可以使用ServletRegistrationBean,FilterRegistrationBeanServletListenerRegistrationBean类以实现完全控制。spring-doc.cadn.net.cn

通常,将 filter bean 保持无序状态是安全的。 如果需要特定顺序,则应注释Filter@Order或使其实现Ordered. 您无法配置Filter通过使用@Order. 如果无法更改Filter类来添加@Order或实施Ordered中,您必须定义一个FilterRegistrationBean对于Filter并使用setOrder(int)方法。 避免配置一个过滤器,该过滤器在Ordered.HIGHEST_PRECEDENCE,因为它可能违背应用程序的字符编码配置。 如果 servlet 过滤器包装了请求,则应将其配置为小于或等于OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER.spring-doc.cadn.net.cn

要查看每个Filter在您的应用程序中,为web 日志记录组 (logging.level.web=debug). 然后,将在启动时记录已注册过滤器的详细信息,包括它们的顺序和 URL 模式。
注册时要小心Filterbean,因为它们在应用程序生命周期的早期就被初始化了。 如果您需要注册Filter,请考虑使用DelegatingFilterProxyRegistrationBean相反。

1.3.2. Servlet 上下文初始化

嵌入式 servlet 容器不会直接执行jakarta.servlet.ServletContainerInitializerinterface 或 Spring 的org.springframework.web.WebApplicationInitializer接口。 这是一个有意的设计决策,旨在降低设计为在 war 中运行的第三方库可能会破坏 Spring Boot 应用程序的风险。spring-doc.cadn.net.cn

如果需要在 Spring Boot 应用程序中执行 servlet 上下文初始化,则应注册一个实现org.springframework.boot.web.servlet.ServletContextInitializer接口。 单onStartupmethod 提供对ServletContext并且,如有必要,可以很容易地用作现有WebApplicationInitializer.spring-doc.cadn.net.cn

扫描 Servlet、Filter 和侦听器

使用嵌入式容器时,自动注册带有@WebServlet,@WebFilter@WebListener可以通过使用@ServletComponentScan.spring-doc.cadn.net.cn

@ServletComponentScan在独立容器中不起作用,其中使用容器的内置发现机制。

1.3.3. ServletWebServerApplicationContext

在后台,Spring Boot 使用不同类型的ApplicationContext,了解嵌入式 servlet 容器支持。 这ServletWebServerApplicationContext是一种特殊类型的WebApplicationContext它通过搜索单个ServletWebServerFactory豆。 通常为TomcatServletWebServerFactory,JettyServletWebServerFactoryUndertowServletWebServerFactory已自动配置。spring-doc.cadn.net.cn

您通常不需要了解这些 implementation classes。 大多数应用程序都是自动配置的,并且适当的ApplicationContextServletWebServerFactory是代表您创建的。

在嵌入式容器设置中,ServletContext设置为服务器启动的一部分,该启动发生在应用程序上下文初始化期间。 由于这个 bean 在ApplicationContext不能使用ServletContext. 解决这个问题的一种方法是注射ApplicationContext作为 Bean 的依赖项,并访问ServletContext仅在需要时。 另一种方法是在服务器启动后使用回调。 这可以使用ApplicationListener它监听ApplicationStartedEvent如下:spring-doc.cadn.net.cn

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

}

1.3.4. 自定义嵌入式 Servlet 容器

可以使用 Spring 配置常见的 servlet 容器设置Environment性能。 通常,您会在application.propertiesapplication.yaml文件。spring-doc.cadn.net.cn

常见的服务器设置包括:spring-doc.cadn.net.cn

Spring Boot 会尽可能多地公开公共设置,但这并不总是可能的。 对于这些情况,专用命名空间提供特定于服务器的自定义(请参阅server.tomcatserver.undertow). 例如,可以使用嵌入式 servlet 容器的特定功能配置访问日志spring-doc.cadn.net.cn

请参阅ServerProperties类以获取完整列表。
SameSite Cookie

SameSiteWeb 浏览器可以使用 cookie 属性来控制是否以及如何在跨站点请求中提交 cookie。 该属性与现代 Web 浏览器尤其相关,这些浏览器已开始更改在缺少该属性时使用的默认值。spring-doc.cadn.net.cn

如果要更改SameSite属性,您可以使用server.servlet.session.cookie.same-site财产。 自动配置的 Tomcat、Jetty 和 Undertow 服务器支持此属性。 它还用于配置基于 Spring Session servletSessionRepository豆。spring-doc.cadn.net.cn

例如,如果您希望会话 Cookie 具有SameSite属性None中,您可以将以下内容添加到application.propertiesapplication.yaml文件:spring-doc.cadn.net.cn

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

如果要更改SameSite添加到其他 Cookie 的属性HttpServletResponse,您可以使用CookieSameSiteSupplier. 这CookieSameSiteSupplier将传递一个Cookie,并且可能会返回SameSitevalue 或null.spring-doc.cadn.net.cn

您可以使用许多方便的 Factory 和 Filter 方法来快速匹配特定的 Cookie。 例如,添加以下 bean 将自动应用SameSiteLax对于名称与正则表达式匹配的所有 Cookiemyapp.*.spring-doc.cadn.net.cn

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

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

}
字符编码

用于请求和响应处理的嵌入式 servlet 容器的字符编码行为可以使用server.servlet.encoding.*configuration 属性。spring-doc.cadn.net.cn

当请求的Accept-Languageheader 表示请求的语言环境,它将被 servlet 容器自动映射到字符集。 每个容器提供程序都默认区域设置到 charset 映射,您应该验证它们是否满足应用程序的需求。 如果它们不这样做,请使用server.servlet.encoding.mappingconfiguration 属性来自定义映射,如以下示例所示:spring-doc.cadn.net.cn

性能
server.servlet.encoding.mapping.ko=UTF-8
Yaml
server:
  servlet:
    encoding:
      mapping:
        ko: "UTF-8"

在前面的示例中,ko(韩语) locale 已映射到UTF-8. 这相当于<locale-encoding-mapping-list>条目web.xml文件。spring-doc.cadn.net.cn

编程自定义

如果需要以编程方式配置嵌入式 servlet 容器,可以注册一个实现WebServerFactoryCustomizer接口。WebServerFactoryCustomizer提供对ConfigurableServletWebServerFactory,其中包括许多自定义 setter 方法。 以下示例显示了以编程方式设置端口:spring-doc.cadn.net.cn

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

}
Kotlin
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,JettyServletWebServerFactoryUndertowServletWebServerFactory是 的专用变体ConfigurableServletWebServerFactory分别具有 Tomcat、Jetty 和 Undertow 的其他自定义 setter 方法。 以下示例显示如何自定义TomcatServletWebServerFactory,提供对特定于 Tomcat 的配置选项的访问:spring-doc.cadn.net.cn

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

}
Kotlin
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。spring-doc.cadn.net.cn

为许多配置选项提供了 setter。 如果你需要做一些更奇特的事情,还提供了几个受保护的方法 “钩子”。 有关详细信息,请参阅源代码文档spring-doc.cadn.net.cn

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

1.3.5. JSP 限制

当运行使用嵌入式 servlet 容器(并打包为可执行存档)的 Spring Boot 应用程序时,JSP 支持中存在一些限制。spring-doc.cadn.net.cn

2. 反应式 Web 应用程序

Spring Boot 通过为 Spring Webflux 提供自动配置来简化反应式 Web 应用程序的开发。spring-doc.cadn.net.cn

2.1. “Spring WebFlux 框架”

Spring WebFlux 是 Spring Framework 5.0 中引入的新反应式 Web 框架。 与 Spring MVC 不同,它不需要 servlet API,是完全异步和非阻塞的,并通过 Reactor 项目实现反应流规范。spring-doc.cadn.net.cn

Spring WebFlux 有两种风格:函数式和基于 Comments 的。 基于 Comments 的模型与 Spring MVC 模型非常接近,如以下示例所示:spring-doc.cadn.net.cn

Java
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

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 Mono<User> getUser(@PathVariable Long userId) {
        return this.userRepository.findById(userId);
    }

    @GetMapping("/{userId}/customers")
    public Flux<Customer> getUserCustomers(@PathVariable Long userId) {
        return this.userRepository.findById(userId).flatMapMany(this.customerRepository::findByUser);
    }

    @DeleteMapping("/{userId}")
    public Mono<Void> deleteUser(@PathVariable Long userId) {
        return this.userRepository.deleteById(userId);
    }

}
Kotlin
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
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono

@RestController
@RequestMapping("/users")
class MyRestController(private val userRepository: UserRepository, private val customerRepository: CustomerRepository) {

    @GetMapping("/{userId}")
    fun getUser(@PathVariable userId: Long): Mono<User?> {
        return userRepository.findById(userId)
    }

    @GetMapping("/{userId}/customers")
    fun getUserCustomers(@PathVariable userId: Long): Flux<Customer> {
        return userRepository.findById(userId).flatMapMany { user: User? ->
            customerRepository.findByUser(user)
        }
    }

    @DeleteMapping("/{userId}")
    fun deleteUser(@PathVariable userId: Long): Mono<Void> {
        return userRepository.deleteById(userId)
    }

}

WebFlux 是 Spring Framework 的一部分,其参考文档中提供了详细信息。spring-doc.cadn.net.cn

“WebFlux.fn”是功能变体,它将路由配置与请求的实际处理分开,如以下示例所示:spring-doc.cadn.net.cn

Java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicate;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;

import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;

@Configuration(proxyBeanMethods = false)
public class MyRoutingConfiguration {

    private static final RequestPredicate ACCEPT_JSON = accept(MediaType.APPLICATION_JSON);

    @Bean
    public RouterFunction<ServerResponse> monoRouterFunction(MyUserHandler userHandler) {
        return route()
                .GET("/{user}", ACCEPT_JSON, userHandler::getUser)
                .GET("/{user}/customers", ACCEPT_JSON, userHandler::getUserCustomers)
                .DELETE("/{user}", ACCEPT_JSON, userHandler::deleteUser)
                .build();
    }

}
Kotlin
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.MediaType
import org.springframework.web.reactive.function.server.RequestPredicates.DELETE
import org.springframework.web.reactive.function.server.RequestPredicates.GET
import org.springframework.web.reactive.function.server.RequestPredicates.accept
import org.springframework.web.reactive.function.server.RouterFunction
import org.springframework.web.reactive.function.server.RouterFunctions
import org.springframework.web.reactive.function.server.ServerResponse

@Configuration(proxyBeanMethods = false)
class MyRoutingConfiguration {

    @Bean
    fun monoRouterFunction(userHandler: MyUserHandler): RouterFunction<ServerResponse> {
        return RouterFunctions.route(
            GET("/{user}").and(ACCEPT_JSON), userHandler::getUser).andRoute(
            GET("/{user}/customers").and(ACCEPT_JSON), userHandler::getUserCustomers).andRoute(
            DELETE("/{user}").and(ACCEPT_JSON), userHandler::deleteUser)
    }

    companion object {
        private val ACCEPT_JSON = accept(MediaType.APPLICATION_JSON)
    }

}
Java
import reactor.core.publisher.Mono;

import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;

@Component
public class MyUserHandler {

    public Mono<ServerResponse> getUser(ServerRequest request) {
        ...
    }

    public Mono<ServerResponse> getUserCustomers(ServerRequest request) {
        ...
    }

    public Mono<ServerResponse> deleteUser(ServerRequest request) {
        ...
    }

}
Kotlin
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import reactor.core.publisher.Mono

@Component
class MyUserHandler {

    fun getUser(request: ServerRequest?): Mono<ServerResponse> {
        return ServerResponse.ok().build()
    }

    fun getUserCustomers(request: ServerRequest?): Mono<ServerResponse> {
        return ServerResponse.ok().build()
    }

    fun deleteUser(request: ServerRequest?): Mono<ServerResponse> {
        return ServerResponse.ok().build()
    }

}

“WebFlux.fn” 是 Spring Framework 的一部分,详细信息可在其参考文档中找到。spring-doc.cadn.net.cn

您可以定义任意数量的RouterFunctionbean 来模块化路由器的定义。 如果需要应用优先级,则可以对 bean 进行排序。

要开始使用,请添加spring-boot-starter-webflux模块添加到您的应用程序。spring-doc.cadn.net.cn

将两者相加spring-boot-starter-webspring-boot-starter-webfluxmodules 会导致 Spring Boot 自动配置 Spring MVC,而不是 WebFlux。 之所以选择这种行为,是因为许多 Spring 开发人员在spring-boot-starter-webflux到他们的 Spring MVC 应用程序以使用响应式WebClient. 您仍然可以通过将所选应用程序类型设置为SpringApplication.setWebApplicationType(WebApplicationType.REACTIVE).

2.1.1. Spring WebFlux 自动配置

Spring Boot 为 Spring WebFlux 提供了自动配置,适用于大多数应用程序。spring-doc.cadn.net.cn

自动配置在 Spring 的默认值之上添加了以下功能:spring-doc.cadn.net.cn

如果要保留 Spring Boot WebFlux 功能,并且想要添加其他 WebFlux 配置,则可以添加自己的@Configurationtype 类WebFluxConfigurer但没有 @EnableWebFlux.spring-doc.cadn.net.cn

如果您想完全控制 Spring WebFlux,您可以添加自己的@Configuration注解@EnableWebFlux.spring-doc.cadn.net.cn

2.1.2. Spring WebFlux 转换服务

如果要自定义ConversionService由 Spring WebFlux 使用,您可以提供WebFluxConfigurer带有addFormatters方法。spring-doc.cadn.net.cn

转换也可以使用spring.webflux.format.*configuration 属性。 如果未配置,则使用以下默认值:spring-doc.cadn.net.cn

财产 DateTimeFormatter

spring.webflux.format.datespring-doc.cadn.net.cn

ofLocalizedDate(FormatStyle.SHORT)spring-doc.cadn.net.cn

spring.webflux.format.timespring-doc.cadn.net.cn

ofLocalizedTime(FormatStyle.SHORT)spring-doc.cadn.net.cn

spring.webflux.format.date-timespring-doc.cadn.net.cn

ofLocalizedDateTime(FormatStyle.SHORT)spring-doc.cadn.net.cn

2.1.3. 使用 HttpMessageReaders 和 HttpMessageWriters 的 HTTP 编解码器

Spring WebFlux 使用HttpMessageReaderHttpMessageWriter接口来转换 HTTP 请求和响应。 它们配置了CodecConfigurer通过查看 Classpath 中可用的库来获得合理的默认值。spring-doc.cadn.net.cn

Spring Boot 为编解码器提供了专用的配置属性,spring.codec.*. 它还通过使用CodecCustomizer实例。 例如spring.jackson.*配置键应用于 Jackson 编解码器。spring-doc.cadn.net.cn

如果需要添加或自定义编解码器,可以创建自定义CodecCustomizer组件,如以下示例所示:spring-doc.cadn.net.cn

Java
import org.springframework.boot.web.codec.CodecCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.codec.ServerSentEventHttpMessageReader;

@Configuration(proxyBeanMethods = false)
public class MyCodecsConfiguration {

    @Bean
    public CodecCustomizer myCodecCustomizer() {
        return (configurer) -> {
            configurer.registerDefaults(false);
            configurer.customCodecs().register(new ServerSentEventHttpMessageReader());
            // ...
        };
    }

}
Kotlin
import org.springframework.boot.web.codec.CodecCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.http.codec.CodecConfigurer
import org.springframework.http.codec.ServerSentEventHttpMessageReader

class MyCodecsConfiguration {

    @Bean
    fun myCodecCustomizer(): CodecCustomizer {
        return CodecCustomizer { configurer: CodecConfigurer ->
            configurer.registerDefaults(false)
            configurer.customCodecs().register(ServerSentEventHttpMessageReader())
        }
    }

}

2.1.4. 静态内容

默认情况下, Spring Boot 从名为/static(或/public/resources/META-INF/resources) 中。 它使用ResourceWebHandler从 Spring WebFlux,以便您可以通过添加自己的WebFluxConfigurer并覆盖addResourceHandlers方法。spring-doc.cadn.net.cn

默认情况下,资源映射在 上,但您可以通过设置/**spring.webflux.static-path-pattern财产。 例如,将所有资源重新定位到/resources/**可以按如下方式实现:spring-doc.cadn.net.cn

性能
spring.webflux.static-path-pattern=/resources/**
Yaml
spring:
  webflux:
    static-path-pattern: "/resources/**"

您还可以使用spring.web.resources.static-locations. 这样做会将默认值替换为目录位置列表。 如果这样做,默认的欢迎页面检测将切换到您的自定义位置。 因此,如果存在index.html在启动时的任何位置,它都是应用程序的主页。spring-doc.cadn.net.cn

除了前面列出的“标准”静态资源位置之外,Webjars 内容还有一个特殊情况。 默认情况下,路径为/webjars/**如果 jar 文件以 Webjars 格式打包,则从 jar 文件中提供。 可以使用spring.webflux.webjars-path-pattern财产。spring-doc.cadn.net.cn

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

2.1.5. 欢迎页面

Spring Boot 支持静态和模板化欢迎页面。 它首先查找index.html文件。 如果未找到,则查找index模板。 如果找到任何一个,它将自动用作应用程序的欢迎页面。spring-doc.cadn.net.cn

2.1.6. 模板引擎

除了 REST Web 服务之外,您还可以使用 Spring WebFlux 来提供动态 HTML 内容。 Spring WebFlux 支持多种模板技术,包括 Thymeleaf、FreeMarker 和 Mustache。spring-doc.cadn.net.cn

Spring Boot 包括对以下模板引擎的自动配置支持:spring-doc.cadn.net.cn

当您使用具有默认配置的模板引擎之一时,您的模板会自动从src/main/resources/templates.spring-doc.cadn.net.cn

2.1.7. 错误处理

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

在直接在 Spring Boot 中自定义错误处理之前,您可以利用 Spring WebFlux 中的 RFC 7807 问题详细信息支持。 Spring WebFlux 可以使用application/problem+jsonmedia 类型,例如:spring-doc.cadn.net.cn

{
  "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.webflux.problemdetails.enabledtrue.spring-doc.cadn.net.cn

自定义此功能的第一步通常涉及使用现有机制,但替换或扩充错误内容。 为此,您可以添加ErrorAttributes.spring-doc.cadn.net.cn

要更改错误处理行为,您可以实现ErrorWebExceptionHandler并注册该类型的 bean 定义。 因为ErrorWebExceptionHandler相当底层,Spring Boot 还提供了一个方便的AbstractErrorWebExceptionHandler让你以 WebFlux 功能方式处理错误,如以下示例所示:spring-doc.cadn.net.cn

Java
import reactor.core.publisher.Mono;

import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.function.server.ServerResponse.BodyBuilder;

@Component
public class MyErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {

    public MyErrorWebExceptionHandler(ErrorAttributes errorAttributes, WebProperties webProperties,
            ApplicationContext applicationContext, ServerCodecConfigurer serverCodecConfigurer) {
        super(errorAttributes, webProperties.getResources(), applicationContext);
        setMessageReaders(serverCodecConfigurer.getReaders());
        setMessageWriters(serverCodecConfigurer.getWriters());
    }

    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(this::acceptsXml, this::handleErrorAsXml);
    }

    private boolean acceptsXml(ServerRequest request) {
        return request.headers().accept().contains(MediaType.APPLICATION_XML);
    }

    public Mono<ServerResponse> handleErrorAsXml(ServerRequest request) {
        BodyBuilder builder = ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR);
        // ... additional builder calls
        return builder.build();
    }

}
Kotlin
import org.springframework.boot.autoconfigure.web.WebProperties
import org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler
import org.springframework.boot.web.reactive.error.ErrorAttributes
import org.springframework.context.ApplicationContext
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.codec.ServerCodecConfigurer
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.server.RouterFunction
import org.springframework.web.reactive.function.server.RouterFunctions
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import reactor.core.publisher.Mono

@Component
class MyErrorWebExceptionHandler(
        errorAttributes: ErrorAttributes, webProperties: WebProperties,
        applicationContext: ApplicationContext, serverCodecConfigurer: ServerCodecConfigurer
) : AbstractErrorWebExceptionHandler(errorAttributes, webProperties.resources, applicationContext) {

    init {
        setMessageReaders(serverCodecConfigurer.readers)
        setMessageWriters(serverCodecConfigurer.writers)
    }

    override fun getRoutingFunction(errorAttributes: ErrorAttributes): RouterFunction<ServerResponse> {
        return RouterFunctions.route(this::acceptsXml, this::handleErrorAsXml)
    }

    private fun acceptsXml(request: ServerRequest): Boolean {
        return request.headers().accept().contains(MediaType.APPLICATION_XML)
    }

    fun handleErrorAsXml(request: ServerRequest): Mono<ServerResponse> {
        val builder = ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR)
        // ... additional builder calls
        return builder.build()
    }

}

为了获得更完整的图片,您还可以子类化DefaultErrorWebExceptionHandler直接并覆盖特定方法。spring-doc.cadn.net.cn

在某些情况下,指标基础设施不会记录在控制器或处理程序函数级别处理的错误。 应用程序可以通过将 handled 异常设置为 request 属性来确保此类异常与请求指标一起记录:spring-doc.cadn.net.cn

Java
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.reactive.result.view.Rendering;
import org.springframework.web.server.ServerWebExchange;

@Controller
public class MyExceptionHandlingController {

    @GetMapping("/profile")
    public Rendering userProfile() {
        // ...
        throw new IllegalStateException();
    }

    @ExceptionHandler(IllegalStateException.class)
    public Rendering handleIllegalState(ServerWebExchange exchange, IllegalStateException exc) {
        exchange.getAttributes().putIfAbsent(ErrorAttributes.ERROR_ATTRIBUTE, exc);
        return Rendering.view("errorView").modelAttribute("message", exc.getMessage()).build();
    }

}
Kotlin
import org.springframework.boot.web.reactive.error.ErrorAttributes
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.reactive.result.view.Rendering
import org.springframework.web.server.ServerWebExchange

@Controller
class MyExceptionHandlingController {

    @GetMapping("/profile")
    fun userProfile(): Rendering {
        // ...
        throw IllegalStateException()
    }

    @ExceptionHandler(IllegalStateException::class)
    fun handleIllegalState(exchange: ServerWebExchange, exc: IllegalStateException): Rendering {
        exchange.attributes.putIfAbsent(ErrorAttributes.ERROR_ATTRIBUTE, exc)
        return Rendering.view("errorView").modelAttribute("message", exc.message ?: "").build()
    }

}
自定义错误页面

如果要显示给定状态代码的自定义 HTML 错误页面,可以添加从error/*,例如,通过将文件添加到/error目录。 错误页面可以是静态 HTML(即,添加到任何静态资源目录下),也可以是用模板构建的。 文件名应为确切的状态代码、状态代码系列掩码,或error,如果没有其他匹配项,则为 default。 请注意,默认错误视图的路径是error/error,而使用 Spring MVC 时,默认错误视图为error.spring-doc.cadn.net.cn

例如,要将404转换为静态 HTML 文件,则您的目录结构将如下所示:spring-doc.cadn.net.cn

src/
 +- main/
     +- java/
     |   + <source code>
     +- resources/
         +- public/
             +- error/
             |   +- 404.html
             +- <other public assets>

要映射所有5xx错误,您的目录结构将如下所示:spring-doc.cadn.net.cn

src/
 +- main/
     +- java/
     |   + <source code>
     +- resources/
         +- templates/
             +- error/
             |   +- 5xx.mustache
             +- <other templates>

2.1.8. Web 过滤器

Spring WebFlux 提供了一个WebFilter接口,该接口可以实现以过滤 HTTP 请求-响应交换。WebFilter在应用程序上下文中找到的 bean 将自动用于过滤每个交换。spring-doc.cadn.net.cn

如果过滤器的顺序很重要,他们可以实施Ordered或带有@Order. Spring Boot 自动配置可以为您配置 Web 过滤器。 执行此作时,将使用下表中显示的订单:spring-doc.cadn.net.cn

Web 过滤器 次序

ServerHttpObservationFilter(微米可观察性)spring-doc.cadn.net.cn

Ordered.HIGHEST_PRECEDENCE + 1spring-doc.cadn.net.cn

WebFilterChainProxy(Spring Security性)spring-doc.cadn.net.cn

-100spring-doc.cadn.net.cn

HttpExchangesWebFilterspring-doc.cadn.net.cn

Ordered.LOWEST_PRECEDENCE - 10spring-doc.cadn.net.cn

2.2. 嵌入式反应式服务器支持

Spring Boot 包括对以下嵌入式反应式 Web 服务器的支持:Reactor Netty、Tomcat、Jetty 和 Undertow。 大多数开发人员使用适当的 “Starter” 来获取完全配置的实例。 默认情况下,嵌入式服务器在端口 8080 上侦听 HTTP 请求。spring-doc.cadn.net.cn

2.2.1. 自定义反应式服务器

常见的反应式 Web 服务器设置可以使用 Spring 进行配置Environment性能。 通常,您会在application.propertiesapplication.yaml文件。spring-doc.cadn.net.cn

常见的服务器设置包括:spring-doc.cadn.net.cn

Spring Boot 会尽可能多地公开通用设置,但这并不总是可能的。 在这些情况下,专用命名空间(例如server.netty.*提供特定于服务器的自定义设置。spring-doc.cadn.net.cn

请参阅ServerProperties类以获取完整列表。
编程自定义

如果需要以编程方式配置反应式 Web 服务器,可以注册一个实现WebServerFactoryCustomizer接口。WebServerFactoryCustomizer提供对ConfigurableReactiveWebServerFactory,其中包括许多自定义 setter 方法。 以下示例显示了以编程方式设置端口:spring-doc.cadn.net.cn

Java
import org.springframework.boot.web.reactive.server.ConfigurableReactiveWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;

@Component
public class MyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableReactiveWebServerFactory> {

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

}
Kotlin
import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.boot.web.reactive.server.ConfigurableReactiveWebServerFactory
import org.springframework.stereotype.Component

@Component
class MyWebServerFactoryCustomizer : WebServerFactoryCustomizer<ConfigurableReactiveWebServerFactory> {

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

}

JettyReactiveWebServerFactory,NettyReactiveWebServerFactory,TomcatReactiveWebServerFactoryUndertowServletWebServerFactory是 的专用变体ConfigurableReactiveWebServerFactory分别具有 Jetty、Reactor Netty、Tomcat 和 Undertow 的其他自定义 setter 方法。 以下示例显示如何自定义NettyReactiveWebServerFactory,提供对 Reactor Netty 特定的配置选项的访问:spring-doc.cadn.net.cn

Java
import java.time.Duration;

import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;

@Component
public class MyNettyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {

    @Override
    public void customize(NettyReactiveWebServerFactory factory) {
        factory.addServerCustomizers((server) -> server.idleTimeout(Duration.ofSeconds(20)));
    }

}
Kotlin
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory
import org.springframework.boot.web.server.WebServerFactoryCustomizer
import org.springframework.stereotype.Component
import java.time.Duration

@Component
class MyNettyWebServerFactoryCustomizer : WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {

    override fun customize(factory: NettyReactiveWebServerFactory) {
        factory.addServerCustomizers({ server -> server.idleTimeout(Duration.ofSeconds(20)) })
    }

}
直接自定义 ConfigurableReactiveWebServerFactory

对于需要您从ReactiveWebServerFactory,您可以自己公开此类 bean。spring-doc.cadn.net.cn

为许多配置选项提供了 setter。 如果你需要做一些更奇特的事情,还提供了几个受保护的方法 “钩子”。 有关详细信息,请参阅源代码文档spring-doc.cadn.net.cn

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

2.3. 反应式服务器资源配置

在自动配置 Reactor Netty 或 Jetty 服务器时, Spring Boot 将创建特定的 bean,这些 bean 将向服务器实例提供 HTTP 资源:ReactorResourceFactoryJettyResourceFactory.spring-doc.cadn.net.cn

默认情况下,这些资源也将与 Reactor Netty 和 Jetty 客户端共享,以获得最佳性能,前提是:spring-doc.cadn.net.cn

开发人员可以通过提供自定义ReactorResourceFactoryJettyResourceFactorybean - 这将应用于客户端和服务器。spring-doc.cadn.net.cn

您可以在 WebClient 运行时 部分了解有关客户端资源配置的更多信息。spring-doc.cadn.net.cn

3. 优雅关机

所有四个嵌入式 Web 服务器(Jetty、Reactor Netty、Tomcat 和 Undertow)以及反应式和基于 servlet 的 Web 应用程序都支持正常关闭。 它是作为关闭应用程序上下文的一部分发生的,并在停止的最早阶段执行SmartLifecycle豆。 此停止处理使用超时,该超时提供宽限期,在此期间将允许完成现有请求,但不允许新请求。spring-doc.cadn.net.cn

不允许新请求的确切方式因所使用的 Web 服务器而异。 实现可能会在网络层停止接受请求,或者它们可能会返回具有特定 HTTP 状态代码或 HTTP 标头的响应。 使用持久连接还可以更改停止接受请求的方式。spring-doc.cadn.net.cn

要了解有关 Web 服务器使用的特定方法的更多信息,请参阅shutDownGracefully适用于 TomcatWebServerNettyWebServerJettyWebServerUndertowWebServer 的 javadoc。

Jetty、Reactor Netty 和 Tomcat 将停止在网络层接受新请求。 Undertow 将接受新连接,但会立即响应服务不可用 (503) 响应。spring-doc.cadn.net.cn

使用 Tomcat 正常关闭需要 Tomcat 9.0.33 或更高版本。

要启用正常关闭,请配置server.shutdown属性,如以下示例所示:spring-doc.cadn.net.cn

性能
server.shutdown=graceful
Yaml
server:
  shutdown: "graceful"

要配置超时时间,请配置spring.lifecycle.timeout-per-shutdown-phase属性,如以下示例所示:spring-doc.cadn.net.cn

性能
spring.lifecycle.timeout-per-shutdown-phase=20s
Yaml
spring:
  lifecycle:
    timeout-per-shutdown-phase: "20s"
如果 IDE 没有发送正确的SIGTERM信号。 有关更多详细信息,请参阅 IDE 的文档。

4. Spring Security

如果 Spring Security 在 Classpath 上,则默认情况下 Web 应用程序是安全的。 Spring Boot 依靠 Spring Security 的内容协商策略来确定是否使用httpBasicformLogin. 要向 Web 应用程序添加方法级安全性,您还可以添加@EnableGlobalMethodSecurity替换为所需的设置。 其他信息可以在 Spring Security Reference Guide 中找到。spring-doc.cadn.net.cn

默认的UserDetailsService具有单个用户。 用户名为user,并且密码是随机的,并且在应用程序启动时以 WARN 级别打印,如以下示例所示:spring-doc.cadn.net.cn

Using generated security password: 78fa095d-3f4c-48b1-ad50-e24c31d5cf35

This generated password is for development use only. Your security configuration must be updated before running your application in production.
如果您微调日志记录配置,请确保org.springframework.boot.autoconfigure.securitycategory 设置为 logWARN-level 消息。 否则,不会打印默认密码。

您可以通过提供spring.security.user.namespring.security.user.password.spring-doc.cadn.net.cn

默认情况下,您在 Web 应用程序中获得的基本功能包括:spring-doc.cadn.net.cn

  • 一个UserDetailsService(或ReactiveUserDetailsService如果是 WebFlux 应用程序),则具有内存存储和具有生成密码的单个用户(参见SecurityProperties.User对于用户的属性)。spring-doc.cadn.net.cn

  • 基于表单的登录或 HTTP Basic 安全性(取决于Accept标头)对于整个应用程序(如果 Actuator 在 Classpath 上,则包括 Actuator 端点)。spring-doc.cadn.net.cn

  • 一个DefaultAuthenticationEventPublisher用于发布身份验证事件。spring-doc.cadn.net.cn

您可以提供不同的AuthenticationEventPublisher通过为其添加 Bean 来获取。spring-doc.cadn.net.cn

4.1. MVC 安全性

默认安全配置在SecurityAutoConfigurationUserDetailsServiceAutoConfiguration.SecurityAutoConfiguration进口SpringBootWebSecurityConfiguration用于 Web 安全,以及UserDetailsServiceAutoConfiguration配置身份验证,这在非 Web 应用程序中也相关。 要完全关闭默认的 Web 应用程序安全配置或组合多个 Spring Security 组件(例如 OAuth2 客户端和资源服务器),请添加SecurityFilterChain(这样做不会禁用UserDetailsServiceconfiguration 或 Actuator 的安全性)。spring-doc.cadn.net.cn

要同时关闭UserDetailsServiceconfiguration 中,您可以添加UserDetailsService,AuthenticationProviderAuthenticationManager.spring-doc.cadn.net.cn

可以通过添加自定义SecurityFilterChain豆。 Spring Boot 提供了方便的方法,可用于覆盖 actuator endpoints 和 static 资源的访问规则。EndpointRequest可用于创建RequestMatcher基于management.endpoints.web.base-path财产。PathRequest可用于创建RequestMatcher以获取常用位置中的资源。spring-doc.cadn.net.cn

4.2. WebFlux 安全

与 Spring MVC 应用程序类似,您可以通过添加spring-boot-starter-securityDependency。 默认安全配置在ReactiveSecurityAutoConfigurationUserDetailsServiceAutoConfiguration.ReactiveSecurityAutoConfiguration进口WebFluxSecurityConfiguration用于 Web 安全,以及UserDetailsServiceAutoConfiguration配置身份验证,这在非 Web 应用程序中也相关。 要完全关闭默认的 Web 应用程序安全性配置,可以添加WebFilterChainProxy(这样做不会禁用UserDetailsServiceconfiguration 或 Actuator 的安全性)。spring-doc.cadn.net.cn

要同时关闭UserDetailsServiceconfiguration 中,您可以添加ReactiveUserDetailsServiceReactiveAuthenticationManager.spring-doc.cadn.net.cn

访问规则和多个 Spring Security 组件(如 OAuth 2 客户端和资源服务器)的使用可以通过添加自定义SecurityWebFilterChain豆。 Spring Boot 提供了方便的方法,可用于覆盖 actuator endpoints 和 static 资源的访问规则。EndpointRequest可用于创建ServerWebExchangeMatcher基于management.endpoints.web.base-path财产。spring-doc.cadn.net.cn

PathRequest可用于创建ServerWebExchangeMatcher以获取常用位置中的资源。spring-doc.cadn.net.cn

例如,您可以通过添加如下内容来自定义您的安全配置:spring-doc.cadn.net.cn

Java
import org.springframework.boot.autoconfigure.security.reactive.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;

import static org.springframework.security.config.Customizer.withDefaults;

@Configuration(proxyBeanMethods = false)
public class MyWebFluxSecurityConfiguration {

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        http.authorizeExchange((exchange) -> {
            exchange.matchers(PathRequest.toStaticResources().atCommonLocations()).permitAll();
            exchange.pathMatchers("/foo", "/bar").authenticated();
        });
        http.formLogin(withDefaults());
        return http.build();
    }

}
Kotlin
import org.springframework.boot.autoconfigure.security.reactive.PathRequest
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.Customizer.withDefaults
import org.springframework.security.config.web.server.ServerHttpSecurity
import org.springframework.security.web.server.SecurityWebFilterChain

@Configuration(proxyBeanMethods = false)
class MyWebFluxSecurityConfiguration {

    @Bean
    fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
        http.authorizeExchange { spec ->
            spec.matchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
            spec.pathMatchers("/foo", "/bar").authenticated()
        }
        http.formLogin(withDefaults())
        return http.build()
    }

}

4.3. OAuth2

OAuth2 是 Spring 支持的广泛使用的授权框架。spring-doc.cadn.net.cn

4.3.1. 客户端

如果你有spring-security-oauth2-client在您的 Classpath 上,您可以利用一些自动配置来设置 OAuth2/Open ID Connect 客户端。 此配置使用OAuth2ClientProperties. 相同的属性适用于 servlet 和 reactive 应用程序。spring-doc.cadn.net.cn

您可以在spring.security.oauth2.clientprefix,如以下示例所示:spring-doc.cadn.net.cn

性能
spring.security.oauth2.client.registration.my-login-client.client-id=abcd
spring.security.oauth2.client.registration.my-login-client.client-secret=password
spring.security.oauth2.client.registration.my-login-client.client-name=Client for OpenID Connect
spring.security.oauth2.client.registration.my-login-client.provider=my-oauth-provider
spring.security.oauth2.client.registration.my-login-client.scope=openid,profile,email,phone,address
spring.security.oauth2.client.registration.my-login-client.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.my-login-client.client-authentication-method=client_secret_basic
spring.security.oauth2.client.registration.my-login-client.authorization-grant-type=authorization_code

spring.security.oauth2.client.registration.my-client-1.client-id=abcd
spring.security.oauth2.client.registration.my-client-1.client-secret=password
spring.security.oauth2.client.registration.my-client-1.client-name=Client for user scope
spring.security.oauth2.client.registration.my-client-1.provider=my-oauth-provider
spring.security.oauth2.client.registration.my-client-1.scope=user
spring.security.oauth2.client.registration.my-client-1.redirect-uri={baseUrl}/authorized/user
spring.security.oauth2.client.registration.my-client-1.client-authentication-method=client_secret_basic
spring.security.oauth2.client.registration.my-client-1.authorization-grant-type=authorization_code

spring.security.oauth2.client.registration.my-client-2.client-id=abcd
spring.security.oauth2.client.registration.my-client-2.client-secret=password
spring.security.oauth2.client.registration.my-client-2.client-name=Client for email scope
spring.security.oauth2.client.registration.my-client-2.provider=my-oauth-provider
spring.security.oauth2.client.registration.my-client-2.scope=email
spring.security.oauth2.client.registration.my-client-2.redirect-uri={baseUrl}/authorized/email
spring.security.oauth2.client.registration.my-client-2.client-authentication-method=client_secret_basic
spring.security.oauth2.client.registration.my-client-2.authorization-grant-type=authorization_code

spring.security.oauth2.client.provider.my-oauth-provider.authorization-uri=https://my-auth-server.com/oauth2/authorize
spring.security.oauth2.client.provider.my-oauth-provider.token-uri=https://my-auth-server.com/oauth2/token
spring.security.oauth2.client.provider.my-oauth-provider.user-info-uri=https://my-auth-server.com/userinfo
spring.security.oauth2.client.provider.my-oauth-provider.user-info-authentication-method=header
spring.security.oauth2.client.provider.my-oauth-provider.jwk-set-uri=https://my-auth-server.com/oauth2/jwks
spring.security.oauth2.client.provider.my-oauth-provider.user-name-attribute=name
Yaml
spring:
  security:
    oauth2:
      client:
        registration:
          my-login-client:
            client-id: "abcd"
            client-secret: "password"
            client-name: "Client for OpenID Connect"
            provider: "my-oauth-provider"
            scope: "openid,profile,email,phone,address"
            redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
            client-authentication-method: "client_secret_basic"
            authorization-grant-type: "authorization_code"

          my-client-1:
            client-id: "abcd"
            client-secret: "password"
            client-name: "Client for user scope"
            provider: "my-oauth-provider"
            scope: "user"
            redirect-uri: "{baseUrl}/authorized/user"
            client-authentication-method: "client_secret_basic"
            authorization-grant-type: "authorization_code"

          my-client-2:
            client-id: "abcd"
            client-secret: "password"
            client-name: "Client for email scope"
            provider: "my-oauth-provider"
            scope: "email"
            redirect-uri: "{baseUrl}/authorized/email"
            client-authentication-method: "client_secret_basic"
            authorization-grant-type: "authorization_code"

        provider:
          my-oauth-provider:
            authorization-uri: "https://my-auth-server.com/oauth2/authorize"
            token-uri: "https://my-auth-server.com/oauth2/token"
            user-info-uri: "https://my-auth-server.com/userinfo"
            user-info-authentication-method: "header"
            jwk-set-uri: "https://my-auth-server.com/oauth2/jwks"
            user-name-attribute: "name"

对于支持 OpenID Connect 发现的 OpenID Connect 提供商,可以进一步简化配置。 提供程序需要配置一个issuer-uri,这是它断言为其颁发者标识符的 URI。 例如,如果issuer-uri如果是 “https://example.com”,则将向 “https://example.com/.well-known/openid-configuration” 发出“OpenID 提供程序配置请求”。 结果应为“OpenID Provider Configuration Response”。 以下示例显示了如何使用issuer-uri:spring-doc.cadn.net.cn

性能
spring.security.oauth2.client.provider.oidc-provider.issuer-uri=https://dev-123456.oktapreview.com/oauth2/default/
Yaml
spring:
  security:
    oauth2:
      client:
        provider:
          oidc-provider:
            issuer-uri: "https://dev-123456.oktapreview.com/oauth2/default/"

默认情况下,Spring Security 的OAuth2LoginAuthenticationFilter仅处理匹配的 URL/login/oauth2/code/*. 如果要自定义redirect-uri要使用其他模式,您需要提供配置来处理该自定义模式。 例如,对于 servlet 应用程序,您可以添加自己的SecurityFilterChain类似于以下内容:spring-doc.cadn.net.cn

Java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
public class MyOAuthClientConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests((requests) -> requests
                .anyRequest().authenticated()
            )
            .oauth2Login((login) -> login
                .redirectionEndpoint((endpoint) -> endpoint
                    .baseUri("/login/oauth2/callback/*")
                )
            );
        return http.build();
    }

}
Kotlin
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.invoke
import org.springframework.security.web.SecurityFilterChain

@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
open class MyOAuthClientConfiguration {

    @Bean
    open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http {
            authorizeHttpRequests {
                authorize(anyRequest, authenticated)
            }
            oauth2Login {
                redirectionEndpoint {
                    baseUri = "/login/oauth2/callback/*"
                }
            }
        }
        return http.build()
    }

}
Spring Boot 会自动配置InMemoryOAuth2AuthorizedClientServiceSpring Security 使用它来管理 Client 端注册。 这InMemoryOAuth2AuthorizedClientService功能有限,我们建议仅将其用于开发环境。 对于生产环境,请考虑使用JdbcOAuth2AuthorizedClientService或创建您自己的OAuth2AuthorizedClientService.
常见提供商的 OAuth2 客户端注册

对于常见的 OAuth2 和 OpenID 提供程序(包括 Google、Github、Facebook 和 Okta),我们提供了一组提供程序默认值 (google,github,facebookokta)。spring-doc.cadn.net.cn

如果您不需要自定义这些提供程序,则可以设置provider属性设置为需要推断 defaults 的 URL。 此外,如果 Client 端注册的密钥与默认支持的提供程序匹配,则 Spring Boot 也会推断这一点。spring-doc.cadn.net.cn

换句话说,以下示例中的两个配置使用 Google 提供程序:spring-doc.cadn.net.cn

性能
spring.security.oauth2.client.registration.my-client.client-id=abcd
spring.security.oauth2.client.registration.my-client.client-secret=password
spring.security.oauth2.client.registration.my-client.provider=google
spring.security.oauth2.client.registration.google.client-id=abcd
spring.security.oauth2.client.registration.google.client-secret=password
Yaml
spring:
  security:
    oauth2:
      client:
        registration:
          my-client:
            client-id: "abcd"
            client-secret: "password"
            provider: "google"
          google:
            client-id: "abcd"
            client-secret: "password"

4.3.2. 资源服务器

如果你有spring-security-oauth2-resource-server在你的 Classpath 上,Spring Boot 可以设置一个 OAuth2 资源服务器。 对于 JWT 配置,需要指定 JWK Set URI 或 OIDC Issuer URI,如以下示例所示:spring-doc.cadn.net.cn

性能
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://example.com/oauth2/default/v1/keys
Yaml
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: "https://example.com/oauth2/default/v1/keys"
性能
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://dev-123456.oktapreview.com/oauth2/default/
Yaml
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: "https://dev-123456.oktapreview.com/oauth2/default/"
如果授权服务器不支持 JWK 集 URI,则可以使用用于验证 JWT 签名的公钥来配置资源服务器。 这可以使用spring.security.oauth2.resourceserver.jwt.public-key-location属性,其中该值需要指向包含 PEM 编码的 x509 格式的公钥的文件。

spring.security.oauth2.resourceserver.jwt.audiences属性可用于指定 JWT 中 aud 声明的预期值。 例如,要求 JWT 包含值为my-audience:spring-doc.cadn.net.cn

性能
spring.security.oauth2.resourceserver.jwt.audiences[0]=my-audience
Yaml
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          audiences:
            - "my-audience"

相同的属性适用于 servlet 和响应式应用程序。 或者,您可以定义自己的JwtDecoderbean 用于 servlet 应用程序或ReactiveJwtDecoder用于响应式应用。spring-doc.cadn.net.cn

如果使用不透明令牌而不是 JWT,您可以配置以下属性以通过自省验证令牌:spring-doc.cadn.net.cn

性能
spring.security.oauth2.resourceserver.opaquetoken.introspection-uri=https://example.com/check-token
spring.security.oauth2.resourceserver.opaquetoken.client-id=my-client-id
spring.security.oauth2.resourceserver.opaquetoken.client-secret=my-client-secret
Yaml
spring:
  security:
    oauth2:
      resourceserver:
        opaquetoken:
          introspection-uri: "https://example.com/check-token"
          client-id: "my-client-id"
          client-secret: "my-client-secret"

同样,相同的属性适用于 servlet 和 reactive 应用程序。 或者,您可以定义自己的OpaqueTokenIntrospectorbean 用于 servlet 应用程序或ReactiveOpaqueTokenIntrospector用于响应式应用。spring-doc.cadn.net.cn

4.3.3. 授权服务器

如果你有spring-security-oauth2-authorization-server在你的 Classpath 上,你可以利用一些自动配置来设置基于 Servlet 的 OAuth2 授权服务器。spring-doc.cadn.net.cn

您可以在spring.security.oauth2.authorizationserver.clientprefix,如以下示例所示:spring-doc.cadn.net.cn

性能
spring.security.oauth2.authorizationserver.client.my-client-1.registration.client-id=abcd
spring.security.oauth2.authorizationserver.client.my-client-1.registration.client-secret={noop}secret1
spring.security.oauth2.authorizationserver.client.my-client-1.registration.client-authentication-methods[0]=client_secret_basic
spring.security.oauth2.authorizationserver.client.my-client-1.registration.authorization-grant-types[0]=authorization_code
spring.security.oauth2.authorizationserver.client.my-client-1.registration.authorization-grant-types[1]=refresh_token
spring.security.oauth2.authorizationserver.client.my-client-1.registration.redirect-uris[0]=https://my-client-1.com/login/oauth2/code/abcd
spring.security.oauth2.authorizationserver.client.my-client-1.registration.redirect-uris[1]=https://my-client-1.com/authorized
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[0]=openid
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[1]=profile
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[2]=email
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[3]=phone
spring.security.oauth2.authorizationserver.client.my-client-1.registration.scopes[4]=address
spring.security.oauth2.authorizationserver.client.my-client-1.require-authorization-consent=true
spring.security.oauth2.authorizationserver.client.my-client-2.registration.client-id=efgh
spring.security.oauth2.authorizationserver.client.my-client-2.registration.client-secret={noop}secret2
spring.security.oauth2.authorizationserver.client.my-client-2.registration.client-authentication-methods[0]=client_secret_jwt
spring.security.oauth2.authorizationserver.client.my-client-2.registration.authorization-grant-types[0]=client_credentials
spring.security.oauth2.authorizationserver.client.my-client-2.registration.scopes[0]=user.read
spring.security.oauth2.authorizationserver.client.my-client-2.registration.scopes[1]=user.write
spring.security.oauth2.authorizationserver.client.my-client-2.jwk-set-uri=https://my-client-2.com/jwks
spring.security.oauth2.authorizationserver.client.my-client-2.token-endpoint-authentication-signing-algorithm=RS256
Yaml
spring:
  security:
    oauth2:
      authorizationserver:
        client:
          my-client-1:
            registration:
              client-id: "abcd"
              client-secret: "{noop}secret1"
              client-authentication-methods:
                - "client_secret_basic"
              authorization-grant-types:
                - "authorization_code"
                - "refresh_token"
              redirect-uris:
                - "https://my-client-1.com/login/oauth2/code/abcd"
                - "https://my-client-1.com/authorized"
              scopes:
                - "openid"
                - "profile"
                - "email"
                - "phone"
                - "address"
            require-authorization-consent: true
          my-client-2:
            registration:
              client-id: "efgh"
              client-secret: "{noop}secret2"
              client-authentication-methods:
                - "client_secret_jwt"
              authorization-grant-types:
                - "client_credentials"
              scopes:
                - "user.read"
                - "user.write"
            jwk-set-uri: "https://my-client-2.com/jwks"
            token-endpoint-authentication-signing-algorithm: "RS256"
client-secret属性的格式必须与配置的PasswordEncoder. 默认的PasswordEncoder通过以下方式创建PasswordEncoderFactories.createDelegatingPasswordEncoder().

Spring Boot 为 Spring Authorization Server 提供的自动配置旨在快速入门。 大多数应用程序都需要定制,并且希望定义多个 bean 以覆盖自动配置。spring-doc.cadn.net.cn

可以将以下组件定义为 bean 以覆盖特定于 Spring Authorization Server 的自动配置:spring-doc.cadn.net.cn

Spring Boot 会自动配置InMemoryRegisteredClientRepositorySpring Authorization Server 使用它来管理已注册的 Client 端。 这InMemoryRegisteredClientRepository功能有限,我们建议仅将其用于开发环境。 对于生产环境,请考虑使用JdbcRegisteredClientRepository或创建您自己的RegisteredClientRepository.

4.4. SAML 2.0

4.4.1. 依赖方

如果你有spring-security-saml2-service-provider在您的 Classpath 上,您可以利用一些自动配置来设置 SAML 2.0 依赖方。 此配置使用Saml2RelyingPartyProperties.spring-doc.cadn.net.cn

信赖方注册表示身份提供程序 (IDP) 和服务提供商 (SP) 之间的配对配置。 您可以在spring.security.saml2.relyingpartyprefix,如以下示例所示:spring-doc.cadn.net.cn

性能
spring.security.saml2.relyingparty.registration.my-relying-party1.signing.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party1.signing.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party1.decryption.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party1.decryption.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party1.singlelogout.url=https://myapp/logout/saml2/slo
spring.security.saml2.relyingparty.registration.my-relying-party1.singlelogout.response-url=https://remoteidp2.slo.url
spring.security.saml2.relyingparty.registration.my-relying-party1.singlelogout.binding=POST
spring.security.saml2.relyingparty.registration.my-relying-party1.assertingparty.verification.credentials[0].certificate-location=path-to-verification-cert
spring.security.saml2.relyingparty.registration.my-relying-party1.assertingparty.entity-id=remote-idp-entity-id1
spring.security.saml2.relyingparty.registration.my-relying-party1.assertingparty.sso-url=https://remoteidp1.sso.url

spring.security.saml2.relyingparty.registration.my-relying-party2.signing.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party2.signing.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party2.decryption.credentials[0].private-key-location=path-to-private-key
spring.security.saml2.relyingparty.registration.my-relying-party2.decryption.credentials[0].certificate-location=path-to-certificate
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.verification.credentials[0].certificate-location=path-to-other-verification-cert
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.entity-id=remote-idp-entity-id2
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.sso-url=https://remoteidp2.sso.url
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.singlelogout.url=https://remoteidp2.slo.url
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.singlelogout.response-url=https://myapp/logout/saml2/slo
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.singlelogout.binding=POST
Yaml
spring:
  security:
    saml2:
      relyingparty:
        registration:
          my-relying-party1:
            signing:
              credentials:
              - private-key-location: "path-to-private-key"
                certificate-location: "path-to-certificate"
            decryption:
              credentials:
              - private-key-location: "path-to-private-key"
                certificate-location: "path-to-certificate"
            singlelogout:
               url: "https://myapp/logout/saml2/slo"
               response-url: "https://remoteidp2.slo.url"
               binding: "POST"
            assertingparty:
              verification:
                credentials:
                - certificate-location: "path-to-verification-cert"
              entity-id: "remote-idp-entity-id1"
              sso-url: "https://remoteidp1.sso.url"

          my-relying-party2:
            signing:
              credentials:
              - private-key-location: "path-to-private-key"
                certificate-location: "path-to-certificate"
            decryption:
              credentials:
              - private-key-location: "path-to-private-key"
                certificate-location: "path-to-certificate"
            assertingparty:
              verification:
                credentials:
                - certificate-location: "path-to-other-verification-cert"
              entity-id: "remote-idp-entity-id2"
              sso-url: "https://remoteidp2.sso.url"
              singlelogout:
                url: "https://remoteidp2.slo.url"
                response-url: "https://myapp/logout/saml2/slo"
                binding: "POST"

对于 SAML2 注销,默认情况下, Spring Security 的Saml2LogoutRequestFilterSaml2LogoutResponseFilter仅处理匹配的 URL/logout/saml2/slo. 如果要自定义urlAP 发起的注销请求将发送到哪个 AP 或response-urlAP 向其发送注销响应,要使用其他模式,您需要提供配置以处理该自定义模式。 例如,对于 servlet 应用程序,您可以添加自己的SecurityFilterChain类似于以下内容:spring-doc.cadn.net.cn

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

import static org.springframework.security.config.Customizer.withDefaults;

@Configuration(proxyBeanMethods = false)
public class MySamlRelyingPartyConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated());
        http.saml2Login(withDefaults());
        http.saml2Logout((saml2) -> saml2.logoutRequest((request) -> request.logoutUrl("/SLOService.saml2"))
            .logoutResponse((response) -> response.logoutUrl("/SLOService.saml2")));
        return http.build();
    }

}

5. Spring Session

Spring Boot 为各种数据存储提供 Spring Session 自动配置。 在构建 servlet Web 应用程序时,可以自动配置以下存储:spring-doc.cadn.net.cn

servlet 自动配置取代了使用@Enable*HttpSession.spring-doc.cadn.net.cn

如果 Classpath 上存在单个 Spring Session 模块,则 Spring Boot 会自动使用该 store 实现。 如果您有多个实现,则 Spring Boot 使用以下 Sequences 来选择特定实现:spring-doc.cadn.net.cn

  1. Redisspring-doc.cadn.net.cn

  2. JDBCspring-doc.cadn.net.cn

  3. Hazelcastspring-doc.cadn.net.cn

  4. MongoDB 数据库spring-doc.cadn.net.cn

  5. 如果 Redis、JDBC、Hazelcast 和 MongoDB 都不可用,我们不会配置SessionRepository.spring-doc.cadn.net.cn

在构建反应式 Web 应用程序时,可以自动配置以下 store:spring-doc.cadn.net.cn

reactive 自动配置取代了使用@Enable*WebSession.spring-doc.cadn.net.cn

与 Servlet 配置类似,如果你有多个实现,Spring Boot 使用以下 Sequences 来选择特定的实现:spring-doc.cadn.net.cn

  1. Redisspring-doc.cadn.net.cn

  2. MongoDB 数据库spring-doc.cadn.net.cn

  3. 如果 Redis 和 MongoDB 都不可用,我们不会配置ReactiveSessionRepository.spring-doc.cadn.net.cn

每个商店都有特定的附加设置。 例如,可以自定义 JDBC 存储的表的名称,如以下示例所示:spring-doc.cadn.net.cn

性能
spring.session.jdbc.table-name=SESSIONS
Yaml
spring:
  session:
    jdbc:
      table-name: "SESSIONS"

要设置会话的超时,您可以使用spring.session.timeout财产。 如果未使用 servlet Web 应用程序设置该属性,则自动配置将回退到server.servlet.session.timeout.spring-doc.cadn.net.cn

你可以使用 Spring Session 的配置来控制@Enable*HttpSession(servlet) 或@Enable*WebSession(反应式)。 这将导致自动配置回退。 然后可以使用 Comments 的属性而不是前面描述的配置属性来配置 Spring Session。spring-doc.cadn.net.cn

6. 适用于 GraphQL 的 Spring

如果要构建 GraphQL 应用程序,可以利用 Spring Boot 对 Spring for GraphQL 的自动配置。 Spring for GraphQL 项目基于 GraphQL Java。 您将需要spring-boot-starter-graphqlstarter 的最小值。 由于 GraphQL 与传输无关,因此您还需要在应用程序中添加一个或多个额外的Starters,以便在 Web 上公开 GraphQL API:spring-doc.cadn.net.cn

起动机 运输 实现

spring-boot-starter-webspring-doc.cadn.net.cn

HTTP 协议spring-doc.cadn.net.cn

Spring MVCspring-doc.cadn.net.cn

spring-boot-starter-websocketspring-doc.cadn.net.cn

WebSocket 浏览器spring-doc.cadn.net.cn

用于 Servlet 应用程序的 WebSocketspring-doc.cadn.net.cn

spring-boot-starter-webfluxspring-doc.cadn.net.cn

HTTP、WebSocketspring-doc.cadn.net.cn

Spring WebFluxspring-doc.cadn.net.cn

spring-boot-starter-rsocketspring-doc.cadn.net.cn

TCP、WebSocketspring-doc.cadn.net.cn

Reactor Netty 上的 Spring WebFluxspring-doc.cadn.net.cn

6.1. GraphQL 架构

Spring GraphQL 应用程序在启动时需要定义的架构。 默认情况下,您可以在src/main/resources/graphql/**Spring Boot 将自动选取它们。 您可以使用spring.graphql.schema.locations和带有spring.graphql.schema.file-extensions.spring-doc.cadn.net.cn

如果您希望 Spring Boot 检测该位置的所有应用程序模块和依赖项中的 schema 文件, 您可以设置spring.graphql.schema.locations"classpath*:graphql/**/"(请注意classpath*:前缀)。

在以下部分中,我们将考虑此示例 GraphQL 架构,定义两种类型和两种查询:spring-doc.cadn.net.cn

type Query {
    greeting(name: String! = "Spring"): String!
    project(slug: ID!): Project
}

""" A Project in the Spring portfolio """
type Project {
    """ Unique string id used in URLs """
    slug: ID!
    """ Project name """
    name: String!
    """ URL of the git repository """
    repositoryUrl: String!
    """ Current support status """
    status: ProjectStatus!
}

enum ProjectStatus {
    """ Actively supported by the Spring team """
    ACTIVE
    """ Supported by the community """
    COMMUNITY
    """ Prototype, not officially supported yet  """
    INCUBATING
    """ Project being retired, in maintenance mode """
    ATTIC
    """ End-Of-Lifed """
    EOL
}
默认情况下,架构上将允许现场自省,因为 GraphiQL 等工具需要它。 如果您不想公开有关架构的信息,可以通过设置spring.graphql.schema.introspection.enabledfalse.

6.2. GraphQL 运行时布线

The GraphQL JavaRuntimeWiring.Builder可用于注册自定义标量类型、指令、类型解析器、DataFetcher等。 您可以声明RuntimeWiringConfigurerbeans 来访问RuntimeWiring.Builder. Spring Boot 会检测此类 bean 并将它们添加到 GraphQlSource 构建器中。spring-doc.cadn.net.cn

但是,应用程序通常不会实现DataFetcher直接创建带注释的控制器。 Spring Boot 将自动检测@Controller类,并将其注册为DataFetchers. 以下是我们的问候查询的示例实现,其中@Controller类:spring-doc.cadn.net.cn

Java
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller;

@Controller
public class GreetingController {

    @QueryMapping
    public String greeting(@Argument String name) {
        return "Hello, " + name + "!";
    }

}
Kotlin
import org.springframework.graphql.data.method.annotation.Argument
import org.springframework.graphql.data.method.annotation.QueryMapping
import org.springframework.stereotype.Controller

@Controller
class GreetingController {

    @QueryMapping
    fun greeting(@Argument name: String): String {
        return "Hello, $name!"
    }

}

6.3. Querydsl 和 QueryByExample 存储库支持

Spring Data 提供对 Querydsl 和 QueryByExample 存储库的支持。 Spring GraphQL 可以将 Querydsl 和 QueryByExample 存储库配置为DataFetcher.spring-doc.cadn.net.cn

带注释的 Spring Data 存储库@GraphQlRepository以及扩展 one of:spring-doc.cadn.net.cn

被 Spring Boot 检测到并被视为DataFetcher用于匹配的顶级查询。spring-doc.cadn.net.cn

6.4. 传输

6.4.1. HTTP 和 WebSocket

GraphQL HTTP 终端节点位于 HTTP POST/graphql默认情况下。 路径可以使用spring.graphql.path.spring-doc.cadn.net.cn

Spring MVC 和 Spring WebFlux 的 HTTP 端点都由RouterFunction带有@Order0. 如果您定义自己的RouterFunctionbeans 中,您可能希望添加适当的@Order注解,以确保它们被正确排序。

默认情况下,GraphQL WebSocket 终端节点处于关闭状态。要启用它:spring-doc.cadn.net.cn

Spring GraphQL 提供了一个 Web 拦截模型。 这对于从 HTTP 请求标头中检索信息并在 GraphQL 上下文中设置信息或从同一上下文获取信息并将其写入响应标头非常有用。 使用 Spring Boot,您可以声明WebInterceptorbean 将其注册到 Web 传输中。spring-doc.cadn.net.cn

Spring MVCSpring WebFlux 支持 CORS(跨域资源共享)请求。 CORS 是 GraphQL 应用程序的 Web 配置的关键部分,这些应用程序可以从使用不同域的浏览器访问。spring-doc.cadn.net.cn

Spring Boot 在spring.graphql.cors.*Namespace;下面是一个简短的配置示例:spring-doc.cadn.net.cn

性能
spring.graphql.cors.allowed-origins=https://example.org
spring.graphql.cors.allowed-methods=GET,POST
spring.graphql.cors.max-age=1800s
Yaml
spring:
  graphql:
    cors:
      allowed-origins: "https://example.org"
      allowed-methods: GET,POST
      max-age: 1800s

6.4.2. RSocket

还支持将 RSocket 作为 WebSocket 或 TCP 之上的传输。 配置 RSocket 服务器后,我们可以使用spring.graphql.rsocket.mapping. 例如,将该映射配置为"graphql"意味着我们可以在使用RSocketGraphQlClient.spring-doc.cadn.net.cn

Spring Boot 会自动配置RSocketGraphQlClient.Builder<?>bean 中可以注入到组件中:spring-doc.cadn.net.cn

Java
@Component
public class RSocketGraphQlClientExample {

    private final RSocketGraphQlClient graphQlClient;

    public RSocketGraphQlClientExample(RSocketGraphQlClient.Builder<?> builder) {
        this.graphQlClient = builder.tcp("example.spring.io", 8181).route("graphql").build();
    }
Kotlin
@Component
class RSocketGraphQlClientExample(private val builder: RSocketGraphQlClient.Builder<*>) {

然后发送请求:spring-doc.cadn.net.cn

Java
Mono<Book> book = this.graphQlClient.document("{ bookById(id: \"book-1\"){ id name pageCount author } }")
    .retrieve("bookById")
    .toEntity(Book.class);
Kotlin
val book = graphQlClient.document(
    """
    {
        bookById(id: "book-1"){
            id
            name
            pageCount
            author
        }
    }               
    """
)
    .retrieve("bookById").toEntity(Book::class.java)

6.5. 异常处理

Spring GraphQL 使应用程序能够注册一个或多个 SpringDataFetcherExceptionResolver按顺序调用的组件。 异常必须解析为graphql.GraphQLError对象,请参阅 Spring GraphQL 异常处理文档。 Spring Boot 将自动检测DataFetcherExceptionResolverbean 并将它们注册到GraphQlSource.Builder.spring-doc.cadn.net.cn

6.6. GraphiQL 和 Schema 打印机

Spring GraphQL 提供了基础设施,可帮助开发人员使用或开发 GraphQL API。spring-doc.cadn.net.cn

Spring GraphQL 附带一个默认的 GraphiQL 页面,该页面在"/graphiql"默认情况下。 默认情况下,此页面处于禁用状态,可以使用spring.graphql.graphiql.enabled财产。 许多公开此类页面的应用程序将首选自定义版本。 默认实现在开发过程中非常有用,这就是为什么它会自动公开spring-boot-devtools在开发过程中。spring-doc.cadn.net.cn

您还可以选择以文本格式公开 GraphQL 架构,网址为/graphql/schemaspring.graphql.schema.printer.enabled属性已启用。spring-doc.cadn.net.cn

7. Spring HATEOAS

如果您开发使用超媒体的 RESTful API,Spring Boot 将为 Spring HATEOAS 提供自动配置,该配置适用于大多数应用程序。 自动配置取代了使用@EnableHypermediaSupport并注册了大量 bean 以简化基于超媒体的应用程序的构建,包括LinkDiscoverers(用于客户端支持)和ObjectMapper配置为将响应正确封送到所需的表示形式中。 这ObjectMapper通过设置各种spring.jackson.*属性,或者,如果存在,则通过Jackson2ObjectMapperBuilder豆。spring-doc.cadn.net.cn

你可以通过使用@EnableHypermediaSupport. 请注意,这样做会禁用ObjectMapper自定义。spring-doc.cadn.net.cn

spring-boot-starter-hateoas特定于 Spring MVC,不应与 Spring WebFlux 结合使用。 为了将 Spring HATEOAS 与 Spring WebFlux 一起使用,您可以添加对org.springframework.hateoas:spring-hateoas以及spring-boot-starter-webflux.

默认情况下,接受application/json将收到一个application/hal+json响应。 要禁用此行为,请将spring.hateoas.use-hal-as-default-json-media-typefalse并定义一个HypermediaMappingInformationHalConfiguration配置 Spring HATEOAS 以满足您的应用程序及其客户端的需求。spring-doc.cadn.net.cn

8. 下一步要读什么

现在,您应该对如何使用 Spring Boot 开发 Web 应用程序有了很好的了解。 接下来的几节描述了 Spring Boot 如何与各种数据技术消息传递系统和其他 IO 功能集成。 您可以根据应用程序的需要选择其中任何一个。spring-doc.cadn.net.cn