Spring Boot学习笔记四(Spring Boot特性)

23 SpringApplication

SpringApplication提供了便捷的方式去引导Sprng应用程序从main()方法启动。大多数情况下,你只需要委托给静态SpringApplication.run方法。

public static void main(String[] args) {
    SpringApplication.run(MySpringConfiguration.class, args);
}

当你的应用程序启动的时候,你可以看到下面的熟悉界面:

.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::   v2.0.0.BUILD-SNAPSHOT

2013-07-31 00:08:16.117  INFO 56603 --- [           main] o.s.b.s.app.SampleApplication            : Starting SampleApplication v0.1.0 on mycomputer with PID 56603 (/apps/myapp.jar started by pwebb)
2013-07-31 00:08:16.166  INFO 56603 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@6e5a8246: startup date [Wed Jul 31 00:08:16 PDT 2013]; root of context hierarchy
2014-03-04 13:09:54.912  INFO 41370 --- [           main] .t.TomcatEmbeddedServletContainerFactory : Server initialized with port: 8080
2014-03-04 13:09:56.501  INFO 41370 --- [           main] o.s.b.s.app.SampleApplication            : Started SampleApplication in 2.992 seconds (JVM running for 3.658)

默认会输出INFO的日志信息,包括一些相关的启动的细节,例如启动应用程序的用户。

23.1 启动失败

如果你的应用程序启动失败,注册的FailureAnalyzers能提供专业的错误信息以及具体的行动去修复这个问题。实际上,如果你在8080端口启动web应用程序,并且这个端口已经被占用,你会看到类似下面的语句:

***************************
APPLICATION FAILED TO START
***************************

Description:

Embedded servlet container failed to start. Port 8080 was already in use.

Action:

Identify and stop the process that's listening on port 8080 or configure this application to listen on another port.

注:Spring Boot提供了众多的FailureAnalyzer实现,你可以很容易地加入到你的项目。

如果没有错误分析工具去处理这个异常,你仍然可以通过显示完整的自动配置报告来帮助你更好地分析错误。为了实现日志分析,你需要为org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer开启debug属性和日志记录。

实际上,如果你使用java -jar运行你的应用程序,你可以这样开启debug模式:

$ java -jar myproject-0.0.1-SNAPSHOT.jar --debug

23.2 自定义Banner

通过在类路径(比如放到resources文件夹)添加banner.txt文件或者在这样的文件位置上设置banner.location,可以改变启动输出的banner。如果文件有特殊编码,你可以设置banner.charset(默认为UTF-8)。除了可以加入txt文件外,你也可以加入banner.gifbanner.jpgbanner.png等图片文件,或者通过设置banner.image.location属性来实现。图片会被转换成一个ASCII艺术表现并打印在文本banner之上。

在banner.txt里,你可以使用以下的任意占位符:

变量 描述
${application.version} 声明在MANIFEST.MF中的应用程序版本号。例如,Implementation-Version: 1.0被声明为1.0
${application.formatted-version} 声明在MANIFEST.MF中显示被格式化的应用版本号(用括号包裹并且前缀为v)。例如(v1.0)
${spring-boot.version} Spring Boot版本号。例如2.0.0.BUILD-SNAPSHOT
${spring-boot.formatted-version} 显示被格式化的Spring Boot版本号(括号包裹并以V为前缀)。例如(v2.0.0.BUILD-SNAPSHOT)
${Ansi.NAME} (or ${AnsiColor.NAME}, ${AnsiBackground.NAME}, ${AnsiStyle.NAME}) NAME是ANSI的转义编码。详情请看AnsiPropertySource 
${application.title} 声明在MANIFEST.MF中应用程序的标题。例如,Implementation-Title: MyApp并打印为MyApp

示例 banner.txt:

${spring-boot.version}:1.0
${application.title}:MyApp

注:如果你想以编程方式生成banner,可以使用SpringApplication.setBanner(…​)方法。继承org.springframework.boot.Banner接口,并实现自己的printBanner()方法。

如果日志信息在在console通过System.out输出,你也可以使用spring.main.banner-mode属性控制banner使用配置的日志记录(log)输出或关闭日志(off)。

输出的banner会在springBootBanner名下被注册为一个单独的bean。

注:如果你想禁用banner,可以设置模式为off。

spring:
    main:
        banner-mode: "off"

23.3 自定义应用程序

如果SpringApplication默认的属性不能满足你的需求,你可以创建一个本地实例并进行自定义。例如,关闭banner,你可以这样实现:

public static void main(String[] args) {
    SpringApplication app = new SpringApplication(MySpringConfiguration.class);
    app.setBannerMode(Banner.Mode.OFF);
    app.run(args);
}

注:通过构造器传递给应用程序的参数是spring beans的配置源。大多数情况下,它们是@Configuration注解的类的引用,但是也可以是XML配置的引用或扫描到的包的引用。尽可能地使用application.properties文件来配置SpringApplication。

尽可能地使用application.properties文件来配置SpringApplication

23.4 流畅的构建API

如果你需要构建ApplicationContext的层次结构(混合父/子关系的上下文),或者你只是更喜欢使用一个流畅的构建API,你可以使用SpringApplicationBuilder

SpringApplicationBuilder允许你将多个方法调用绑定在一起,允许你创建一个包括父和子方法的层次结构。例如:

new SpringApplicationBuilder()
        .sources(Parent.class)
        .child(Application.class)
        .bannerMode(Banner.Mode.OFF)
        .run(args);

注:当创建ApplicationContext层次结构是有些限制,例如Web组件必须包含在子上下文中,并且同样的Environment会同时被父和子上下文使用。

23.5 Application事件和监听器

除了一些常用的Spring框架事件,例如ContextRefreshedEventSpringApplication还提供了一些额外的应用程序事件。

注:某些事件实际上在ApplicationContext创建之前就已经触发了,所以你不能在@Bean上注册监听器。你可以通过SpringApplication.addListeners(…​)SpringApplicationBuilder.listeners(…​)等方法注册事件监听器。如果你想忽略应用程序创建的方式并自动注册这些监听器,你可以在项目中加入META-INF/spring.factories文件,使用org.springframework.context.ApplicationListener key来引用你的监听器。

org.springframework.context.ApplicationListener=com.example.project.MyListener

当你启动应用程序,应用事件会按照以下顺序发送:

  1. 在运行开始,任何处理之前但是除了登记事件监听器和初始化模块外,一个ApplicationStartingEvent会被发送。
  2. 在context创建之前,当Environment被应用于已知的上下文,一个ApplicationEnvironmentPreparedEvent会被发送。
  3. 在启动刷新之前,加载类定义之后,ApplicationPreparedEvent会被发送。
  4. 在刷新之后,任何有关联的回调都被处理完表明应用程序已经准备好服务请求时,一个ApplicationReadyEvent会被发送。
  5. 如果启动的时候发送异常,一个ApplicationFailedEvent会被发送。

注:你不是经常使用应用事件,但是知道他们的存在是便利的。Spring Boot使用事件来处理各种各样的任务。

23.6 Web环境

一个SpringApplication会试图为你创建正确类型的ApplicationContext。默认地,使用AnnotationConfigApplicationContextAnnotationConfigEmbeddedWebApplicationContext取决于你是否正在开发web应用。

用于确定一个web环境的算法是相当简单的(基于一些类的存在)。如果你需要覆盖默认行为,可以实现setWebEnvironment(boolean webEnvironment)。通过调用setApplicationContextClass(…​),你完全可以控制ApplicationContext的类型。

注:当在JUnit测试类中使用SpringApplication,调用setWebEnvironment(false)通常是可取的。

23.7 访问应用程序参数

如果你需要访问被传递到SpringApplication.run(…​)的应用程序参数,你可以注入一个org.springframework.boot.ApplicationArguments类。ApplicationArguments接口提供访问原始的String[]参数以及被解析的optionnon-option参数。

import org.springframework.boot.*
import org.springframework.beans.factory.annotation.*
import org.springframework.stereotype.*

@Component
public class MyBean {

    @Autowired
    public MyBean(ApplicationArguments args) {
        boolean debug = args.containsOption("debug");
        List<String> files = args.getNonOptionArgs();

        // if run with "--debug logfile.txt" debug=true, files=["logfile.txt"]
    }

}

注:Spring Boot同时会在Spring环境中注册一个CommandLinePropertySource。这允许你使用@Value注解注入一个单一的应用程序参数。

23.8 使用ApplicationRunner或.CommandLineRunner

一旦SpringApplication已经被启动,如果你需要运行一些特殊的代码,你可以实现ApplicationRunner或者CommandLineRunner接口。在SpringApplication.run(…​)完成之前调用的所有的接口,以同样的方式工作以及提供唯一的run方法。CommandLineRunner接口提供把应用程序参数作为一个简单的字符串数组访问的方法,然而ApplicationRunner使用ApplicationArguments接口来探讨实现方法。

import org.springframework.boot.*
import org.springframework.stereotype.*

@Component
public class MyBean implements CommandLineRunner {

    public void run(String... args) {
        // Do something...
    }

}

此外,如果好几个CommandLineRunnerApplicationRunner bean类必须按照特定的顺序回调,你还可以实现org.springframework.core.Ordered接口或使用org.springframework.core.annotation.Order注解。

23.9 Application退出

每个SpringApplication都会在JVM中注册一个关闭的钩子来确保ApplicationContext能被优雅地关掉。所有标准的Spring风格的回调(例如DisposableBean接口,或者@PreDestroy注解)都能被使用。

另外,如果希望在应用程序结束后返回一个特殊的退出code值,beans可以实现org.springframework.boot.ExitCodeGenerator接口。

23.10 管理功能

通过指定spring.application.admin.enabled配置来为应用程序开启admin-related功能。这会在MBeanServer平台上暴露SpringApplicationAdminMXBean。你可以使用这个功能管理你的远程Spring Boot应用。这对于任何服务包装实现类也是有用的。

注:如果你想知道应用正在运行的HTTP端口,通过key local.server.port来获取属性。

注:当开启MBean暴露一个方法去关闭应用这个功能时,要注意安全问题。

24 客观化配置

Spring Boot允许你客观化你的配置以至于你可以在不同的环境运行同一个应用。你可以使用配置文件,YAML文件,环境变量和command-line(命令行)参数来客观化配置。使用@value注解直接注入属性值可以到你beans中,通过Spring的Environment抽象或者通过@ConfigurationProperties绑定到结构对象来访问。

Spring Boot使用一个非常特殊的PropertySource规则来合理地覆盖值。需要考虑以下顺序的属性:

  1. 在您的主目录下的Devtools全局设置属性(~/.spring-boot-devtools.properties,启动devtools时)
  2. 测试类中的@TestPropertySource注解
  3. 测试类中的@SpringBootTest#properties注解
  4. 命令行参数
  5. SPRING_APPLICATION_JSON的属性(联合JSON嵌入到一个环境变量或者系统属性中)
  6. ServletConfig初始化参数
  7. ServletContext初始化参数
  8. java:comp/env的JNDI属性
  9. Java系统属性(System.getProperties()
  10. OS环境变量
  11. 只有在random.*中包含属性的RandomValuePropertySource 
  12. 在你打包的jar包外部的应用特殊配置文件(application-{profile}.properties和YAML变异体)
  13. 在你打包的jar包内部的应用特殊配置文件(application-{profile}.properties和YAML变异体)
  14. 在你打包的jar包外部的应用配置文件(application.properties和YAML变异体)
  15. 在你打包的jar包内部的应用配置文件(application.properties和YAML变异体)
  16. 在你的带有@Configuration类上的@PropertySource注解。
  17. 默认配置文件(指定使用SpringApplication.setDefaultProperties

提供一个具体的例子,支持你开发一个带有name属性的@Component:

import org.springframework.stereotype.*
import org.springframework.beans.factory.annotation.*

@Component
public class MyBean {

    @Value("${name}")
    private String name;

    // ...

}

在你的应用程序类路径下(例如在你的jar包里),你可以创建一个application.properties(resourece目录下)来给name配置合理的默认属性值。当在一个新的环境中运行,可以在jar包外提供一个application.properties覆盖name属性。一次性测试时,你可以使用特殊的命令行开关运行(例如java -jar app.jar --name="Spring"

注:SPRING_APPLICATION_JSON属性可以在命令行上使用一个环境变量来提供。例如在一个UN*X脚本里:

$ SPRING_APPLICATION_JSON='{"foo":{"bar":"spam"}}' java -jar myapp.jar

在示例中,你可以在Spring Environment中使用foo.bar=spam来结束。你也可以提供JSON作为系统变量里的spring.application.json

$ java -Dspring.application.json='{"foo":"bar"}' -jar myapp.jar

或命令行参数:

$ java -jar myapp.jar --spring.application.json='{"foo":"bar"}'

或者作为一个JNDI变量java:comp/env/spring.application.json

24.1 配置随机值

RandomValuePropertySource在注入随机值时是很有用的(例如在密钥或测试案例中)。它可以生成整数,long类型的数字,uuid或者字符串,例如:

my.secret=${random.value}
my.number=${random.int}
my.bignumber=${random.long}
my.uuid=${random.uuid}
my.number.less.than.ten=${random.int(10)}
my.number.in.range=${random.int[1024,65536]}

random.int*语法是OPEN value (,max) CLOSE,此处OPEN,CLOSE是任何字符,value,max是整数。如果提供max,那么value是最小值,max是最大值(不包含等于的情况)。

24.2 访问命令行属性

默认地,SpringApplication会把所有命令行选项参数(以'--'开头,例如--server.port=9000)转换成一个属性并且加载到Spring环境中。如上所述,命令行配置通常优先级在其他配置资源文件之上。

如果你不想让命令行参数加入到Environment中,可以使用SpringApplication.setAddCommandLineProperties(false)禁用该功能。

24.3 应用程序属性文件

SpringApplication会在以下位置的application.properties文件中加载属性并把他们加入到Spring Environment中:

  1. 当前目录的/config子目录
  2. 当前目录的/config子目录
  3. classpath下的/config包
  4. classpath根路径

这个列表是按照优先级排序的(从高优先级到低优先级)。

注:你可以使用YAML ('.yml')文件替代'.properties'文件。

如果你不喜欢application.properties作为配置文件的名字,可以通过指定spring.config.name环境属性切换到其他名称。你也可以使用spring.config.location环境属性指定一个明确的位置(目录位置或者文件路径是用逗号隔开的列表)。

$ java -jar myproject.jar --spring.config.name=myproject

或者

$ java -jar myproject.jar --spring.config.location=classpath:/default.properties,classpath:/override.properties

注:spring.config.namespring.config.location很早就被用来确认加载哪个文件,所以他们必须定义为一个环境属性(代表OS env,系统属性或者命令行参数)。

如果spring.config.location包含了目录(而不是文件),应该以 / 结束(加载前,会追加从spring.config.name产生的名字,包括哪些特殊的文件名)。spring.config.location中指定的文件会被原样使用,不支持特殊概要的变体,并且会重写一切的特殊概要配置文件。

不管spring.config.location设置为什么值,默认搜索路径classpath:,classpath:/config,file:,file:config/总会被使用。搜索路径按照从低到高优先级排序(file:config/ 优先级最高)。如果你指定了自己的文件位置,他们会凌驾于所有默认的位置,并使用同样的从最低到最高的排序方式。那样你就可以在application.properties里设置你的应用程序默认值(或者你通过spring.config.name选择的其他基础名称),并在运行时使用不同的文件覆盖他,保持默认值。

注:如果你使用环境变量而不是系统配置文件,大多数操作系统不支持句号隔开的key名称,但是你可以使用下划线替代(例如SPRING_CONFIG_NAME替代spring.config.name)。

注:如果您正如果你正在运行在一个容器,JNDI属性(在java:comp/env里)或servlet上下文初始化参数或环境变量或系统变量都可以被使用来替代。

24.4 特殊的概要文件属性

除了application.properties文件外,特殊概要文件配置属性同样可以使用application-{profile}.properties命名约定来定义。如果没有设置可用的配置文件,Environment有一系列的默认配置文件(默认 [default])被使用(也就是说如果没有明确的有效配置文件,会加载application-default.properties配置文件。)特殊配置文件会在同样的位置以标准的application.properties被加载,特殊配置文件通常会重写非特殊的配置文件,不管特殊配置文件是在你的jar包里面还是外面。

如果指定了多个配置文件,最后获胜的策略会被应用。例如,通过spring.profiles.active属性指定的配置文件会被添加到SpringApplication API之后,并取得优先级。

注:如果你在spring.config.location中指定了一切文件,这些文件的特殊配置文件变体将不会再被考虑。如果你想使用特殊配置文件,可以使用'spring.config.location'中的目录。

24.5 配置文件中的占位符

application.properties中的值被使用时,他们会被存在的Environment过滤,所以你可以参考之前定义的值(例如从系统配置文件定义的)。

app.name=MyApp
app.description=${app.name} is a Spring Boot application

注:你也可以使用这种技术来为存在的Spring Boot配置文件创建'short'变异特性。详情请看71.4章节

24.6 使用YAML代替Properties配置文件

YAML是JSON的一个超集,而它本身是一个非常方便的指定分层配置数据的格式。当你的classpath下包含SnakeYAML时,SpringApplication类自动支持YAML作为properties配置文件的替代品。

注:如果你使用'Starters',spring-boot-starter会自动提供SnakeYAML

24.6.1 加载YAML

Spring框架提供了两个方便的类去加载YAML文档。YamlPropertiesFactoryBean会把YAML加载成PropertiesYamlMapFactoryBean会把YAML加载成Map

例如,以下的YAML文档:

environments:
    dev:
        url: http://dev.bar.com
        name: Developer Setup
    prod:
        url: http://foo.bar.com
        name: My Cool App

会被转换成这些配置

environments.dev.url=http://dev.bar.com
environments.dev.name=Developer Setup
environments.prod.url=http://foo.bar.com
environments.prod.name=My Cool App

YAML列表被表示成带有[index]下标的属性keys,例如这个YAML:

my:
   servers:
       - dev.bar.com
       - foo.bar.com

会被转换成以下形式:

my.servers[0]=dev.bar.com
my.servers[1]=foo.bar.com

使用Spring DataBinder实用工具绑定那样的属性(这是@ConfigurationProperties做的事)你需要确定在目标bean中有java.util.List(或Set)的属性类型,然后你需要提供一个setter方法或初始化它为可变的值,例如像上面那样把它绑定到配置文件中

package com.examle.service;

import java.util.ArrayList;
import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

@Component
@ConfigurationProperties(prefix="my")
public class Config {

     //YAML中同名的servers属性列表会自动匹配装载
     private List<String> servers = new ArrayList<String>();

     public List<String> getServers() {
         return this.servers;
     }
}
 

然后在controller中注入引用

package com.examle.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.examle.bean.User;
import com.examle.service.AccountService;
import com.examle.service.Config;

@RestController
public class ConfigController {

    @Autowired
    Config config;
    
    @RequestMapping("/config/getConfig")
    @ResponseBody
    public Object getConfig(){
        return config;
    }
}

启动应用,浏览器输入:http://localhost:8080/config/getConfig,会出现以下文字:

{
    "servers": [
        "dev.bar.com",
        "foo.bar.com"
    ]
}

24.6.2 在Spring环境中把YAML暴露成配置文件

在Spring Environment里,YamlPropertySourceLoader可以被用于把YAML暴露为一个PropertySource。这允许你使用熟悉的@Value占位符语法去访问YAML配置文件。

24.6.3 Multi-profile YAML文档

你可以单个文件中指定多个特殊配置的YAML文档,并通过spring.profiles key提示何时文档适用。例如:

server:
    address: 192.168.1.100
---
spring:
    profiles: development
server:
    address: 127.0.0.1
---
spring:
    profiles: production
server:
    address: 192.168.1.120

在上面的示例,如果development配置被激活,server.address将会是127.0.0.1developmentproduction没有被启动,则属性值将会是192.168.1.100

如果应用上下文启动时没有明确的激活配置,默认配置会被激活。所以我们在YAML中设置的security.user.password值仅会在默认配置中可用。

server:
  port: 8000
---
spring:
  profiles: default
security:
  user:
    password: weak

然而在本例中,密码是经常被设置的,因为它不附加在任何配置上,并且它必须被明确地重置在所有其他的配置中。

server:
  port: 8000
security:
  user:
    password: weak

 使用"spring.profiles"元素指定的Spring配置可以使用!字符来否定。如果否定和非否定的配置都是指定同一个文档,至少一个非否定的配置必须匹配,没有否定的配置可能匹配。

24.6.4 YAML的不足

YAML不能通过@PropertySource注解来加载。在这种情况下,如果你想要通过那种方式加载值,必须使用一个properties配置文件。

24.6.5 合并YAML列表

正如我们所见,任何的YAML内容最终都会转换成properties配置文件。当覆盖"list"属性这通过一个配置时,这个过程可能有违直观。

例如,假设一个MyPojo对象的namedescription的默认值为空。让我们从FooProperties中暴露MyPojo列表:

@Component
@ConfigurationProperties("foo")
public class FooProperties {

    private final List<MyPojo> list = new ArrayList<>();

    public List<MyPojo> getList() {
        return this.list;
    }

}

考虑以下配置:

foo:
  list:
    - name: my name
      description: my description
---
spring:
  profiles: dev
foo:
  list:
    - name: my another name

如果dev配置没有激活,FooProperties.list会包含一个MyPojo实体类(第一个foo)作为以上定义的。如果激活了devlist仍然会包含一个实体类(名称为"my another name",描述为空)。这个配置不会加入第二个MyPojo实例到列表中,并且不会合并items。

当一个集合指定了多个配置文件,最高优先级那个会被使用(并且只会启用一个):

foo:
  list:
    - name: my name
      description: my description
    - name: another name
      description: another description
---
spring:
  profiles: dev
foo:
  list:
     - name: my another name

上面的例子中,如果dev配置被激活,FooProperties.list会包含一个MyPojo实体类(名为"my another name",描述为null)。

24.7 类型安全的配置属性

通过@Value("${property}")注解注入配置属性有时候会显示笨重,特别是你使用多个配置文件或你的数据本质上是分层的。Spring Boot提供了一个允许强类型的beans去管理和校验你的应用配置。

package com.example;

import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("foo")
public class FooProperties {

    private boolean enabled;

    private InetAddress remoteAddress;

    private final Security security = new Security();

    public boolean isEnabled() { ... }

    public void setEnabled(boolean enabled) { ... }

    public InetAddress getRemoteAddress() { ... }

    public void setRemoteAddress(InetAddress remoteAddress) { ... }

    public Security getSecurity() { ... }

    public static class Security {

        private String username;

        private String password;

        private List<String> roles = new ArrayList<>(Collections.singleton("USER"));

        public String getUsername() { ... }

        public void setUsername(String username) { ... }

        public String getPassword() { ... }

        public void setPassword(String password) { ... }

        public List<String> getRoles() { ... }

        public void setRoles(List<String> roles) { ... }

    }
}

上面的POJO中定义了以下属性:

foo.enabled, 默认值为false
foo.remote-address, 可以从String强制转换的类型
foo.security.username,包含了由属性名称决定的一个嵌套安全。特别是返回类型没有被所有方法使用,并且可能是SecurityProperties
foo.security.password
foo.security.roles,一个String集合

注:Getters和setters通常是强制的,因为绑定是通过标准的Java Beans属性描述符,就像Spring MVC里一样。以下例子中,setter可以被忽略:

  • Maps,只要他们被初始化,必须得有getter方法,但是setter并不是必须的,因为他们会被binder突变。
  • 集合和数组可以通过index下标来访问或者使用一个单一的逗号分隔开的值。对于后者,setter是必须提供的。我们建议在这种类型尽可能提供setter。如果你初始化一个集合,确保它是不变的(正如上面的例子)
  • 如果内嵌的POJO属性被初始化(像上面示例中的Security字段),setter不是必须的。如果你使用它的默认构造器,就必须提供setter方法。

某些人使用项目的Lombok去自动添加getter和setter方法。确保Lombok不会产生任何特殊的构造器,因为它在容器初始化项目的时候,会被自动调用。

你还需要列出属性类注册到@EnableConfigurationProperties注解里:

@Configuration
@EnableConfigurationProperties(FooProperties.class)
public class MyConfiguration {
}

注:当@ConfigurationProperties bean通过这种方法被注册,这个bean会有一个约定俗成的名字:<prefix>-<fqn>,<prefix>是在@ConfigurationProperties注解中指定的环境key前缀,<fqn>是bean的完全限定名称(包路径+类名)。如果注解没有提供任何前缀,仅有bean的全限定名称会被使用。上面例子中的bean名称将会是foo-com.example.FooProperties

尽管上面的配置会为FooProperties创建一个规范的bean,但是我们建议@ConfigurationProperties仅用于处理环境,特别是不要从上下文注入其他beans。话虽如此,@EnableConfigurationProperties注解会被自动应用到你的项目中,所以任何现有的带有@ConfigurationProperties注解的bean将会从Environment被配置。你可以捷径以上的MyConfiguration来确保FooProperties已经是一个bean:

@Component
@ConfigurationProperties(prefix="foo")
public class FooProperties {

    // ... see above

}

SpringApplication外部YAML配置的配置风格工作得特别好:

# application.yml

foo:
    remote-address: 192.168.1.1
    security:
        username: foo
        roles:
          - USER
          - ADMIN

# additional configuration as required

你可以像注入其他bean一样注入@ConfigurationProperties注解的beans。

package com.examle.service;

import javax.annotation.PostConstruct;

import org.apache.catalina.Server;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class MyService {

    @Autowired
    Config config
;
    
    @PostConstruct
    public Object getServers() {
        return this.config.getServers();
        // ...
    }
    
}

24.7.1 第三方配置

正如使用@ConfigurationProperties注解一个类,你也可以在公共的@Bean方法上使用它。当你想绑定在你控制之外的第三方组件的属性,这是相当有用的。 

为了从Environment配置文件配置一个bean,在bean注册时加入@ConfigurationProperties:

@ConfigurationProperties(prefix = "bar")
@Bean
public BarComponent barComponent() {
    ...
}

任何以bar前缀定义的属性都会以类似的方式被匹配到BarComponent bean中,正如上面FooProperties的例子。

24.7.2 松散的绑定

Spring Boot使用一些松散的规则为@ConfigurationProperties beans绑定Environment属性,所以在Environment属性名和bean属性名并不需要精确的匹配。常见的例子中有用的包括虚线分割(例如context-path绑定contextPath),大写的(例如PORT绑定port)环境属性。

示例:

@ConfigurationProperties(prefix="person")
public class OwnerProperties {

    private String firstName;

    public String getFirstName() {
        return this.firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

}

下面De属性名都能被使用到上面的OwnerProperties类:
 

属性 注释
person.firstName 标准驼峰语法
person.first-name 虚线符号,建议用于.properties和.yml文件
person.first_name 下划线符号,建议用于.properties和.yml文件
PERSON_FIRST_NAME 大小格式,建议使用到系统变量中

24.7.3 属性转换

当外部应用属性绑定到@ConfigurationProperties beans,Spring会试图把他们强制把转换成正确的类型。如果你需要自定义类型转换,你可以提供一个ConversionService bean(bean的id为conversionService)或者自定义属性编辑器(通过一个CustomEditorConfigurer bean)或者自定义Converters(带有注解@ConfigurationPropertiesBinding的bean)

24.7.4 @ConfigurationProperties检验

@ConfigurationProperties类被Spring的@Validated注解注释时,Spring Boot试图去校验该类。你可以在你的配置类中直接使用JSR-303 javax.validation约束注解。简单地确保JSR-303已经在你的类路径下实现,然后在你的字段中加入约束注解:

@ConfigurationProperties(prefix="foo")
@Validated
public class FooProperties {

    @NotNull
    private InetAddress remoteAddress;

    // ... getters and setters

}

为了校验嵌入的属性值,你必须注释相关字段为@Valid去触发校验器。例如,在以上例子的基础上改造:

@ConfigurationProperties(prefix="connection")
@Validated
public class FooProperties {

    @NotNull
    private InetAddress remoteAddress;

    @Valid
    private final Security security = new Security();

    // ... getters and setters

    public static class Security {

        @NotEmpty
        public String username;

        // ... getters and setters

    }

}
 

你也可以通过创建一个命名问configurationPropertiesValidator的bean来添加自定义的Spring Validator。这个@Bean方法必须定义为static。配置属性校验器在应用的生命周期的早期就被创建了,而把@Bean方法声明为static,可以允许创建bean时不必实例化@Configuration类。这可以避免早期初始化引起的问题。

24.7.5 @ConfigurationProperties和@Value

@Value是一个核心容器特性,它不会提供类型安全配置属性这样的特性。下面的表是@ConfigurationProperties@Value特性的概述:

特性 @ConfigurationProperties @Value

轻松绑定

元数据支持
SpEL评估

如果你为你的组件定义了一系列的配置keys,我们建议你使用@ConfigurationProperties把他们分组到一个POJO注解中。请注意一点,因为@Value不支持松散绑定,如果你需要使用环境变量提供value,最好不要使用@Value。

最后,虽然你可以在@Value里写SpEL表达式,但是这样的表达式在应用配置文件中不能被处理。

25 配置文件

Spring配置文件提供了一种方法可以分离你应用中的部分配置,并使他们在正确的环境中可用。当@Component或者@Configuration被加载时,可以使用@Profile标志环境。

@Configuration
@Profile("production")
public class ProductionConfiguration {

    // ...

}

在一般的Spring方式中,你可以使用spring.profiles.active环境属性去指定激活哪个配置文件。你可以使用任何方式指定该属性,例如在你的application.properties

spring.profiles.active=dev,hsqldb

或者在命令行使用开关 --spring.profiles.active=dev,hsqldb

25.1 添加激活的配置

spring.profiles.active属性和其他属性一样,跟随同样的排序规则,最高优先级的PropertySource会获胜。这意味着你可以在application.properties里指定激活的配置文件,但是你可以使用命令行开关去取代配置文件中激活的配置。

有些时候,将特殊的配置文件加入到生效的配置文件而不是替代它们是很有用的。spring.profiles.include可以被用来无条件地添加生效的配置文件。SpringApplication入口点也提供了一个Java API去设置额外的配置文件(例如那些通过spring.profiles.active属性激活的配置之上的):具体的请看setAdditionalProfiles()方法。

例如,当一个应用使用以下配置,并使用开关 --spring.profiles.active=prod,那么proddbprodmq配置文件都会被激活:

---
my.property: fromyamlfile
---
spring.profiles: prod
spring.profiles.include:
  - proddb
  - prodmq

25.2 以编程的方式设置配置文件

在程序运行之前,你可以通过调用SpringApplication.setAdditionalProfiles(…​)以编程的方式设置激活的配置文件。也可以使用ConfigurableEnvironment接口去激活配置文件。

25.3 特定的配置文件

application.properties(或者application.yml)和通过@ConfigurationProperties引用的文件这两种特定的配置文件都会被加载。

26 日志

Spring Boot所有的内部日志使用的是Commons Logging,但是开放底层的日志实现。默认为Java Util Logging,Log4J2Logback提供配置。在不同情况下,日志输出都会被预配置去使用控制台输出或者可选文件输出。

默认地,如果你使用"Starters",Logback会被使用。引入适当的Logback确保使用Java Util Logging,Commons Logging,Log4J或者SLF4J的依赖环境。

注: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, DEBUGTRACE
  • 进程ID
  • ---分隔符去区分真实日志信息的开头
  • 线程名称--在方括号里面(可能会被控制台截断)
  • 日志名称--通常是源代码类的名称(通常是缩写的)
  • 日志信息

注:Logback没有FATAL级别(FATAL可以匹配到ERROR

26.2 控制台输出

默认的日志配置会在写日志信息的时候把它们输出到控制台。默认情况下,ERRORWARNINFO级别的信息会被记录。你也可以在运行你的应用时通过--debug标志开启"debug"模式。

$ java -jar myapp.jar --debug

注:你也不可以在application.properties中指定debug=true来开启debug模式。

当启用debug模式,一大堆的核心日志记录器(内嵌容器,Hibernate和Spring Boot)会被配置以输出更多的信息。启用debug模式并把级别设置为DEBUG,可以选择性输出信息。

同样地,你也可以使用 --trace标志启用"trace"模式。

26.3 文件输出

默认地,Spring Boot会把诶之输出到控制台而并不会写日志文件。如果你想在输出到控制台的同时写日志文件,你需要设置logging.file或者logging.path属性(可以在application.properties文件中设置)。
下表说明了logging.*属性的一起使用的效果:

logging.file logging.path 示例 描述
(none) (none)   指在控制台输出日志信息
指定文件 (none) my.log 把日志信息写到指定的日志文件。名称可以是具体的路径,也可以是相对当前文件夹的路径。
(none) 指定文件夹 /var/log 在指定文件夹写入spring.log日志文件。名称可以是具体的路径,也可以是相对当前文件夹的路径。

当日志文件达到10MB的时候,它们就会被循坏(分割),默认会输出ERROR, WARN和INFO级别的日志信息。

注:日志系统在应用生命周期的早期就会被初始化,同样地,通过@PropertySource注解的日志属性不能在属性文件中被找到。

注:日志属性是独立的真实日志基础功能。因此,指定配置keys(例如logback.configurationFile或者Logback)不归Spring Boot管理。

26.4 日志级别

所以支持日志输出的系统都会在Spring的Environment中(例如在application.properties中)使用'logging.level.*=LEVEL'来定义日志的级别,'LEVEL'可以是TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF。

root日志key9i使用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的日志配置入口。

因为日志在ApplicationContext创建之前就已经初始化了,所以不可能在Spring的@Configuration文件从@PropertySources控制日志配置。但是系统属性以及常规的Spring Boot外部配置属性文件可以可以通过这种方式控制。

根据你的日志系统,下面的文件会被加载:

日志系统 定制化
Logback logback-spring.xml, logback-spring.groovy, logback.xml 或 logback.groovy
Log4j2 log4j2-spring.xml 或 log4j2.xml
JDK (Java Util Logging) logging.properties

注:我们推荐你尽可能地使用-spring变体去配置你的日志(例如logback-spring.xml而不是logback.xml)。如果你使用标准配置位置,Spring不能完全控制日志的初始化。

注:目前,当运行一个'executable jar'时,使用Java Util Logging会引发一些问题。我们建议你尽可能不要使用Java Util Logging。

为了帮助定制一些其他属性从Spring Environment转换到系统属性:

Spring环境变量 系统属性 注释
logging.exception-conversion-word LOG_EXCEPTION_CONVERSION_WORD 这个转换字在日志异常的时候被使用。
logging.file LOG_FILE 如果定义,在默认日志配置里使用。
logging.path LOG_PATH 如果定义,在默认日志配置里使用。
logging.pattern.console CONSOLE_LOG_PATTERN 使用在控制台的日志方案(stdout)。(仅支持使用默认的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里的默认配置例子。

注:如果你想在日志属性中使用占位符,你应该使用Spring Boot的语法而不是框架下的语法。尤其是,如果你正在使用Logback,你应该使用作为属性名和值得分割,而不是 :-

26.6 Logback扩展

Spring Boot提供了一系列的Logback扩展对日志进行高级配置。你可以在logback-spring.xml配置文件中使用。

注:你不能在标准的logback.xml配置文件中使用扩展,因为它的加载在应用早期。你可以使用logback-spring.xml或者定义一个logging.config属性。

注:扩展不能使用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 name="staging">
    <!-- 激活"staging"配置 -->
</springProfile>

<springProfile name="dev, staging">
    <!-- 激活"dev"或者"staging"配置 -->
</springProfile>

<springProfile name="!production">
    <!-- 不激活"dev"或者"production"配置 -->
</springProfile>

26.6.2 环境配置文件

<springProperty>允许你使用Logback从Spring的Environment中选择配置文件。如果你想在application.properties文件访问到你的logback配置文件,这是很有用的。这个标签跟Logback的标准<property>标签是一样的原理,但并不是直接指定一个value,而是指定属性的source(从Environment)。如果你需要在某些地方转存属性而不是本地,可以使用scope属性。如果你需要的值没有在Environment中设置,你可以使用defaultValue属性。

<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>

27 开发web应用

Spring Boot非常适合开发web应用。你可以使用内嵌的Tomcat,Jetty,或者Undertow很轻松地创建一个独立的HTTP服务器。大多数的web应用会使用spring-boot-starter-web模块来快速搭建和运行。

27.1 Spring Web MVC框架

Spring Web MVC框架(通常简称为'Spring MVC')是一个富"模型,视图,控制器"的web框架。Spring MVC允许你创建一个特殊的@Controller或者@RestController beans去处理传入的HTTP请求。使用@RequestMapping注解可以把你控制器里的方法映射到相应的HTTP请求。

下面是@RestController输出JSON服务数据的典型案例:

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

}

27.1.1 Spring MVC自动配置

Spring Boot为多数应用提供了支佛那个配置。在Spring默认的基础上,加入以下属性:

  • 引入ContentNegotiatingViewResolverBeanNameViewResolver beans。
  • 支持静态资源,包括对WebJars的支持。
  • 自动注册Converter, GenericConverter, Formatter beans。
  • 支持HttpMessageConverters
  • 自动注册MessageCodesResolver
  • 支持静态index.html
  • 支持定制的Favicon
  • 自动使用ConfigurableWebBindingInitializer bean。

如果你想在引入Spring Boot MVC的属性基础上,加入额外的MVC配置(拦截器,格式化器,视图控制器等等),你可以加上WebMvcConfigurerAdapter类型的@Configuration类,但不必使用@EnableWebMvc。如果你想提供自定义RequestMappingHandlerMappingRequestMappingHandlerAdapter或者ExceptionHandlerExceptionResolver实例,可以声明WebMvcRegistrationsAdapter实例来提供这样的组件。

如果想完全控制Spring MVC,你可以添加@Configuration,并用@EnableWebMvc对其进行注解。

27.1.2 HttpMessageConverters

Spring MVC使用HttpMessageConverter接口转换HTTP的请求和响应。合理的默认值被包含得开箱即用,例如对象可以被自动转换成JSON(使用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数据,可能想写一个自己的JsonSerializerJsonDeserializer类。自定义序列化器通常是通过一个模板注册Jackson,但是Spring Boot提供了替代的@JsonComponent注解使得注册Spring Beans更简单直接。

你可以在JsonSerializerJsonDeserializer实现类上直接使用@JsonComponent。你还可以把它作为内部类在包含serializers/deserializers的类上使用。例如:

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 beans都会被自动注册到Jackson,并且因为@JsonComponent@Component的后设注解,会使用到通常的组件扫描规则。

27.1.4 MessageCodesResolver

Spring MVC对于绑定错误中呈现错误信息生成的错误编码有一个成熟的策略:MessageCodesResolver。如果你设置spring.mvc.message-codes-resolver.format属性为PREFIX_ERROR_CODE或者POSTFIX_ERROR_CODE(看DefaultMessageCodesResolver.Format里的列举),Spring Boot也会为你创建这样的一个策略。

27.1.5 静态内容

默认地,Spring Boot会从类路径下命名为/static (或/public/resources/META-INF/resources) 的目录下或者ServletContext的根目录下提供静态资源。它使用Spring MVC的ResourceHttpRequestHandler,所以你可以通过WebMvcConfigurerAdapter修改这个行为,并通过addResourceHandlers重写这个方法。

在一个独立的web应用中,容器中默认的servlet也会作为后备被启用,如果Spring不决定处理它,服务内容会从ServletContext的根目录提供。大多数情况下并不会发生(除非你修改了默认的MVC配置),因为Spring通常会通过DispatcherServlet处理请求。

默认地,资源会被映射到/**,但是你可以通过spring.mvc.static-path-pattern调整。例如,把所有资源映射到/resources/**

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

你也可以使用spring.resources.static-locations自定义静态资源的位置(取代位置目录列表的默认值)。如果你改变了静态资源的位置,那么默认的欢迎页也会切换到你的自定义位置,所以如果启动时你的任何文件路径下有index.html,它就会被作为应用的主页。除了以上的标准静态资源位置外,还有Webjars content这样的特殊例子。

如果jar包被打包成Webjars格式,jar包文件中每一个带有/webjars/**路径的资源都会被保存。

注:如果你的应用要被打包成jar包,不要使用src/main/webapp目录。尽管这是一个普通的标准目录,但是它仅在war包中生效,并且如果你生成jar包,它会被大多数构建工具忽略。

Spring Boot也支持Spring MVC提供的高级资源处理属性,允许缓存静态资源或使用版本未知的URLs。

在Webjars里使用版本不可知的URLs,通常需要加入webjars-locator依赖。然后声明你的Webjar,例如jQuery,"/webjars/jquery/dist/jquery.min.js"的结果是"/webjars/jquery/x.y.z/dist/jquery.min.js"x.y.z是Wenjar的版本号。

注:如果你正在使用JBoss,你需要引入webjars-locator-jboss-vfs依赖而不是webjars-locator;否则,所有的Webjars都会报404错误。

为了使用缓存,需要为所有的静态资源配置以下的配置,实际上是在URLs上加入hash内容,例如<link href="/css/spring-2a2d595e6ed9a0b24f027f2b63b134d6.css"/>

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

27.1.6 自定义图标

Spring Boot会在配置的静态内容的位置和根路径下寻找favicon.ico。如果找到,就会被自动作为应用的图标。

27.1.7 可配置的Web绑定初始化器

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

27.1.8 模板引擎

正如REST web服务,你也可以使用Spring MVC提供动态HTML内容。Spring MVC支持一系列的模板引擎,包括Thymeleaf, FreeMarker和JSPs。很多其他的模板引擎也提供了它们自己的Spring MVC集成。

Spring Boot为以下模板引擎引入了自动配置支持:

  • FreeMarker
  • Groovy
  • Thymeleaf
  • Mustache

注:JSPs应该尽可能避免使用,因为在使用内嵌服务容器时会有几个已知的限制。

当你正在使用某一模板的默认配置时,你用到的模板会从src/main/resources/templates获取。

27.1.9 错误处理

Spring Boot默认提供了一个/error映射合理地处理所有的错误,并且它会在服务容器中被注册成一个的全局错误页,为客户端机器生成错误细节的JSON响应,HTTP状态和异常信息。在浏览器中会有一个白色标签错误视图以HTML格式渲染同样的数据(可以通过添加一个解析错误的View来自定义它)。为了完全替换默认行为,你可以实现ErrorController和注册一个该类型的bean定义,或者简单地以现有的机制添加ErrorAttributes类型的bean但替换内容。

你也可以定义一个@ControllerAdvice去自定义JSON文档来返回一个特殊控制器and/or的异常类型。

@ControllerAdvice(basePackageClasses = FooController.class)
public class FooControllerAdvice 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被一个与FooController定义在同一个包的控制器抛出,CustomerErrorType实体类描述会替代ErrorAttributes描述。 

自定义错误页

如果你想为给定的状态编码显示一个自定义的HTML错误页,可以在/error文件夹中加入文件。错误页可以是一个静态页面,也可以使用模板构建。文件的名称应该是精确的状态编码或者系列的统称。

例如,映射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接口的beans:

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方法和@ControllerAdviceErrorController会拦截所有没有处理的异常。

Spring MVC以外的映射错误页面

如果应用没有使用Spring MVC,你可以直接使用ErrorPageRegistrar去注册ErrorPages。这个抽象接口直接与底层的内嵌容器工作,甚至可以在没有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"));
    }

}

N.B. 如果你为一个路径注册了一个ErrorPage,它最终会被一个Filter处理(例如对于一些非Spring web的普通框架,像Jersey和Wicket),然后Filter会精准地注册ERROR分发器,例如:

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

(默认的FilterRegistrationBean没有包含ERROR分发器类型)

27.1.10 Spring HATEOAS

如果你正在使用多媒体开发一个RESTful API,Spring Boot会为大多数应用自动配置Spring HATEOAS。自动配置取代了必须使用@EnableHypermediaSupport的需求和注册了一系列的beans来轻松构建包含LinkDiscoverers基于多媒体的应用(在客户端支持),ObjectMapper会把响应正确地映射到对应的地方。

27.1.11 CORS支持

跨域资源共享(CORS)是W3C被多数浏览器实现的一个规范,允许你灵活地指定跨域请求和权限验证的类型,而不是像IFRAME或者JSONP那样使用缺少安全验证和有效访问的方法。

在4.2版本中,Spring MVC支持CORS开箱即用。在Spring Boot应用中只需要加入@CrossOrigin注解配置CORS,除此之外并不需要额外的特殊配置。全局的CORS配置可以通过注册一个WebMvcConfigurer bean的addCorsMappings(CorsRegistry)自定义方法来定义:

@Configuration
public class MyConfiguration {

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

27.2 JAX-RS和Jersey

如果你更喜欢JAX-RS为REST端点提供的程序模型,你可以使用任一种可用的实现取代Spring MVC。如果你仅仅想在程序上下文注册他们的ServletFilter为一个@Bean,Jersey 1.x 和Apache CXF会工作得非常好。Jersey 2.x有一些原生的Spring支持,所以我们会在Spring Boot中提供自动配置。

引入spring-boot-starter-jersey依赖就可以使用Jersey 2.x,然后在注册所有端点的地方,你需要一个ResourceConfig类型的@Bean
 

@Component
public class JerseyConfig extends ResourceConfig {

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

}

注:Jersey扫描可执行文件的能力非常有限。例如,当正在运行一个可执行war文件时,它不能扫描发现WEB-INF/classes的包里的终端。为了避免这种限制,packages方法应该避免使用,并且终端必须使用register另外注册。

所有注册的端点必须使用@Component和HTTP资源(比如@GET),例如:

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

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

}

因为Endpoint是一个Spring的@Component(组件),所以它的生命周期由Spring管理,你可以@Autowired(自动装配)依赖并通过@Value注入值。Jersey会被注册并默认映射到 /*。你可以在ResourceConfig中加入@ApplicationPath改变映射路径。

默认地,Jersey会在类型为ServletRegistrationBean@Bean里被建立为名为jerseyServletRegistration的Servlet。默认地,servlet会被懒加载,但是你可以通过spring.jersey.servlet.load-on-startup自定义。通过创建你自己的同名的bean,可以禁用或覆盖原来的bean。你也可以通过spring.jersey.type=filter来使用Filter取代Servlet(在这种情况下,取代或覆盖的@BeanjerseyFilterRegistration)。该servlet有@Order,你可以通过spring.jersey.filter.order设置。不管是Servlet还是Filter注册,都可以使用spring.jersey.init.* 指定一个配置文件映射来传入初始化参数。

27.3 内嵌的服务容器支持

Spring Boot引入了对内嵌Tomcat,Jetty和一些底层服务容器的支持。大多数开发者只需要引入"Starter"就可以获取一个完整的配置实例。默认的,内嵌服务器监听8080端口的HTTP请求。

注:如果你在CentOS上选择使用Tomcat,请意识到一点,默认地,编译的JSPs,上传的文件等等会被存储到临时目录下。但是如果你的应用运行失败,这个临时目录就会被tmpwatch删除。为了避免这种情况,你可以自定义tmpwatch配置使得tomcat.*目录不会被删掉,或配置server.tomcat.basedir使得内嵌Tomcat使用一个不同的位置。

27.3.1 Servlets, Filters,和 listeners

当使用内嵌servlet容器时,你可以根据Servlet的规则使用Spring beans或者扫描Servlet组件来注册Servlets,Filter和所有的listener(例如HttpSessionListener)。

把Servlets, Filters,和listeners注册成Spring Beans

任一Servlet, Filter或Servlet *Listener实例都会被内嵌容器注册成一个Spring Bean。在配置期间如果你想从application.properties读取value值,这将会特别的方便。

默认地,如果上下文仅仅包含一个简单的Servlet,它将会被映射到/。对于多个Servlet而言,bean的名称会被作为路径的前缀。Filters会被映射到 /*

如果默认的基础映射方案对于你来说不够灵活,你可以使用ServletRegistrationBean, FilterRegistrationBeanServletListenerRegistrationBean来完全控制映射路径。

27.3.2 Servlet上下文初始化

内嵌的服务容器不会直接执行Servlet 3.0+的javax.servlet.ServletContainerInitializer接口,或者Spring的org.springframework.web.WebApplicationInitializer接口。这是为了降低第三方库在war里运行导致Spring Boot程序崩溃的风险。

如果你需要在Spring Boot应用程序中执行servlet上下文初始化,牛需要注册一个实现org.springframework.boot.context.embedded.ServletContextInitializer接口的bean。单个的onStartup方法提供了对ServletContext的访问,并且如果必要,它可以很容易成为存在的WebApplicationInitializer的适配器。

扫描servlet, Filter和listener

当使用内嵌容器时,带有@WebServlet, @WebFilter,和@WebListener等自动注册注解的类可以使用@ServletComponentScan启用。

注:@ServletComponentScan在独立的容器中不会起作用,此时容器内置的发现机制会被启用。

27.3.3 EmbeddedWebApplicationContext

Spring Boot使用了一个新类型的ApplicationContext来更好地支持内嵌服务容器。EmbeddedWebApplicationContext是一个特殊类型的WebApplicationContext,它通过搜索一个单一的EmbeddedServletContainerFactory类来开机设定它自己。通常地,一个TomcatEmbeddedServletContainerFactory, JettyEmbeddedServletContainerFactory,或UndertowEmbeddedServletContainerFactory会被自动设定。

注:通常情况下,你不需要知道这些实现。大多数的应用都会为你自动配置好ApplicationContextEmbeddedServletContainerFactory

27.3.4 自定义内嵌的servlet容器

普通的servlet容器配置可以通过Spring的Environment配置文件配置。通常地,你可以在application.properties文件中定义这些属性。

常见的服务器配置如下:

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

Spring Boot尝试尽可能多地暴露常见设置但这并不总是可能的。对于那些情况下,会为特殊的服务自定义配置提供专门的命名空间(例如server.tomcatserver.undertow)。例如,访问日志可以通过内嵌的服务容器的特殊属性进行配置。

编程定制

如果需要以编程的方式配置你的内嵌服务容器,你可以注册一个实现了EmbeddedServletContainerCustomizer接口的Spring类。EmbeddedServletContainerCustomizer提供了访问ConfigurableEmbeddedServletContainer的自定义setter方法。

import org.springframework.boot.context.embedded.*;
import org.springframework.stereotype.Component;

@Component
public class CustomizationBean implements EmbeddedServletContainerCustomizer {

    @Override
    public void customize(ConfigurableEmbeddedServletContainer container) {
        container.setPort(9000);
    }

}

直接自定义ConfigurableEmbeddedServletContainer

如果上述的自定义技术没有满足你的要求。你可以注册TomcatEmbeddedServletContainerFactory, JettyEmbeddedServletContainerFactoryUndertowEmbeddedServletContainerFactory

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

许多配置选项都提供了Setters。好几个保护方法的"钩子"也会提供让你做更多特殊的处理。

27.3.5 JSP限制

28 权限管理

如果Spring的权限管理是在根目录下,那么web应用对于所有HTTP请求都会默认为基础权限验证。你可以在配置中加入@EnableGlobalMethodSecurity实现web应用的方法级权限管理。

默认地AuthenticationManager有一个简单的用户(用户名为user,密码是随机的,在应用启动的时候可以通过INFO级别把默认用户信息打印出来)。

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

注:如果你调整了你的日志配置,确保org.springframework.boot.autoconfigure.security被设置为INFO日志级别。否则默认密码不会被输出。

你可以配置security.user.password来改变密码。其他有用的属性也是通过SecurityProperties实现的(属性以"security"开头)。

默认的权限管理配置在SecurityAutoConfiguration中被实现并被导入到类中使用(SpringBootWebSecurityConfiguration权限管理和AuthenticationManagerConfiguration身份验证)。为了完全关闭默认的web权限管理,你可以创建一个带有@EnableWebSecurity的bean(这并不会禁用身份校验管理配置或者执行器的权限管理)。为了自定义权限管理,你通常需要使用外部的属性文件和WebSecurityConfigurerAdapter类型的bean(例如加入form-based登录)。为了关闭身份验证管理配置,你可以加入AuthenticationManager类型的bean,或者通过在@Configuration类的一个方法中注入AuthenticationManagerBuilder的方式配置一个全局的AuthenticationManager。权限管理例子 https://github.com/spring-projects/spring-boot/tree/master/spring-boot-samples/

以下基础特性,在web应用程序中可以达到开箱即用:

  • 存储在内存并有单个用户的AuthenticationManager(参考SecurityProperties.User用户属性)。
  • 忽略(不安全的)普通静态资源路径(/css/**, /js/**, /images/**, /webjars/** and **/favicon.ico)。
  • 对所有HTTP请求实现基础权限管理。
  • 权限管理事件会被发布到ApplicationEventPublisher(成功和失败的身份校验以及拒接访问的记录) 。
  • Spring Security提供的普通低级别特性(HSTS, XSS, CSRF, caching)也会被默认带上。

以上所有的配置都可以通过(security.*)配置文件开启或关闭或修改。为了在不改变其他自动配置属性的情况重写访问规则,可以通过@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)加入WebSecurityConfigurerAdapter类型的@Bean,并按照你的需求进行相应配置。

28.1 OAuth2

如果在你的根路径下包含了spring-security-oauth2,你可以利用一些自动配置来更容易地设置身份验证或者资源服务器。

28.1.1 授权服务器

为了创建一个授权服务器并授予访问令牌,你需要使用@EnableAuthorizationServer以及提供security.oauth2.client.client-idsecurity.oauth2.client.client-secret]属性。客户端会在内存资源库中注册。

完成以上步骤,你就可以使用客户端的证书创建授权令牌,例如:

$ curl client:secret@localhost:8080/oauth/token -d grant_type=password -d username=user -d password=pwd

/token的基础授权认证信息是client-idclient-secret。用户凭证是正常的Spring Security用户详细信息(默认为"user"用户和随机密码)。

关闭自动配置和配置授权服务器属性,仅仅需要新加一个AuthorizationServerConfigurer类型的@Bean

28.1.2 资源服务器

为了使用授权令牌,你需要一个资源服务器(可以是跟授权服务器一样的服务器)。创建一个资源服务器是很容易的,仅仅需要加入@EnableResourceServer以及提供一些配置允许服务器对访问令牌进行解码。如果你的应用已经是一个授权服务器,那么已经可以实现解码功能,就不需要做任何操作。如果你的应用是一个独立的服务,你需要为它增加一些额外的配置,以下配置二选一:

  • security.oauth2.resource.user-info-uri使用/me资源(例如PWS上的https://uaa.run.pivotal.io/userinfo)
  • security.oauth2.resource.token-info-uri使用token解码(例如PWS上的https://uaa.run.pivotal.io/check_token)

如果你指定user-info-uritoken-info-uri,然后你就可以给首选的uri设置一个标志(默认为prefer-token-info=true)。二选其一(user-info-uri或者token-info-uri),如果tokens是JWTs,你可以配置security.oauth2.resource.jwt.key-value来进行本地解码(前提是这个value的key必须是一个验证key)。验证的key值可以是一个对称的秘钥或者PEM解码的RSA公共秘钥。如果你没有这个key值,而它是公共的,你可以通过security.oauth2.resource.jwt.key-uri提供一个URI来下载(作为一个JSON对象的值字段)。例如PWS:

$ curl https://uaa.run.pivotal.io/token_key
{"alg":"SHA256withRSA","value":"-----BEGIN PUBLIC KEY-----\nMIIBI...\n-----END PUBLIC KEY-----\n"}

注:如果你使用security.oauth2.resource.jwt.key-uri,那么在启动应用时,需要把授权服务器也启动起来。如果找不到这个key值,会有提醒级别的日志输出,然后告诉你怎样处理。

OAuth2资源通过security.oauth2.resource.filter-order命令被一个过滤器链保护着。

28.2 用户信息中的令牌类型

谷歌以及其他的第三方身份提供者,对于发送给用户的头部信息中的令牌类型的名称的管理是相当严格的。默认的"Bearer"适合大多数提供者和匹配的规范,但是你可以设置security.oauth2.resource.token-type来改变这个值。

28.3 自定义用户信息RestTemplate

如果你有一个user-info-uri,资源服务器特性使用一个OAuth2RestTemplate在内部取得用户授权的详细信息。这会被提供为UserInfoRestTemplateFactory类型的@Bean。对于大多数提供者而言,默认设置就够用了,但是偶尔你可能需要加入一些额外的拦截器,或者改变请求的认证器(这个令牌如何附着在外向的请求)。为了加一个自定义模板,只需要创建一个UserInfoRestTemplateCustomizer类型的bean,这个bean会自带身份验证。除此之外,你也可以定义你自己的UserInfoRestTemplateFactory @Bean

注:在YAML文件中使用"|"管道进行分割设置一个RSA秘钥,注意key的value值要缩进(它是一个标准的YAML语言特性)。例如:

security:
    oauth2:
        resource:
            jwt:
                keyValue: |
                    -----BEGIN PUBLIC KEY-----
                    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC...
                    -----END PUBLIC KEY-----

28.3.1 客户端

为了让你的web-app加入OAuth2客户端,你可以简单地加入@EnableOAuth2Client,然后Spring Boot就会创建一个OAuth2ClientContextOAuth2ProtectedResourceDetails是创建OAuth2RestOperations必须的一个类。Spring Boot不会自动创建这样的bean,但是你可以很容易自己创建:

@Bean
public OAuth2RestTemplate oauth2RestTemplate(OAuth2ClientContext oauth2ClientContext,
        OAuth2ProtectedResourceDetails details) {
    return new OAuth2RestTemplate(details, oauth2ClientContext);
}

在配置中使用security.oauth2.client.*作为认证信息(跟你使用认证服务器是一样的),但是额外需要知道认证信息和令牌在认证服务器中的URIs。例如:

application.yml.

security:
    oauth2:
        client:
            clientId: bd1c0a783ccdd1c9b9e4
            clientSecret: 1a9030fbca47a5b2c28e92f19050bb77824b5ad1
            accessTokenUri: https://github.com/login/oauth/access_token
            userAuthorizationUri: https://github.com/login/oauth/authorize
            clientAuthenticationScheme: form

 

28.3.2 单点登录

一个OAuth2客户端可以被用来从提供者中获取用户详细信息(如果这样的属性是生效的),然后把这些用户信息转换成Spring Security的Authentication令牌。资源服务器支持基于OAuth2的单点登录的user-info-uri,Spring Boot通过@EnableOAuth2Sso注解很容易实现这一点。上述的Github客户端可以使用Github的/usr/端点保护它的所有资源和权限信息,通过加入这个注解和声明端点的位置(除了security.oauth2.client.*之外的配置已经在上面列出)。

application.yml. 

security:
    oauth2:
...
    resource:
        userInfoUri: https://api.github.com/user
        preferTokenInfo: false

因为所有的路径默认都处于保护状态,所以没有一个主页可以保存未经授权的用户并邀请他们再次登录(通过访问/login路径,或通过security.oauth2.sso.login-path指定路径)。

为了自定义项目的访问规则或路径,你可以在WebSecurityConfigurerAdapter中加入@EnableOAuth2Sso,这个注解会使得在调用/login前必须经过WebSecurityConfigurerAdapter。例如,这里我们只允许匿名通过"/"访问主页,并对一切保持默认:

@Configuration
static class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    public void init(WebSecurity web) {
        web.ignoring().antMatchers("/");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/**").authorizeRequests().anyRequest().authenticated();
    }
}

28.4 执行安全

如果执行器也在使用,你可以发现:

  • 即使应用端点是不安全的,管理端点还是安全的
  • 安全事件会被转换成AuditEvent实例,并发布到AuditEventRepository
  • 默认用户会同时拥有ACTUATOR和USER角色

执行器安全属性可以使用外部属性(management.security.*)进行修改。加入WebSecurityConfigurerAdapter类型的@Bean去重写应用的访问规则,如果你不想重写执行器访问规则,可以使用@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER),反之,则使用@Order(ManagementServerProperties.ACCESS_OVERRIDE_ORDER)

29 使用SQL数据库

Spring框架对运行SQL数据库提供了广泛的支持。如同Hibernate那样使用JdbcTemplate直接进行JDBC连接完成"对象映射关系"的技术。Spring Data提供了额外的功能,从接口直接创建Repository实现,从你的方法名按照规则生成查询。

29.1 配置一个数据源

Java的javax.sql.DataSource接口提供了一个运行数据库连接的标准方法。传统的数据源使用一个URL和一些认证信息来建立一个数据库连接。

29.1.1 内嵌的数据支持

使用一个在内存中的内嵌数据库开发应用通常是很方便的。明显地,内存数据库不会提供持久化存储,你需要在应用启动时填充你的数据库,在你的应用结束前做好丢掉数据的准备。

Spring Boot可以自动配置内嵌的H2,HSQL以及Derby数据库。你不需要提供任何连接URLs,仅需要引入一个你想要使用的内嵌数据库的依赖。

注:如果你在测试中使用这个属性,你可以会注意到,不管你使用了多少个应用上下文,同样的数据库会在你的测试用例中被反复引用。如果你想确保每个上下文都有独立的数据库,你需要设置spring.datasource.generate-unique-nametrue.

例如。典型的POM依赖如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.hsqldb</groupId>
    <artifactId>hsqldb</artifactId>
    <scope>runtime</scope>
</dependency>

注:你需要为自动配置的内嵌数据库引入spring-jdbc依赖。在本例中通过spring-boot-starter-data-jpa实现。

注:不管出于什么原因,如果你为内嵌数据库配置了连接URL,注意确保数据库的自动关闭功能是禁用的。如果你使用H2,你应该使用DB_CLOSE_ON_EXIT=FALSE来禁用自动关闭功能;如果你使用HSQLDB,确保shutdown=true没有被使用。禁用数据库的自动关闭功能使得Spring Boot可以自主控制关闭数据库的时间,从而确保它连接一次数据库之后就不需要再连接。

29.1.2 连接到一个生产数据库

生产数据库的连接同样也可以使用DataSource池进行自动配置。这是选择一个特定的实现算法:

  • 由于Tomcat出色的性能和并发性,我们更倾向于使用Tomcat。
  • 否则,如果HikariCP可用,我们也可以退而求其次选择HikariCP。
  • 如果Tomcat和HikariCP都不可用,普通的DBCP2可用,我们会选择DBCP2。

如果你使用spring-boot-starter-jdbcspring-boot-starter-data-jpa 'starters',你会自动获取到tomcat-jdbc依赖。

注:你可以忽略完整的算法并使用spring.datasource.type属性指定连接池。如果你正在默认提供的tomcat中运行应用,这是非常重要的。

注:另外连接池通常可以手动配置。如果你定义了自己的DataSource类,就不会再自动化创建这个类。

数据源配置可以通过spring.datasource.*里的外部配置属性来控制。例如,你可以在application.properties中声明以下选项:

spring.datasource.url=jdbc:mysql://localhost/test
spring.datasource.username=dbuser
spring.datasource.password=dbpass
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
  • 注:如果你不使用spring.datasource.url属性指定url,Spring Boot会尝试自动配置一个内嵌数据库。
  • 注:大多数情况下,你都不需要指定driver-class-name,因为Spring boot能从url中推断中大多数的数据库
  • 注:在创建数据源之前我们需要确保Driver类是可用的。设置spring.datasource.driver-class-name=com.mysql.jdbc.Driver可以载入Driver类。

查看DataSourceProperties以了解更多的支持选项。不管真实的实现如何,这些都是标准的选项。使用它们各自的前缀(spring.datasource.tomcat.*, spring.datasource.hikari.*, 以及spring.datasource.dbcp2.*)可以调整具体的实现。

例如,如果你正在使用Tomcat连接池,你可以自定义很多额外的设置:

# Number of ms to wait before throwing an exception if no connection is available.
spring.datasource.tomcat.max-wait=10000

# Maximum number of active connections that can be allocated from this pool at the same time.
spring.datasource.tomcat.max-active=50

# Validate the connection before borrowing it from the pool.
spring.datasource.tomcat.test-on-borrow=true

29.1.3 连接JNDI数据源

如果你在应用服务器部署Spring Boot应用,你可以希望使用应用服务器内置属性来配置和管理你的数据源,并使用JNDI访问数据源。

spring.datasource.jndi-name可以被用于取代spring.datasource.urlspring.datasource.usernamespring.datasource.password从指定的JNDI位置访问数据源。例如,以下在application.properties中如何访问一个JBosss数据源:

spring.datasource.jndi-name=java:jboss/datasources/customers

29.2 使用JdbcTemplate

Spring的JdbcTemplateNamedParameterJdbcTemplate类会被自动配置,你可以在你的类中通过@Autowire注解直接注入:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

@Component
public class MyBean {

    private final JdbcTemplate jdbcTemplate;

    @Autowired
    public MyBean(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    // ...

}

29.3 JPA和"Spring Data"

Java持久化API是一个标准的技术,它允许你把对象映射到相关的数据库。spring-boot-starter-data-jpa POM文件提供了一个通过以下key依赖快速获取started的方法:

  • Hibernate -- 最流行的JPA实现之一
  • Spring Data JPA -- 更容易实现基于JPA仓库
  • Spring ORMs -- Spring框架支持的核心ORM

29.3.1 实体类 

习惯上,JPA实体类可以在persistence.xml文件中指定。在Spring Boot中这个文件就不是必须的,取而代之的是"Entity Scanning"。默认地,所有在你主配置类下的包(使用@EnableAutoConfiguration@SpringBootApplication注解的类)都会被扫描到。

任何使用@Entity, @Embeddable@MappedSuperclass都会被考虑。一个传统的实体类如下:

package com.example.myapp.domain;

import java.io.Serializable;
import javax.persistence.*;

@Entity
public class City implements Serializable {

    @Id
    @GeneratedValue
    private Long id;

    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    private String state;

    // ... additional members, often include @OneToMany mappings

    protected City() {
        // no-args constructor required by JPA spec
        // this one is protected since it shouldn't be used directly
    }

    public City(String name, String state) {
        this.name = name;
        this.country = country;
    }

    public String getName() {
        return this.name;
    }

    public String getState() {
        return this.state;
    }

    // ... etc

}

29.3.2 Spring Data JPA仓库

Spring Data JPA仓库是可以定义访问数据的接口。JPA查询会根据你的方法名字自动创建。例如,一个CityRepository接口可能定义一个findAllByState(String state)方法去根据给出的stat找出所有的城市。

你可以使用Spring Data的Query注解实现更复杂的查询。

Spring Data仓库通常是从RepositoryCrudRepository接口扩展得到。如果你习惯于自动配置,仓库会从包含了你主配置类的包被搜索到(带有@EnableAutoConfiguration@SpringBootApplication注解的类)。

下面是传统的Spring Data仓库:

package com.example.myapp.domain;

import org.springframework.data.domain.*;
import org.springframework.data.repository.*;

public interface CityRepository extends Repository<City, Long> {

    Page<City> findAll(Pageable pageable);

    City findByNameAndCountryAllIgnoringCase(String name, String country);

}

29.3.3 创建和删除JPA数据库

默认地,JPA数据库仅在你使用内嵌数据库(H2, HSQL or Derby)时被自动创建。你可以使用spring.jpa.*属性明确地配置JPA设置。例如,如果要创建或者删除数据库表,你可以在你的application.properties文件中加入以下语句:

spring.jpa.hibernate.ddl-auto=create-drop

Hibernate内置的该属性名为hibernate.hbm2ddl.auto。你可以使用Hibernate本地属性spring.jpa.properties.*来设置它。例如:

spring.jpa.properties.hibernate.globally_quoted_identifiers=true

通过hibernate.globally_quoted_identifiers对Hibernate实体类进行管理。

默认地,DDL的执行(或验证)会在ApplicationContext启动之后。也有spring.jpa.generate-ddl标志,但是如果Hibernate自动配置是激活的,它不会被使用,因为ddl-auto设置更加细致。

29.3.4 打开EntityManager的视图

如果你正在运行一个web应用,Spring Boot会默认注册OpenEntityManagerInViewInterceptor去应用"打开EntityManager的视图"模式,例如允许web视图懒加载。如果你不想允许这种行为,应该在你的application.propertiesspring.jpa.open-in-view设置为false

29.4 使用H2的web控制台

H2数据库为你提供了一个Spring Boot自动配置好的基于浏览器的控制台。当以下条件得到满足,控制台将会自动配置:

  • 你正在开发一个web应用
  • com.h2database:h2在类路径下
  • 你正在使用Spring Boot开发者工具

注:如果你没有使用Spring Boot的开发者工具,但仍然希望使用H2的控制台,你可以配置spring.h2.console.enabledtrue。H2的控制台仅仅在开发期间使用,所以在生产环境要注意把spring.h2.console.enabled设置为false

29.4.1 改变H2控制台的路径

默认地,控制台会在/h2-console路径下可使用,你可以使用spring.h2.console.path属性自定义控制台的路径。

29.4.2 管理H2控制台

当Spring的权限管理在类路径下并且基础的权限验证可用的情况下,H2的控制台会自动配置基础的权限验证。以下属性可以被用来自定义权限管理配置:

  • security.user.role
  • security.basic.authorize-mode
  • security.basic.enabled

29.5 使用jOOQ

Java面向对象的查询(jOOQ)是一个受欢迎的产品,它可以从你的数据库生成Java代码,并通过它完整的API生成类型安全的SQL查询语句。Spring Boot的商业版和开源特别版都支持jOOQ。

29.5.1 代码生成

为了使用类型安全的JOOQ查询,你需要从你的数据库模型中生成java类。你可以按照JOOQ的用书手册的说明去操作。如果你正在使用jooq-codegen-maven插件(并且你也在使用spring-boot-starter-parent这个父类POM文件),你可以放心地忽略插件的版本号。你同样可以使用Spring Boot定义可用的版本号(例如 h2.version)去声明插件的数据库依赖。如下:

<plugin>
    <groupId>org.jooq</groupId>
    <artifactId>jooq-codegen-maven</artifactId>
    <executions>
        ...
    </executions>
    <dependencies>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>${h2.version}</version>
        </dependency>
    </dependencies>
    <configuration>
        <jdbc>
            <driver>org.h2.Driver</driver>
            <url>jdbc:h2:~/yourdatabase</url>
        </jdbc>
        <generator>
            ...
        </generator>
    </configuration>
</plugin>

29.5.2 使用DSLContext

jOOQ提供的完整的API通过org.jooq.DSLContext接口加载。Spring Boot会自动配置一个DSLContext作为一个Spring类,并且把它连接到你的应用的DataSource。你仅需通过@Autowire注入,就可以使用DSLContext

@Component
public class JooqExample implements CommandLineRunner {

    private final DSLContext create;

    @Autowired
    public JooqExample(DSLContext dslContext) {
        this.create = dslContext;
    }

}

注:jOOQ手册尝试使用一个名为create的变量去定义DSLContext,我们就按照示例进行定义。

你可以使用DSLContext构建查询:

public List<GregorianCalendar> authorsBornAfter1980() {
    return this.create.selectFrom(AUTHOR)
        .where(AUTHOR.DATE_OF_BIRTH.greaterThan(new GregorianCalendar(1980, 0, 1)))
        .fetch(AUTHOR.DATE_OF_BIRTH);
}

29.5.3 自定义jOOQ
你可以设置application.properties中的jOOQ属性spring.jooq.sql-dialect来自定义方言。例如,指定你将要引入的数据库:

spring.jooq.sql-dialect=Postgres

当jOOQ配置被创建时,通过定义@Bean可以进行更高级的定制。你可以为以下jOOQ类型定义beans:

  • ConnectionProvider
  • TransactionProvider
  • RecordMapperProvider
  • RecordListenerProvider
  • ExecuteListenerProvider
  • VisitListenerProvider

如果你想完全控制jOOQ配置,你可以创建你自己的org.jooq.Configuration @Bean

30 使用NoSQL技术

Spring Data支持MongoDB, Neo4J, Elasticsearch, Solr, Redis, Gemfire, Cassandra, Couchbase和LDAP等类型的NoSQL技术。

30.1 Redis

Redis是一个高速缓存,消息代理和特色丰富的key-value存储仓库。Spring Boot为Jedis客户端库提供了基础的自动配置并提取Spring Data Redis为它提供的内容。只需要简单地引入spring-boot-starter-data-redis "Starter",就可以使用Redis。

30.1.1 连接到Redis

你可以在任一个的Spring Bean中注入RedisConnectionFactory, StringRedisTemplateRedisTemplate接口。默认地,这些接口都会尝试使用localhost:6379去连接Redis服务器。

@Component
public class MyBean {

    private StringRedisTemplate template;

    @Autowired
    public MyBean(StringRedisTemplate template) {
        this.template = template;
    }

    // ...

}

如果你注入任意一个自动配置类型的@Bean,它会取代默认的@Bean(排除RedisTemplate的情况,因为RedisTemplate必须根据bean的名字注入,不能按类型注入)。

30.2 MongoDB

MongoDB是一个开源的NoSQL文档型数据库,它使用的是JSON类型的数据结构而不是传统的基于表的关联数据。引入spring-boot-starter-data-mongodbspring-boot-starter-data-mongodb-reactive,就可以使用MongoDB。

30.2.1 连接到MongoDB数据库

你可以注入org.springframework.data.mongodb.MongoDbFactory去访问Mongo数据库。默认地,这个实例会尝试使用mongodb://localhost/test连接一个MongoDB服务器:

import org.springframework.data.mongodb.MongoDbFactory;
import com.mongodb.DB;

@Component
public class MyBean {

    private final MongoDbFactory mongo;

    @Autowired
    public MyBean(MongoDbFactory mongo) {
        this.mongo = mongo;
    }

    // ...

    public void example() {
        DB db = mongo.getDb();
        // ...
    }

}

你可以设置spring.data.mongodb.uri来改变URL,并配置额外的设置例如副本集:

spring.data.mongodb.uri=mongodb://user:[email protected]:12345,mongo2.example.com:23456/test

另外,只要你正在使用Mongo 2.x版本,就可以指定一个 host/port。例如,你可以在application.properties中定义以下内容:

spring.data.mongodb.host=mongoserver
spring.data.mongodb.port=27017
  • 注:在Mongo 3.0及以上版本不支持spring.data.mongodb.hostspring.data.mongodb.port。在这种情况下,spring.data.mongodb.uri应该被用于所有的配置。
  • 注:如果不指定spring.data.mongodb.port,默认是27017
  • 注:如果你不使用Spring Data Mongo,你可以注入com.mongodb.Mongo替代MongoDbFactory

如果你想完全控制建立MongoDB连接,可以定义自己的MongoDbFactoryMongo类。

30.2.2 MongoTemplate

Spring Data Mongo提供了一个在设计上跟Spring的JdbcTemplate非常相似的MongoTemplate类。和JdbcTemplate一样,Spring Boot注入MongoTemplate的方式很简单:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Component;

@Component
public class MyBean {

    private final MongoTemplate mongoTemplate;

    @Autowired
    public MyBean(MongoTemplate mongoTemplate) {
        this.mongoTemplate = mongoTemplate;
    }

    // ...

}

30.2.3 Spring Data MongoDB仓库

Spring Data引入了支持MongoDB的仓库。跟我们之前讨论过的JPA仓库一样,MongoDB会根据方法名自动构建查询。

实际上,Spring Data JPA和Spring Data MongoDB共享公共的基础配置;所以你可以把早期的JPA例子中的City假设为一个Mongo数据而不是一个JPA @Entuty,然后他们工作原理是一样的。

package com.example.myapp.domain;

import org.springframework.data.domain.*;
import org.springframework.data.repository.*;

public interface CityRepository extends Repository<City, Long> {

    Page<City> findAll(Pageable pageable);

    City findByNameAndCountryAllIgnoringCase(String name, String country);

}

30.2.4 内嵌的Mongo

Spring Boot为内嵌的Mongo提供了自动配置。在Spring Boot应用中只需要加入de.flapdoodle.embed:de.flapdoodle.embed.mongo依赖。

使用spring.data.mongodb.port属性可以配置Mongo的监听端口,设置为0表示使用随机分配端口。MongoAutoConfiguration创建的MongoClient会使用随机分配的端口自动连接。

  • 注:如果你不配置一个自定义端口,内嵌的Mongo会默认使用一个随机端口(而不是27017)。

如果你类路径下有SLF4J,Mongo的日志输出会自动路由到一个名为org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongo的日志文件中。

你可以声明自己的IMongodConfigIRuntimeConfig来控制Mongo的日志路由。

30.3 Neo4j

Neo4j是一个开源的NoSQL图表数据库,它使用一个首先类关系的富数据模型节点,比传统关系型途径更适合连接大数据。引入spring-boot-starter-data-neo4j 'Starter'就可以在Spring Boot中使用Neo4j。

30.3.1 连接到Neo4j数据库

你可以像注入其他Spring类一样注入Neo4jSession, SessionNeo4jOperations实例。默认地,这个实例会尝试使用localhost:7474连接Neo4j服务器:

@Component
public class MyBean {

    private final Neo4jTemplate neo4jTemplate;

    @Autowired
    public MyBean(Neo4jTemplate neo4jTemplate) {
        this.neo4jTemplate = neo4jTemplate;
    }

    // ...

}

加入自定义的org.neo4j.ogm.config.Configuration @Bean就可以完全控制配置。同样地,加入Neo4jOperations类型的@Bean可以禁用自动配置。

通过spring.data.neo4j.*配置文件可以配置用户和权限信息:

spring.data.neo4j.uri=http://my-server:7474
spring.data.neo4j.username=neo4j
spring.data.neo4j.password=secret

30.3.2使用嵌入模式

如果你在应用中加入org.neo4j:neo4j-ogm-embedded-driver依赖,Spring Boot会自动配置一个进程内的Neo4j内嵌实例,在你的应用关闭时,它不会持久化任何数据。你可以使用spring.data.neo4j.embedded.enabled=false禁用该模式。你同样可以为嵌入模式开启持久化:

spring.data.neo4j.uri=file://var/tmp/graph.db

30.3.3 Neo4jSession

默认地,如果你正在运行一个web应用,session会绑定到线程中处理请求(例如"Open Session in View"模式)。

30.3.4 Spring Data Neo4j仓库

Spring Data提供了支持Neo4j的仓库:

实际上,Spring Data JPA和Spring Data Neo4j共享公共的基础配置;所以你可以把早期的JPA例子中的City假设为一个Neo4j OGM @NodeEntity而不是一个JPA @Entuty,然后他们工作原理是一样的。

注:你可以使用@EntityScan注解自定义实体扫描位置。开启仓库支持(可选择支持@Transactional),在你的Spring配置下加入下面两个注解:

@EnableNeo4jRepositories(basePackages = "com.example.myapp.repository")
@EnableTransactionManagement

30.3.5 仓库例子

package com.example.myapp.domain;

import org.springframework.data.domain.*;
import org.springframework.data.repository.*;

public interface CityRepository extends GraphRepository<City> {

    Page<City> findAll(Pageable pageable);

    City findByNameAndCountry(String name, String country);

}

30.4 Gemfire

Spring Data Gemfire提供了方便的Spring友好工具去访问Pivotal Gemfire数据管理平台。使用spring-boot-starter-data-gemfire 'Starter'可以方便地获取到依赖。目前并没有自动配置支持Gemfire,但是你可以启用Spring Boot仓库的简单注解(@EnableGemfireRepositories)。

30.5 Solr(全文检索)

Apache全文检索是一个搜索引擎。Spring Boot为Solr5客户端库提供基础自动配置,引入spring-boot-starter-data-solr "Starter"以使用Solr。

30.5.1 连接到Solr

你可以像注入其他Spring Bean一样注入一个自动配置的SolrClient实例。默认地,这个实例会试图使用localhost:8983/solr去连接服务器:

@Component
public class MyBean {

    private SolrClient solr;

    @Autowired
    public MyBean(SolrClient solr) {
        this.solr = solr;
    }

    // ...

}

如果你创建了一个SolrClient类型的@Bean,它会取代默认的。

30.5.2 Spring Data Solr仓库

Spring Data引入支持Apache Solr的仓库。正如前面讨论的JPA仓库,基础的原理是根据你方法名创建查询。

实际上,Spring Data JPA和Spring Data Solr共享相同的普通基础结构;所以你可以把早期的JPA例子中的City假设为一个@SolrDocument类而不是一个JPA @Entuty,然后他们工作原理是一样的。

30.6 Elasticsearch(搜索引擎解决方案)

Elasticsearch是一个开源的,分布式的,实时搜索和分析的引擎。Spring Boot为Elasticsearch提供了基础的自动配置,只需要简单地引入spring-boot-starter-data-elasticsearch "Starter"。

30.6.1 使用Jest连接到Elasticsearch

如果在类路径下有Jest,你可以注入一个自动配置的JestClient,默认指向localhost:9200。你可以进一步优化客户端配置:

spring.elasticsearch.jest.uris=http://search.example.com:9200
spring.elasticsearch.jest.read-timeout=10000
spring.elasticsearch.jest.username=user
spring.elasticsearch.jest.password=secret

你也可以注册任意数量的实现HttpClientConfigBuilderCustomizer的beans去更好地自定义配置。下面例子调整了附加的HTTP设置:

static class HttpSettingsCustomizer implements HttpClientConfigBuilderCustomizer {

    @Override
    public void customize(HttpClientConfig.Builder builder) {
        builder.maxTotalConnection(100).defaultMaxTotalConnectionPerRoute(5);
    }

}

 

30.6.2 使用Spring Data连接到Elasticsearch 

你可以想注入其他Spring Bean一样,注入一个自动配置的ElasticsearchTemplate或Elasticsearch的Client实例。默认地,这个实例会内嵌一个本地的内存服务器(Elasticsearch中的一个节点),并且会使用现有的工作目录作为服务器的主目录。在这个步骤中,首先得告诉Elasticsearch文件应该存储到哪里:

spring.data.elasticsearch.properties.path.home=/foo/bar

或者,你可以通过一个逗号隔开的"host:port"列表切换到远程服务器:

spring.data.elasticsearch.cluster-nodes=localhost:9300

@Component
public class MyBean {

    private ElasticsearchTemplate template;

    @Autowired
    public MyBean(ElasticsearchTemplate template) {
        this.template = template;
    }

    // ...

}

如果你定义了ElasticsearchTemplate@Bean,它会取代默认的。

猜你喜欢

转载自my.oschina.net/u/2484728/blog/826713