26. 日志

Spring Boot 为所有的内部日志使用 Commons Logging,但是开放底层日志实现。为 Java Util Logging、Log4J2 和 Logback 提供了默认配置。在不同情况下,日志记录器都预先配置为使用控制台输出,同时还提供可选的文件输出。

默认情况下,如果你使用启动器,则使用 Logback 进行日志记录。还包括适当的 Logback 路由,以确保使用 Java Util Logging、Commons Logging、Log4J 或 SLF4J 的依赖库都能正常工作。

[Tip] Tip

对于 Java 来说,有很多日志框架可用。不要担心上面的列表看上去混乱。一般来说,不需要更改日志依赖关系,Spring Boot 在默认状态下就能工作的很好。

26.1 日志格式

Spring Boot 默认的日志输出类似于以下示例:

2014-03-05 10:57:51.112  INFO 45469 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/7.0.52
2014-03-05 10:57:51.253  INFO 45469 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2014-03-05 10:57:51.253  INFO 45469 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1358 ms
2014-03-05 10:57:51.698  INFO 45469 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean        : Mapping servlet: 'dispatcherServlet' to [/]
2014-03-05 10:57:51.702  INFO 45469 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]

输出下列项目:

  • 日期和时间:精确到毫秒,且易于排序。
  • 日志级别:ERROR, WARN, INFO, DEBUG, 或 TRACE。
  • 进程 ID。
  • --- 分隔符,用于区分实际日志信息开头。
  • 线程名称:包括在方括号中(控制台输出可能会被截断)。
  • 日志名称:通常是源 class 的类名(缩写)。
  • 日志信息。
[Note] Note

Logback 没有 FATAL 级别。它会映射到 ERROR。

26.2 控制台输出

默认的日志配置在写日志时会将它们回显到控制台。默认情况下,ERROR、WARN 和 INFO 级别的消息会被记录。你可以通过 --debug 标识启动应用程序来启用 “debug” 模式。

$ java -jar myapp.jar --debug
[Note] Note

你也可以在 application.properties 中指定 debug=true。

当启用 debug 模式时,一系列核心记录器(嵌入式容器、Hibernate 和 Spring Boot)被配置成输出更多信息。启用 debug 模式不会配置应用程序记录所有的 DEBUG 级别的消息。

或者,你可以通过 --trace 标识(或在 application.properties 设置 trace=true)启动应用程序来启用 “trace” 模式。这样做可以为一系列核心记录器(嵌入式容器、Hibernate schema generation 和所有 Spring portfolio)启用追踪日志记录。

26.2.1 颜色编码输出

如果你的终端支持 ANSI,则可以使用颜色输出来帮助可读性。你可以将 spring.output.ansi.enabled 设置为支持的值,以重写自动设置的值。

颜色编码是通过使用 %clr 转换字来配置的。在最简单的形式中,转换器根据日志级别对输出进行着色,如下面的示例所示:

%clr(%5p)

下表描述了日志级别到颜色的映射:

级别 颜色

FATAL

Red

ERROR

Red

WARN

Yellow

INFO

Green

DEBUG

Green

TRACE

Green

或者,你可以通过将其作为转换的选项指定颜色或样式来使用。例如,要使文本为黄色,请使用以下设置:

%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){yellow}

支持以下颜色和样式:

  • blue
  • cyan
  • faint
  • green
  • magenta
  • red
  • yellow

26.3 文件输出

默认情况下,Spring Boot 只记录到控制台,不写入日志文件。如果除了控制台输出之外,还需要写入日志文件,则需要设置 logging.file 或 logging.path 属性(例如,在 application.properties 中)。

下表展示了如何将 logging.* 属性一起使用:

表格 26.1. 日志属性

logging.file logging.path 示例 描述

(none)

(none)

 

仅控制台日志记录。

指定文件

(none)

my.log

写入指定的日志文件。名称可以是一个确切的位置或相对于当前目录。

(none)

指定目录

/var/log

将 spring.log 写入指定目录。名称可以是一个确切的位置或相对于当前目录。


日志文件在达到 10 MB 时切换,与控制台输出一样,默认情况下记录 ERROR、WARN 和 INFO 级别消息。使用 logging.file.max-size 属性可以改变大小限制。除非已设置 logging.file.max-history 属性,否则先前切换的文件将被无限期地存档。

[Note] Note

日志记录系统在应用程序生命周期中被提前初始化。因此,在通过 @PropertySource 注解加载的属性文件中找不到日志记录属性。

[Tip] Tip

日志记录属性与实际的日志记录基础结构无关。因此,特定的配置键(例如, logback.configurationFile 用于 Logback)不是由 Spring Boot管理的。

26.4 日志级别

所有 Spring Boot 支持的日志系统都可以在 Spring Environment 中设置级别(例如,在 application.properties 中),设置格式为 logging.level.<logger-name>=<level> ,其中 level 是 TRACE、DEBUG、INFO、WARN、ERROR、FATAL 或 OFF 中的一个。root 记录器可以通过使用 logging.level.root 进行配置。

下面的示例展示了 application.properties 中有可能的日志记录设置。

logging.level.root=WARN
logging.level.org.springframework.web=DEBUG
logging.level.org.hibernate=ERROR

26.5 自定义日志配置

可以通过在类路径上包含适当的库来激活各种日志记录系统,并可通过在类路径的根目录或在 Spring Environment 属性(logging.config)指定的位置提供合适的配置文件来进一步定制。

你还可以通过使用 org.springframework.boot.logging.LoggingSystem 系统属性强制 Spring Boot 使用特定的日志记录系统。这个值应该是 LoggingSystem 实现的完全限定类名。还可以使用 none 值完全禁用 Spring Boot 的日志配置。

[Note] Note

由于在 ApplicationContext 创建之前初始化了日志记录,因此无法从Spring @Configuration 文件中的 @PropertySources 控制日志记录。更改日志记录系统或完全禁用日志记录的唯一方法是通过系统属性。

根据日志记录系统,加载以下文件:

日志记录系统 定制

Logback

logback-spring.xml、logback-spring.groovy、logback.xml 或 logback.groovy

Log4j2

log4j2-spring.xml 或 log4j2.xml

JDK (Java Util Logging)

logging.properties

[Note] Note

在可能的情况下,我们建议你使用 -spring 变体来进行日志记录配置(例如,logback-spring.xml,而不是 logback.xml)。如果使用标准配置位置,Spring 无法完全控制日志初始化。

[Warning] Warning

使用 Java Util 日志记录存在已知的类加载问题,这会导致从可执行的 jar 运行时出现问题。我们建议你尽可能避免从可执行的 jar 中运行。

为了帮助定制,一些其它属性从 Spring Environment 传递到系统属性,如下表所述:

Spring Environment 系统属性 备注

logging.exception-conversion-word

LOG_EXCEPTION_CONVERSION_WORD

转换异常时使用的转换字。

logging.file

LOG_FILE

如果定义,它将在默认日志配置中使用。

logging.file.max-size

LOG_FILE_MAX_SIZE

最大日志文件大小(如果启用 LOG_FILE)。(仅支持默认的 Logback 设置)。

logging.file.max-history

LOG_FILE_MAX_HISTORY

要保存的存档日志文件的最大数量(如果启用 LOG_FILE)。(仅支持默认的 Logback 设置)。

logging.path

LOG_PATH

如果定义,它将在默认日志配置中使用。

logging.pattern.console

CONSOLE_LOG_PATTERN

在控制台上使用的日志模式(stdout)。(仅支持默认的 Logback 设置)。

logging.pattern.dateformat

LOG_DATEFORMAT_PATTERN

日志日期格式的附带模式。(仅支持默认的 Logback 设置)。

logging.pattern.file

FILE_LOG_PATTERN

在文件中使用的日志模式(如果启用 LOG_FILE)。(仅支持默认的 Logback 设置)。

logging.pattern.level

LOG_LEVEL_PATTERN

在呈现日志级别时使用的格式(默认 %5p)。(仅支持默认的 Logback 设置)。

PID

PID

当前进程ID(如果可能的话,当还没有定义为 OS 环境变量时发现)。

所有支持的日志记录系统在解析它们的配置文件时都可以查询系统属性。参见 spring-boot.jar 中的默认配置示例:

  • Logback
  • Log4j 2
  • Java Util logging
[Tip] Tip

如果希望在日志记录属性中使用占位符,则应使用 Spring Boot 的语法,而不是使用基础框架的语法。值得注意的是,如果使用 Logback,则应该使用 : 作为属性名称与其默认值之间的分隔符,而不使用 :-。

[Tip] Tip

你可以通过只重写 LOG_LEVEL_PATTERN (或 Logback 使用的 logging.pattern.level) 将 MDC 和其它 ad-hoc 内容添加到日志行。例如,如果你使用 logging.pattern.level=user:%X{user} %5p,则默认日志格式包含 "user" 的 MDC 条目,如果存在,如下面的示例说是。

2015-09-30 12:30:04.031 user:someone INFO 22174 --- [  nio-8080-exec-0] demo.Controller
Handling authenticated request

26.6 Logback 扩展

Spring Boot 包含一些 Logback 进行高级配置的扩展。可以在 logback-spring.xml 配置文件中使用这些扩展。

[Note] Note

因为标准 logback.xml 配置文件加载得太早,所以不能在其中使用扩展。你需要使用 logback-spring.xml 或定义 logging.config 属性。

[Warning] Warning

这些扩展不能用于 Logback 的 配置扫描 。如果你尝试这样做,则对配置文件进行更改会导致类似于以下记录之一的错误:

ERROR in ch.qos.logback.core.joran.spi.Interpreter@4:71 - no applicable action for [springProperty], current ElementPath is [[configuration][springProperty]]
ERROR in ch.qos.logback.core.joran.spi.Interpreter@4:71 - no applicable action for [springProfile], current ElementPath is [[configuration][springProfile]]

26.6.1 指定配置组配置

<springProfile> 标签允许你根据激活的 Spring 配置组可选地包含或排除配置的各个部分。在 <configuration> 元素中的任何地方都支持配置组部分。使用 name 属性指定哪个配置组接受配置。<springProfile> 标签可以包含一个简单的配置组名称(例如,'staging') 或一个配置组表达式。配置组表达式允许更复杂的配置逻辑来表示,例如 production & (eu-central | eu-west)。更多详细信息请参阅参考指南。下面的列表展示了三个示例配置组:

<springProfile name="staging">
	<!-- configuration to be enabled when the "staging" profile is active -->
</springProfile>

<springProfile name="dev | staging">
	<!-- configuration to be enabled when the "dev" or "staging" profiles are active -->
</springProfile>

<springProfile name="!production">
	<!-- configuration to be enabled when the "production" profile is not active -->
</springProfile>

26.6.2 环境属性

<springProperty> 标签允许你在 Spring Environment 中公开属性,以便在 Logback 中使用。如果你想在 Logback 配置中从 application.properties 文件中访问值,那么这样做可能会很有用。这个标签的工作方式类似于 Logback 的标准 <property> 标签。但是,不是指定直接值,而是指定属性的来源(来自 Environment)。如果需要将该属性存储在本地范围之外的其它地方,则可以使用 scope 属性。如果你需要返回值(如果该属性未设置在 Environment 中),则可以使用 defaultValue 属性。下面的示例展示了如何公开在 Logback 中使用的属性:

<springProperty scope="context" name="fluentHost" source="myapp.fluentd.host"
		defaultValue="localhost"/>
<appender name="FLUENT" class="ch.qos.logback.more.appenders.DataFluentAppender">
	<remoteHost>${fluentHost}</remoteHost>
	...
</appender>
[Note] Note

source 必须为指定的串格式(例如,my.property-name)。但是,属性可以使用松散规则添加到 Environment 中。

27. 开发 Web 应用程序

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

如果你还没开发过一个 Spring Boot web 应用程序,可以参考新手入门部分的 "Hello World!" 示例。

27.1 Spring Web MVC 框架

Spring Web MVC 框架(通常称为“Spring MVC”)是一个丰富的 “model view controller” web 框架。Spring MVC 允许你创建特殊的 @Controller 或 @RestController bean 来处理传入的 HTTP 请求。你的控制器中的方法通过使用 @RequestMapping 注解映射到 HTTP。

下面的代码展示了一个服务于 JSON 数据的典型的 @RestController:

@RestController
@RequestMapping(value="/users")
public class MyRestController {

	@RequestMapping(value="/{user}", method=RequestMethod.GET)
	public User getUser(@PathVariable Long user) {
		// ...
	}

	@RequestMapping(value="/{user}/customers", method=RequestMethod.GET)
	List<Customer> getUserCustomers(@PathVariable Long user) {
		// ...
	}

	@RequestMapping(value="/{user}", method=RequestMethod.DELETE)
	public User deleteUser(@PathVariable Long user) {
		// ...
	}

}

Spring MVC 是核心 Spring 框架的一部分,在参考文档中有详细的信息。spring.io/guides 上也有几个涵盖 Spring MVC 的指南。

27.1.1 Spring MVC 自动配置

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

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

  • 包含 ContentNegotiatingViewResolver 和 BeanNameViewResolver bean。
  • 静态资源服务的支持,包括对 WebJars 的支持(文档后面介绍)。
  • Converter, GenericConverter, 和 Formatter beans 的自动注册。
  • HttpMessageConverters 的支持 (文档后面介绍)。
  • MessageCodesResolver 的自动注册 (文档后面介绍)。
  • 静态 index.html 支持。
  • 自定义 Favicon 支持 (文档后面介绍)。
  • 自动运用 ConfigurableWebBindingInitializer bean (文档后面介绍)。

如果你想保持 Spring Boot MVC 的特性,并希望添加额外的 MVC 配置(拦截器、格式化程序、视图控制器和其它特性),则可以添加 WebMvcConfigurer 类型的 @Configuration 类,但不要使用 @EnableWebMvc。如果你希望提供 RequestMappingHandlerMapping、RequestMappingHandlerAdapter 或 ExceptionHandlerExceptionResolver 的自定义实例,则可以声明 WebMvcRegistrationsAdapter 实例来提供这些组件。

如果你想完全控制 Spring MVC,则可以添加使用 @EnableWebMvc 的 @Configuration 注解。

27.1.2 HttpMessageConverters

Spring MVC 使用 HttpMessageConverter 接口来转换 HTTP 请求和响应。包含了非常好的合理默认值。例如,对象可以自动转换为 JSON(使用 Jackson 库)或 XML(使用 Jackson XML 扩展,如果可能的话,当 Jackson XML 扩展不可用时使用 JAXB),默认情况下,字符串使用 UTF-8 编码。

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

import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.context.annotation.*;
import org.springframework.http.converter.*;

@Configuration
public class MyConfiguration {

	@Bean
	public HttpMessageConverters customConverters() {
		HttpMessageConverter<?> additional = ...
		HttpMessageConverter<?> another = ...
		return new HttpMessageConverters(additional, another);
	}

}

在上下文中存在的任何 HttpMessageConverter bean 都被添加到转换器列表中。你也可以以相同的方式重写默认转换器。

27.1.3 自定义 JSON 序列化和反序列化

如果你使用 Jackson 来序列化和反序列化 JSON 数据,则可能需要编写自己的 JsonSerializer 和 JsonDeserializer 类。自定义序列化程序通常通过模块注册到 Jackson,但是 Spring Boot 提供了一个替代的 @JsonComponent 注解,使得更容易直接注册到 Spring bean。

可以直接在 JsonSerializer 或 JsonDeserializer 实现中使用 @JsonComponent 注解。你也可以在包含序列化程序/反序列化程序的类中使用它作为内部类,如下面的示例所示:

import java.io.*;
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import org.springframework.boot.jackson.*;

@JsonComponent
public class Example {

	public static class Serializer extends JsonSerializer<SomeObject> {
		// ...
	}

	public static class Deserializer extends JsonDeserializer<SomeObject> {
		// ...
	}

}

ApplicationContext 中的所有 @JsonComponent bean 都会自动注册到 Jackson。因为 @JsonComponent 是用 @Component 元注解的,所有通常使用组件扫描规则。

Spring Boot 还提供了 JsonObjectSerializer 和 JsonObjectDeserializer 的基础类,这些类在序列化对象时为标准的 Jackson 版本提供有用的替代品。更多详细信息请参阅 JsonObjectSerializer和 JsonObjectDeserializer。

27.1.4 MessageCodesResolver

Spring MVC 有一个用于从绑定错误中渲染错误消息的错误代码生成策略:MessageCodesResolver。如果设置 spring.mvc.message-codes-resolver.format 属性为 PREFIX_ERROR_CODE 或 POSTFIX_ERROR_CODE,Spring Boot 将为你创建一个(详见 DefaultMessageCodesResolver.Format 中的枚举)。

27.1.5 静态内容

默认情况下,Spring Boot 从类路径或 ServletContext 的根目录中名为 /static(或 /public 或 /resources 或 /META-INF/resources)的目录中提供静态内容。它使用 Spring MVC 中的 ResourceHttpRequestHandler,以便通过自己的 WebMvcConfigurer 和重写 addResourceHandlers 方法来修改该行为。

在一个独立的 web 应用程序中,容器的默认 servlet 也被启用并作为返回,如果 Spring 决定不处理它,则从 ServletContext 的根提供服务内容。大多数情况下,这是不会发生的(除非你修改默认 MVC 配置),因为 Spring 总是可以通过 DispatcherServlet 来处理请求。

默认情况下,资源映射到 /**,但可以用 spring.mvc.static-path-pattern 属性进行调整。例如,将所有资源重新分配给 /resources/** 可以实现如下:

spring.mvc.static-path-pattern=/resources/**

你还可以通过使用 spring.resources.static-locations 属性(用目录位置列表替换默认值)来自定义静态资源位置。Servlet 上下文根路径,"/",也会自动添加到一个位置。

除了前面提到的标准静态资源位置之外,还有 Webjars 内容制造的特殊情况。任何带有 /webjars/** 路径的资源都是从 jar 文件中提供的,如果它们以 Webjars 格式打包。

[Tip] Tip

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

Spring Boot 还支持 Spring MVC 提供的高级资源处理功能,允许使用缓存中断静态资源或使用 Webjars 版本未知 URL 等例子。

若要为 Webjars 使用版本未知 URL,请添加 webjars-locator-core 依赖。然后声明你的 Webjar。以使用 jQuery 为例,添加 "/webjars/jquery/jquery.min.js" 结果到 "/webjars/jquery/x.y.z/jquery.min.js",其中 x.y.z 是 Webjar 版本。

[Note] Note

如果你使用 JBoss,则需要声明 webjars-locator-jboss-vfs 依赖来替换 webjars-locator-core。否则,所有 Webjars 解析为 404。

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

spring.resources.chain.strategy.content.enabled=true
spring.resources.chain.strategy.content.paths=/**
[Note] Note

在运行时,资源的链接在模版中被重写,这得益于为 Thymeleaf 和 FreeMarker 自动配置的 ResourceUrlEncodingFilter。在使用 JSP 时,你应该手动声明此过滤器。其它模版引擎目前不自动支持,但可以与自定义模版宏/助手和 ResourceUrlProvider 使用。

当动态加载资源时,例如,使用 JavaScript 模块加载时,重命名文件不是一个选项。这就是为什么其它策略也得到支持和结合。一个固定策略是在 URL 中添加静态版本字符串,而不是更改文件名,如下面的示例所示:

spring.resources.chain.strategy.content.enabled=true
spring.resources.chain.strategy.content.paths=/**
spring.resources.chain.strategy.fixed.enabled=true
spring.resources.chain.strategy.fixed.paths=/js/lib/
spring.resources.chain.strategy.fixed.version=v12

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

查看 ResourceProperties 获得更多支持的选项。

[Tip] Tip

这个特性已经在一个专门的博客文章和 Spring 框架的参考文档中进行了详尽的描述。

27.1.6 欢迎页

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

27.1.7 自定义图标

Spring Boot 在配置的静态内容位置和类路径的根中查找 favicon.ico(按顺序)。如果存在这样的文件,它将被自动用作应用程序的图标。

27.1.8 路径匹配和内容协商

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

Spring Boot 默认选择禁用后缀模式匹配,这意味着类似于 "GET /projects/spring-boot.json" 的请求将不能与 @GetMapping("/projects/spring-boot") 映射相匹配。这被认为是 Spring MVC 应用程序的最佳实践。这个特性过去主要用于 HTTP 客户端,而 HTTP 客户端没有发送正确的接收请求头;我们需要确保向客户端发送正确的内容类型。现今,内容协商更加可靠。

还有其它方法来处理 HTTP 客户端,它们不总是发送正确的接收请求头。为代替使用后缀匹配,我们可以使用查询参数来确保像 "GET /projects/spring-boot?format=json" 的被映射到 @GetMapping("/projects/spring-boot"):

spring.mvc.contentnegotiation.favor-parameter=true

# We can change the parameter name, which is "format" by default:
# spring.mvc.contentnegotiation.parameter-name=myparam

# We can also register additional file extensions/media types with:
spring.mvc.contentnegotiation.media-types.markdown=text/markdown

如果你理解这些注意事项,并且仍然希望应用程序使用后缀模式匹配,则需要以下配置:

spring.mvc.contentnegotiation.favor-path-extension=true

# You can also restrict that feature to known extensions only
# spring.mvc.pathmatch.use-registered-suffix-pattern=true

# We can also register additional file extensions/media types with:
# spring.mvc.contentnegotiation.media-types.adoc=text/asciidoc

27.1.9 ConfigurableWebBindingInitializer

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

27.1.10 模版引擎

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

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

  • FreeMarker
  • Groovy
  • Thymeleaf
  • Mustache
[Tip] Tip

如果可能的话,应该避免 JSP。当使用嵌入式 servlet 容器时,有几个已知的限制。

当使用默认配置的这些模板引擎时,你的模板将自动从 src/main/resources/templates 中拾取。

[Tip] Tip

根据你运行应用程序的方式,IntelliJ IDEA 指定不同的类路径。在 IDE 中从主方法运行应用程序会与使用 Maven 或 Gradle 或 从其打包的 jar 运行应用程序得到不同的指定。这可能导致 Spring Boot 无法找到类路径上的模版。如果你有这个问题,可以重新指定 IDE 中的类路径,以放置模块的类和资源。或者,你可以配置模版前缀来搜索类路径上的每个模版目录,如下:classpath*:/templates/.

27.1.11 错误处理

默认情况下,Spring Boot 提供一个 /error 映射以合理的方式处理所有错误,并将其注册为 servlet 容器中的全局错误页。对于机器客户端,它生成一个 JSON 响应,其中包含错误、HTTP 状态和异常消息的详细信息。对于浏览器客户端,有一个“白名单”错误视图,用 HTML 格式呈现相同的数据(定制它,添加一个解决错误的视图)。若要完全替换默认行为,可以实现 ErrorController 并注册该类型的 bean 定义,或者添加 ErrorAttributes 类型的 bean 来使用现有机制,但会替换内容。

[Tip] Tip

BasicErrorController 可以用作自定义 ErrorController 的基类。如果你想为新的内容类型添加一个处理程序(默认情况下,它是专门处理 text/html 并为其它内容提供一个返回),这尤其有用。为此,扩展 BasicErrorController,添加一个具有生成属性的 @RequestMapping 的公共方法,并创建一个新类型的 bean。

你还可以定义一个用 @ControllerAdvice 注解的类,来定制 JSON 文档以返回特定的控制器和/或异常类型,如下面的示例所示:

@ControllerAdvice(basePackageClasses = AcmeController.class)
public class AcmeControllerAdvice extends ResponseEntityExceptionHandler {

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

	private HttpStatus getStatus(HttpServletRequest request) {
		Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
		if (statusCode == null) {
			return HttpStatus.INTERNAL_SERVER_ERROR;
		}
		return HttpStatus.valueOf(statusCode);
	}

}

在前面的示例中,如果 YourException 由同一个包中定义为 AcmeController 的控制器抛出,则使用 CustomErrorType POJO 的 JSON 表示来替代 ErrorAttributes 表示。

自定义错误页

如果你想要为给定的状态代码显示自定义 HTML 错误页,可以将文件添加到 /error 文件夹中。错误页既可以是静态 HTML(也就是在任何静态资源文件夹下添加),也可以通过使用模版来构建。文件的名称应该是确切的状态吗或串联掩码。

例如,要将 404 映射到静态 HTML 文件,你的文件夹结构应该如下:

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

要使用 FreeMarker 模版映射所有 5xx,你的文件夹结构应该如下:

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

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

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
		return ...
	}

}

你还可以使用常规的 Spring MVC 特性,例如 @ExceptionHandler 方法 和 @ControllerAdvice。然后 ErrorController 抓取任何未处理的异常。

映射 Spring MVC 外部的错误页

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

@Bean
public ErrorPageRegistrar errorPageRegistrar(){
	return new MyErrorPageRegistrar();
}

// ...

private static class MyErrorPageRegistrar implements ErrorPageRegistrar {

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

}
[Note] Note

如果你用一个最终被过滤器处理的路径注册一个 ErrorPage(就像一些非 Spring web 框架,如 Jersey 和 Wicket 一样),那么过滤器必须显式地注册为一个错误调度器,如下面的例子所示:

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

请注意,默认的 FilterRegistrationBean 不包含错误分派器类型。

警告:当部署到 servlet 容器时,Spring Boot 使用其错误页面过滤器将具有错误状态的请求转发到适当的错误页。如果响应尚未提交,则只能将请求转发到正确的错误页。默认情况下,WebSphere Application Server 8.0 和稍后在成功完成 servlet 的服务方法时提交响应。你应该通过将 com.ibm.ws.webcontainer.invokeFlushAfterService 设置为false来禁用此行为。

27.1.12 Spring HATEOAS

如果你开发了一个使用超媒体的 RESTful API,Spring Boot 为 Spring HATEOAS 提供了自动配置,它可以很好地应用于大多数应用程序。自动配置替代了使用 @EnableHypermediaSupport 的必要性,并注册了一些 bean,以便于构建基于超媒体的应用程序,包括 LinkDiscoverers(用户客户端支持)和配置为正确响应封送到所需表示的 ObjectMapper。ObjectMapper 是通过设置不同的 spring.jackson.* 属性来定制的,或者如果存在的话,可以用 Jackson2ObjectMapperBuilder bean 来定制。

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

27.1.13 CORS 支持

跨域资源共享(CORS)是由大多数浏览器实现的 W3C 规范,它允许你灵活地指定什么样的跨域请求被授权,而不是使用一些不太安全和不太强大的方法,如 IFRAME 或 JSONP。

对于版本 4.2,Spring MVC 支持 CORS。在 Spring Boot 应用程序中使用控制器方法 CORS 配置与 @CrossOrigin 注解而不需要任何特定的配置。全局 CORS 配置可以通过注册 WebMvcConfigurer bean 与自定义 addCorsMappings(CorsRegistry) 方法来定义,如下面的示例所示:

@Configuration
public class MyConfiguration {

	@Bean
	public WebMvcConfigurer corsConfigurer() {
		return new WebMvcConfigurer() {
			@Override
			public void addCorsMappings(CorsRegistry registry) {
				registry.addMapping("/api/**");
			}
		};
	}
}

27.2 Spring WebFlux 框架

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

Spring WebFlux 有两个特点:函数式和基于注解。基于注解是一个非常接近 Spring MVC 模型,如下面的示例所示:

@RestController
@RequestMapping("/users")
public class MyRestController {

	@GetMapping("/{user}")
	public Mono<User> getUser(@PathVariable Long user) {
		// ...
	}

	@GetMapping("/{user}/customers")
	public Flux<Customer> getUserCustomers(@PathVariable Long user) {
		// ...
	}

	@DeleteMapping("/{user}")
	public Mono<User> deleteUser(@PathVariable Long user) {
		// ...
	}

}

“WebFlux.fn”,一个函数式变体,将路由配置与请求的实际处理分开,如下面的示例所示:

@Configuration
public class RoutingConfiguration {

	@Bean
	public RouterFunction<ServerResponse> monoRouterFunction(UserHandler userHandler) {
		return route(GET("/{user}").and(accept(APPLICATION_JSON)), userHandler::getUser)
				.andRoute(GET("/{user}/customers").and(accept(APPLICATION_JSON)), userHandler::getUserCustomers)
				.andRoute(DELETE("/{user}").and(accept(APPLICATION_JSON)), userHandler::deleteUser);
	}

}

@Component
public class UserHandler {

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

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

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

WebFlux 是 Spring Framework 的一部分,其参考文档中有详细的信息。

[Tip] Tip

你可以定义尽可能多的 RouterFunction bean 来模块化路由器的定义。如果需要应用优先级,可以对 bean 进行排序。

为了开始,在你的应用程序中添加 spring-boot-starter-webflux 模块。

[Note] Note

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

27.2.1 Spring WebFlux 自动配置

Spring Boot 为 Spring WebFlux 提供了自动配置,这与大多数应用程序都工作的很好。

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

  • 为 HttpMessageReader 和 HttpMessageWriter 实例配置编解码器(文档后面部分描述)
  • 静态资源的支持,包括对 WebJars 的支持(文档后面部分描述)。

如果你想保持 Spring Boot WebFlux 特性,并希望添加额外的 WebFlux 配置,你可以添加自己的 WebFluxConfigurer 类型的 @Configuration 类,但不要使用 @EnableWebFlux。

如果你想完全控制 Spring WebFlux,可以添加自己的 @Configuration 注解和 @EnableWebFlux。

27.2.2 使用 HttpMessageReaders 和 HttpMessageWriters 的 HTTP 编解码器

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

Spring Boot 通过使用 CodecCustomizer 实例进一步定制。例如,spring.jackson.* 配置关键字应用于 Jackson 编解码器。

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

import org.springframework.boot.web.codec.CodecCustomizer;

@Configuration
public class MyConfiguration {

	@Bean
	public CodecCustomizer myCodecCustomizer() {
		return codecConfigurer -> {
			// ...
		}
	}

}

你还可以利用 Boot 的自定义 JSON 序列化器和反序列化器。

27.2.3 静态内容

默认情况下,Spring Boot 从类路径中一个名为 /static(或 /public 或 /resources 或 /META-INF/resources)的目录中提供静态内容。它使用 Spring WebFlux 中的 ResourceWebHandler,这样你就可以通过添加自己的 WebFluxConfigurer 和重写 addResourceHandlers 方法来修改该行为。

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

spring.webflux.static-path-pattern=/resources/**

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

除了前面列出的标准静态资源位置之外,还对 Webjars 内容进行了特殊处理。任何带有路径 /webjars/** 的资源都是从 jar 文件中提供的,如果它们以 webjar 格式打包。

[Tip] Tip

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

27.2.4 模版引擎

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

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

  • FreeMarker
  • Thymeleaf
  • Mustache

当使用默认配置的这些模板引擎时,你的模板将自动从 src/main/resources/templates 中拾取。

27.2.5 错误处理

Spring Boot 提供了一个 WebExceptionHandler,它以合理的方式处理所有错误。它在处理顺序中的位置处于 WebFlux 提供的处理程序之前,WebFlux 提供的处理程序是最后考虑的。对于机器客户端,它生成一个 JSON 响应,其中包含错误、HTTP 状态和异常消息的详细信息。对于浏览器客户端,有一个“白名单”错误视图,用 HTML 格式呈现相同的数据。你还可以提供自己的 HTML 模板来显示错误(详见下一节)。

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

若要更改错误处理行为,可以实现 ErrorWebExceptionHandler 并注册该类型的 bean 定义。因为 WebExceptionHandler 是非常低级别的,Spring Boot 还提供了一个方便的 AbstractErrorWebExceptionHandler,允许你处理 WebFlux 函数式方式中的错误,如下面的示例所示:

public class CustomErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {

	// Define constructor here

	@Override
	protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {

		return RouterFunctions
				.route(aPredicate, aHandler)
				.andRoute(anotherPredicate, anotherHandler);
	}

}

对于更完整的描述,你可以直接写 DefaultErrorWebExceptionHandler 子类,并重写特定的方法。

自定义错误页

如果你想要为给定的状态代码显示自定义 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>

27.2.6 Web 过滤器

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

在过滤器的顺序很重要的情况下,它们可以实现 Ordered 或用 @Order 注解。Spring Boot 自动配置可以为你配置 web 过滤器。当它这样做时,将使用下表所示的顺序:

Web 过滤器 顺序

MetricsWebFilter

Ordered.HIGHEST_PRECEDENCE + 1

WebFilterChainProxy (Spring Security)

-100

HttpTraceWebFilter

Ordered.LOWEST_PRECEDENCE - 10

27.3 JAX-RS 和 Jersey

如果你喜欢使用 REST 端点的 JAX-RS 编程模型,可以使用可用的实现之一而不是 Spring MVC。Jersey 和 Apache CXF 的工作非常出色。CXF 要求你将其 Servlet 或 Filter 注册为应用程序上下文中的 @Bean。Jersey 有一些本地的 Spring 支持,所以我们也提供支持它在 Spring Boot 的自动配置,连同 starter 一起。

要开始使用 Jersey,将 spring-boot-starter-jersey 包含为依赖项,然后需要一个 ResourceConfig 类型的 @Bean,其中注册所有端点,如下面的示例所示:

@Component
public class JerseyConfig extends ResourceConfig {

	public JerseyConfig() {
		register(Endpoint.class);
	}

}
[Warning] Warning

Jersey 对扫描可执行文件的支持相当有限。例如,当运行可执行的 war 文件时,它不能扫描 WEB-INF/classes 中发现的包中的端点。为了避免这种限制,不应该使用 packages 方法,并且端点应该使用 register 方法单独注册,如前面的示例所示。

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

所有注册端点都应该是带有 HTTP 资源注解的 @Components(@GET和其它),如下面的示例所示:

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

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

}

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

默认情况下,Jersey 在 @Bean 中设置为 Servlet,其类型为 ServletRegistrationBean 并命名为 jerseyServletRegistration。默认情况下,servlet 是懒初始化的,但是你可以通过设置 spring.jersey.servlet.load-on-startup来自定义。你可以通过创建一个具体相同名称的来禁用或重写该 bean。还可以通过设置 spring.jersey.type=filter 来使用过滤器而不是 servlet(在这种情况下,替换或重写的 @Bean 是 jerseyFilterRegistration)。过滤器有一个 @Order,可以用 spring.jersey.filter.order 设置。servlet 和过滤器注册都以通过 spring.jersey.init.* 来指定初始化参数,以指定属性映射。

这有一个 Jersey 样例,你可以看到如何设置。

27.4 嵌入式 Servlet 容器支持

Spring Boot 包含对嵌入式 Tomcat、Jetty 和 Undertow 服务器的支持。大多数开发人员使用适当的启动器来获得完全配置的实例。默认情况下,嵌入式服务器监听端口 8080 上的 HTTP 请求。

[Warning] Warning

如果你选择在 CentOS 上使用Tomcat,请注意,默认情况下,临时目录用于存储编译后的 JSP、文件上传等。在运行应用程序时,可能删除 tmpwatch 目录,从而导致故障。为了避免这种行为,你可能希望自定义 tmpwatch 配置,以便不删除 tomcat.* 目录或配置 server.tomcat.basedir ,使得嵌入式的 Tomcat 使用不同的位置。

27.4.1 Servlets、过滤器和监听器

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

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

任何一个 Servlet、Filter 或 servlet *Listener 实例都是一个 Spring bean,它是用嵌入式的容器注册的。如果你想在配置期间从 application.properties 中引用一个值,这可能特别方便。

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

如果基于约定的映射不够灵活,你可以使用 ServletRegistrationBean、FilterRegistrationBean 和 ServletListenerRegistrationBean 类来进行完全控制。

Spring Boot 具有许多可以定义过滤器 bean 的自动配置。这里有几个过滤器的例子和它们各自的顺序(低阶值意味着更高的优先级):

Servlet 过滤器 顺序

OrderedCharacterEncodingFilter

Ordered.HIGHEST_PRECEDENCE

WebMvcMetricsFilter

Ordered.HIGHEST_PRECEDENCE + 1

ErrorPageFilter

Ordered.HIGHEST_PRECEDENCE + 1

HttpTraceFilter

Ordered.LOWEST_PRECEDENCE - 10

通常让过滤器 bean 无序是安全。

如果需要特定的顺序,则应避免配置在 Ordered.HIGHEST_PRECEDENCE 中读取请求体的过滤器,因为它可能违背应用程序的字符编码配置。如果 servlet 过滤器包装请求,则应该配置一个小于或等于 FilterRegistrationBean.REQUEST_WRAPPER_FILTER_MAX_ORDER 的顺序。

27.4.2 Servlet 上下文初始化

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

如果需要在 Spring Boot 应用程序中执行 servlet 上下文初始化,则应该注册一个实现 org.springframework.boot.web.servlet.ServletContextInitializer 接口的 bean。单机 onStartup 方法提供对 ServletContext 的访问,如果需要,可以很容易地将其用作现有 WebApplicationInitializer 的适配器。

扫描 Servlets、过滤器和监听器

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

[Tip] Tip

@ServletComponentScan 在一个独立的容器中没有作用,而是使用容器的内置发现机制来代替。

27.4.3 ServletWebServerApplicationContext

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

[Note] Note

通常你不需要知道这些实现类。大多数应用程序都是自动配置的,在你预期上创建合适的 ApplicationContext 和 ServletWebServerFactory。

27.4.4 自定义嵌入式 Servlet 容器

公共 servlet 容器设置可以通过使用 Spring Environment 属性来配置。通常,你将在 application.properties 文件中定义属性。

常用服务器设置包括:

  • 网络设置:监听传入 HTTP 请求(server.port)端口,绑定到 server.address 的接口地址,等等。
  • 会话设置:会话是否持久(server.servlet.session.persistence)、会话超时(server.servlet.session.timeout)、会话数据的位置(server.servlet.session.store-dir)和会话 cookie 配置(server.servlet.session.cookie.*)。
  • 错误管理:错误页的位置(server.error.path)等。
  • SSL
  • HTTP 压缩

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

[Tip] Tip

完整列表详见 ServerProperties 类。

编程方式定制

如果你需要以编程方式配置嵌入式 servlet 容器,则可以注册一个实现 WebServerFactoryCustomizer 接口的 Spring bean。WebServerFactoryCustomizer 提供对 ConfigurableServletWebServerFactory 的访问,其中包括许多自定义设置程序方法,下面的示例展示了以编程方式设置端口:

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

@Component
public class CustomizationBean implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {

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

}
[Note] Note

TomcatServletWebServerFactory、JettyServletWebServerFactory 和 UndertowServletWebServerFactory 是 ConfigurableServletWebServerFactory 的专用变体,它们分别为 Tomcat、Jetty 和 Undertow 具有额外的自定义设置器方法。

直接配置 ConfigurableServletWebServerFactory

如果前面的定制技术太有限,你可以自己注册 TomcatServletWebServerFactory、JettyServletWebServerFactory 或 UndertowServletWebServerFactory bean。

@Bean
public ConfigurableServletWebServerFactory webServerFactory() {
	TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
	factory.setPort(9000);
	factory.setSessionTimeout(10, TimeUnit.MINUTES);
	factory.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/notfound.html"));
	return factory;
}

设置器用于许多配置选项。还提供了几种保护方法“钩子”,如果你需要做一些更异乎寻常的事情。更多详情请参阅源代码文档。

27.4.5 JSP 限制

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

  • 使用 Jetty 和 Tomcat 时,如果你使用 war 包,它应该工作。一个可执行的 war 将在 java -jar 启动时运行,并且也可以部署到任何标准容器中。使用可执行 jar 时不支持 JSP。
  • Undertow 不支持 JSP。
  • 创建自定义 error.jsp 页面不会重写错误处理的默认视图。应该使用自定义错误页。

这有一个JSP 示例,这样你就可以看到如何设置。

28. 安全

如果 Spring Security 在类路径上,那么默认情况下 web 应用程序将得到安全保护。Spring Boot 依赖于 Spring Security 的内容协商策略来确定是否使用 httpBasic 或 formLogin。若要向 web 应用程序添加方法级安全性,还可以使用所需的设置添加 @EnableGlobalMethodSecurity。附加信息可以在 Spring Security 参考指南中找到。

默认 UserDetailsService 有一个用户。用户名是 user,密码是随机的,并且在应用程序启动时在 INFO 级别上打印,如下面的示例所示:

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

如果微调日志记录配置,请确保将 org.springframework.boot.autoconfigure.security 类型设置为日志 INFO 级别消息。否则,将不打印默认密码。

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

在 web 应用程序中默认的基本特征是:

  • 一个 UserDetailsService(或在 WebFlux 应用程序中的 ReactiveUserDetailsService)bean 和内存中的一个用户和一个生成密码的单个用户(参见 SecurityProperties.User 属性的用户)。
  • 对于整个应用程序,基于表单的登录或 HTTP 基本安全性(取决于内容类型)(如果执行器位于类路径上,包括执行器端点)。
  • 用于发布验证事件的 DefaultAuthenticationEventPublisher。

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

28.1 MVC 安全

默认安全配置时在 SecurityAutoConfiguration 和 UserDetailsServiceAutoConfiguration 中实现的。SecurityAutoConfiguration 为 web 安全导入 SpringBootWebSecurityConfiguration、UserDetailsServiceAutoConfiguration 配置身份验证,这在 非 web 应用程序中也是相关的。若要完全关闭默认 web 应用程序安全配置,可以添加 WebSecurityConfigurerAdapter 类型的 bean(这样做不会禁用 UserDetailsService 配置或执行器的安全性)。

若要关闭 UserDetailsService 配置,可以添加一个类型为 UserDetailsService、AuthenticationProvider 或 AuthenticationManager 的 bean。在 Spring Boot 示例中有几个安全的应用程序,这可以让你开始使用常见的用例。

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

28.2 WebFlux 安全

类似于 Spring MVC 应用程序,你可以通过添加 spring-boot-starter-security 依赖来确保 WebFlux 应用程序的安全性。默认安全配置在 ReactiveSecurityAutoConfiguration 和 UserDetailsServiceAutoConfiguration 中实现。ReactiveSecurityAutoConfiguration 为 web 安全导入 WebFluxSecurityConfiguration、UserDetailsServiceAutoConfiguration 配置身份验证,这在非 web 应用程序中也是相关的。若要完全关闭默认 web 应用程序安全配置,可以添加 WebFilterChainProxy 类型的 bean(这样做不会禁用 UserDetailsService 配置或执行器的安全性)。

若要关闭 UserDetailsService 配置,可以添加 ReactiveUserDetailsService 或 ReactiveAuthenticationManager 的 bean。

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

PathRequest 可用于创建常用的资源中的 ServerWebExchangeMatcher。

例如,你可以通过添加一些类似的内容来自定义安全配置:

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

28.3 OAuth2

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

28.3.1 客户端

如果你的类路径上有 spring-security-oauth2-client,那么可以利用一些自动配置来方便地建立 OAuth2/Open ID 连接客户端。这种配置利用了 OAuth2ClientProperties 下的属性。

你可以在 spring.security.oauth2.client.provider 前缀下注册多个 OAuth2/OpenID 连接提供者,如下面的示例所示:

spring.security.oauth2.client.provider.my-oauth-provider.authorization-uri=http://my-auth-server/oauth/authorize
spring.security.oauth2.client.provider.my-oauth-provider.token-uri=http://my-auth-server/oauth/token
spring.security.oauth2.client.provider.my-oauth-provider.user-info-uri=http://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=http://my-auth-server/token_keys
spring.security.oauth2.client.provider.my-oauth-provider.user-name-attribute=name

对于支持 OpenID 连接发现的 OpenID 连接提供者,可以进一步简化配置。提供者需要配置一个 issuer-uri,这个 URI 是它声明作为发行者标识符的。例如,如果提供的 issuer-uri 是 "https://example.com",则 OpenID 提供者配置请求将被发送到 "https://example.com/.well-known/openid-configuration"。预期结果是 OpenID 提供者配置响应。下面的示例展示如何使用 issuer-uri 配置 OpenID 连接提供者:

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

OpenID 连接登录客户端注册

你可以在 spring.security.oauth2.client.registration.login 前缀下注册多个 Open ID 连接客户端,如下面的示例所示:

spring.security.oauth2.client.registration.login.my-client-1.client-id=abcd
spring.security.oauth2.client.registration.login.my-client-1.client-secret=password
spring.security.oauth2.client.registration.login.my-client-1.client-name=Client for user scope
spring.security.oauth2.client.registration.login.my-client-1.provider=my-oauth-provider
spring.security.oauth2.client.registration.login.my-client-1.scope=user
spring.security.oauth2.client.registration.login.my-client-1.redirect-uri=http://localhost:8080/login/oauth2/code/my-client-1
spring.security.oauth2.client.registration.login.my-client-1.client-authentication-method=basic
spring.security.oauth2.client.registration.login.my-client-1.authorization-grant-type=authorization_code

spring.security.oauth2.client.registration.login.my-client-2.client-id=abcd
spring.security.oauth2.client.registration.login.my-client-2.client-secret=password
spring.security.oauth2.client.registration.login.my-client-2.client-name=Client for email scope
spring.security.oauth2.client.registration.login.my-client-2.provider=my-oauth-provider
spring.security.oauth2.client.registration.login.my-client-2.scope=email
spring.security.oauth2.client.registration.login.my-client-2.redirect-uri=http://localhost:8080/login/oauth2/code/my-client-2
spring.security.oauth2.client.registration.login.my-client-2.client-authentication-method=basic
spring.security.oauth2.client.registration.login.my-client-2.authorization-grant-type=authorization_code

默认情况下,Spring Security 的 OAuth2LoginAuthenticationFilter 仅处理匹配 /login/oauth2/code/* 的 URL。如果要自定义 redirect-uri 以使用不同的模式,则需要提供配置来处理该自定义模式。例如,对于 servlet 应用程序,你可以添加类似于以下内容的 WebSecurityConfigurerAdapter:

public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			.authorizeRequests()
				.anyRequest().authenticated()
				.and()
			.oauth2Login()
				.redirectionEndpoint()
					.baseUri("/custom-callback");
	}
}

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

OpenID 授权代码客户端注册

你可以在 spring.security.oauth2.client.registration.authorization-code 前缀下注册多个 Open ID authorization_code 客户端,如下面的示例所示:

spring.security.oauth2.client.registration.authorization-code.my-client-1.client-id=abcd
spring.security.oauth2.client.registration.authorization-code.my-client-1.client-secret=password
spring.security.oauth2.client.registration.authorization-code.my-client-1.client-name=Client for user scope
spring.security.oauth2.client.registration.authorization-code.my-client-1.provider=my-oauth-provider
spring.security.oauth2.client.registration.authorization-code.my-client-1.scope=user
spring.security.oauth2.client.registration.authorization-code.my-client-1.redirect-uri=http://my-redirect-uri.com
spring.security.oauth2.client.registration.authorization-code.my-client-1.client-authentication-method=basic
spring.security.oauth2.client.registration.authorization-code.my-client-1.authorization-grant-type=authorization_code

spring.security.oauth2.client.registration.authorization-code.my-client-2.client-id=abcd
spring.security.oauth2.client.registration.authorization-code.my-client-2.client-secret=password
spring.security.oauth2.client.registration.authorization-code.my-client-2.client-name=Client for email scope
spring.security.oauth2.client.registration.authorization-code.my-client-2.provider=my-oauth-provider
spring.security.oauth2.client.registration.authorization-code.my-client-2.scope=email
spring.security.oauth2.client.registration.authorization-code.my-client-2.redirect-uri=http://my-redirect-uri.com
spring.security.oauth2.client.registration.authorization-code.my-client-2.client-authentication-method=basic
spring.security.oauth2.client.registration.authorization-code.my-client-2.authorization-grant-type=authorization_code

对于公共提供者的 OAuth2 客户端注册

对于常见的 OAuth2 和 OpenID 提供者,包括 Google、Github、Facebook 和 Okta,我们提供了提供者的缺省设置(分别是 google、github、facebook 和 okta)。

如果不需要自定义这些提供者,可以将提供者属性设置为需要推断默认值的提供者属性。此外,如果客户端的 ID 与默认支持的提供者匹配,Spring Boot 也会推断出这一点。

换句话说,下面的示例中的两个配置使用 Google 提供者:

spring.security.oauth2.client.registration.login.my-client.client-id=abcd
spring.security.oauth2.client.registration.login.my-client.client-secret=password
spring.security.oauth2.client.registration.login.my-client.provider=google

spring.security.oauth2.client.registration.login.google.client-id=abcd
spring.security.oauth2.client.registration.login.google.client-secret=password

28.3.2 资源服务

如果在类路径上有 spring-security-oauth2-resource-server,Spring Boot 可以设置 OAuth2 资源服务,只要指定了 JWK Set URI 或 OIDC Issuer URI,如下面的示例所示:

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/

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

或者,你可以为 servlet 应用程序定义自己的 JwtDecoder bean,或者为响应式应用程序定义一个 ReactiveJwtDecoder。

28.3.3 授权服务

目前,Spring Security 不支持实现 OAuth 2.0 授权服务。然而,这个功能可以从 Spring Security OAuth 项目获得,它最终将被 Spring Security 完全取代。在此之前,你可以使用 spring-security-oauth2-autoconfigure 模块轻松设置 OAuth 2.0 授权服务。查看其文档以获取指令。

28.4 执行器安全

出于安全目的,默认情况下禁用除 /health 和 /info 以外的所有执行器。可以使用 management.endpoints.web.exposure.include 属性来启用执行器。

如果 Spring Security 在类路径上,并且没有其它 WebSecurityConfigurerAdapter,则执行器通过 Spring Boot 自动配置进行安全保护。如果你定义了自定义 WebSecurityConfigurerAdapter,Spring Boot 自动配置将退出,你将完全控制执行器访问规则。

[Note] Note

在设置 management.endpoints.web.exposure.include 之前,确保所暴露的执行器不包含敏感信息和/或通过将它们放置在防火墙或类似 Spring Security 之类的情况下进行安全保护。

28.4.1 跨站点请求伪造保护

由于 Spring Boot 依赖于 Spring Security 的默认值,因此默认情况下将启用 CSRF 保护。这意味着在使用默认安全配置时,需要一个 POST(关机和记录器端点)、PUT 或 DELETE 的执行端点将得到 403 的禁用错误。

[Note] Note

我们建议仅在创建非浏览器客户端使用的服务时完全禁用 CSRF 保护。

关于 CSRF 保护的附加信息可以在 Spring Security 参考指南中找到。