Appearance
Spring Boot:Web
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 或 @RestController bean 来处理传入的 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);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
“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();
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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();
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Spring MVC 是核心 Spring 框架的一部分,详细信息请参考参考文档。还有几个关于 Spring MVC 的指南可以在 spring.io/guides 找到。
Note:你可以定义任意多个
RouterFunctionbean 来模块化路由定义。bean 可以排序以应用优先级。
1.1.1. Spring MVC 自动配置
Spring Boot 为 Spring MVC 提供了自动配置,适用于大多数应用程序。它替代了 @EnableWebMvc,两者不能同时使用。除了 Spring MVC 的默认设置,自动配置还提供了以下功能:
- 包含
ContentNegotiatingViewResolver和BeanNameViewResolverbean。 - 支持提供静态资源,包括对 WebJars 的支持(在本文档后面部分有介绍)。
- 自动注册
Converter、GenericConverter和Formatterbean。 - 支持
HttpMessageConverters(在本文档后面部分有介绍)。 - 自动注册
MessageCodesResolver(在本文档后面部分有介绍)。 - 静态
index.html支持。 - 自动使用
ConfigurableWebBindingInitializerbean(在本文档后面部分有介绍)。
如果你想保留这些 Spring Boot MVC 自定义项并进行更多 MVC 自定义(拦截器、格式化程序、视图控制器和其他功能),可以添加自己的 @Configuration 类型为 WebMvcConfigurer 但不要使用 @EnableWebMvc。
如果你想提供 RequestMappingHandlerMapping、RequestMappingHandlerAdapter 或 ExceptionHandlerExceptionResolver 的自定义实例,同时保留 Spring Boot MVC 自定义项,可以声明一个 WebMvcRegistrations 类型的 bean,并使用它提供这些组件的自定义实例。自定义实例将被 Spring MVC 进一步初始化和配置。为了参与并在必要时覆盖后续处理,应使用 WebMvcConfigurer。
如果你不想使用自动配置并希望完全控制 Spring MVC,添加自己的 @Configuration 并用 @EnableWebMvc 注解。或者,按照 @EnableWebMvc 的 Javadoc 描述,添加自己的 @Configuration 注解的 DelegatingWebMvcConfiguration。
1.1.2. Spring MVC 转换服务
Spring MVC 使用与从 application.properties 或 application.yaml 文件中转换值的 ConversionService 不同的 ConversionService。这意味着 Period、Duration 和 DataSize 转换器不可用,@DurationUnit 和 @DataSizeUnit 注解将被忽略。
如果你想自定义 Spring MVC 使用的 ConversionService,可以提供一个带有 addFormatters 方法的 WebMvcConfigurer bean。从这个方法中,你可以注册任何你喜欢的转换器,或者你可以委托给 ApplicationConversionService 上的静态方法。
转换也可以使用 spring.mvc.format.* 配置属性进行自定义。未配置时,使用以下默认值:
| 属性 | DateTimeFormatter |
|---|---|
spring.mvc.format.date | ofLocalizedDate(FormatStyle.SHORT) |
spring.mvc.format.time | ofLocalizedTime(FormatStyle.SHORT) |
spring.mvc.format.date-time | ofLocalizedDateTime(FormatStyle.SHORT) |
1.1.3. HttpMessageConverters
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);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
上下文中存在的任何 HttpMessageConverter bean 都会被添加到转换器列表中。你也可以以同样的方式覆盖默认转换器。
1.1.4. MessageCodesResolver
Spring MVC 有一个策略,用于从绑定错误生成错误代码以呈现错误消息:MessageCodesResolver。如果你设置 spring.mvc.message-codes-resolver-format 属性为 PREFIX_ERROR_CODE 或 POSTFIX_ERROR_CODE,Spring Boot 会为你创建一个(参见 DefaultMessageCodesResolver.Format 中的枚举)。
1.1.5. 静态内容
默认情况下,Spring Boot 从类路径中的 /static(或 /public 或 /resources 或 /META-INF/resources)目录或 ServletContext 根目录提供静态内容。它使用 Spring MVC 的 ResourceHttpRequestHandler,因此你可以通过添加自己的 WebMvcConfigurer 并覆盖 addResourceHandlers 方法来修改该行为。
在独立的 Web 应用程序中,容器的默认 Servlet 是不启用的。可以使用 server.servlet.register-default-servlet 属性启用它。
默认 Servlet 充当回退,从 ServletContext 根目录提供内容,如果 Spring 决定不处理它。大多数情况下,这不会发生(除非你修改默认 MVC 配置),因为 Spring 可以通过 DispatcherServlet 始终处理请求。
默认情况下,资源映射到 /**,但你可以使用 spring.mvc.static-path-pattern 属性调整它。例如,将所有资源重新定位到 /resources/** 可以如下进行:
Properties
spring.mvc.static-path-pattern=/resources/**你也可以使用 spring.web.resources.static-locations 属性自定义静态资源位置(用一系列目录位置替换默认值)。根 Servlet 上下文路径 "/" 也会自动添加为位置。
除了前面提到的 “标准” 静态资源位置,还为 Webjars 内容做了特殊处理。路径在 /webjars/** 的任何资源都会从打包为 Webjars 格式的 jar 文件中提供。
Note:如果你的应用程序打包为 jar,不要使用
src/main/webapp目录。虽然这个目录是一个常见的标准,但它仅适用于 war 打包,并且大多数构建工具在生成 jar 时会默默忽略它。
Spring Boot 还支持 Spring MVC 提供的高级资源处理功能,允许使用案例,如缓存破坏静态资源或使用版本无关的 URL 进行 Webjars。
要使用版本无关的 URL 进行 Webjars,添加 webjars-locator-core 依赖项。然后声明你的 Webjar。例如,添加 "/webjars/jquery/jquery.min.js" 会导致 "/webjars/jquery/x.y.z/jquery.min.js",其中 x.y.z 是 Webjar 版本。
Tip:如果你使用 JBoss,需要声明
webjars-locator-jboss-vfs依赖项,而不是webjars-locator-core。否则,所有 Webjars 都会解析为404。
要使用缓存破坏,以下配置为所有静态资源配置了一个缓存破坏解决方案,有效地在 URL 中添加内容哈希,例如 <link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>:
Properties
spring.web.resources.chain.strategy.content.enabled=true
spring.web.resources.chain.strategy.content.paths=/**Tip:链接到资源在运行时在模板中重写,感谢自动配置的
ResourceUrlEncodingFilter适用于 Thymeleaf 和 FreeMarker。你应该在使用 JSP 时手动声明此过滤器。其他模板引擎目前不自动支持,但可以通过自定义模板宏/助手和使用ResourceUrlProvider支持。
当使用 JavaScript 模块加载器动态加载资源时,重命名文件不是一个选项。这就是为什么其他策略也受支持并可以组合使用。“固定” 策略在 URL 中添加静态版本字符串,而不改变文件名,如下例所示:
Properties
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使用此配置,位于 "/js/lib/" 下的 JavaScript 模块使用固定版本策略("/v12/js/lib/mymodule.js"),而其他资源仍使用内容策略(<link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>)。
有关更多支持的选项,请参阅 WebProperties.Resources。
1.1.6. 欢迎页面
Spring Boot 支持静态和模板化的欢迎页面。它首先在配置的静态内容位置中查找 index.html 文件。如果找不到,则查找 index 模板。如果找到任何一个,则自动用作应用程序的欢迎页面。
1.1.7. 自定义 Favicon
与其他静态资源一样,Spring Boot 会在配置的静态内容位置中查找 favicon.ico。如果存在此文件,则自动用作应用程序的 favicon。
1.1.8. 路径匹配和内容协商
Spring MVC 可以通过查看请求路径并将其与应用程序中定义的映射(例如,控制器方法上的 @GetMapping 注解)匹配,将传入的 HTTP 请求映射到处理程序。
Spring Boot 默认禁用后缀模式匹配,这意味着请求如 "GET /projects/spring-boot.json" 不会匹配到 @GetMapping("/projects/spring-boot") 映射。这被认为是 Spring MVC 应用程序的最佳实践。这个功能主要在过去对于没有发送正确的 “Accept” 请求头的 HTTP 客户端很有用;我们需要确保向客户端发送正确的内容类型。如今,内容协商更加可靠。
有其他方法来处理没有一致发送正确的 “Accept” 请求头的 HTTP 客户端。可以使用查询参数,确保请求如 "GET /projects/spring-boot?format=json" 映射到 @GetMapping("/projects/spring-boot"):
Properties
spring.mvc.contentnegotiation.favor-parameter=true或者如果你更喜欢使用不同的参数名称:
Properties
spring.mvc.contentnegotiation.favor-parameter=true
spring.mvc.contentnegotiation.parameter-name=myparam大多数标准媒体类型都受支持,但你也可以定义新的媒体类型:
Properties
spring.mvc.contentnegotiation.media-types.markdown=text/markdown后缀模式匹配已弃用,将在未来版本中删除。如果你理解这些注意事项并仍希望你的应用程序使用后缀模式匹配,则需要以下配置:
Properties
spring.mvc.contentnegotiation.favor-path-extension=true
spring.mvc.pathmatch.use-suffix-pattern=true或者,与其打开所有后缀模式,不如只支持注册的后缀模式更安全:
Properties
spring.mvc.contentnegotiation.favor-path-extension=true
spring.mvc.pathmatch.use-registered-suffix-pattern=true从 Spring Framework 5.3 开始,Spring MVC 支持两种策略来匹配请求路径到控制器。默认情况下,Spring Boot 使用 PathPatternParser 策略。PathPatternParser 是一个优化实现,但与 AntPathMatcher 策略相比,它有一些限制。PathPatternParser 限制了某些路径模式变体的使用。它也与后缀模式匹配(spring.mvc.pathmatch.use-suffix-pattern、spring.mvc.pathmatch.use-registered-suffix-pattern)和使用路径前缀配置 DispatcherServlet(spring.mvc.servlet.path)不兼容。
策略可以使用 spring.mvc.pathmatch.matching-strategy 配置属性进行配置,如下例所示:
Properties
spring.mvc.pathmatch.matching-strategy=ant-path-matcher默认情况下,如果找不到请求的处理程序,Spring MVC 会发送 404 Not Found 错误响应。要改为抛出 NoHandlerFoundException,请将 spring.mvc.throw-exception-if-no-handler-found 设置为 true。请注意,默认情况下,静态内容提供映射到 /**,因此将提供所有请求的处理程序。要抛出 NoHandlerFoundException,你还必须将 spring.mvc.static-path-pattern 设置为更具体的值,例如 /resources/**,或将 spring.web.resources.add-mappings 设置为 false,以完全禁用静态内容提供。
1.1.9. ConfigurableWebBindingInitializer
Spring MVC 使用 WebBindingInitializer 初始化特定请求的 WebDataBinder。如果你创建自己的 ConfigurableWebBindingInitializer @Bean,Spring Boot 会自动配置 Spring MVC 使用它。
1.1.10. 模板引擎
除了 REST Web 服务,你还可以使用 Spring MVC 提供动态 HTML 内容。Spring MVC 支持各种模板技术,包括 Thymeleaf、FreeMarker 和 JSP。还有许多其他模板引擎包括它们自己的 Spring MVC 集成。
Spring Boot 包括对以下模板引擎的自动配置支持:
Note:如果可能,应避免使用 JSP。有几个已知限制在使用它们与嵌入式 Servlet 容器时。
当你使用这些模板引擎的默认配置时,你的模板会自动从 src/main/resources/templates 中捡起。
Note:根据你运行应用程序的方式,你的 IDE 可能会以不同的顺序排列类路径。从其主方法运行应用程序会导致不同的排序,而使用 Maven 或 Gradle 或从其打包的 jar 运行应用程序会导致不同的排序。这可能会导致 Spring Boot 找不到预期的模板。如果你有这个问题,可以重新排序 IDE 中的类路径,使模块的类和资源首先出现。
1.1.11. 错误处理
默认情况下,Spring Boot 提供了一个 /error 映射,以合理的方式处理所有错误,并在 Servlet 容器中注册为 “全局” 错误页面。对于机器客户端,它生成一个包含错误详细信息、HTTP 状态和异常消息的 JSON 响应。对于浏览器客户端,有一个 “白标” 错误视图,它以 HTML 格式呈现相同的数据(要自定义它,添加一个解析为 error 的 View)。
有几个 server.error 属性可以设置,如果你想自定义默认错误处理行为。请参阅 “服务器属性” 部分的附录。
要完全替换默认行为,可以实现 ErrorController 并注册该类型的 bean 定义,或者添加一个 ErrorAttributes 类型的 bean 以使用现有机制但替换内容。
Note:
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;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
在上例中,如果 MyException 由与 SomeController 在同一包中定义的控制器抛出,则使用 MyErrorBody POJO 的 JSON 表示,而不是 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";
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
1.1.11.1. 自定义错误页面
如果你想为给定状态代码显示自定义 HTML 错误页面,可以添加一个文件到 /error 目录。错误页面可以是静态 HTML(即添加到任何静态资源目录下),也可以使用模板构建。文件名应该是确切的状态代码或一系列掩码。
例如,要将 404 映射到静态 HTML 文件,你的目录结构将如下所示:
Text
src/
+- main/
+- java/
| + <source code>
+- resources/
+- public/
+- error/
| +- 404.html
+- <other public assets>要使用 FreeMarker 模板映射所有 5xx 错误,你的目录结构将如下所示:
Text
src/
+- main/
+- java/
| + <source code>
+- resources/
+- templates/
+- error/
| +- 5xx.ftlh
+- <other templates>对于更复杂的映射,你还可以添加实现 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) {
// 使用请求或状态可选地返回 ModelAndView
if (status == HttpStatus.INSUFFICIENT_STORAGE) {
// 我们可以在这里添加自定义模型值
new ModelAndView("myview");
}
return null;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
你也可以使用常规的 Spring MVC 功能,如 @ExceptionHandler 方法和 @ControllerAdvice。然后 ErrorController 会捡起任何未处理的异常。
1.1.11.2. 在 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"));
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Tip:如果你注册了一个
ErrorPage,其路径最终由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;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
请注意,默认的 FilterRegistrationBean 不包括 ERROR 调度程序类型。
1.1.11.3. 部署到 WAR 时的错误处理
当部署到 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.12. CORS 支持
跨域资源共享(CORS)是 W3C 规范,由大多数浏览器实现,允许你以灵活的方式指定哪些跨域请求是授权的,而不是使用一些不太安全和不太强大的方法,如 IFRAME 或 JSONP。
从 4.2 版本开始,Spring MVC 支持 CORS。在你的 Spring Boot 应用程序中使用控制器方法 CORS 配置与 @CrossOrigin 注解不需要任何特定配置。全局 CORS 配置可以通过注册一个带有自定义 addCorsMappings(CorsRegistry) 方法的 WebMvcConfigurer bean 来定义,如下例所示:
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/**");
}
};
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1.2. JAX-RS 和 Jersey
如果你更喜欢 JAX-RS 编程模型来实现 REST 端点,可以使用现有的实现之一,而不是 Spring MVC。Jersey 和 Apache CXF 在开箱即用的情况下运行得非常好。CXF 要求你在应用程序上下文中将其 Servlet 或 Filter 注册为 @Bean。Jersey 有一些本地 Spring 支持,因此我们还为它提供了 Spring Boot 中的自动配置支持,以及一个启动器。
要开始使用 Jersey,请将 spring-boot-starter-jersey 作为依赖项包含在内,然后你需要一个 ResourceConfig 类型的 @Bean,在其中注册所有端点,如下例所示:
Java
import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.stereotype.Component;
@Component
public class MyJerseyConfig extends ResourceConfig {
public MyJerseyConfig() {
register(MyEndpoint.class);
}
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
Warning:Jersey 对扫描可执行存档的支持相当有限。例如,它无法扫描在完全可执行的 jar 文件中找到的包中的端点,也无法在运行可执行 war 文件时在
WEB-INF/classes中找到端点。为了避免这种限制,不应使用packages方法,而应使用register方法逐个注册端点,如前面的例子所示。
对于更高级的自定义,你还可以注册任意数量的实现 ResourceConfigCustomizer 的 bean。
所有注册的端点都应该是带有 HTTP 资源注解(@GET 等)的 @Components,如下例所示:
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";
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
由于 Endpoint 是 Spring @Component,其生命周期由 Spring 管理,你可以使用 @Autowired 注解注入依赖项,并使用 @Value 注解注入外部配置。默认情况下,Jersey servlet 被注册并映射到 /*。你可以通过在 ResourceConfig 中添加 @ApplicationPath 来更改映射。
默认情况下,Jersey 被设置为 ServletRegistrationBean 类型的 @Bean 命名为 jerseyServletRegistration 中的 servlet。默认情况下,servlet 是懒加载的,但你可以通过设置 spring.jersey.servlet.load-on-startup 来自定义该行为。你可以通过创建一个同名的 bean 来禁用或覆盖该 bean。你还可以通过设置 spring.jersey.type=filter 使用过滤器而不是 servlet(在这种情况下,要替换或覆盖的 @Bean 是 jerseyFilterRegistration)。过滤器有一个 @Order,你可以使用 spring.jersey.filter.order 设置它。当使用 Jersey 作为过滤器时,必须存在一个 servlet,它将处理未被 Jersey 拦截的任何请求。如果你的应用程序不包含这样的 servlet,你可能希望通过将 server.servlet.register-default-servlet 设置为 true 来启用默认 servlet。servlet 和过滤器注册都可以使用 spring.jersey.init.* 指定 init 参数的属性映射。
1.3. 嵌入式 Servlet 容器支持
对于 Servlet 应用程序,Spring Boot 包括对嵌入式 Tomcat、Jetty 和 Undertow 服务器的支持。大多数开发人员使用适当的 “启动器” 来获取完全配置的实例。默认情况下,嵌入式服务器监听端口 8080 上的 HTTP 请求。
1.3.1. Servlets、Filters 和 Listeners
使用嵌入式 servlet 容器时,你可以注册 servlets、filters 和所有 listeners(例如 HttpSessionListener),这些都是从 servlet 规范中注册的,要么使用 Spring beans,要么扫描 servlet 组件。
1.3.1.1. 将 Servlets、Filters 和 Listeners 注册为 Spring Beans
任何 Servlet、Filter 或 servlet *Listener 实例,如果是 Spring bean,都会注册到嵌入式容器中。这在你想在配置过程中引用 application.properties 中的值时特别方便。
默认情况下,如果上下文只包含一个 Servlet,它将映射到 /。在多个 servlet beans 的情况下,bean 名称用作路径前缀。Filters 映射到 /*。
如果基于约定的映射不够灵活,你可以使用 ServletRegistrationBean、FilterRegistrationBean 和 ServletListenerRegistrationBean 类进行完全控制。
通常可以安全地将 filter beans 保持未排序。如果需要特定顺序,应使用 @Order 注解 Filter,或使其实现 Ordered。不能通过在 bean 方法上使用 @Order 注解来配置 Filter 的顺序。如果无法更改 Filter 类以添加 @Order 或实现 Ordered,则必须为 Filter 定义 FilterRegistrationBean,并使用 setOrder(int) 方法设置注册 bean 的顺序。避免在 Ordered.HIGHEST_PRECEDENCE 配置可能与应用程序的字符编码配置相悖的过滤器,因为它可能会读取请求体。如果 servlet 过滤器包装请求,则应将其配置为具有小于或等于 OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER 的顺序。
Note:要查看应用程序中每个
Filter的顺序,请为web日志组启用调试级别日志记录(logging.level.web=debug)。启动时将记录注册的过滤器的详细信息,包括它们的顺序和 URL 模式。
Warning:在注册
Filterbeans 时要小心,因为它们在应用程序生命周期的非常早期初始化。如果你需要注册一个与其他 beans 交互的Filter,请考虑使用DelegatingFilterProxyRegistrationBean代替。
1.3.2. Servlet 上下文初始化
嵌入式 servlet 容器不会直接执行 servlet 3.0+ javax.servlet.ServletContainerInitializer 接口或 Spring 的 org.springframework.web.WebApplicationInitializer 接口。这是一个有意的设计决策,旨在减少第三方库设计用于 war 中运行的应用程序可能会破坏 Spring Boot 应用程序的风险。
如果你需要在 Spring Boot 应用程序中执行 servlet 上下文初始化,应注册一个实现 org.springframework.boot.web.servlet.ServletContextInitializer 接口的 bean。单个 onStartup 方法提供对 ServletContext 的访问,并且如果需要,可以很容易地用作现有 WebApplicationInitializer 的适配器。
1.3.2.1. 扫描 Servlets、Filters 和 Listeners
使用嵌入式容器时,可以通过使用 @ServletComponentScan 启用自动注册带有 @WebServlet、@WebFilter 和 @WebListener 注解的类。
Note:
@ServletComponentScan在独立容器中没有效果,而是使用容器的内置发现机制。
1.3.3. ServletWebServerApplicationContext
在幕后,Spring Boot 使用不同类型的 ApplicationContext 来支持嵌入式 servlet 容器。ServletWebServerApplicationContext 是一种特殊类型的 WebApplicationContext,它通过搜索单个 ServletWebServerFactory bean 来引导自身。通常会自动配置 TomcatServletWebServerFactory、JettyServletWebServerFactory 或 UndertowServletWebServerFactory。
Tip:你通常不需要了解这些实现类。大多数应用程序是自动配置的,并且会为你创建适当的
ApplicationContext和ServletWebServerFactory。
在嵌入式容器设置中,ServletContext 作为服务器启动的一部分设置,这发生在应用程序上下文初始化期间。由于这一点,ApplicationContext 中的 beans 无法可靠地使用 ServletContext 进行初始化。一种解决方法是将 ApplicationContext 作为 bean 的依赖项注入,并仅在需要时访问 ServletContext。另一种方法是使用回调,一旦服务器启动。这可以通过使用监听 ApplicationStartedEvent 的 ApplicationListener 来完成,如下所示:
Java
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1.3.4. 自定义嵌入式 Servlet 容器
可以使用 Spring Environment 属性配置常见的 servlet 容器设置。通常,你会在 application.properties 或 application.yaml 文件中定义这些属性。
常见的服务器设置包括:
网络设置:监听传入 HTTP 请求的端口(
server.port)、要绑定到的接口地址server.address等。会话设置:会话是否持久(
server.servlet.session.persistent)、会话超时(server.servlet.session.timeout)、会话数据的位置(server.servlet.session.store-dir)和会话 cookie 配置(server.servlet.session.cookie.*)。错误管理:错误页面的位置(
server.error.path)等。
Spring Boot 尽可能多地暴露常见设置,但这并不总是可能的。对于这些情况,专用命名空间提供了特定于服务器的自定义(请参阅 server.tomcat 和 server.undertow)。例如,访问日志可以使用嵌入式 servlet 容器的特定功能进行配置。
Note:有关完整列表,请参阅
ServerProperties类。
1.3.4.1. SameSite Cookies
SameSite cookie 属性可以由 Web 浏览器用来控制是否以及如何在跨站点请求中提交 cookie。该属性对于现代 Web 浏览器特别相关,这些浏览器已经开始更改在缺少该属性时使用的默认值。
如果你想更改会话 cookie 的 SameSite 属性,可以使用 server.servlet.session.cookie.same-site 属性。此属性受自动配置的 Tomcat、Jetty 和 Undertow 服务器支持。它还用于配置基于 servlet 的 SessionRepository beans 的 Spring Session。
例如,如果你希望会话 cookie 具有 SameSite 属性 None,可以在 application.properties 或 application.yaml 文件中添加以下内容:
Properties
server.servlet.session.cookie.same-site=none如果你想更改添加到 HttpServletResponse 的其他 cookie 的 SameSite 属性,可以使用 CookieSameSiteSupplier。CookieSameSiteSupplier 传递一个 Cookie,并可能返回一个 SameSite 值,或 null。
有许多方便的工厂和过滤器方法可以快速匹配特定的 cookie。例如,添加以下 bean 将自动为所有名称与正则表达式 myapp.* 匹配的 cookie 应用 SameSite 的 Lax:
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.*");
}
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
1.3.4.2. 字符编码
可以使用 server.servlet.encoding.* 配置属性配置嵌入式 servlet 容器的字符编码行为,以处理请求和响应。
当请求的 Accept-Language 标头指示请求的语言环境时,它将由 servlet 容器自动映射到字符集。每个容器都提供默认的语言环境到字符集的映射,你应该验证它们是否满足应用程序的需求。当它们不满足时,使用 server.servlet.encoding.mapping 配置属性自定义映射,如下例所示:
Properties
server.servlet.encoding.mapping.ko=UTF-8在前面的例子中,ko(韩语)语言环境已映射到 UTF-8。这相当于传统 war 部署的 web.xml 文件中的 <locale-encoding-mapping-list> 条目。
1.3.4.3. 编程自定义
如果你需要以编程方式配置嵌入式 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);
}
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
TomcatServletWebServerFactory、JettyServletWebServerFactory 和 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()));
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
1.3.4.4. 直接自定义 ConfigurableServletWebServerFactory
对于需要从 ServletWebServerFactory 扩展的更高级用例,你可以自己公开这种类型的 bean。
为许多配置选项提供了设置器。如果你需要做一些更奇特的事情,还提供了几个受保护的方法 “钩子”。有关详细信息,请参阅源代码文档。
Tip:自动配置的自定义项仍然应用于你的自定义工厂,因此请小心使用该选项。
1.3.5. JSP 限制
在运行使用嵌入式 servlet 容器的 Spring Boot 应用程序(并打包为可执行存档)时,JSP 支持存在一些限制。
- 使用 Jetty 和 Tomcat,如果你使用 war 打包,它应该可以工作。可执行 war 在使用
java -jar启动时可以工作,并且也可以部署到任何标准容器。JSP 在使用可执行 jar 时不受支持。 - 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,是完全异步和非阻塞的,并实现了 Reactive Streams 规范通过 Reactor 项目。
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);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
“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();
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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) {
...
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
WebFlux 是 Spring Framework 的一部分,详细信息请参阅其参考文档。
Note:您可以定义任意多个
RouterFunctionbean 以模块化定义路由。如果需要应用优先级,可以对 bean 进行排序。
要开始使用,请将 spring-boot-starter-webflux 模块添加到您的应用程序中。
Tip:在应用程序中同时添加
spring-boot-starter-web和spring-boot-starter-webflux模块会导致 Spring Boot 自动配置 Spring MVC,而不是 WebFlux。这种行为是因为许多 Spring 开发人员在其 Spring MVC 应用程序中添加spring-boot-starter-webflux以使用响应式WebClient。您仍然可以通过将应用程序类型设置为SpringApplication.setWebApplicationType(WebApplicationType.REACTIVE)来强制您的选择。
2.1.1. Spring WebFlux 自动配置
Spring Boot 为 Spring WebFlux 提供了适用于大多数应用程序的自动配置。
自动配置在 Spring 的默认值之上添加了以下功能:
如果您想保留 Spring Boot WebFlux 功能并添加额外的 WebFlux 配置,可以添加自己的 @Configuration 类类型为 WebFluxConfigurer 但不带 @EnableWebFlux。
如果您想完全控制 Spring WebFlux,可以添加自己的带有 @EnableWebFlux 注解的 @Configuration。
2.1.2. Spring WebFlux 转换服务
如果要自定义 Spring WebFlux 使用的 ConversionService,可以提供带有 addFormatters 方法的 WebFluxConfigurer bean。
转换也可以使用 spring.webflux.format.* 配置属性进行自定义。未配置时,使用以下默认值:
| 属性 | DateTimeFormatter |
|---|---|
spring.webflux.format.date | ofLocalizedDate(FormatStyle.SHORT) |
spring.webflux.format.time | ofLocalizedTime(FormatStyle.SHORT) |
spring.webflux.format.date-time | ofLocalizedDateTime(FormatStyle.SHORT) |
2.1.3. 使用 HttpMessageReaders 和 HttpMessageWriters 的 HTTP 编解码器
Spring WebFlux 使用 HttpMessageReader 和 HttpMessageWriter 接口来转换 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());
// ...
};
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
您还可以利用 Boot 的自定义 JSON 序列化器和反序列化器。
2.1.4. 静态内容
默认情况下,Spring Boot 从类路径中名为 /static(或 /public 或 /resources 或 /META-INF/resources)的目录提供静态内容。它使用 Spring WebFlux 的 ResourceWebHandler,因此您可以通过添加自己的 WebFluxConfigurer 并覆盖 addResourceHandlers 方法来修改该行为。
默认情况下,资源映射到 /**,但您可以通过设置 spring.webflux.static-path-pattern 属性来调整它。例如,将所有资源重新定位到 /resources/** 可以如下实现:
Properties
spring.webflux.static-path-pattern=/resources/**您还可以使用 spring.web.resources.static-locations 自定义静态资源位置。这样做会用目录位置列表替换默认值。如果这样做,默认的欢迎页面检测将切换到您的自定义位置。因此,如果在启动时任何位置中有 index.html,它将成为应用程序的主页。
除了前面列出的 “标准” 静态资源位置外,还为 Webjars 内容做了特殊处理。任何路径在 /webjars/** 的资源都从 jar 文件提供服务,如果它们以 Webjars 格式打包。
Note:Spring WebFlux 应用程序不严格依赖于 Servlet API,因此它们不能作为 war 文件部署,也不使用
src/main/webapp目录。
2.1.5. 欢迎页面
Spring Boot 支持静态和模板化的欢迎页面。它首先在配置的静态内容位置中查找 index.html 文件。如果找不到,则查找 index 模板。如果找到其中任何一个,它将自动用作应用程序的欢迎页面。
2.1.6. 模板引擎
除了 REST Web 服务外,您还可以使用 Spring WebFlux 提供动态 HTML 内容。Spring WebFlux 支持各种模板技术,包括 Thymeleaf、FreeMarker 和 Mustache。
Spring Boot 包括对以下模板引擎的自动配置支持:
使用默认配置时,您的模板会自动从 src/main/resources/templates 中提取。
2.1.7. 错误处理
Spring Boot 提供了一个 WebExceptionHandler,它以合理的方式处理所有错误。其处理顺序位于 WebFlux 提供的处理程序之前,这些处理程序被视为最后的处理程序。对于机器客户端,它生成一个包含错误详细信息、HTTP 状态和异常消息的 JSON 响应。对于浏览器客户端,有一个 “白标” 错误处理程序,它以 HTML 格式呈现相同的数据。您还可以提供自己的 HTML 模板来显示错误(请参阅下一节)。
自定义此功能的第一步通常是使用现有机制,但替换或增强错误内容。为此,可以添加 ErrorAttributes 类型的 bean。
要更改错误处理行为,可以实现 ErrorWebExceptionHandler 并注册该类型的 bean 定义。由于 ErrorWebExceptionHandler 非常低级,Spring Boot 还提供了一个方便的 AbstractErrorWebExceptionHandler,以便您以 WebFlux 功能性方式处理错误,如下例所示:
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);
// ... 其他构建器调用
return builder.build();
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
为了获得更完整的图景,您还可以直接子类化 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();
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2.1.7.1. 自定义错误页面
如果要为给定状态代码显示自定义 HTML 错误页面,可以添加从 error/* 解析的视图,例如通过将文件添加到 /error 目录。错误页面可以是静态 HTML(即,添加到任何静态资源目录下)或使用模板构建。文件名应为确切的状态代码、状态代码系列掩码或默认的 error(如果没有其他匹配)。请注意,默认错误视图的路径是 error/error,而在 Spring MVC 中默认错误视图是 error。
例如,将 404 映射到静态 HTML 文件,您的目录结构如下:
Text
src/
+- main/
+- java/
| + <source code>
+- resources/
+- public/
+- error/
| +- 404.html
+- <other public assets>要使用 Mustache 模板映射所有 5xx 错误,您的目录结构如下:
Text
src/
+- main/
+- java/
| + <source code>
+- resources/
+- templates/
+- error/
| +- 5xx.mustache
+- <other templates>2.1.8. Web 过滤器
Spring WebFlux 提供了一个 WebFilter 接口,可以实现它来过滤 HTTP 请求-响应交换。在应用程序上下文中找到的 WebFilter bean 将自动用于过滤每个交换。
如果过滤器的顺序很重要,它们可以实现 Ordered 或使用 @Order 注解。Spring Boot 自动配置可能会为您配置 Web 过滤器。如果这样做,将使用以下表中显示的顺序:
| Web 过滤器 | 顺序 |
|---|---|
MetricsWebFilter | Ordered.HIGHEST_PRECEDENCE + 1 |
WebFilterChainProxy(Spring Security) | -100 |
HttpTraceWebFilter | Ordered.LOWEST_PRECEDENCE - 10 |
2.2. 嵌入式响应式服务器支持
Spring Boot 包括对以下嵌入式响应式 Web 服务器的支持:Reactor Netty、Tomcat、Jetty 和 Undertow。大多数开发人员使用适当的 “Starter” 来获取完全配置的实例。默认情况下,嵌入式服务器监听来自端口 8080 的 HTTP 请求。
2.2.1. 自定义响应式服务器
可以使用 Spring Environment 属性配置常见的响应式 Web 服务器设置。通常,您会在 application.properties 或 application.yaml 文件中定义属性。
常见的服务器设置包括:
网络设置:用于传入 HTTP 请求的监听端口(
server.port)、要绑定到的接口地址server.address等。错误管理:错误页面的位置(
server.error.path)等。
Spring Boot 尽可能多地暴露常见设置,但这并不总是可能的。对于这些情况,专用命名空间(如 server.netty.*)提供特定于服务器的自定义。
Note:请参阅
ServerProperties类以获取完整列表。
2.2.1.1. 编程自定义
如果需要以编程方式配置响应式 Web 服务器,可以注册实现 WebServerFactoryCustomizer 接口的 Spring bean。WebServerFactoryCustomizer 提供对 ConfigurableReactiveWebServerFactory 的访问,其中包括许多自定义设置器方法。以下示例显示了以编程方式设置端口:
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);
}
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
JettyReactiveWebServerFactory、NettyReactiveWebServerFactory、TomcatReactiveWebServerFactory 和 UndertowServletWebServerFactory 是 ConfigurableReactiveWebServerFactory 的专用变体,它们具有针对 Jetty、Reactor Netty、Tomcat 和 Undertow 的额外自定义设置器方法。以下示例显示了如何自定义 NettyReactiveWebServerFactory,它提供对 Reactor Netty 特定配置选项的访问:
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)));
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
2.2.1.2. 直接自定义 ConfigurableReactiveWebServerFactory
对于需要从 ReactiveWebServerFactory 扩展的更高级用例,您可以自己公开该类型的 bean。
为许多配置选项提供了设置器。还提供了几个受保护的方法 “钩子”,以防您需要做一些更奇特的事情。请参阅源代码文档以获取详细信息。
Tip:自动配置的自定义程序仍然应用于您的自定义工厂,因此请谨慎使用该选项。
2.3. 响应式服务器资源配置
在自动配置 Reactor Netty 或 Jetty 服务器时,Spring Boot 将创建特定的 bean,这些 bean 将为服务器实例提供 HTTP 资源:ReactorResourceFactory 或 JettyResourceFactory。
默认情况下,这些资源也将与 Reactor Netty 和 Jetty 客户端共享,以获得最佳性能,前提是:
- 服务器和客户端使用相同的技术
- 客户端实例是使用 Spring Boot 自动配置的
WebClient.Builderbean 构建的
开发人员可以通过提供自定义 ReactorResourceFactory 或 JettyResourceFactory bean 来覆盖 Jetty 和 Reactor Netty 的资源配置 - 这将应用于客户端和服务器。
您可以在 WebClient 运行时部分中了解有关客户端资源配置的更多信息。
3. 优雅关闭
所有四种嵌入式 Web 服务器(Jetty、Reactor Netty、Tomcat 和 Undertow)以及响应式和基于 Servlet 的 Web 应用程序都支持优雅关闭。它作为关闭应用程序上下文的一部分进行,并在停止 SmartLifecycle bean 的最早阶段执行。此停止处理使用超时,该超时提供了一个宽限期,在此期间允许现有请求完成,但不允许新请求。不允许新请求的确切方式因使用的 Web 服务器而异。Jetty、Reactor Netty 和 Tomcat 将在网络层停止接受请求。Undertow 将接受请求,但立即响应服务不可用(503)。
Tip:Tomcat 的优雅关闭需要 Tomcat 9.0.33 或更高版本。
要启用优雅关闭,请配置 server.shutdown 属性,如下例所示:
Properties
server.shutdown=graceful要配置超时期限,请配置 spring.lifecycle.timeout-per-shutdown-phase 属性,如下例所示:
Properties
spring.lifecycle.timeout-per-shutdown-phase=20sWarning:使用 IDE 进行优雅关闭可能无法正常工作,如果它没有发送正确的
SIGTERM信号。请参阅您的 IDE 文档以获取更多详细信息。
4. Spring Security
如果 Spring Security 在类路径中,则 Web 应用程序默认是安全的。Spring Boot 依赖于 Spring Security 的内容协商策略来确定是使用 httpBasic 还是 formLogin。要向 Web 应用程序添加方法级别的安全性,您还可以添加 @EnableGlobalMethodSecurity 和您所需的设置。有关更多信息,请参阅 Spring Security 参考指南。
默认的 UserDetailsService 有一个用户。用户名为 user,密码是随机的,并在应用程序启动时以 WARN 级别打印,如下例所示:
Text
使用生成的安全密码:78fa095d-3f4c-48b1-ad50-e24c31d5cf35
此生成的密码仅用于开发。在生产环境中运行应用程序之前,必须更新您的安全配置。Tip:如果您调整了日志配置,请确保将
org.springframework.boot.autoconfigure.security类别设置为记录WARN级别的消息。否则,默认密码不会被打印。
您可以通过提供 spring.security.user.name 和 spring.security.user.password 来更改用户名和密码。
您在 Web 应用程序中默认获得的基本功能是:
一个带有内存存储和一个用户的
UserDetailsService(或ReactiveUserDetailsService,在 WebFlux 应用程序的情况下)bean,该用户具有生成的密码(请参阅SecurityProperties.User以获取用户的属性)。基于表单的登录或 HTTP Basic 安全(取决于请求中的
Accept头)适用于整个应用程序(包括 actuator 端点,如果 actuator 在类路径中)。一个
DefaultAuthenticationEventPublisher用于发布身份验证事件。
您可以通过添加一个 bean 来提供不同的 AuthenticationEventPublisher。
4.1. MVC 安全性
默认的安全配置在 SecurityAutoConfiguration 和 UserDetailsServiceAutoConfiguration 中实现。SecurityAutoConfiguration 导入 SpringBootWebSecurityConfiguration 用于 Web 安全,UserDetailsServiceAutoConfiguration 配置身份验证,这在非 Web 应用程序中也是相关的。要完全关闭默认的 Web 应用程序安全配置,或者结合多个 Spring Security 组件(如 OAuth2 客户端和资源服务器),请添加一个 SecurityFilterChain 类型的 bean(这样做不会禁用 UserDetailsService 配置或 Actuator 的安全)。
要同时关闭 UserDetailsService 配置,可以添加一个 UserDetailsService、AuthenticationProvider 或 AuthenticationManager 类型的 bean。
可以通过添加自定义 SecurityFilterChain 或 WebSecurityConfigurerAdapter bean 来覆盖访问规则。Spring Boot 提供了便捷方法,可以用于覆盖 actuator 端点和静态资源的访问规则。EndpointRequest 可以用于创建基于 management.endpoints.web.base-path 属性的 RequestMatcher。PathRequest 可以用于为常用位置中的资源创建 RequestMatcher。
4.2. WebFlux 安全性
与 Spring MVC 应用程序类似,您可以通过添加 spring-boot-starter-security 依赖项来保护您的 WebFlux 应用程序。默认的安全配置在 ReactiveSecurityAutoConfiguration 和 UserDetailsServiceAutoConfiguration 中实现。ReactiveSecurityAutoConfiguration 导入 WebFluxSecurityConfiguration 用于 Web 安全,UserDetailsServiceAutoConfiguration 配置身份验证,这在非 Web 应用程序中也是相关的。要完全关闭默认的 Web 应用程序安全配置,可以添加一个 WebFilterChainProxy 类型的 bean(这样做不会禁用 UserDetailsService 配置或 Actuator 的安全)。
要同时关闭 UserDetailsService 配置,可以添加一个 ReactiveUserDetailsService 或 ReactiveAuthenticationManager 类型的 bean。
可以通过添加自定义 SecurityWebFilterChain bean 来配置访问规则和使用多个 Spring Security 组件(如 OAuth 2 客户端和资源服务器)。Spring Boot 提供了便捷方法,可以用于覆盖 actuator 端点和静态资源的访问规则。EndpointRequest 可以用于创建基于 management.endpoints.web.base-path 属性的 ServerWebExchangeMatcher。
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();
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
4.3. OAuth2
OAuth2 是一个广泛使用的授权框架,Spring 支持它。
4.3.1. 客户端
如果您的类路径中有 spring-security-oauth2-client,您可以利用一些自动配置来设置 OAuth2/Open ID Connect 客户端。此配置使用 OAuth2ClientProperties 下的属性。相同的属性适用于 Servlet 和响应式应用程序。
您可以在 spring.security.oauth2.client 前缀下注册多个 OAuth2 客户端和提供程序,如下例所示:
Properties
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对于支持 OpenID Connect 发现的 OpenID Connect 提供程序,配置可以进一步简化。提供程序需要配置一个 issuer-uri,这是它声明的 Issuer Identifier 的 URI。例如,如果提供的 issuer-uri 是 “https://example.com”,那么将发出一个 OpenID Provider Configuration Request 到 “https://example.com/.well-known/openid-configuration”。预期结果是一个 OpenID Provider Configuration Response。以下示例显示了如何使用 issuer-uri 配置 OpenID Connect 提供程序:
Properties
spring.security.oauth2.client.provider.oidc-provider.issuer-uri=https://dev-123456.oktapreview.com/oauth2/default/默认情况下,Spring Security 的 OAuth2LoginAuthenticationFilter 仅处理匹配 /login/oauth2/code/* 的 URL。如果要自定义 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();
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
Note:Spring Boot 自动配置了一个
InMemoryOAuth2AuthorizedClientService,Spring Security 用它来管理客户端注册。InMemoryOAuth2AuthorizedClientService的功能有限,建议仅在开发环境中使用。对于生产环境,请考虑使用JdbcOAuth2AuthorizedClientService或创建自己的OAuth2AuthorizedClientService实现。
4.3.1.1. 常见提供程序的 OAuth2 客户端注册
对于常见的 OAuth2 和 OpenID 提供程序,包括 Google、Github、Facebook 和 Okta,我们提供了一组提供程序默认值(分别为 google、github、facebook 和 okta)。
如果您不需要自定义这些提供程序,可以将 provider 属性设置为您需要推断默认值的提供程序。此外,如果客户端注册的键与支持的默认提供程序匹配,Spring Boot 也会推断出该提供程序。
换句话说,以下示例中的两个配置都使用 Google 提供程序:
Properties
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=password4.3.2. 资源服务器
如果您的类路径中有 spring-security-oauth2-resource-server,Spring Boot 可以设置一个 OAuth2 资源服务器。对于 JWT 配置,需要指定 JWK Set URI 或 OIDC Issuer URI,如下例所示:
Properties
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://example.com/oauth2/default/v1/keysProperties
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://dev-123456.oktapreview.com/oauth2/default/Tip:如果授权服务器不支持 JWK Set URI,您可以使用用于验证 JWT 签名的公钥来配置资源服务器。这可以使用
spring.security.oauth2.resourceserver.jwt.public-key-location属性来完成,其中值需要指向包含公钥的文件,格式为 PEM 编码的 x509。
spring.security.oauth2.resourceserver.jwt.audiences 属性可以用于指定 JWT 中 aud 声明的预期值。例如,要求 JWT 包含值为 my-audience 的 aud 声明:
Properties
spring.security.oauth2.resourceserver.jwt.audiences[0]=my-audience相同的属性适用于 Servlet 和响应式应用程序。或者,您可以为 Servlet 应用程序定义自己的 JwtDecoder bean,或者为响应式应用程序定义 ReactiveJwtDecoder。
在使用不透明令牌而不是 JWT 的情况下,您可以配置以下属性以通过内省验证令牌:
Properties
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相同的属性适用于 Servlet 和响应式应用程序。或者,您可以为 Servlet 应用程序定义自己的 OpaqueTokenIntrospector bean,或者为响应式应用程序定义 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 前缀下注册多个依赖方,如下例所示:
Properties
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.reponse-url=https://myapp/logout/saml2/slo
spring.security.saml2.relyingparty.registration.my-relying-party2.assertingparty.singlelogout.binding=POST对于 SAML2 注销,默认情况下,Spring Security 的 Saml2LogoutRequestFilter 和 Saml2LogoutResponseFilter 仅处理匹配 /logout/saml2/slo 的 URL。如果要自定义 url 以使 AP 发起的注销请求发送到或 response-url 以使 AP 发送注销响应到,以使用不同的模式,您需要提供配置以处理该自定义模式。例如,对于 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 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();
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
5. Spring Session
Spring Boot 提供了 Spring Session 自动配置,适用于各种数据存储。在构建 Servlet Web 应用程序时,可以自动配置以下存储:
- JDBC
- Redis
- Hazelcast
- MongoDB
此外,Spring Boot for Apache Geode 提供了自动配置以使用 Apache Geode 作为会话存储。
Servlet 自动配置替换了使用 @Enable*HttpSession 的需要。
在构建响应式 Web 应用程序时,可以自动配置以下存储:
- Redis
- MongoDB
响应式自动配置替换了使用 @Enable*WebSession 的需要。
如果类路径中存在单个 Spring Session 模块,Spring Boot 会自动使用该存储实现。如果您有多个实现,您必须选择要使用的 StoreType 来存储会话。例如,要使用 JDBC 作为后端存储,可以如下配置您的应用程序:
Properties
spring.session.store-type=jdbcNote:您可以通过将
store-type设置为none来禁用 Spring Session。
每个存储都有特定的附加设置。例如,可以自定义 JDBC 存储的表名,如下例所示:
Properties
spring.session.jdbc.table-name=SESSIONS要设置会话的超时时间,可以使用 spring.session.timeout 属性。如果该属性在 Servlet Web 应用程序中未设置,自动配置将回退到 server.servlet.session.timeout 的值。
您可以使用 @Enable*HttpSession(Servlet)或 @Enable*WebSession(响应式)来控制 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 | Spring MVC |
spring-boot-starter-websocket | WebSocket | Servlet 应用程序的 WebSocket |
spring-boot-starter-webflux | HTTP, WebSocket | Spring WebFlux |
spring-boot-starter-rsocket | TCP, WebSocket | 基于 Reactor Netty 的 Spring WebFlux |
6.1. GraphQL 模式
Spring GraphQL 应用程序在启动时需要定义的模式。默认情况下,您可以在 src/main/resources/graphql/** 下编写 ".graphqls" 或 ".gqls" 模式文件,Spring Boot 会自动捕获它们。您可以使用 spring.graphql.schema.locations 自定义位置,使用 spring.graphql.schema.file-extensions 自定义文件扩展名。
Tip:如果您希望 Spring Boot 在该位置的所有应用程序模块和依赖项中检测模式文件,可以将
spring.graphql.schema.locations设置为"classpath*:graphql/**/"(注意classpath*:前缀)。
在以下部分中,我们将考虑此示例 GraphQL 模式,定义了两种类型和两个查询:
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
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Tip:默认情况下,字段内省将允许在模式上进行内省,因为它是工具(如 GraphiQL)所必需的。如果您不希望公开有关模式的信息,可以通过将
spring.graphql.schema.introspection.enabled设置为false来禁用内省。
6.2. GraphQL RuntimeWiring
GraphQL Java 的 RuntimeWiring.Builder 可以用于注册自定义标量类型、指令、类型解析器、DataFetcher 等。您可以在 Spring 配置中声明 RuntimeWiringConfigurer bean,以访问 RuntimeWiring.Builder。Spring Boot 检测这些 bean,并将它们添加到 GraphQlSource 构建器。
然而,通常情况下,应用程序不会直接实现 DataFetcher,而是创建注解控制器。Spring Boot 会自动检测带有注解处理程序方法的 @Controller 类,并将它们注册为 DataFetcher。以下是我们问候查询的 @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 + "!";
}
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
6.3. Querydsl 和 QueryByExample 存储库支持
Spring Data 提供了对 Querydsl 和 QueryByExample 存储库的支持。Spring GraphQL 可以配置 Querydsl 和 QueryByExample 存储库作为 DataFetcher。
带有 @GraphQlRepository 注解并扩展以下之一的 Spring Data 存储库:
QuerydslPredicateExecutorReactiveQuerydslPredicateExecutorQueryByExampleExecutorReactiveQueryByExampleExecutor
被 Spring Boot 检测并视为匹配顶级查询的 DataFetcher 候选。
6.4. 传输
6.4.1. HTTP 和 WebSocket
GraphQL HTTP 端点默认为 HTTP POST /graphql。可以使用 spring.graphql.path 自定义路径。
Note:HTTP 端点对于 Spring MVC 和 Spring WebFlux 都由带有
@Order为0的RouterFunctionbean 提供。如果您定义自己的RouterFunctionbean,可能需要添加适当的@Order注解,以确保它们正确排序。
GraphQL WebSocket 端点默认情况下是关闭的。要启用它:
- 对于 Servlet 应用程序,添加 WebSocket 启动器
spring-boot-starter-websocket - 对于 WebFlux 应用程序,不需要额外的依赖项
- 对于两者,必须设置
spring.graphql.websocket.path应用程序属性
Spring GraphQL 提供了 Web 拦截模型。这对于从 HTTP 请求头中检索信息并将其设置在 GraphQL 上下文中,或从同一上下文中获取信息并将其写入响应头中非常有用。使用 Spring Boot,您可以声明一个 WebInterceptor bean,以便将其注册到 Web 传输中。
Spring MVC 和 Spring WebFlux 支持 CORS(跨源资源共享)请求。CORS 是 GraphQL 应用程序的 Web 配置的重要部分,这些应用程序是从浏览器中使用不同域访问的。
Spring Boot 支持 spring.graphql.cors.* 命名空间下的许多配置属性;以下是一个简短的配置示例:
Properties
spring.graphql.cors.allowed-origins=https://example.org
spring.graphql.cors.allowed-methods=GET,POST
spring.graphql.cors.max-age=1800s6.4.2. RSocket
RSocket 也被支持作为传输,通过 WebSocket 或 TCP。一旦配置了 RSocket 服务器,我们可以使用 spring.graphql.rsocket.mapping 在特定路由上配置我们的 GraphQL 处理程序。例如,将该映射配置为 "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();
}
}然后发送请求:
Java
Mono<Book> book = this.graphQlClient.document("{ bookById(id: \"book-1\"){ id name pageCount author } }")
.retrieve("bookById")
.toEntity(Book.class);1
2
3
2
3
6.5. 异常处理
Spring GraphQL 使应用程序能够注册一个或多个 Spring DataFetcherExceptionResolver 组件,这些组件将按顺序调用。异常必须解析为 graphql.GraphQLError 对象列表,请参阅 Spring GraphQL 异常处理文档。Spring Boot 会自动检测 DataFetcherExceptionResolver bean,并将它们注册到 GraphQlSource.Builder。
6.6. GraphiQL 和模式打印机
Spring GraphQL 提供了帮助开发人员在消费或开发 GraphQL API 时的基础设施。
Spring GraphQL 附带了一个默认的 GraphiQL 页面,默认情况下公开在 "/graphiql"。此页面默认情况下是禁用的,可以使用 spring.graphql.graphiql.enabled 属性打开。许多公开此页面的应用程序会更喜欢自定义构建。默认实现在开发期间非常有用,这就是为什么它会在开发期间使用 spring-boot-devtools 自动公开。
您还可以选择在启用 spring.graphql.schema.printer.enabled 属性时以文本格式在 /graphql/schema 公开 GraphQL 模式。
7. Spring HATEOAS
如果您开发了一个使用超媒体的 RESTful API,Spring Boot 提供了适用于大多数应用程序的 Spring HATEOAS 自动配置。自动配置替换了使用 @EnableHypermediaSupport 的需要,并注册了一些 bean 以简化构建基于超媒体的应用程序,包括 LinkDiscoverers(用于客户端支持)和配置为正确编组响应的 ObjectMapper。ObjectMapper 通过设置各种 spring.jackson.* 属性进行自定义,或者如果存在,通过 Jackson2ObjectMapperBuilder bean 进行自定义。
您可以使用 @EnableHypermediaSupport 来控制 Spring HATEOAS 的配置。请注意,这样做会禁用前面描述的 ObjectMapper 自定义。
Warning:
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-type 设置为 false,并定义 HypermediaMappingInformation 或 HalConfiguration 以配置 Spring HATEOAS 以满足应用程序及其客户端的需求。
8. 接下来阅读什么
现在,您应该对如何使用 Spring Boot 开发 Web 应用程序有了很好的理解。接下来的几节将描述 Spring Boot 如何与各种数据技术、消息传递系统和其他 IO 功能集成。您可以根据应用程序的需求选择其中任何一个。