Spring Boot 非常适合 Web 应用程序开发。您可以使用嵌入式 Tomcat、Jetty、Undertow 或 Netty 创建一个自包含的 HTTP 服务器。大多数 Web 应用程序使用该spring-boot-starter-web模块来快速启动和运行。您还可以选择使用该spring-boot-starter-webflux模块构建反应式 Web 应用程序。

如果您还没有开发过 Spring Boot Web 应用程序,可以关注“Hello World!” 入门部分中的示例。

1. Servlet Web 应用程序

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

1.1。“Spring Web MVC 框架”

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

以下代码显示了一个典型@RestController的提供 JSON 数据的代码:

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

}
科特林
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController


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

    @GetMapping("/{userId}")
    fun getUser(@PathVariable userId: Long): User {
        return userRepository.findById(userId).get()
    }

    @GetMapping("/{userId}/customers")
    fun getUserCustomers(@PathVariable userId: Long): List<Customer> {
        return userRepository.findById(userId).map(customerRepository::findByUser).get()
    }

    @DeleteMapping("/{userId}")
    fun deleteUser(@PathVariable userId: Long) {
        userRepository.deleteById(userId)
    }

}

功能变体“WebMvc.fn”将路由配置与请求的实际处理分开,如下例所示:

java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.function.RequestPredicate;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.ServerResponse;

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

@Configuration(proxyBeanMethods = false)
public class MyRoutingConfiguration {

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

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

}
科特林
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.MediaType
import org.springframework.web.servlet.function.RequestPredicates.accept
import org.springframework.web.servlet.function.RouterFunction
import org.springframework.web.servlet.function.RouterFunctions
import org.springframework.web.servlet.function.ServerResponse

@Configuration(proxyBeanMethods = false)
class MyRoutingConfiguration {

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

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

}
java
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;

@Component
public class MyUserHandler {

    public ServerResponse getUser(ServerRequest request) {
        ...
        return ServerResponse.ok().build();
    }

    public ServerResponse getUserCustomers(ServerRequest request) {
        ...
        return ServerResponse.ok().build();
    }

    public ServerResponse deleteUser(ServerRequest request) {
        ...
        return ServerResponse.ok().build();
    }

}
科特林
import org.springframework.stereotype.Component
import org.springframework.web.servlet.function.ServerRequest
import org.springframework.web.servlet.function.ServerResponse

@Component
class MyUserHandler {

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

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

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

}

Spring MVC 是核心 Spring Framework 的一部分,详细信息可在参考文档中找到。spring.io/guides上还有一些涵盖 Spring MVC 的指南。

您可以定义任意数量的RouterFunctionbean 来模块化路由器的定义。如果您需要应用优先级,可以订购 Bean。

1.1.1。Spring MVC 自动配置

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

自动配置在 Spring 的默认值之上添加了以下特性:

  • 包括ContentNegotiatingViewResolverBeanNameViewResolver豆类。

  • 支持提供静态资源,包括对 WebJars 的支持(本文档后面会介绍)。

  • 自动注册ConverterGenericConverterFormatterbean。

  • 支持HttpMessageConverters本文档稍后介绍)。

  • 自动注册MessageCodesResolver本文档稍后介绍)。

  • 静态index.html支持。

  • ConfigurableWebBindingInitializerbean的自动使用(本文档稍后会介绍)。

如果您想保留那些 Spring Boot MVC 自定义并进行更多MVC 自定义(拦截器、格式化程序、视图控制器和其他功能),您可以添加自己@Configuration的类型类WebMvcConfigurer @EnableWebMvc添加.

如果您想提供、 或的自定义实例RequestMappingHandlerMapping,并且仍然保留 Spring Boot MVC 自定义,则可以声明一个类型的 bean并使用它来提供这些组件的自定义实例。RequestMappingHandlerAdapterExceptionHandlerExceptionResolverWebMvcRegistrations

如果你想完全控制 Spring MVC,你可以添加你自己的@Configuration注释@EnableWebMvc,或者添加你自己的@Configuration-annotated DelegatingWebMvcConfiguration,如@EnableWebMvc.

Spring MVC 使用一种不同于ConversionService用于从您的application.propertiesapplication.yaml文件转换值的方法。这意味着Period,DurationDataSize转换器不可用,并且@DurationUnit@DataSizeUnit注释将被忽略。

如果你想自定义ConversionServiceSpring MVC 使用的,你可以提供一个WebMvcConfigurer带有addFormatters方法的 bean。通过这个方法,你可以注册任何你喜欢的转换器,或者你可以委托给ApplicationConversionService.

1.1.2。HttpMessage转换器

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

如果需要添加或自定义转换器,可以使用 Spring Boot 的HttpMessageConverters类,如下清单所示:

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

}
科特林
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)
    }

}

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

1.1.3。MessageCodesResolver

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

1.1.4。静态内容

默认情况下,Spring Boot 从类路径中名为/static(或/public/resources/META-INF/resources)的目录或从ServletContext. 它使用ResourceHttpRequestHandler来自 Spring MVC 的方法,因此您可以通过添加自己的方法WebMvcConfigurer并覆盖该addResourceHandlers方法来修改该行为。

在独立的 Web 应用程序中,容器中的默认 servlet 也被启用并充当后备,ServletContext如果 Spring 决定不处理它,则从根目录提供内容。大多数时候,这不会发生(除非你修改了默认的 MVC 配置),因为 Spring 总是可以通过DispatcherServlet.

默认情况下,资源映射在 上/**,但您可以使用该spring.mvc.static-path-pattern属性对其进行调整。例如,将所有资源重新定位到/resources/**可以实现如下:

特性
spring.mvc.static-path-pattern=/resources/**
yaml
spring:
  mvc:
    static-path-pattern: "/resources/**"

您还可以使用该spring.web.resources.static-locations属性自定义静态资源位置(将默认值替换为目录位置列表)。根 servlet 上下文路径"/"也会自动添加为位置。

除了前面提到的“标准”静态资源位置之外,还为Webjars 内容做了一个特殊情况。/webjars/**如果以 Webjars 格式打包,则任何具有路径的资源都将从 jar 文件中提供。

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

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

要为 Webjar 使用与版本无关的 URL,请添加webjars-locator-core依赖项。然后声明你的 Webjar。以jQuery为例,添加"/webjars/jquery/jquery.min.js"结果在"/webjars/jquery/x.y.z/jquery.min.js"哪里x.y.z是Webjar版本。

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

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

特性
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 模块加载器动态加载资源时,重命名文件不是一种选择。这就是为什么还支持其他策略并且可以组合的原因。“固定”策略在 URL 中添加静态版本字符串而不更改文件名,如下例所示:

特性
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"

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

有关WebProperties.Resources更多支持的选项,请参阅。

此功能已在专门的博客文章和 Spring Framework 的参考文档中进行了详细描述。

1.1.5。欢迎页面

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

1.1.6。自定义网站图标

与其他静态资源一样,Spring Bootfavicon.ico在配置的静态内容位置中检查 a。如果存在这样的文件,它会自动用作应用程序的图标。

1.1.7。路径匹配和内容协商

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

Spring Boot 默认选择禁用后缀模式匹配,这意味着 like 的请求"GET /projects/spring-boot.json"不会匹配到@GetMapping("/projects/spring-boot")映射。这被认为是Spring MVC 应用程序的最佳实践。过去,此功能主要用于未发送正确“Accept”请求标头的 HTTP 客户端;我们需要确保向客户端发送正确的内容类型。如今,内容协商更加可靠。

还有其他方法可以处理不能始终发送正确的“接受”请求标头的 HTTP 客户端。代替使用后缀匹配,我们可以使用查询参数来确保请求 like"GET /projects/spring-boot?format=json"将被映射到@GetMapping("/projects/spring-boot")

特性
spring.mvc.contentnegotiation.favor-parameter=true
yaml
spring:
  mvc:
    contentnegotiation:
      favor-parameter: true

或者,如果您更喜欢使用不同的参数名称:

特性
spring.mvc.contentnegotiation.favor-parameter=true
spring.mvc.contentnegotiation.parameter-name=myparam
yaml
spring:
  mvc:
    contentnegotiation:
      favor-parameter: true
      parameter-name: "myparam"

大多数标准媒体类型都支持开箱即用,但您也可以定义新的媒体类型:

特性
spring.mvc.contentnegotiation.media-types.markdown=text/markdown
yaml
spring:
  mvc:
    contentnegotiation:
      media-types:
        markdown: "text/markdown"

后缀模式匹配已弃用,并将在未来版本中删除。如果您了解这些警告并且仍然希望您的应用程序使用后缀模式匹配,则需要以下配置:

特性
spring.mvc.contentnegotiation.favor-path-extension=true
spring.mvc.pathmatch.use-suffix-pattern=true
yaml
spring:
  mvc:
    contentnegotiation:
      favor-path-extension: true
    pathmatch:
      use-suffix-pattern: true

或者,与其打开所有后缀模式,不如只支持已注册的后缀模式更安全:

特性
spring.mvc.contentnegotiation.favor-path-extension=true
spring.mvc.pathmatch.use-registered-suffix-pattern=true
yaml
spring:
  mvc:
    contentnegotiation:
      favor-path-extension: true
    pathmatch:
      use-registered-suffix-pattern: true

从 Spring Framework 5.3 开始,Spring MVC 支持多种实现策略,用于将请求路径匹配到 Controller 处理程序。它以前只支持该AntPathMatcher策略,但现在它也提供PathPatternParser. Spring Boot 现在提供了一个配置属性来选择和选择新策略:

特性
spring.mvc.pathmatch.matching-strategy=path-pattern-parser
yaml
spring:
  mvc:
    pathmatch:
      matching-strategy: "path-pattern-parser"

有关为什么应该考虑这个新实现的更多详细信息,请参阅 专门的博客文章

PathPatternParser是一种优化的实现,但限制了 某些路径模式变体的使用, 并且与后缀模式匹配 ( spring.mvc.pathmatch.use-suffix-pattern, spring.mvc.pathmatch.use-registered-suffix-pattern) 或DispatcherServlet 使用 servlet 前缀 ( spring.mvc.servlet.path) 映射不兼容。

1.1.8。ConfigurableWebBindingInitializer

Spring MVC 使用 a来为特定请求WebBindingInitializer初始化 a 。WebDataBinder如果你自己创建ConfigurableWebBindingInitializer @Bean,Spring Boot 会自动配置 Spring MVC 来使用它。

1.1.9。模板引擎

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

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

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

当您将这些模板引擎之一与默认配置一起使用时,您的模板会自动从src/main/resources/templates.

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

1.1.10。错误处理

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

server.error如果要自定义默认错误处理行为,可以设置许多属性。请参阅附录的“服务器属性”部分。

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

BasicErrorController可以用作自定义的基 类ErrorController。如果您想为新的内容类型添加处理程序,这特别有用(默认是text/html专门处理并为其他所有内容提供后备)。为此,请扩展BasicErrorController,添加一个@RequestMapping具有produces属性的公共方法,并创建一个新类型的 bean。

您还可以定义一个带有注释的类@ControllerAdvice来自定义 JSON 文档以返回特定控制器和/或异常类型,如以下示例所示:

java
import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@ControllerAdvice(basePackageClasses = SomeController.class)
public class MyControllerAdvice extends ResponseEntityExceptionHandler {

    @ResponseBody
    @ExceptionHandler(MyException.class)
    public ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {
        HttpStatus status = getStatus(request);
        return new ResponseEntity<>(new MyErrorBody(status.value(), ex.getMessage()), status);
    }

    private HttpStatus getStatus(HttpServletRequest request) {
        Integer code = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
        HttpStatus status = HttpStatus.resolve(code);
        return (status != null) ? status : HttpStatus.INTERNAL_SERVER_ERROR;
    }

}
科特林
import 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
import javax.servlet.RequestDispatcher
import javax.servlet.http.HttpServletRequest

@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由定义在与 相同的包中的控制器抛出,则使用 POJOSomeController的 JSON 表示MyErrorBody而不是ErrorAttributes表示。

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

java
import javax.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";
    }

}
科特林
import org.springframework.boot.web.servlet.error.ErrorAttributes
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.ExceptionHandler
import javax.servlet.http.HttpServletRequest

@Controller
class MyController {

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

}
自定义错误页面

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

例如,要映射404到静态 HTML 文件,您的目录结构如下:

源/
 +- 主要/
     +- java/
     | + <源代码>
     +- 资源/
         +- 公开/
             +- 错误/
             | +- 404.html
             +- <其他公共资产>

要使用 FreeMarker 模板映射所有5xx错误,您的目录结构如下:

源/
 +- 主要/
     +- java/
     | + <源代码>
     +- 资源/
         +- 模板/
             +- 错误/
             | +- 5xx.ftlh
             +- <其他模板>

对于更复杂的映射,您还可以添加实现ErrorViewResolver接口的 bean,如下例所示:

java
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.ModelAndView;

public class MyErrorViewResolver implements ErrorViewResolver {

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

}
科特林
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver
import org.springframework.http.HttpStatus
import org.springframework.web.servlet.ModelAndView
import javax.servlet.http.HttpServletRequest

class MyErrorViewResolver : ErrorViewResolver {

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

}

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

在 Spring MVC 之外映射错误页面

对于不使用 Spring MVC 的应用,可以使用ErrorPageRegistrar接口直接注册ErrorPages。这种抽象直接与底层嵌入式 servlet 容器一起工作,即使您没有 Spring MVC 也可以工作DispatcherServlet

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

@Configuration(proxyBeanMethods = false)
public class MyErrorPagesConfiguration {

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

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

}
科特林
import org.springframework.boot.web.server.ErrorPage
import org.springframework.boot.web.server.ErrorPageRegistrar
import org.springframework.boot.web.server.ErrorPageRegistry
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpStatus

@Configuration(proxyBeanMethods = false)
class MyErrorPagesConfiguration {

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

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

}
如果您ErrorPage使用最终由 a 处理的路径注册 an Filter(这在某些非 Spring Web 框架中很常见,如 Jersey 和 Wicket),则Filter必须显式注册为ERROR调度程序,如以下示例所示:
java
import java.util.EnumSet;

import javax.servlet.DispatcherType;

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

@Configuration(proxyBeanMethods = false)
public class MyFilterConfiguration {

    @Bean
    public FilterRegistrationBean<MyFilter> myFilter() {
        FilterRegistrationBean<MyFilter> registration = new FilterRegistrationBean<>(new MyFilter());
        // ...
        registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
        return registration;
    }

}
科特林
import org.springframework.boot.web.servlet.FilterRegistrationBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.util.EnumSet
import javax.servlet.DispatcherType

@Configuration(proxyBeanMethods = false)
class MyFilterConfiguration {

    @Bean
    fun myFilter(): FilterRegistrationBean<MyFilter> {
        val registration = FilterRegistrationBean(MyFilter())
        // ...
        registration.setDispatcherTypes(EnumSet.allOf(DispatcherType::class.java))
        return registration
    }

}

请注意,默认值FilterRegistrationBean不包括ERROR调度程序类型。

战争部署中的错误处理

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

如果响应尚未提交,错误页面过滤器只能将请求转发到正确的错误页面。默认情况下,WebSphere Application Server 8.0 和更高版本在成功完成 servlet 的服务方法时提交响应。com.ibm.ws.webcontainer.invokeFlushAfterService您应该通过设置来禁用此行为false

如果您使用 Spring Security 并希望在错误页面中访问主体,则必须配置 Spring Security 的过滤器以在错误调度时调用。为此,请将spring.security.filter.dispatcher-types属性设置为async, error, forward, request

1.1.11。CORS 支持

跨域资源共享(CORS) 是大多数浏览器实现的W3C 规范,它允许您以灵活的方式指定授权哪种跨域请求,而不是使用 IFRAME 或 JSONP 等一些不太安全和不太强大的方法。

从 4.2 版开始,Spring MVC支持 CORS。在 Spring Boot 应用程序中使用带有注释的控制器方法 CORS 配置@CrossOrigin不需要任何特定配置。 可以通过使用自定义方法注册 bean来定义全局 CORS 配置,如下例所示:WebMvcConfigureraddCorsMappings(CorsRegistry)

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

        };
    }

}
科特林
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 和泽西岛

如果您更喜欢 REST 端点的 JAX-RS 编程模型,则可以使用其中一种可用的实现来代替 Spring MVC。 JerseyApache CXF开箱即用,效果很好。CXF 要求您在应用程序上下文中将其Servlet或注册Filter为 a 。@BeanJersey 有一些原生的 Spring 支持,所以我们还在 Spring Boot 中为它提供了自动配置支持,以及一个 starter。

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

java
import org.glassfish.jersey.server.ResourceConfig;

import org.springframework.stereotype.Component;

@Component
public class MyJerseyConfig extends ResourceConfig {

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

}
科特林
import org.glassfish.jersey.server.ResourceConfig
import org.springframework.stereotype.Component

@Component
class MyJerseyConfig : ResourceConfig() {

    init {
        register(MyEndpoint::class.java)
    }

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

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

所有注册的端点都应该@Components带有 HTTP 资源注释(@GET和其他),如以下示例所示:

java
import javax.ws.rs.GET;
import javax.ws.rs.Path;

import org.springframework.stereotype.Component;

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

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

}
科特林
import org.springframework.stereotype.Component
import javax.ws.rs.GET
import javax.ws.rs.Path

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

    @GET
    fun message(): String {
        return "Hello"
    }

}

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

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

1.3. 嵌入式 Servlet 容器支持

对于 servlet 应用程序,Spring Boot 包括对嵌入式TomcatJettyUndertow服务器的支持。大多数开发人员使用适当的“Starter”来获取完全配置的实例。默认情况下,嵌入式服务器在 port 上侦听 HTTP 请求8080

1.3.1。Servlet、过滤器和侦听器

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

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

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

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

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

让过滤器豆无序通常是安全的。如果需要特定的顺序,您应该使用注释Filter@Order使其实现Ordered。您不能通过使用Filter注释其 bean 方法来配置 a 的顺序@Order。如果您无法更改Filter要添加@Order或实现的类Ordered,则必须FilterRegistrationBean为定义一个并使用该方法Filter设置注册 bean 的顺序。setOrder(int)避免在 处配置读取请求正文的过滤器Ordered.HIGHEST_PRECEDENCE,因为它可能会违反应用程序的字符编码配置。如果 servlet 过滤器包装了请求,则应使用小于或等于 的顺序对其进行配置OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER

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

1.3.2. Servlet 上下文初始化

嵌入式 servlet 容器不直接执行 servlet 3.0+javax.servlet.ServletContainerInitializer接口或 Spring 的org.springframework.web.WebApplicationInitializer接口。这是一个有意的设计决策,旨在降低设计为在战争中运行的第三方库可能会破坏 Spring Boot 应用程序的风险。

如果您需要在 Spring Boot 应用程序中执行 servlet 上下文初始化,您应该注册一个实现该org.springframework.boot.web.servlet.ServletContextInitializer接口的 bean。单一onStartup方法提供对 的访问,ServletContext并且在必要时可以轻松用作现有WebApplicationInitializer.

扫描 Servlet、过滤器和侦听器

使用嵌入式容器时,使用 、 和 注释的类的自动@WebServlet注册@WebFilter可以@WebListener使用@ServletComponentScan.

@ServletComponentScan在独立容器中无效,在该容器中使用容器的内置发现机制。

1.3.3. ServletWebServerApplicationContext

在底层,Spring Boot 使用不同类型的ApplicationContext嵌入式 servlet 容器支持。这是一种通过搜索单个bean来引导自身ServletWebServerApplicationContext的特殊类型。通常 a 、或已自动配置。WebApplicationContextServletWebServerFactoryTomcatServletWebServerFactoryJettyServletWebServerFactoryUndertowServletWebServerFactory

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

在嵌入式容器设置中,ServletContext设置为在应用程序上下文初始化期间发生的服务器启动的一部分。因为这个 beanApplicationContext不能用ServletContext. 解决此问题的一种方法是将注入ApplicationContext作为 bean 的依赖项并ServletContext仅在需要时访问。另一种方法是在服务器启动后使用回调。这可以使用ApplicationListener如下来完成ApplicationStartedEvent

import javax.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 容器

可以使用 SpringEnvironment属性配置常见的 servlet 容器设置。通常,您会在application.propertiesorapplication.yaml文件中定义属性。

常见的服务器设置包括:

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

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

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

  • SSL

  • HTTP 压缩

Spring Boot 尽可能地尝试公开常用设置,但这并不总是可行的。对于这些情况,专用命名空间提供特定于服务器的定制(参见server.tomcatserver.undertow)。例如,可以使用嵌入式 servlet 容器的特定功能配置访问日志。

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

Web 浏览器可以使用SameSitecookie 属性来控制是否以及如何在跨站点请求中提交 cookie。该属性与现代 Web 浏览器特别相关,这些浏览器已开始更改缺少该属性时使用的默认值。

如果要更改SameSite会话 cookie 的属性,可以使用该server.servlet.session.cookie.same-site属性。自动配置的 Tomcat、Jetty 和 Undertow 服务器支持此属性。它还用于配置基于 Spring Session servlet 的SessionRepositorybean。

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

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

如果您想更改SameSite添加到您的其他 cookie 上的属性HttpServletResponse,您可以使用CookieSameSiteSupplier. CookieSameSiteSupplier传递了一个并且Cookie可能返回一个SameSite值,或者null

您可以使用许多便利工厂和过滤器方法来快速匹配特定的 cookie。例如,添加以下 bean 将自动为所有名称与正则表达式匹配的 cookie应用 a SameSiteof 。Laxmyapp.*

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

}
科特林
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 容器,您可以注册一个实现该WebServerFactoryCustomizer接口的 Spring bean。 WebServerFactoryCustomizer提供对 的访问ConfigurableServletWebServerFactory,其中包括许多自定义设置方法。以下示例显示了以编程方式设置端口:

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

}
科特林
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)
    }

}

TomcatServletWebServerFactoryJettyServletWebServerFactory并且UndertowServletWebServerFactory是它们的专用变体,ConfigurableServletWebServerFactory分别为 Tomcat、Jetty 和 Undertow 提供了额外的自定义设置方法。以下示例显示了如何自定义TomcatServletWebServerFactory提供对 Tomcat 特定配置选项的访问权限:

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

}
科特林
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。

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

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

1.3.5。JSP 限制

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

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

  • Undertow 不支持 JSP。

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

2. 反应式 Web 应用程序

Spring Boot 通过为 Spring Webflux 提供自动配置来简化响应式 Web 应用程序的开发。

2.1。“Spring WebFlux 框架”

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

Spring WebFlux 有两种风格:函数式和基于注释的。基于注解的模型非常接近 Spring MVC 模型,如下例所示:

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

}
科特林
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.fn”将路由配置与请求的实际处理分开,如下例所示:

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

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

}
科特林
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 是 Spring Framework 的一部分,详细信息可在其参考文档中找到。

您可以定义任意数量的RouterFunctionbean 来模块化路由器的定义。如果您需要应用优先级,可以订购 Bean。

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

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

2.1.1。Spring WebFlux 自动配置

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

自动配置在 Spring 的默认值之上添加了以下特性:

如果你想保留 Spring Boot WebFlux 的特性并且你想添加额外的 WebFlux 配置,你可以添加你自己@Configuration的类型类WebFluxConfigurer没有 @EnableWebFlux.

如果你想完全控制 Spring WebFlux,你可以添加自己的@Configuration带有@EnableWebFlux.

2.1.2. 带有 HttpMessageReaders 和 HttpMessageWriters 的 HTTP 编解码器

Spring WebFlux 使用HttpMessageReaderHttpMessageWriter接口来转换 HTTP 请求和响应。CodecConfigurer通过查看类路径中可用的库,它们被配置为具有合理的默认值。

Spring Boot 为编解码器提供了专用的配置属性,spring.codec.*. 它还通过使用CodecCustomizer实例来应用进一步的定制。例如,spring.jackson.*配置密钥应用于 Jackson 编解码器。

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

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

}
科特林
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.3。静态内容

默认情况下,Spring Boot 从类路径中名为/static(or /publicor /resourcesor /META-INF/resources) 的目录中提供静态内容。它使用ResourceWebHandler来自 Spring WebFlux 的方法,因此您可以通过添加自己的方法WebFluxConfigurer并覆盖该addResourceHandlers方法来修改该行为。

默认情况下,资源映射在 上/**,但您可以通过设置spring.webflux.static-path-pattern属性来调整它。例如,将所有资源重新定位到/resources/**可以实现如下:

特性
spring.webflux.static-path-pattern=/resources/**
yaml
spring:
  webflux:
    static-path-pattern: "/resources/**"

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

除了前面列出的“标准”静态资源位置之外,还为Webjars 内容做了一个特殊情况。/webjars/**如果以 Webjars 格式打包,则任何具有路径的资源都将从 jar 文件中提供。

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

2.1.4。欢迎页面

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

2.1.5。模板引擎

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

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

当您将这些模板引擎之一与默认配置一起使用时,您的模板会自动从src/main/resources/templates.

2.1.6。错误处理

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

自定义此功能的第一步通常涉及使用现有机制但替换或增加错误内容。为此,您可以添加一个 bean 类型ErrorAttributes

要更改错误处理行为,您可以实现ErrorWebExceptionHandler并注册该类型的 bean 定义。由于 anErrorWebExceptionHandler是相当底层的,Spring Boot 还提供了一种方便AbstractErrorWebExceptionHandler的方式让您以 WebFlux 函数式的方式处理错误,如下例所示:

java
import reactor.core.publisher.Mono;

import org.springframework.boot.autoconfigure.web.WebProperties.Resources;
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.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, Resources resources,
            ApplicationContext applicationContext) {
        super(errorAttributes, resources, applicationContext);
    }

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

}
科特林
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.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?, resources: WebProperties.Resources?,
    applicationContext: ApplicationContext?) : AbstractErrorWebExceptionHandler(errorAttributes, resources, applicationContext) {

    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直接子类化并覆盖特定的方法。

在某些情况下,在控制器或处理程序功能级别处理的错误不会被度量基础设施记录。应用程序可以通过将处理的异常设置为请求属性来确保将此类异常与请求指标一起记录:

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

}
科特林
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目录。错误页面可以是静态 HTML(即添加到任何静态资源目录下)或使用模板构建。文件名应该是准确的状态码或序列掩码。

例如,要映射404到静态 HTML 文件,您的目录结构如下:

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

要使用 Mustache 模板映射所有5xx错误,您的目录结构如下:

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

2.1.7。网页过滤器

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

如果过滤器的顺序很重要,它们可以实现Ordered或用@Order. Spring Boot 自动配置可能会为您配置 Web 过滤器。这样做时,将使用下表中显示的订单:

网页过滤器 命令

MetricsWebFilter

Ordered.HIGHEST_PRECEDENCE + 1

WebFilterChainProxy(Spring安全)

-100

HttpTraceWebFilter

Ordered.LOWEST_PRECEDENCE - 10

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

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

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

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

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

  • 服务器和客户端使用相同的技术

  • 客户端实例是使用WebClient.BuilderSpring Boot 自动配置的 bean构建的

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

您可以在WebClient 运行时部分了解有关客户端资源配置的更多信息。

3. 优雅关机

所有四个嵌入式 Web 服务器(Jetty、Reactor Netty、Tomcat 和 Undertow)以及响应式和基于 servlet 的 Web 应用程序都支持优雅关闭。它作为关闭应用程序上下文的一部分发生,并在停止SmartLifecyclebean 的最早阶段执行。此停止处理使用超时提供宽限期,在此期间将允许完成现有请求但不允许新请求。不允许新请求的确切方式因所使用的 Web 服务器而异。Jetty、Reactor Netty 和 Tomcat 将停止接受网络层的请求。Undertow 将接受请求,但会立即以服务不可用 (503) 响应进行响应。

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

要启用正常关机,请配置该server.shutdown属性,如以下示例所示:

特性
server.shutdown=graceful
yaml
server:
  shutdown: "graceful"

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

特性
spring.lifecycle.timeout-per-shutdown-phase=20s
yaml
spring:
  lifecycle:
    timeout-per-shutdown-phase: "20s"
如果没有发送正确的SIGTERM信号,则对 IDE 使用正常关闭可能无法正常工作。有关更多详细信息,请参阅 IDE 的文档。

4. Spring 安全

如果Spring Security在类路径上,则默认情况下 Web 应用程序是安全的。Spring Boot 依赖 Spring Security 的内容协商策略来确定是否httpBasic使用formLogin. 要为 Web 应用程序添加方法级别的安全性,您还可以添加@EnableGlobalMethodSecurity所需的设置。更多信息可以在Spring Security Reference Guide中找到。

默认UserDetailsService只有一个用户。用户名为user,密码是随机的,在应用启动时打印在WARN级别,如下例所示:

使用生成的安全密码:78fa095d-3f4c-48b1-ad50-e24c31d5cf35

此生成的密码仅供开发使用。在生产中运行您的应用程序之前,必须更新您的安全配置。
如果您微调日志配置,请确保将org.springframework.boot.autoconfigure.security类别设置为日志WARN级别消息。否则,不会打印默认密码。

您可以通过提供spring.security.user.name和来更改用户名和密码spring.security.user.password

默认情况下,您在 Web 应用程序中获得的基本功能是:

  • 一个UserDetailsService(或ReactiveUserDetailsService在 WebFlux 应用程序的情况下)具有内存存储的 bean 和具有生成密码的单个用户(参见SecurityProperties.User用户属性)。

  • Accept整个应用程序(包括执行器端点,如果执行器在类路径上)的基于表单的登录或 HTTP 基本安全性(取决于请求中的标头)。

  • ADefaultAuthenticationEventPublisher用于发布身份验证事件。

AuthenticationEventPublisher您可以通过为其添加 bean 来提供不同的。

4.1。MVC 安全

默认安全配置在SecurityAutoConfiguration和中实现UserDetailsServiceAutoConfiguration。 为 Web 安全SecurityAutoConfiguration导入并配置身份验证,这在非 Web 应用程序中也相关。要完全关闭默认的 Web 应用程序安全配置或组合多个 Spring Security 组件(例如 OAuth2 客户端和资源服务器),请添加一个类型的 bean (这样做不会禁用配置或执行器的安全性)。SpringBootWebSecurityConfigurationUserDetailsServiceAutoConfigurationSecurityFilterChainUserDetailsService

要关闭配置,您可以添加类型为 、或UserDetailsService的 bean 。UserDetailsServiceAuthenticationProviderAuthenticationManager

可以通过添加自定义SecurityFilterChainWebSecurityConfigurerAdapterbean 来覆盖访问规则。Spring Boot 提供了方便的方法,可用于覆盖执行器端点和静态资源的访问规则。 EndpointRequest可用于创建RequestMatcher基于management.endpoints.web.base-path属性的。 PathRequest可用于RequestMatcher在常用位置创建资源。

4.2. WebFlux 安全

与 Spring MVC 应用程序类似,您可以通过添加spring-boot-starter-security依赖项来保护您的 WebFlux 应用程序。默认安全配置在ReactiveSecurityAutoConfiguration和中实现UserDetailsServiceAutoConfiguration。 为 Web 安全ReactiveSecurityAutoConfiguration导入并配置身份验证,这在非 Web 应用程序中也相关。要完全关闭默认的 Web 应用程序安全配置,您可以添加一个类型的 bean (这样做不会禁用配置或执行器的安全性)。WebFluxSecurityConfigurationUserDetailsServiceAutoConfigurationWebFilterChainProxyUserDetailsService

要关闭配置,您可以添加类型为或UserDetailsService的 bean 。ReactiveUserDetailsServiceReactiveAuthenticationManager

SecurityWebFilterChain可以通过添加自定义bean来配置访问规则和多个 Spring Security 组件(例如 OAuth 2 客户端和资源服务器)的使用。Spring Boot 提供了方便的方法,可用于覆盖执行器端点和静态资源的访问规则。 EndpointRequest可用于创建ServerWebExchangeMatcher基于management.endpoints.web.base-path属性的。

PathRequest可用于ServerWebExchangeMatcher在常用位置创建资源。

例如,您可以通过添加以下内容来自定义您的安全配置:

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

}
科特林
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

@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()
        return http.build()
    }

}

4.3. OAuth2

OAuth2是 Spring 支持的广泛使用的授权框架。

4.3.1。客户

如果您有spring-security-oauth2-client类路径,则可以利用一些自动配置来设置 OAuth2/Open ID Connect 客户端。此配置使用OAuth2ClientProperties. 相同的属性适用于 servlet 和响应式应用程序。

您可以在前缀下注册多个 OAuth2 客户端和提供程序spring.security.oauth2.client,如下例所示:

特性
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=https://my-redirect-uri.com
spring.security.oauth2.client.registration.my-client-1.client-authentication-method=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=https://my-redirect-uri.com
spring.security.oauth2.client.registration.my-client-2.client-authentication-method=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/oauth/authorize
spring.security.oauth2.client.provider.my-oauth-provider.token-uri=https://my-auth-server/oauth/token
spring.security.oauth2.client.provider.my-oauth-provider.user-info-uri=https://my-auth-server/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/token_keys
spring.security.oauth2.client.provider.my-oauth-provider.user-name-attribute=name
yaml
spring:
  security:
    oauth2:
      client:
        registration:
          my-client-1:
            client-id: "abcd"
            client-secret: "password"
            client-name: "Client for user scope"
            provider: "my-oauth-provider"
            scope: "user"
            redirect-uri: "https://my-redirect-uri.com"
            client-authentication-method: "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: "https://my-redirect-uri.com"
            client-authentication-method: "basic"
            authorization-grant-type: "authorization_code"

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

对于支持OpenID Connect 发现的 OpenID Connect 提供者,可以进一步简化配置。提供者需要配置一个issuer-uriURI,它声明为它的颁发者标识符。例如,如果issuer-uri提供的是“https://example.com”,那么OpenID Provider Configuration Request将对“https://example.com/.well-known/openid-configuration”进行设置。结果预计为OpenID Provider Configuration Response. 以下示例显示了如何使用以下内容配置 OpenID Connect 提供程序issuer-uri

特性
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 SecurityOAuth2LoginAuthenticationFilter只处理匹配的 URL /login/oauth2/code/*。如果您想自定义redirect-uri使用不同的模式,您需要提供配置来处理该自定义模式。例如,对于 servlet 应用程序,您可以添加自己的SecurityFilterChain类似于以下内容:

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.web.SecurityFilterChain;

@Configuration(proxyBeanMethods = false)
public class MyOAuthClientConfiguration {

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

}
科特林
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

@Configuration(proxyBeanMethods = false)
class MyOAuthClientConfiguration {

    @Bean
    fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http.authorizeRequests().anyRequest().authenticated()
        http.oauth2Login().redirectionEndpoint().baseUri("custom-callback")
        return http.build()
    }

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

对于常见的 OAuth2 和 OpenID 提供程序,包括 Google、Github、Facebook 和 Okta,我们提供了一组提供程序默认值(分别为googlegithubfacebookokta)。

如果您不需要自定义这些提供程序,您可以将provider属性设置为您需要为其推断默认值的那个。此外,如果客户端注册的密钥与默认支持的提供程序匹配,Spring Boot 也会推断出这一点。

换句话说,以下示例中的两个配置使用了 Google 提供程序:

特性
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你的类路径,Spring Boot 可以设置一个 OAuth2 资源服务器。对于 JWT 配置,需要指定 JWK Set URI 或 OIDC Issuer URI,如以下示例所示:

特性
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 Set URI,您可以为资源服务器配置用于验证 JWT 签名的公钥。这可以使用spring.security.oauth2.resourceserver.jwt.public-key-location属性来完成,其中值需要指向包含 PEM 编码 x509 格式的公钥的文件。

相同的属性适用于 servlet 和响应式应用程序。

或者,您可以为 servlet 应用程序或响应式应用程序定义自己的JwtDecoderbean 。ReactiveJwtDecoder

在使用不透明令牌而不是 JWT 的情况下,您可以配置以下属性以通过自省来验证令牌:

特性
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 和响应式应用程序。

或者,您可以为 servlet 应用程序或响应式应用程序定义自己的OpaqueTokenIntrospectorbean 。ReactiveOpaqueTokenIntrospector

4.3.3. 授权服务器

目前,Spring Security 不支持实现 OAuth 2.0 授权服务器。但是,此功能可从Spring Security OAuth项目中获得,该项目最终将被 Spring Security 完全取代。在此之前,您可以使用该spring-security-oauth2-autoconfigure模块轻松设置 OAuth 2.0 授权服务器;有关说明,请参阅其文档

4.4. SAML 2.0

4.4.1。信赖方

如果您spring-security-saml2-service-provider的类路径中有,您可以利用一些自动配置来设置 SAML 2.0 依赖方。此配置使用Saml2RelyingPartyProperties.

依赖方注册代表身份提供者 IDP 和服务提供者 SP 之间的配对配置。您可以在前缀下注册多个信赖方spring.security.saml2.relyingparty,如下例所示:

特性
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.reponse-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.reponse-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"
               reponse-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"
                reponse-url: "https://myapp/logout/saml2/slo"
                binding: "POST"

对于 SAML2 注销,默认情况下,Spring SecuritySaml2LogoutRequestFilterSaml2LogoutResponseFilter处理匹配的 URL /logout/saml2/slo。如果您想自定义urlAP 发起的注销请求发送到的对象或response-urlAP 发送注销响应的对象,以使用不同的模式,您需要提供配置来处理该自定义模式。例如,对于 servlet 应用程序,您可以添加自己的SecurityFilterChain类似于以下内容:

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;

@Configuration(proxyBeanMethods = false)
public class MySamlRelyingPartyConfiguration {

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

}

5.Spring会议

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

  • JDBC

  • 雷迪斯

  • 榛树

  • MongoDB

servlet 自动配置取代了使用@Enable*HttpSession.

在构建响应式 Web 应用程序时,可以自动配置以下存储:

  • 雷迪斯

  • MongoDB

反应式自动配置取代了使用@Enable*WebSession.

如果类路径中存在单个 Spring Session 模块,则 Spring Boot 会自动使用该存储实现。如果您有多个实现,则必须选择StoreType您希望用来存储会话的那个。例如,要将 JDBC 用作后端存储,您可以按如下方式配置您的应用程序:

特性
spring.session.store-type=jdbc
yaml
spring:
  session:
    store-type: "jdbc"
store-type您可以通过将 设置为来 禁用 Spring Session none

每个商店都有特定的附加设置。例如,可以为 JDBC 存储自定义表的名称,如下例所示:

特性
spring.session.jdbc.table-name=SESSIONS
yaml
spring:
  session:
    jdbc:
      table-name: "SESSIONS"

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

@Enable*HttpSession您可以使用(servlet) 或@Enable*WebSession(reactive)控制 Spring Session 的配置。这将导致自动配置退出。然后可以使用注解的属性而不是前面描述的配置属性来配置 Spring Session。

6. Spring for GraphQL

如果您想构建 GraphQL 应用程序,您可以利用 Spring Boot 的Spring for GraphQL自动配置。Spring for GraphQL 项目基于GraphQL Java。您spring-boot-starter-graphql至少需要启动器。由于 GraphQL 与传输无关,因此您还需要在应用程序中添加一个或多个启动器,以通过 Web 公开您的 GraphQL API:

起动机 运输 执行

spring-boot-starter-web

HTTP

SpringMVC

spring-boot-starter-websocket

网络套接字

用于 Servlet 应用程序的 WebSocket

spring-boot-starter-webflux

HTTP、WebSocket

Spring WebFlux

spring-boot-starter-rsocket

TCP、网络套接字

Reactor Netty 上的 Spring WebFlux

6.1。GraphQL 架构

Spring GraphQL 应用程序需要在启动时定义模式。默认情况下,您可以在下编写“.graphqls”或“.gqls”模式文件src/main/resources/graphql/**,Spring Boot 会自动选择它们。您可以使用 自定义位置spring.graphql.schema.locations和使用spring.graphql.schema.file-extensions.

在以下部分中,我们将考虑这个示例 GraphQL 模式,定义两种类型和两个查询:

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.enabled来禁用自省false

6.2. GraphQL 运行时接线

GraphQL JavaRuntimeWiring.Builder可用于DataFetcher`s, and more. You can declare `RuntimeWiringConfigurer在 Spring 配置中注册自定义标量类型、指令、类型解析器、bean,以访问RuntimeWiring.Builder. Spring Boot 检测到此类 bean 并将它们添加到GraphQlSource builder中。

然而,通常情况下,应用程序不会DataFetcher直接实现,而是创建带注释的控制器。Spring Boot 将自动检测@Controller带有注释处理程序方法的类并将它们注册为DataFetcher`s. Here’s a sample implementation for our greeting query with a `@Controller类:

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 + "!";
    }

}
科特林
;

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 Data 存储库使用@GraphQlRepository以下之一进行注释和扩展:

  • QuerydslPredicateExecutor

  • ReactiveQuerydslPredicateExecutor

  • QueryByExampleExecutor

  • ReactiveQueryByExampleExecutor

被 Spring Boot 检测到并被视为DataFetcher匹配顶级查询的候选者。

6.4. 运输

6.4.1. HTTP 和 WebSocket

GraphQL HTTP 端点默认位于 HTTP POST "/graphql"。可以使用自定义路径spring.graphql.path

Spring MVC 和 Spring WebFlux 的 HTTP 端点由带有or的RouterFunctionbean 提供。如果您定义自己的bean,您可能需要添加适当的注释以确保它们被正确排序。 @Order0RouterFunction@Order

GraphQL WebSocket 端点默认关闭。要启用它:

  • 对于 Servlet 应用程序,添加 WebSocket 启动器spring-boot-starter-websocket

  • 对于 WebFlux 应用程序,不需要额外的依赖项

  • 对于两者,spring.graphql.websocket.path必须设置应用程序属性

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

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

Spring Boot 支持命名空间下的许多配置属性spring.graphql.cors.*;这是一个简短的配置示例:

特性
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 Boot 自动配置一个RSocketGraphQlClient.Builder<?>bean,您可以将其注入组件中:

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();
    }
科特林
@Component
class RSocketGraphQlClientExample(private val builder: RSocketGraphQlClient.Builder<*>) {

然后发送请求:

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

6.5。异常处理

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

6.6. GraphiQL 和 Schema 打印机

Spring GraphQL 在使用或开发 GraphQL API 时提供了帮助开发人员的基础设施。

Spring GraphQL 附带一个默认公开的默认GraphiQL页面"/graphiql"。此页面默认禁用,可以使用spring.graphql.graphiql.enabled属性打开。许多公开此类页面的应用程序将更喜欢自定义构建。默认实现在开发过程中非常有用,这就是它spring-boot-devtools在开发过程中自动公开的原因。

您还可以选择在启用/graphql/schemaspring.graphql.schema.printer.enabled属性时以文本格式公开 GraphQL 架构。

7. Spring HATEOAS

如果您开发使用超媒体的 RESTful API,Spring Boot 为 Spring HATEOAS 提供自动配置,适用于大多数应用程序。自动配置取代了使用@EnableHypermediaSupport和注册许多 bean 以简化构建基于超媒体的应用程序的需要,包括一个LinkDiscoverers(用于客户端支持)和一个ObjectMapper配置为将响应正确编组为所需表示的配置。可以通过ObjectMapper设置各种spring.jackson.*属性来定制,或者,如果存在的话,可以通过Jackson2ObjectMapperBuilderbean 来定制。

您可以使用 . 来控制 Spring HATEOAS 的配置@EnableHypermediaSupport。请注意,这样做会禁用ObjectMapper前面描述的自定义。

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

8.接下来要读什么

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


1. see XML Configuration