自己看着 google 翻译手打的 SpringBoot 的官方文档,希望对初学者有帮助。
使用 Spring Boot 可以很容易地构建出能直接运行的、独立的、生产级别的、基于 Spring 的 Java 应用程序。
让我们使用 Java 开发一个简单的 Hello World!
web 应用程序,以便体现 Spring Boot 的一些关键特性。我们将使用 Maven 构建该项目,因为大多数 IDE 都支持它。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.examplegroupId>
<artifactId>myprojectartifactId>
<version>0.0.1-SNAPSHOTversion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.6.7version>
parent>
project>
Spring Boot 提供了多个 “Starter”,可以让你方便地将 jar 添加到 classpath 下。我们的示例应用已经在 POM 的 parent
标签使用了 spring-boot-starter-parent
。spring-boot-starter-parent
是一个特殊 Starter,它提供了有用的 Maven 默认配置。此外它还提供了依赖管理功能,你可以忽略这些依赖的版本标签 version
。
其他 Starter 只提供在开发特定应用时可能需要用到的依赖。由于我们正在开发一个 web 应用,因此我们将添加一个 spring-boot-starter-web
Starter,但在此之前,让我们来看看目前拥有的。
mvn dependency:tree
[INFO] com.example:myproject:jar:0.0.1-SNAPSHOT
mvn dependency:tree
命令以树的形式打印项目的依赖。你可以看到 spring-boot-starter-parent
本身不提供依赖。我们可以在 parent
下方编辑 pom.xml
并添加 spring-boot-starter-web
依赖:
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
dependencies>
如果你再次运行 mvn dependency:tree
,将会看到现在有许多附加的依赖,包括了 Tomcat web 服务器和 Spring Boot 本身。
要完成我们的应用,我们需要创建一个 Java 文件。默认情况下,Maven 将从 src/main/java
目录下编译源代码,因此你需要创建该文件夹结构,之后添加一个名为 src/main/java/MyApplication.java
的文件:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@EnableAutoConfiguration
public class MyApplication {
@RequestMapping("/")
String home() {
return "Hello World!";
}
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
类中的第一个注解是 @RestController
,该注解被称为 stereotype 注解。它能为代码阅读者提供一些提示,对于 Spring 而言,这个类具有特殊作用。在本示例中,我们的类是一个 web @Controller
,因此 Spring 在处理传入的 web 请求时会使用它。
@RequestMapping
注解提供了 routing(路由)信息。它告诉 Spring,任何具有路径为 /
的 HTTP 请求都应映射到 home
方法。@RestController
注解告知 Spring 渲染结果字符串直接返回给调用者。
第二个类级别注解是 @EnableAutoConfiguration
。此注解告知 Spring Boot 根据你添加的 jar 依赖来 “猜测” 你想如何配置 Spring 并进行自动配置,由于 spring-boot-starter-web
添加了 Tomcat 和 Spring MVC,auto-configuration(自动配置)将假定你要开发 web 应用并相应设置了 Spring。
应用的最后一部分是 main
方法。这只是一个标准方法,其遵循 Java 规范中定义的应用程序入口点。我们的 main 方法通过调用 run
来委托 Spring Boot 的 SpringApplication
类,SpringApplication
类将引导我们的应用,启动 Spring,然后启动自动配置的 Tomcat web 服务器。我们需要将 MyApplication.class
作为一个参数传递给 run
方法来告知 SpringApplication
,它是 Spring 主组件。同时还传递 args
数组以暴露所有命令行参数。
很多 Spring Boot 开发者总是使用 @Configuration
、@EnableAutoConfiguration
和 @ComponentScan
注解标记在主类上。由于这些注解经常一起使用。Spring Boot 提供了一个更方便的 @SpringBootApplication
注解可用来替代这个组合。
@SpringBootApplication
注解相当于使用 @Configuration
、@EnableAutoConfiguration
和 @ComponentScan
及他们的默认属性:
package com.example.myapplication;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication // 相当于使用 @Configuration @EnableAutoConfiguration @ComponentScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
此时,我们的应用应该是可以工作了。由于你使用了 spring-boot-starter-parent
POM,因此你可以使用 run
来启动应用程序。在根目录下输入 mvn spring-boot:run
以启动应用:
$ mvn spring-boot:run
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.6.7)
....... . . .
....... . . . (log output here)
....... . . .
........ Started MyApplication in 2.222 seconds (JVM running for 6.514)
如果你用浏览器打开了 localhost:8080
,你应该会看到以下输出:
Hello World!
本章节将详细介绍如何使用 Spring Boot。它覆盖了诸如构建系统、自动配置和如何运行应用等主题。我们还介绍一些 Spring Boot 最佳实践。
强烈推荐你选择一个支持依赖管理的构建系统,你可以使用它将 artifact 发布到 Maven Central 仓库。我们建议你选择 Maven 或者 Gradle。虽然可以让 Spring Boot 与其他构建系统(如 Ant)配合工作,但它们不会得到特别好的支持。
每一次 Spring Boot 发行都提供了一个它所支持的依赖清单。实际上,你不需要为构建配置提供任何依赖版本,因为 Spring Boot 已经帮你管理这些了。当你升级 Spring Boot 时,这些依赖也将以一致的方式进行升级。
Maven 用户可以继承 spring-boot-starter-parent
项目以获得合适的默认值,父项目提供了以下功能:
spring-boot-dependencies
的 POM,允许你省略常见依赖的
标签要将项目配置继承 spring-boot-starter-parent
,只需要按以下方式设置 parent
:
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.0.0.RELEASEversion>
parent>
通过该设置,你还可以重写自己项目中的配置属性来覆盖个别依赖。例如,要升级到另一个 Spring Data 发行版本,你需要将以下内容添加到 pom.xml
文件中。
<properties>
<spring-data-releasetrain.version>Fowler-SR2spring-data-releasetrain.version>
properties>
不是每个人都喜欢从 spring-boot-starter-parent
继承 POM。你可能需要使用自己公司标准的父 POM,或者你可能只是希望明确地声明所有 Maven 配置。
如果你不想使用 spring-boot-starter-parent
,则仍然可以通过使用 scope=import
依赖来获得依赖管理的好处:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>2.0.0.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
如上所述,上述示例设置不会让你使用属性来覆盖个别依赖。要达到相同的目的,需要在 spring-boot-dependencies
项之前在项目的 dependencyManagement
中添加一项。例如,要升级到另一个 Spring Data 发行版,你可以将以下元素添加到 pom.xml
中:
<dependencyManagement>
<dependencies>
<!-- 覆盖 Spring Boot 提供的 Spring Data -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-releasetrain</artifactId>
<version>Fowler-SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.0.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Spring Boot 不需要任何特定的代码布局,但是有一些最佳实践是很有用的。
当一个类没有 package
声明时,它就被认为是在 default 包中。通常不鼓励使用 default 包,应该避免使用。对于使用 @ComponentScan
、@EntityScan
或者 @SpringBootApplication
注解的 Spring Boot 应用,这样可能会导致特殊问题发生,因为每一个 jar 中的每一个类将会被读取到。
我们通常建议你将主应用类放在其他类之上的根包中。@EnableAutoConfiguration
注解通常放在主类上,它隐式定义了某些项目的包搜索的基准起点。例如,如果你在编写一个 JPA 应用程序,则被 @EnableAutoConfiguration
注解的类所属的包将被用于搜索标记有 @Entity
注解的类。
使用根包还可以允许使用没有指定 basePackage
属性的 @ComponentScan
注解。如果你的主类在根包中,也可以使用 @SpringBootApplication
注解。
以下是一个经典的包结构:
com
+- example
+- myapplication
+- Application.java
|
+- customer
| +- Customer.java
| +- CustomerController.java
| +- CustomerService.java
| +- CustomerRepository.java
|
+- order
+- Order.java
+- OrderController.java
+- OrderService.java
+- OrderRepository.java
Applicatin.java
文件声明了 main
方法,附带了 @Configuration
注解。
package com.example.myapplication;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Spring Boot 支持基于 Java 的配置。虽然可以在 SpringApplication
中使用 XML 配置源,但我们通常建议主配置源为 @Configuration
类。通常,一个很好的选择是将定义了 main
方法的类作为 @Configuration
。
你不需要把所有的 @Configuration
放在一个类中。@Import
注解可用于导入其他配置类。或者,你可以使用 @ComponentScan
自动扫描所有 Spring 组件,包括 @Configuration
类。
如果你一定要使用基于 XML 的配置,你可以使用 @ImportResource
注解来加载 XML 配置文件。
Spring Boot 自动配置尝试根据你提供的 jar 依赖自动配置 Spring 应用。例如,如果 classpath 下存在 HSQLDB,并且你没有手动配置任何数据库连接 bean,那么 Spring Boot 将自动配置一个内存数据库。
你需要通过将 @EnableAutoConfiguration
或者 @SpringBootApplication
注解添加到其中一个 @Configuration
类之上以启动自动配置。
注意:你应该仅添加一个 @EnableAutoConfiguration
注解。我们通常建议你将其添加到主 @Configuration
类中。
自动配置是非侵入的,你可以随时定义自己的配置来代替自动配置的特定部分。例如,如果你添加了自己的 DataSource
bean,默认的嵌入式数据库支持将不会自动配置。
如果你需要了解当前正在应用的自动配置,以及为什么使用,请使用 --debug
开关启动应用。这样做可以为核心 logger 启用调试日志,并记录到控制台。
如果你发现正在使用不需要的自动配置类,可以通过使用 @EnableAutoConfiguration
和 exclude
属性来禁用它们。
import org.springframework.boot.autoconfigure.*;
import org.springframework.boot.autoconfigure.jdbc.*;
import org.springframework.context.annotation.*;
@Configuration
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
public class MyConfiguration {
}
如果类不在 classpath 下,你可以使用注解 excludeName
属性并指定完全类名。此外,你还可以通过 spring.autoconfigure.exclude
属性控制要排除的自动配置类列表。
你可以自由使用任何标准的 Spring Framework 技术来定义你的 bean 以及它们注入的依赖。我们发现使用 @ComponentScan
来寻找 bean 和结合 @Autowired
构造器注入可以很好地工作。
如果你按照上述的建议来组织代码,则可以添加无参的 @ComponentScan
。所有应用组件(@Component
、@Service
、@Repository
、@Controller
等)将自动注册为 Spring Bean。
以下是一个 @Service
Bean,其使用构造注入方式获取一个必需的 RiskAssessor
bean。
package com.example.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class DatabaseAccountService implements AccountService {
private final RiskAssessor riskAssessor;
@Autowired
public DatabaseAccountService(RiskAssessor riskAssessor) {
this.riskAssessor = riskAssessor;
}
// ...
}
如果 bean 中只有一个构造方法,你可以忽略掉 @Autowired
注解。
@Service
public class DatabaseAccountService implements AccountService {
private final RiskAssessor riskAssessor;
public DatabaseAccountService(RiskAssessor riskAssessor) {
this.riskAssessor = riskAssessor;
}
// ...
}
Spring Boot 包含了一套工具,可以使应用开发体验更加愉快。spring-boot-devtools
模块可包含在任何项目中,以提供额外的开发时功能。要启用 devtools 支持,只需要将模块依赖添加到你的构建配置中即可。
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<optional>trueoptional>
dependency>
dependencies>
使用 spring-boot-devtools
的应用在 classpath 下的文件发生更改时还会自动重启。这对于使用 IDE 工作而言可能是一个非常棒的功能。默认情况下,将监视 classpath 指向的所有文件夹。请注意,某些资源如静态资源和视图模板更改不会重启。
当 DevTools 监视 classpath 资源时,触发重启的唯一方式是更新 classpath。使 classpath 更新的方式取决于你使用的 IDE。在 Eclipse 中,保存修改的文件将更新 classpath,从而触发重启。在 IntelliJ IDEA 中,构建项目(Build -> Make Project
)将产生相同的效果。
自动重启功能与 LiveReload(实时重载)一起使用效果更棒。如果你使用 JRebel,自动重启将会被禁用,以支持动态类重载,但其他 devtools 功能仍然可以使用。
Spring Boot 通过使用两个类加载器来提供了重启技术。不改变的类(例如,第三方 jar)被加载到 base 类加载器中。经常处于开发状态的类被加载到 restart 类加载器中。
当应用重启时,restart 类加载器将被丢弃,并重启创建一个新的。这种方式意味着应用重启比冷启动要快得多,因为省去了 base 类加载器的处理过程,并且可以直接使用。
默认情况下,每次应用重启时,都会记录显示条件评估增量的报告。该报告展示了在你进行更改时对应用自动配置所作出的更改。
要禁用报告的日志记录,请设置以下属性:
spring.devtools.restart.log-condition-evaluation-delta=false
某些资源在更改时不一定需要触发重启。例如,Thymeleaf 模板可以实时编辑。默认情况下,更改 META-INF/maven
、/META-INF/resources
、/resources
、/static
、/public
或者 /templates
不会触发重启,但会触发 LiveReload。如果你想自定义排除项,可以使用 spring.devtools.restart.exclude
属性。例如,仅排除 /static
和 /public
,你可以设置如下内容:
spring.devtools.restart.exclude=static/**,public/**
SpringApplication
类提供了一种可通过运行 main()
方法来启动 Spring 应用的简单方式。多数情况下,你只需要委托给静态的 SpringApplication.run
方法:
public static void main(String[] args) {
SpringApplication.run(MySpringConfiguration.class, args);
}
默认情况下,INFO
会显示日志信息,包括一些相关的启动详细信息,例如启动应用程序的用户。如果你需要 INFO
之外的日志,你可以设置它。spring.main.log-startup-info
设置为 false
可以关闭应用程序日志。
SpringApplication
允许延迟初始化应用程序。当启用延迟初始化时,bean 会在需要时创建,而不是在应用程序启动期间创建。因此,启用延迟初始化可以减少应用程序启动所需的时间。在 Web 应用程序中,启用延迟初始化将导致许多与 Web 相关的 bean 在收到 HTTP 请求之前不会被初始化。
延迟初始化的一个缺点是它会延迟应用程序问题的发现。如果配置错误的 bean 被延迟初始化,那么在启动过程中将不会发生故障,并且只有在 bean 初始化时问题才会变得明显。还必须注意确保 JVM 有足够的内存来容纳所有应用程序的 bean,而不仅仅是那些在启动期间初始化的 bean。
由于这些原因,默认情况下不启用延迟初始化,建议在启用延迟初始化之前对 JVM 的堆大小进行微调。
SpringApplicationBuilder#lazyInitialization
和 SpringApplication#setLazyInitialization
可以以编程方式开启延迟初始化,或者在配置文件中设置 spring.main.lazy-initialization=true
。
如果你想禁用某些 bean 的延迟初始化,同时堆应用程序的其余部分使用延迟初始化,你可以使用 @Lazy(false)
注解将其延迟属性显示设置为 false。
如果 SpringApplication
的默认设置不符合你的想法,你可以创建本地实例进行定制化。
public static void main(String[] args) {
SpringApplication app = new SpringApplication(MySpringConfiguration.class);
app.setBannerMode(Banner.Mode.OFF);
app.run(args);
}
如果你需要构建一个有层级关系的 ApplicationContext
(具有父子关系的多上下文),或者偏向使用 fluent(流式)构建起 API,可以使用 SpringApplicationBuilder
。
SpringApplicationBuilder
运行你链式调用多个方法,包括能构建出具有层次结构的 parent
和 child
方法。
当部署在平台上时,应用程序可以使用 Kubernates Probes 等基础设施向平台提供有关其可用性的信息。Spring Boot 包括对常用 ”liveness“ 和 ”readiness“ 可用性状态的开箱即用支持。如果你使用 Spring Boot 的执行器支持,那么这些状态将作为健康端点组公开。
此外,你还可以通过将 ApplicationAvailability
接口注入到自己的 bean 中来获取可用性状态。
应用程序组件可以随时检索当前的可用性状态,方法是注入 ApplicationAvailability
接口并在其上调用方法。更多时候,应用程序会想要监听状态更新或更新应用程序的状态。
例如,我们可以将应用程序的 ”Readiness“ 状态导出到一个文件中,以便 Kubernetes ”exec Probo“ 可以查看这个文件:
@Component
public class MyReadinessStateExporter {
@EventListener
public void onStateChange(AvailabilityChangeEvent<ReadinessState> event) {
switch (event.getState()) {
case ACCEPTING_TRAFFIC:
// create file /tmp/healthy
break;
case REFUSING_TRAFFIC:
// remove file /tmp/healthy
break;
}
}
}
我们还可以在应用中断且无法恢复时更新应用的状态:
@Component
public class MyLocalCacheVerifier {
private final ApplicationEventPublisher eventPublisher;
public MyLocalCacheVerifier(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void checkLocalCache() {
try {
// ...
}
catch (CacheCompletelyBrokenException ex) {
AvailabilityChangeEvent.publish(this.eventPublisher, ex, LivenessState.BROKEN);
}
}
}
除了常见的 Spring Framework 事件,比如 ContextRefreshedEvent
,SpringApplication
还会发送其他应用程序事件。
注意:在 ApplicationContext
创建之前,实际上触发了一些事件,因此你不能像 @Bean
一样注册监听器。你可以通过 SpringApplication.addListeners(...)
或者 SpringApplicationBuilder.listeners(...)
方法注册它们。
如果你希望无论应用使用何种创建方式都能自动注册这些监听器,你可以将 META-INF/spring.factories
文件添加到项目中,并使用 org.springframework.context.ApplicationListener
属性键指向你的监听器。比如:org.springframework.context.ApplicationListener=com.example.project.MyListener
。
当你运行应用时,应用程序事件将按照如下顺序发送:
ApplicationStartingEvent
Environment
被上下文使用,但是在上下文创建之前,将发送 ApplicationEnvironmentPreparedEvent
ApplicationContext
准备好并调用 ApplicationContextInitializers
,当是在加载任何 bean 定义之前,发送 ApplicationContextInitializedEvent
ApplicationPreparedEvent
ApplicationStartedEvent
ReadinessState.CORRECT
被声明后发送 AvailabilityChangeEvent
,此时应用程序被认为是存活状态ApplicationReadyEvent
ReadinessState.ACCEPTION_TRAFFIC
被声明后发送 AvailabilityChanageEvent
事件,此时应用程序准备处理请求ApplicationFailedEvent
上面列表只包含了与 SpringApplication
绑定的 SpringApplicationEvent
类型的事件,除此之外,下面两个事件将会被发布,它们在 ApplicationPreparedEvent
之前,ApplicationStartedEvent
之后被触发:
WebServer
准备好后触发 WebServerInitializedEvent
事件ApplicationContext
被刷新之后触发 ContextRefreshedEvent
事件应用程序事件的发送使用了 Spring Framework 的事件发布机制。该部分机制确保在子上下文中发布给监听器的事件也会发布给所有祖先上下文中的监听器。因此,如果你的应用程序使用有层级结构的 SpringApplication 实例,则监听器可能会收到同种类型应用程序事件的多个实例。
为了让监听器能够区分其上下文事件和后代上下文事件,你应该注入其应用程序上下文,然后将注入的上下文与事件的上下文进行比较。可以通过实现 ApplicationContextAware
来注入上下文,如果监听器是 bean,则使用 @Autowired
注入上下文。
SpringApplication
试图为你创建正确类型的 ApplicationContext
。确定 WebApplicationType
的算法非常简单:
AnnotationConfigServletWebServerApplicationContext
AnnotationConfigReactiveWebServerApplicationContext
AnnotatioinConfigApplicationContext
这意味着如果你在同一个应用程序中使用了 Spring MVC 和 Spring WebFlux,默认情况下将使用 Spring MVC。你可以通过调用 setWebApplicationType(WebApplicationType)
修改默认行为。
也可以调用 setApplicationContextClass(...)
来完全控制 ApplicationContext
类型。
如果你需要访问从 SpringAppplication.run(...)
传入的应用程序参数,可以注入一个 org.springframework.boot.ApplicationArguments
。
ApplicationArguments
接口提供了访问原始 String[]
参数以及解析后的 option
和 no-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 Enviroment
注册了一个 CommandLinePropertySource
。在运行你可以使用 @Value
注解注入单个应用参数。
如果你需要在 SpringApplication 启动时运行一些代码,可以实现 ApplicationRunner
或者 CommandLineRunner
接口。这两个接口的工作方式是一样的,都提供了一个单独的 run
方法,它将在 SpringApplication.run(...)
完成之前调用。
CommandLineRunner
接口提供了访问应用程序字符串数组形式参数的方法,而 ApplicationRunner
则使用了上述的 ApplicationArguments
接口。以下示例展示 CommandLineRunner
和 run
方法的使用:
import org.springframework.boot.*;
import org.springframework.stereotype.*;
@Component
public class MyBean implements CommandLineRunner {
public void run(String... args) {
// Do something...
}
}
如果你定义了多个 CommandLineRunner
或者 ApplicationRunner
bean,则必需指定调用顺序,你可以实现 org.springframework.core.Ordered
接口,也可以使用 org.springframework.core.annotation.Order
注解解决顺序问题。
Spring Boot 可以让你的配置外部化,以便可以在不同环境中使用相同的应用程序代码。你可以使用 properties 文件、YAML 文件、环境变量或者命令行参数来外部化配置。
可以使用 @Value
注解将属性值注解注入到 bean 中,可通过 Spring 的 Environment
方法,或者通过 @ConfigurationProperties
绑定到结构化对象。
Spring Boot 使用了一个非常特别的 PropertySource
指令,用于智能覆盖默认值。属性将按照如下顺序处理:
SpringApplication#setDefualtProperties
设置)@Configuration
类上的 @PropertySource
注解配置的属性源,请注意,这些属性源不会被添加到 Enviroment
直到 application context 被 refresh。application.properties
文件)RandomValuePropertySource
中的属性System.getProperties()
)java:comp/env
ServletContext
的初始化参数ServletConfig
的初始化参数SPRING_APPLICATION_JSON
属性properties
属性TestPropertySource
注解配置数据文件的加载顺序:
application.properties
文件的属性application-{profile}.properties
)application.properties
属性文件application-{profile}.properties
)默认情况下,SpringApplication
将任何命令行选项参数转换为一个 property
并将它们添加到 Spring 的 Environment
中。如前所述,命令行属性始终优先于基于文件的属性源。
如果你不想将命令行属性添加到 Environment
中,可以使用 SpringApplication.setAddCommandLineProperties(false)
禁用它们。
当你的应用程序启动时,Spring Boot 将自动从以下位置查找并加载 application.properties
或者 application.yaml
文件
classpath/
classpath/config
/config
子目录/config
直接子目录应用程序属性可以使用 spring.config.import
属性从其他位置导入更多配置数据。
例如,你的类路径 application.properties
文件中可能包含以下内容:
spring.application.name=myapp
spring.config.import=optional:file:./dev.properties
这将触发 dev.properties
当前目录中文件的导入(如果存在这样的文件)。导入的值将优先于触发导入的文件。
使用 @Value("${property}")
注解注入配置属性有时会很麻烦,特别是当你使用多个属性或你的数据本质上是分层时。Spring Boot 提供了一种使用属性的替代方法,可以让强类型 bean 管理和验证应用程序的配置。
可以绑定声明标准 JavaBean 属性的 bean,如下例所示:
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("my.service")
public class MyProperties {
private boolean enabled;
private InetAddress remoteAddress;
private final Security security = new Security();
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public InetAddress getRemoteAddress() {
return this.remoteAddress;
}
public void setRemoteAddress(InetAddress remoteAddress) {
this.remoteAddress = remoteAddress;
}
public Security getSecurity() {
return this.security;
}
public static class Security {
private String username;
private String password;
private List<String> roles = new ArrayList<>(Collections.singleton("USER"));
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
public List<String> getRoles() {
return this.roles;
}
public void setRoles(List<String> roles) {
this.roles = roles;
}
}
}
这种方式依赖于默认的空构造函数,并且 getter 和 setter 通常是强制性的。
要使用构造函数绑定,必须使用 @EnableConfigurationProperties
或配置属性扫描启动类。你不能对由常规 Spring 机制创建的 bean 使用构造函数绑定。
import java.net.InetAddress;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;
import org.springframework.boot.context.properties.bind.DefaultValue;
@ConstructorBinding
@ConfigurationProperties("my.service")
public class MyProperties {
private final boolean enabled;
private final InetAddress remoteAddress;
private final Security security;
public MyProperties(boolean enabled, InetAddress remoteAddress, Security security) {
this.enabled = enabled;
this.remoteAddress = remoteAddress;
this.security = security;
}
public boolean isEnabled() {
return this.enabled;
}
public InetAddress getRemoteAddress() {
return this.remoteAddress;
}
public Security getSecurity() {
return this.security;
}
public static class Security {
private final String username;
private final String password;
private final List<String> roles;
public Security(String username, String password, @DefaultValue("USER") List<String> roles) {
this.username = username;
this.password = password;
this.roles = roles;
}
public String getUsername() {
return this.username;
}
public String getPassword() {
return this.password;
}
public List<String> getRoles() {
return this.roles;
}
}
}
在此示例中,@ConstructorBinding
注释用于指示应使用构造函数绑定。这意味着绑定器将期望找到你希望绑定的参数的构造函数。如果你的类有多个构造函数,你可以直接在应该绑定的构造函数上使用 @ConstructorBinding
。
可以使用 @DefaultValue
在构造函数中给参数指定默认值。
Spring Boot 提供了绑定 @ConfigurationProperties
类型并将它们注册为 bean 的基础设施。你可以逐个类启用配置属性,也可以启用于组件扫描类似的配置属性扫描。
有时,带有 @ConfigurationProperties
的类可能不适合扫描,例如,如果你正在开发自己的自动配置或者你想要有条件地启用它们。在这些情况下,可以使用 @EnableConfigurationProperties
注解指定要处理的类型列表。这可以在任何 @Configuration
类上完成,如下例所示:
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SomeProperties.class)
public class MyConfiguration {
}
要使用配置属性扫描,请将 @ConfigurationPropertiesScan
注解添加到你的应用程序。通常,它被添加到带有注@SpringBootApplication
注解的主应用程序类中,它也可以添加到任何 @Configuration
类中。默认情况下,扫描将从声明注解的类的包中进行。如果要定义要扫描的特定包,可以按照以下示例进行操作:
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
@SpringBootApplication
@ConfigurationPropertiesScan({ "com.example.app", "com.example.another" })
public class MyApplication {
}
@ConfigurationProperties
除了用于注释类之外,你还可以在 @Bean
方法上使用它。当你想要将属性绑定到你无法控制的第三方组件时,这样做会特别有用,如为 DataSource 配置参数。
如以下示例所示:
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class ThirdPartyConfiguration {
@Bean
@ConfigurationProperties(prefix = "another")
public AnotherComponent anotherComponent() {
return new AnotherComponent();
}
}
Spring Boot 使用一些宽松的规则将 Environment
属性绑定到 bean,因此属性名称和 bean 属性名称 @ConfigurationProperties
之间不需要完全匹配。Environment
这很有用的常见示例包括用破折号分隔的环境属性和大小的环境属性。
例如,考虑以下 @ConfigurationProperties
类:
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "my.main-project.person")
public class MyPersonProperties {
private String firstName;
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}
使用上述代码,可以使用以下属性名称:
my.main-project.person.first-name
my.main-project.person.firstName
:驼峰式语法my.main-project.person.first_name
:下划线表示法MY_MAINPROJECT_PERSON_FIRSTNAME
:大写格式绑定 Map 属性时,你可能需要使用特殊的括号表示法,以便 key
保留原始值。
例如,考虑将以下属性绑定到一个 Map
:
my.map.[/key1]=value1
my.map.[/key2]=value2
my.map./key3=value3
以下示例为MyProperties
中的 List
配置属性:
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("my")
public class MyProperties {
private final List<MyPojo> list = new ArrayList<>();
public List<MyPojo> getList() {
return this.list;
}
}
考虑以下配置:
my.list[0].name=my name
my.list[0].description=my description
#---
spring.config.activate.on-profile=dev
my.list[0].name=my another name
对于 Map 属性,你可以绑定从多个来源提取的属性值。但是,对于多个源中的相同属性,将使用具有最高优先级的属性。以下示例公开了 Map
:
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("my")
public class MyProperties {
private final Map<String, MyPojo> map = new LinkedHashMap<>();
public Map<String, MyPojo> getMap() {
return this.map;
}
}
考虑以下配置:
my.map.key1.name=my name 1
my.map.key1.description=my description 1
#---
spring.config.activate.on-profile=dev
my.map.key1.name=dev name 1
my.map.key2.name=dev name 2
my.map.key2.description=dev description 2
Spring Boot 在绑定到 bean 时尝试将外部应用程序属性强制转换为正确的类型。如果你需要自定义类型转换,你可以提供一个 ConversionService
bean(一个名为 conversionService
的 bean) 或自定义属性编辑器(通过一个 CustomEditorConfigurer
bean)或自定义 Converters
(带有 @ConfigurationPropertiesBinding
的 bean)。
Spring Boot 专门支持表示持续时间,如果公开 java.time.Duration
属性,则应用程序属性中的以下格式可用:
long
表示java.time.Duration
10s
表示 10 秒)考虑以下示例:
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DurationUnit;
@ConfigurationProperties("my")
public class MyProperties {
@DurationUnit(ChronoUnit.SECONDS)
private Duration sessionTimeout = Duration.ofSeconds(30);
private Duration readTimeout = Duration.ofMillis(1000);
public Duration getSessionTimeout() {
return this.sessionTimeout;
}
public void setSessionTimeout(Duration sessionTimeout) {
this.sessionTimeout = sessionTimeout;
}
public Duration getReadTimeout() {
return this.readTimeout;
}
public void setReadTimeout(Duration readTimeout) {
this.readTimeout = readTimeout;
}
}
如果你更喜欢使用构造函数绑定,则可以公开相同的属性,如下例所示:
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.boot.convert.DurationUnit;
@ConfigurationProperties("my")
@ConstructorBinding
public class MyProperties {
private final Duration sessionTimeout;
private final Duration readTimeout;
public MyProperties(@DurationUnit(ChronoUnit.SECONDS) @DefaultValue("30s") Duration sessionTimeout,
@DefaultValue("1000ms") Duration readTimeout) {
this.sessionTimeout = sessionTimeout;
this.readTimeout = readTimeout;
}
public Duration getSessionTimeout() {
return this.sessionTimeout;
}
public Duration getReadTimeout() {
return this.readTimeout;
}
}
除了持续时间,Spring Boot 还可以使用 java.time.Period
类型,以下格式可用于应用程序属性:
int
表示java.time.Period
1y3d
表示 1年和3天)Spring Framework 有一个 DataSize
值类型,它以字节为单位表示大小。如果公开 DataSize
属性,则应用程序属性中的以下格式可用:
long
表示10MB
意味着 10 兆字节)考虑以下示例:
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DataSizeUnit;
import org.springframework.util.unit.DataSize;
import org.springframework.util.unit.DataUnit;
@ConfigurationProperties("my")
public class MyProperties {
@DataSizeUnit(DataUnit.MEGABYTES)
private DataSize bufferSize = DataSize.ofMegabytes(2);
private DataSize sizeThreshold = DataSize.ofBytes(512);
public DataSize getBufferSize() {
return this.bufferSize;
}
public void setBufferSize(DataSize bufferSize) {
this.bufferSize = bufferSize;
}
public DataSize getSizeThreshold() {
return this.sizeThreshold;
}
public void setSizeThreshold(DataSize sizeThreshold) {
this.sizeThreshold = sizeThreshold;
}
}
如果你更喜欢使用构造函数绑定,则可以公开相同的属性,如下列所示:
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.boot.convert.DataSizeUnit;
import org.springframework.util.unit.DataSize;
import org.springframework.util.unit.DataUnit;
@ConfigurationProperties("my")
@ConstructorBinding
public class MyProperties {
private final DataSize bufferSize;
private final DataSize sizeThreshold;
public MyProperties(@DataSizeUnit(DataUnit.MEGABYTES) @DefaultValue("2MB") DataSize bufferSize,
@DefaultValue("512B") DataSize sizeThreshold) {
this.bufferSize = bufferSize;
this.sizeThreshold = sizeThreshold;
}
public DataSize getBufferSize() {
return this.bufferSize;
}
public DataSize getSizeThreshold() {
return this.sizeThreshold;
}
}
每当使用 Spring 的 @Validated
注解时,Spring Boot 都会尝试验证类 。你可以直接在配置类上使用 JSR-303 约束注释。为此,请确保你的类路径上存在兼容的 JSR-303 实现,然后将约束注释添加到你的实现,如以下示例所示:
import java.net.InetAddress;
import javax.validation.constraints.NotNull;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
@ConfigurationProperties("my.service")
@Validated
public class MyProperties {
@NotNull
private InetAddress remoteAddress;
public InetAddress getRemoteAddress() {
return this.remoteAddress;
}
public void setRemoteAddress(InetAddress remoteAddress) {
this.remoteAddress = remoteAddress;
}
}
为确保始终为嵌套属性触发验证,即使未找到任何属性,关联字段也必须使用 @Valid
注解,以下示例基于前面的示例构建 MyProperties
:
import java.net.InetAddress;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
@ConfigurationProperties("my.service")
@Validated
public class MyProperties {
@NotNull
private InetAddress remoteAddress;
@Valid
private final Security security = new Security();
public InetAddress getRemoteAddress() {
return this.remoteAddress;
}
public void setRemoteAddress(InetAddress remoteAddress) {
this.remoteAddress = remoteAddress;
}
public Security getSecurity() {
return this.security;
}
public static class Security {
@NotEmpty
private String username;
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
}
}
Spring Profiles 提供了一个方法分离你应用程序配置的各个部分,并在确定的环境下可用。任何 @Component
,@Configuration
或者 @ConfigurationProperties
能使用 @Profile
做标记来限制什么时候被加载,如下所示:
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Configuration(proxyBeanMethods = false)
@Profile("production")
public class ProductionConfiguration {
// ...
}
你可以使用 spring.profiles.active
Environment
属性来指定哪个 profiles 处于活跃状态。
spring.profiles.active=dev,hsqldb
你也能够通过命令行来指定:--spring.profiles.active=dev.hsqldb
。
如果没有 profile 处于活跃状态,默认的 profile 将会被开启。默认的 profile 名为 default
,并且可以使用 spring.profiles.default
Environment
属性进行调整,如以下示例所示:
spring.profiles.default=none
有时候,你在应用程序中定义和使用的配置文件过于细化,使用起来很麻烦。例如,你可能拥有用于独立启用数据库和消息传递功能的配置文件 proddb
和 prodmq
。
为了解决这个问题,Spring Boot 允许你定义配置文件组。配置文件组允许你为相关的配置文件组定义逻辑名称。
例如,我们可以创建一个 production
由我们的 proddb
和 prodmq
配置文件组成的组。
spring.profiles.group.production[0]=proddb
spring.profiles.group.production[1]=prodmq
现在,我们启动应用程序时,可以指定 --spring.profiles.active=production
来激活 production
、proddb
、prodmq
配置文件。
你可以通过在应用程序运行之前调用 SpringApplicatioin.setAdditionalProfiles(...)
来以编程方式设置活动配置文件。也可以使用 Spring 的 ConfigurableEnvironment
激活配置文件。
Spring Boot 支持本地化消息,以便你的应用程序可以满足不同语言偏好的用户。默认情况下,Spring Boot 在类路径的根目录中查找 message
资源包的存在。
可以使用命名空间配置资源包的基本名称以及其他几个属性 spring.messages
,如下例所示:
spring.messages.basename=messages,config.i18n.messages
spring.messages.fallback-to-system-locale=false
在上下文中没有 Executor
bean 的情况下,Spring Boot 自动配置一个 ThreadPoolTaskExecutor
合理的默认值,这些默认值可以自动关联到异步任务执行(@EnableAsync
)和 Spring MVC 异步请求处理。
线程池使用 8 个核心线程,可以根据负载增长和收缩。这些默认设置可以使用 spring.task.execution
进行微调,如以下示例所示:
spring.task.execution.pool.max-size=16
spring.task.execution.pool.queue-capacity=100
spring.task.execution.pool.keep-alive=10s
这将线程池更改为使用有界队列,以便当队列已满(100个任务)时,线程池增加到最多16个线程。由于线程在空闲 10 秒时被回收,因此池的收缩具有侵略性。
ThreadPoolTaskScheduler
能够被自动配置如果需要被 scheduled task execution 关联。线程池默认使用一个线程,可以使用 spring.task.scheduling
进行微调,如下例所示:
spring.task.scheduling.thread-name-prefix=scheduling-
spring.task.scheduling.pool.size=2
如果需要创建自定义执行程序和调度程序,则 TaskExecutorBuilder
bean 和 TaskSchedulerBuilder
bean 在上下文都可用。
如果你在一家开发共享库的公司工作,或者如果你在开源或商业库中工作,你可能想要开发自己的自动配置。自动配置类可以捆绑在外部 jar 中,并且仍然可以被 Spring Boot 拾取。
自动配置可以与提供自动配置代码以及你将使用的典型库的 ”Starter“ 相关联。我们首先介绍了构建你自己的自动配置所需了解的内容,然后我们继续介绍创建自定义 Starter 所需的经典步骤。
在底层,自动配置是通过标准 @Configuration
类实现的。附加 @Conditional
注解用于限制何时应用自动配置。通常,自动配置类使用 @ConditionalOnClass
和 @ConditionalOnMissingBean
注解。这确保了自动配置仅在找到相关类并且你没有声明自己的@Configuration
类的时候使用。
Spring Boot 会检查你发布的 jar 中是否存在 META-INF/spring.factories
文件。这个文件应该列出你的配置类,使用 EnableAutoConfiguration
键,如下所示:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.mycorp.libx.autoconfigure.LibXAutoConfiguration,\
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration
注意:自动配置只能以这种方式加载,确保它们是在特定的包空间中定义的,并且它们永远不是组件扫描的目标。此外,自动配置类不应启用组件扫描以查找其他组件,使用 @Import
注解来代替查找其他组件。
如果你的配置需要以特定的顺序调用,你可以使用 @AutoConfigureAfter
和 @AutoConfigureBefore
注解。比如,如果你提供了特定于 web 的配置,你的类可能需要在 WebMvcAutoConfiguration
之后被调用。
如果你想排序一些没有任何关联的自动配置,你可以使用 @AutoConfigureOrder
注解,该注解与常规的 @Order
注解有相同的语义,但是为自动配置提供了专用顺序。
与标准 @Configuration
一样,自动配置类被调用的顺序只会影响它们的 beans 被定义的顺序,它们的 beans 随后被创建的顺序没有影响,beans 的创建顺序和 bean 的依赖和一些 @DependsOn
关系。
你总是想要包含一个或多个 @Conditional
注解在你的自动配置类中,@ConditionalOnMissingBean
是一个公共例子被用于允许开发者覆盖自动配置如果它们对你的默认配置不满意。
Spring Boot 包含许多 @Conditional
注解,你可以在你的代码中复用它们通过在 @Configuration
类或单独的 @Bean
方法上标注,这些注解包括:
@ConditionalOnClass
和 @ConditionalOnMissingClass
注解让 @Configuration
类被包含基于指定的类是否存在。由于注解元数据是使用 ASM 解析的,因此你可以使用 value
属性来引用真实的类,即使类可能实际上不会出现在应用程序的类路径中。你也可以使用 name
属性来指定一个字符串形式的类名。
这种机制对 @Bean
方法的应用方式不同,在 @Bean
方法中,返回类型通常是条件的目标:在方法上的条件应用之前,JVM 将加载类和可能已处理的方法引用,如果类不存在,这些方法引用将失败。
要处理此场景,可以使用单独的 @Configuration
类来隔离条件,如下例所示:
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
// Some conditions ...
public class MyAutoConfiguration {
// Auto-configured beans ...
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(SomeService.class)
public static class SomeServiceConfiguration {
@Bean
@ConditionalOnMissingBean
public SomeService someService() {
return new SomeService();
}
}
}
@ConditionalOnBean
和 @ConditionalOnMissingBean
注解通过判断指定的 bean 是否存在决定包含一个 bean。你可以使用 value
属性指定 bean 的类型或者 name
属性指定 bean 的名字。search
属性允许你限制在搜索 bean 时应考虑的 ApplicationContext
层级结构。
当位于 @Bean
方法时,目标类型默认时方法的返回类型,如下例所示:
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public SomeService someService() {
return new SomeService();
}
}
在上面的例子中,someService
bean 将会被创建,如果在 ApplicationContext
中不存在 SomeService
类型的 bean。
@ConditionalOnProperty
注解让配置被包含基于一个 Spring Environment property。使用 prefix
和 name
属性来指定应检查的属性。默认情况下,属性存在却不等于 false
将会被匹配。你也能创建更多的高级检查通过使用 havingVlaue
和 matchIfMissing
属性。
@ConditionalOnResource
注解让配置被包含基于一个指定的资源是否存在。资源能够被指定使用通常的 Spring 约定。如下例所示:
file:/home/user/test.dat
。
@ConditionalOnWebApplication
和 @ConditionalOnNotWebApplication
注解让配置被包含依赖于应用程序是否是一个 ”web application“,基于 Servlet 的 web 应用程序使用 Spring WebApplicationContext
,定义一个 Session
socpe,或者有一个 ConfigurableWebEnvironment
。reactive web 应用程序使用 ReactiveWebApplication
,或者有一个 ConfigurableReactiveWebEnvironment
。
@ConditionalOnWarDeployment
注解让配置被包含基于应用程序是否是一个传统的 WAR 应用程序部署在一个容器中,如如果应用程序是运行在内嵌服务中则条件不会匹配。
@ConditionalOnExpression
注解让配置被包含基于一个 SpEL expression
的结果。
自动配置能够被很多因素影响:用户的配置(@Bean
定义和 Environment
定制化),条件评估(一个特殊的 library 存在)等等。
具体来说,每个测试应该创建一个良好的 ApplicationContext
定义代表所有定制化的组合。ApplicationContextRunner
提供一个很好的方法来完成它。
ApplicationContextRunner
经常被定义为测试类的一个字段来收集基础公共的配置。下面例子确保了 MyServiceAutoConfiguration
一定被调用。
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MyServiceAutoConfiguration.class));
每一个测试能使用 runner 来代表一个特殊的用例。例如,下面的示例调用用户配置(UserConfiguration
)并检查自动配置是否正确退出。
@Test
void defaultServiceBacksOff() {
this.contextRunner.withUserConfiguration(UserConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(MyService.class);
assertThat(context).getBean("myCustomService").isSameAs(context.getBean(MyService.class));
});
}
@Configuration(proxyBeanMethods = false)
static class UserConfiguration {
@Bean
MyService myCustomService() {
return new MyService("mine");
}
}
这是更容易定制化 Environment
,如下所示:
@Test
void serviceNameCanBeConfigured() {
this.contextRunner.withPropertyValues("user.name=test123").run((context) -> {
assertThat(context).hasSingleBean(MyService.class);
assertThat(context.getBean(MyService.class).getName()).isEqualTo("test123");
});
}
这个 runner 也能被用于展示 ConditionEvaluationReport
,报告能够被打印在 INFO
或者 DEBUG
水平。下面例子展示了如何使用 ConditionEvaluationReportLoggingListener
来打印自动配置测试的报告。
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
class MyConditionEvaluationReportingTests {
@Test
void autoConfigTest() {
new ApplicationContextRunner()
.withInitializer(new ConditionEvaluationReportLoggingListener(LogLevel.INFO))
.run((context) -> {
// Test something...
});
}
}
还可以测试在运行时不存在特定类和包时会发生什么。Spring Boot 附带一个 FilteredClassLoader
能够很容易通过 runner 使用。如下所示,我们断言如果 MyService
不存在,自动配置可能会失效:
@Test
void serviceIsIgnoredIfLibraryIsNotPresent() {
this.contextRunner.withClassLoader(new FilteredClassLoader(MyService.class))
.run((context) -> assertThat(context).doesNotHaveBean("myService"));
}
一个经典的 Spring Boot starter 包含自动配置和自定义给定技术的基础设置的代码,我们称之为 “acme”。为了使其易于扩展,可以将装用命名空间中的许多配置键暴露给环境。最后,提供了一个 “starter” 依赖项来帮助用户尽可能轻松的开始。
具体来说,自定义启动器可以包含以下内容:
autoconfigure
模块,包含自动配置中 ”acme“ 的代码starter
模块,提供对 ”autoconfigure“ 模块的依赖关系,以及 ”acme“ 和任何其他通常有用的依赖关系。简而言之,添加 starter 应该提供开始使用该库所需的一切两个模块的这种分离是没有必要的。如果 ”acme“ 有几种风格、选项或可选功能,那么最好将自动配置分开,因为你可以清除的表达某种功能是可选的。此外,你还可以设计一个 starter,提供关于这些可选依赖项的意见。同时,其他人只能依靠自动配置模块,用不同的观点设计自己的启动器。
如果自动配置相对简单,并且没有可选功能,那么在启动器中合并两个模块肯定是一个选项。
你应该确保提供一个合适的命名空间为你的 starter。不要开始你的模块名用 spring-boot
,即使你使用不同的 Maven groupId
。我们可能提供官方支持为你自动配置的东西在将来。
作为首条规则,你应该命名一个组合模块在 starter 之后。比如,假如你为 ”acme“ 创建了一个 starter,你命名 auto-configure 模块为 acme-spring-boot
,然后 starter 为 acme-spring-boot-starter
。如果你只有一个模块组合这两个,则命名为 acme-spring-boot-starter
。
如果你的 starter 提供配置键,为它们提供一个唯一的命名空间。特别的,不要包含你的键在 Spring Boot 使用的命名空间(比如 server
,management
,spring
等等)。如果你使用相同的命名空间,我们可能修改这些命名空间在将来在不同的方式,这将破坏你的模块。作为顶层的规则,你所有的键前缀使用你自己的命名空间。
确保每一个配置键被记录通过附加字段 javadoc,如下所示:
import java.time.Duration;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("acme")
public class AcmeProperties {
/**
* Whether to check the location of acme resources.
*/
private boolean checkLocation = true;
/**
* Timeout for establishing a connection to the acme server.
*/
private Duration loginTimeout = Duration.ofSeconds(3);
public boolean isCheckLocation() {
return this.checkLocation;
}
public void setCheckLocation(boolean checkLocation) {
this.checkLocation = checkLocation;
}
public Duration getLoginTimeout() {
return this.loginTimeout;
}
public void setLoginTimeout(Duration loginTimeout) {
this.loginTimeout = loginTimeout;
}
}
这里有一些规则我们需要遵守以确保描述是一致的:
The
和 A
开始boolean
类型,描述使用 Whether
或者 Enable
开头java.time.Duration
而不是 long
,描述默认的单位如果不是毫秒,比如 ”If a duration suffix is not specified,seconds will be used“确保触发元数据生成,以便 IDE 协助也可用于 key。你可能需要查看生成的元数据(META-INF/spring configuration metadata.json
),以确保你的密钥得到了正确的记录。在兼容的 IDE 中使用自己的 starter 也是验证元数据质量的一个好主意。
autoconfigure
模块包含任何东西,哪些需要通过 library 在开始时得到的。它也可能会包含配置键定义(ConfigurationProperties
)和任何回调接口能被用于自定义组件如何初始化。
Spring Boot 使用注解处理器来收集元数据文件(META-INF/spring-autoconfigure-metadata.properties
)中的自动配置条件。如果文件存在,它将用于急切地过滤不匹配地自动配置,这将缩短启动时间。建议在包含自动配置地模块中添加以下依赖项:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-autoconfigure-processorartifactId>
<optional>trueoptional>
dependency>
如果你在应用程序中直接定义了自动配置,请确保配置 spring-boot-maven-plugin
以防止 repackage
glob 将依赖项添加到 jar 中:
<project>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-autoconfigure-processorartifactId>
exclude>
excludes>
configuration>
plugin>
plugins>
build>
project>
Starter 实际上是一个空 jar,它的唯一目的是提供必要的依赖项以使用该库。你可以将其视为对入门所需内容的固执已见。
不要对添加 Starter 的项目做出假设。如果你要自动配置的库通常需要其他启动器,请同时提及它们。如果可选依赖项的数量很高,则提供一组适当的默认依赖项可能会很困难,因为你应该避免包含对于库的典型使用而言不必要的依赖。换句话说,你不应该包含可选依赖项。
Spring Boot 默认将 /
所有访问映射到以下目录:
classpath:/static
classpath:/public
classpath:/resources
classpath:/META-INF/resources
如果你想要构建基于 servlet 的 web 应用程序,你可以体会到 Spring Boot 自动配置对于 Spring MVC 或者 Jersey 的好处。
Spring Web MVC Framework 是一个丰富的 model view controller web 框架,Spring MVC 让你创建特殊的 @Controller
或者 @RestController
beans 来处理进入的 HTTP 请求。通过使用 @RequestMapping
注解,controller 中的方法被映射到 HTTP 请求。
下面的例子展示了一个典型的 @RestController
:
import java.util.List;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/users")
public class MyRestController {
private final UserRepository userRepository;
private final CustomerRepository customerRepository;
public MyRestController(UserRepository userRepository, CustomerRepository customerRepository) {
this.userRepository = userRepository;
this.customerRepository = customerRepository;
}
@GetMapping("/{userId}")
public User getUser(@PathVariable Long userId) {
return this.userRepository.findById(userId).get();
}
@GetMapping("/{userId}/customers")
public List<Customer> getUserCustomers(@PathVariable Long userId) {
return this.userRepository.findById(userId).map(this.customerRepository::findByUser).get();
}
@DeleteMapping("/{userId}")
public void deleteUser(@PathVariable Long userId) {
this.userRepository.deleteById(userId);
}
}
Spring MVC 使用 HttpMessageConverter
接口来转换 HTTP 请求和响应,自动使用合理的默认值,比如,对象被自动转换为 JSON 或者 XML。字符串默认使用 UTF-8
编码。
如果你需要添加一个自定义 converter,你可以使用 Spring Boot 提供的 HttpMessageConverters
类,如下所示:
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
@Configuration(proxyBeanMethods = false)
public class MyHttpMessageConvertersConfiguration {
@Bean
public HttpMessageConverters customConverters() {
HttpMessageConverter<?> additional = new AdditionalHttpMessageConverter();
HttpMessageConverter<?> another = new AnotherHttpMessageConverter();
return new HttpMessageConverters(additional, another);
}
}
任何存在在 context 中的 HttpMessageConverter
会被添加到一个 converters List 中,你也可以使用此方法覆盖默认的 converters。
默认情况下,Spring Boot 处理从类路径的 /static
,/public
,/resources
,/META-INF/resources
下处理静态资源,或者从 ServletContext
的根路径。它使用 Spring MVC 的 ResourceHttpRequestHandler
,因此你可以通过添加自己的 WebMvcConfigurer
并重写 addResourceHandlers
方法来修改行为。
默认情况下,Spring Boot 提供 /error
映射来处理所有的处理,它被注册作为 servlet 容器中全局的错误页面,对于机器客户端,它提供一个 JSON 格式的响应来描述 error,HTTP status,exception message。对于浏览器客户端,它提供一个空白 error 页面。
如果你想要自定义 HTML error page 来展示被提供的 status code,你可以在 /error
目录中添加一个文件,error page 可以是一个 HTML 格式的文件,也可以是一个模板引擎。该文件的名字应该是准确的状态码或者序列掩码。
例如,要映射 404
到静态 HTML 文件,你的目录结构如下:
src/
+- main/
+- java/
| +
使用 FreeMarker 模板映射所有的 5xx
errors,你的目录结构如下:
src/
+- main/
+- java/
| +
对于更复杂的映射,你可以添加实现了 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
if (status == HttpStatus.INSUFFICIENT_STORAGE) {
// We could add custom model values here
new ModelAndView("myview");
}
return null;
}
}
对于 Servlet 应用,Spring Boot 包含了内嵌 Tomcat,Jetty 和 Undertow 服务器的支持,大多数开发者使用合适的 “Starter” 来获得一个完整的配置实例,默认情况下,内嵌服务器在 8080
端口监听 HTTP 请求。
当使用内嵌 Servlet 容器时,你可以使用 Spring Bean 或者扫描 Servlet 组件来注册 Servlets,Filters 和 List。
任何作为 Spring bean 的 Servlet
,Filter
或者 Listener
实例都会在内嵌容器中注册。
当使用内嵌 Servlet 容器时,使用 @ServletComponentScan
可以开启 Servlet 3.0 注解的扫描。标注了 @WebServlet
,WebFilter
和 @WebListener
注解的类将会被自动注册。
默认情况下,如果上下文只包含一个 Servlet,它会映射到 /.
,在多个 Servlet bean 的情况下,bean 名称用作路径前缀,过滤器映射到 /*
。
如果基于约定的映射不够灵活,你可以使用 ServletRegistrationBean
、FilterRegistrationBean
和 ServletListenerRegistrationBean
类进行完全控制。
让 Filter Bean 无序通常是正确的,如果需要特定的顺序,你应该在 Filter Bean 上添加 @Order
或者继承 Ordered
,如果你不能在 Filter 类上添加 @Order
注解或者继承 Ordered
类,你可以定义一个 FilterRegistrationBean
,使用 setOrder(int)
方法来对注册的 bean 进行排序。
内嵌 Servlet 容器不会直接执行 servlet 3.0 的 javax.servlet.ServletContainerInitializer
接口或者 Spring 的 org.springframework.web.WebApplicationInitializer
接口。这是一个有意的设计决策,旨在降低在 war 包中运行的第三方类库破坏 Spring Boot 应用程序的风险。
如果你需要在 Spring Boot 应用程序中执行 Servlet 上下文初始化,你应该注册一个实现 org.springframework.boot.web.servlet.ServletContextInitializer
接口的 bean。
在底层,Spring Boot 使用不同类型的 ApplicationContext
对内嵌 Servlet 容器提供支持,ServletWebServerApplicationContext
是一个特殊类型的 WebApplicationContext
,它通过搜索单个的 ServletWebServerFactory
bean 来引导自身。
通常情况下,TomcatServletWebServerFactory
,JettyServletWebServerFactory
和 UndertowServletWebServerFactory
已经被自动配置。
在一个内嵌容器安装过程中,ServletContext
被设置为服务启动的一部分,发生在应用程序上下文初始化期间。因为在 ApplicationContext
中的 bean 不能通过 ServletContext
可靠的初始化。解决此问题的一种方法是将注入 ApplicationContext
作为 bean 的依赖并在有需要的时候访问 ServletContext
。另一种方法是在服务器启动后使用回调,使用 ApplicationListener
监听 ApplicationStartedEvent
事件:
import javax.servlet.ServletContext;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.web.context.WebApplicationContext;
public class MyDemoBean implements ApplicationListener<ApplicationStartedEvent> {
private ServletContext servletContext;
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
ApplicationContext applicationContext = event.getApplicationContext();
this.servletContext = ((WebApplicationContext) applicationContext).getServletContext();
}
}
公共 Servlet 容器配置可以通过 Spring Environment
属性配置。通常,你可以在 application.properties
文件上配置。
公共服务配置包括:
server.port
),接口地址绑定到 server.address
server.servlet.session.persistent
),session 超时时间(server.servlet.session.timeout
),session 数据存放位置(server.servlet.session.store-dir
),session-cookie 配置(server.servlet.session.cookie.*
)server.error.path
)Spring Boot 尽可能多的暴露公共配置,但是这不总是合适的。比如,专用的命名空间提供特定服务器自定义(server.tomcat
和 server.undertow
)。
如果你需要编程配置你的内嵌 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 MyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory server) {
server.setPort(9000);
}
}
TomcatServletWebServerFactory
,JettyServletWebServerFactory
和 UndertowServletWebServerFactory
是 ConfigurableServletWebServerFactory
的专用变体,分别具有用于 Tomcat,Jetty 和 Undertow 的其他自定义 setter 方法。以下示例显示了如何自定义 TomcatServletWebServerFactory
,该示例可访问特定于 Tomcat 的配置选项:
import java.time.Duration;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;
@Component
public class MyTomcatWebServerFactoryCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
@Override
public void customize(TomcatServletWebServerFactory server) {
server.addConnectorCustomizers((connector) -> connector.setAsyncTimeout(Duration.ofSeconds(20).toMillis()));
}
}
Spring Framework 为使用 SQL 数据库提供了广泛的支持,从使用 JdbcTemplate
直接 JDBC 访问到完全 object relational mapping 技术比如 Hibernate。
Java 的 javax.sql.DataSource
接口提供了一种处理数据库连接的标准方法。传统上,DataSource 使用一个 URL 以及一些凭据来建立数据库连接。
使用内存嵌入式数据库开发应用程序通常很方便,显然,内存数据库不提供持久存储。你需要在应用程序启动时填充数据库,并准备在应用程序结束时丢弃数据库。
Spring Boot 能够自动配置嵌入式 H2,HSQL 和 Derby 数据库,你不需要提供任何连接 URL,你只需要添加你想要使用的嵌入式数据库的依赖到类路径中。如果类路径中存在多个嵌入式数据库,spring.datasource.embedded-database-connection
配置属性可以控制使用哪个数据库,设置属性为 none
表示禁用嵌入式数据库的自动配置。
比如,典型的 POM 依赖如下:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
<dependency>
<groupId>org.hsqldbgroupId>
<artifactId>hsqldbartifactId>
<scope>runtimescope>
dependency>
DataSource 配置被前缀为 spring.datasource.*
的外部配置属性控制,如下:
spring.datasource.url=jdbc:mysql://localhost/test
spring.datasource.username=dbuser
spring.datasource.password=dbpass
查看 DataSourceProperties
可以了解更多支持的选项。无论实际实现如何,这些都是标准选项。
通过它们各自的前缀,你也可以微调特定实现的设置,比如 spring.datasource.hikari.*
,spring.datasource.tomcat.*
,spring.datasource.dbcp2.*
和 spring.datasource.oracleucp.*
。
比如,如果你使用 Tomcat connection pool,你可以自定义许多附加的配置,比如:
spring.datasource.tomcat.max-wait=10000
spring.datasource.tomcat.max-active=50
spring.datasource.tomcat.test-on-borrow=true
如果你部署你的 Spring Boot 应用程序到服务器上,你可能想要使用服务器内置功能配置和管理 DataSource,并使用 JNDI 访问它。
spring.datasource.jndi-name
属性能够作为 spring.datasource.url
的替代属性。spring.datasource.username
和 spring.datasource.password
属性从指定的 JNDI 中访问 DataSource
。如下:
spring.datasource.jndi-name=java:jboss/datasources/customers
Spring 的 JdbcTemplate
和 NamedParameterJdbcTemplate
类被自动配置,你可以使用 @Autowire
将它们注入到你的 bean 中。
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
@Component
public class MyBean {
private final JdbcTemplate jdbcTemplate;
public MyBean(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void doSomething() {
this.jdbcTemplate ...
}
}
你是可以使用 spring.jdbc.template.*
属性自定义 template 的一些属性,如下:
spring.jdbc.template.max-rows=500
Java Persistence API 是一个标准技术,让你映射对象到关系型数据库。spring-boot-starter-data-jpa
POM 提供一个快速的方法来启动,它提供了下面关键依赖:
传统上,JAP Entity 被在 persistence.xml
文件中指定。通过 Spring Boot,这个文件不再是必须的并使用实体扫描来替代,默认情况下,在你主启动类类下的所有的包都会被搜索。
任何标注了 @Entity
,@Embeddable
或者 @MappedSuperclass
的类将会被扫描,如下:
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@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 should not be used directly
}
public City(String name, String state) {
this.name = name;
this.state = state;
}
public String getName() {
return this.name;
}
public String getState() {
return this.state;
}
// ... etc
}
Spring Data JPA Repositories 是一个你能够定义如何访问数据的接口。JPA 查询会根据你的方法名自动创建,比如,CityRepository
接口可以声明一个 findAllByState(String state)
方法来根据被给定的 state 来查询所有的 cities。
Spring Data Repositories 经常从 Repository
或者 CrudRepository
接口中进行扩展,如果你使用自动配置,在主启动类下的包中的 Repositories 会被自动搜索。
一个典型的 Spring Data Repository 接口定义如下:
import org.springframework.boot.docs.data.sql.jpaandspringdata.entityclasses.City;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.Repository;
public interface CityRepository extends Repository<City, Long> {
Page<City> findAll(Pageable pageable);
City findByNameAndStateAllIgnoringCase(String name, String state);
}
Spring Data JPA Repositories 提供了三种不同的启动模式:default,deferred 和 lazy,通过 spring.data.jpa.repositories.bootstrap-mode
属性来配置。
默认情况下,JPA 数据库只有在你使用一个嵌入式数据库的时候自动创建,你可以使用 spring.jpa.*
属性显示配置 JPA 设置。比如,创建和删除表你可以添加下面的行到你的配置文件 application.properties
中:
spring.jpa.hibernate.ddl-auto=create-drop
大多数应用程序在某些时候需要处理输入和输出问题。Spring Boot 提供实用程序和与一系列技术的集成,以在你需要 IO 功能时提供帮助。本节涵盖标准 IO 功能(例如缓存和验证)以及更高级的主题(例如调度和分布式事务)。我们还将介绍调用远程 REST 或 SOAP 服务以及发送电子邮件。
Spring Framework 支持透明的向应用程序添加缓存,在其核心,抽象将缓存应用于方法,从而减少基于缓存中可用信息的执行次数。缓存逻辑对应用是透明的,对调用者没有任何干扰。只要使用 @EnableCaching
注解启用缓存支持,Spring Boot 就会自动配置缓存基础结构。
简而言之,要向服务的操作添加缓存,请将相关注释添加到其方法上,如下所示:
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
@Component
public class MyMathService {
@Cacheable("piDecimals")
public int computePiDecimal(int precision) {
...
}
}
此示例演示了在可能代价高昂的操作中使用缓存。在调用 computePiDecimal
之前,抽象会在 piDecimals
缓存中查找与 i
参数匹配的条目,如果找到条目,缓存中的内容会立即返回给调用者,并且不会调用该方法。否则,调用该方法,并在返回值之前更新缓存。
缓存抽象不提供实际的存储,它依赖于 org.springframework.cache.Cache
和 org.springframework.cache.CacheManager
接口实现的抽象。
如果你尚未定义 CacheManager
类型的 bean 或者命名为 cacheResolver
的 CacheResolver
,Spring Boot 尝试发现下列的提供者:
如果 Spring Boot 自动配置了 CacheManager
,你可以在完全初始化之前通过公开实现 CacheManagerCustomizer
接口的 bean 进一步调整其配置,以下示例设置了一个标志,表示 null 值不应该被传递到底层映射。
import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizer;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MyCacheManagerConfiguration {
@Bean
public CacheManagerCustomizer<ConcurrentMapCacheManager> cacheManagerCustomizer() {
return (cacheManager) -> cacheManager.setAllowNullValues(false);
}
}
Spring Boot 为应用程序使用 Quartz Scheduler 提供了很多方便,包括 spring-boot-starter-quartz
Starter。如果 Quartz 是可用的,一个 Scheduler
会被自动配置。
下面类型的 Beans 会被自动拾取并和 Scheduler
进行关联:
JobDetail
:定义一个特殊的任务,JobDetail
实例应该使用 JobBuilder
API 构建Calendar
Trigger
:当一个特殊的 job 被触发的时候被创建默认情况下,使用内存 JobStore
,然而,你也可以使用基于 JDBC 的 Store,这需要有 DataSource bean 在你的应用程序中,并且 spring.quartz.job-store-type
属性配置了相应的值,如下:
spring.quartz.job-store-type=jdbc
当 JDBC Store 被使用的时候,schema 能够在启动的时候被初始化,如下:
spring.quartz.jdbc.initialize-schema=always
要让 Quartz 使用应用程序主 DataSource
之外的 DataSource
,声明一个 DataSource
bean,在方法上标注 @Bean
和 @QuartzDataSource
。这样可以确保指定为 Quartz 的 DataSource
被用于 SchedulerFactoryBean
和 schema 初始化。
类似的,要让 Quartz 使用应用程序主 TransactionManager
之外的 TransactionManager
,声明一个 TransactionManager
bean,在方法上标注 @Bean
和 @QuartzTransactionManager
。
默认情况下,通过配置创建的 Job 不会覆盖已经从持久 Job Store 中读取并注册的 Job,设置 spring.quartz.overwrite-existing-jobs
属性可以启用覆盖已经存在的 Job。
使用 spring.quartz
属性和 SchedulerFactoryBeanCustomizer
beans 能够自定义 Quartz Scheduler 的配置,Quartz Scheduler 的高级配置能够使用 spring.quartz.properties.*
属性配置。
Job 能够使用 setters 注入数据映射属性,常规 bean 也能通过类似的方法注入,如下:
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
public class MySampleJob extends QuartzJobBean {
private MyService myService;
private String name;
// Inject "MyService" bean
public void setMyService(MyService myService) {
this.myService = myService;
}
// Inject the "name" job data property
public void setName(String name) {
this.name = name;
}
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
this.myService.someMethod(context.getFireTime(), this.name);
}
}
如果你的应用调用远程 REST 服务,使用 Spring Boot 提供的 RestTemplate
或者 WebClient
可能会更容易。
如果你需要从你的应用程序中调用远程 REST 服务,你可以使用 Spring Framework 的 RestTemplate
类,因此 RestTemplate
实例经常需要在使用之前被自定义,Spring Boot 没有提供任何单个的自动配置 RestTemplate
bean。
然而,RestTemplateBuilder
可以被用来创建 RestTemplate
实例,RestTemplateBuilder
确保合适的 HttpMessageConverters
被应用到 RestTemplate
实例。
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class MyService {
private final RestTemplate restTemplate;
public MyService(RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.build();
}
public Details someRestCall(String name) {
return this.restTemplate.getForObject("/{name}/details", Details.class, name);
}
}
这里有三种主要的方式来自定义 RestTemplate
,使用哪种依赖于你想要自定义多广泛。
确保任何自定义的范围尽可能小,注入 RestTemplateBuilder
然后调用它的方法。每一个方法返回一个新的 RestTemplateBuilder
实例。
要进行应用程序范围的附加定制,请使用 RestTemplateCustomizer
bean,所有这些 bean 都会自动注册到 RestTemplateBuilder
中,并应用于使用它构建的任何模板。
以下示例显示了一个自定义程序,它为所有主机配置了代理的使用,除了 192.168.0.5
:
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.routing.HttpRoutePlanner;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.DefaultProxyRoutePlanner;
import org.apache.http.protocol.HttpContext;
import org.springframework.boot.web.client.RestTemplateCustomizer;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
public class MyRestTemplateCustomizer implements RestTemplateCustomizer {
@Override
public void customize(RestTemplate restTemplate) {
HttpRoutePlanner routePlanner = new CustomRoutePlanner(new HttpHost("proxy.example.com"));
HttpClient httpClient = HttpClientBuilder.create().setRoutePlanner(routePlanner).build();
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient));
}
static class CustomRoutePlanner extends DefaultProxyRoutePlanner {
CustomRoutePlanner(HttpHost proxy) {
super(proxy);
}
@Override
public HttpHost determineProxy(HttpHost target, HttpRequest request, HttpContext context) throws HttpException {
if (target.getHostName().equals("192.168.0.5")) {
return null;
}
return super.determineProxy(target, request, context);
}
}
}
最后,你可以定义自己的 RestTemplateBuilder
bean。这样做将替换自动配置的构建器,如果你希望任何 RestTemplateCustomizer
beans 被应用于你自定义构建器,就像自动配置所作的那样,请使用 RestTemplateBuilderConfiguer
,以下示例公开了 RestTemplateBuilder
与 Spring Boot 的自动配置所做的匹配,除了自定义连接,还指定了读超时时间:
import java.time.Duration;
import org.springframework.boot.autoconfigure.web.client.RestTemplateBuilderConfigurer;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MyRestTemplateBuilderConfiguration {
@Bean
public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer configurer) {
return configurer.configure(new RestTemplateBuilder()).setConnectTimeout(Duration.ofSeconds(5))
.setReadTimeout(Duration.ofSeconds(2));
}
}
如果你有 Spring WebFlux 在你的类路径中,你也可以选择使用 WebClient
来调用远程 REST 服务。和 RestTemplate
相比较,这个客户端有更多的功能并且是完全反应式的。
Spring Boot 为你创建并预配置了一个 WebClient.Builder
。强烈建议将其注入你的组件并使用它来创建 WebClient
实例,Spring Boot 正在配置该构建器以共享 HTTP 资源。
import org.neo4j.cypherdsl.core.Relationship.Details;
import reactor.core.publisher.Mono;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
@Service
public class MyService {
private final WebClient webClient;
public MyService(WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder.baseUrl("https://example.org").build();
}
public Mono<Details> someRestCall(String name) {
return this.webClient.get().uri("/{name}/details", name).retrieve().bodyToMono(Details.class);
}
}
Spring Boot 提供 WebService 自动配置,因此你所要做的就是定义你的 Endpoints
。
Spring Web Services Features 通过 spring-boot-starter-webservices
模块能够轻松访问。
通过你的 WSDLs 和 XSDs 能够自动创建 SimpleWsdl11Definition
和 SimpleXsdSchema
beans,你只需要配置它们的位置,如下:
spring.webservices.wsdl-locations=classpath:/wsdl
如果你的应用程序需要调用远程 WebService,你可以使用 WebServiceTemplate
类。
WebServiceTemplate
实例在使用之前经常需要被自定义配置,Spring Boot 没有提供任何单独自动配置 WebServiceTemplate
bean。然而,它会自动配置一个 WebServiceTemplateBuilder
,它可以用来创建 WebServiceTemplate
,如下:
import org.springframework.boot.webservices.client.WebServiceTemplateBuilder;
import org.springframework.stereotype.Service;
import org.springframework.ws.client.core.WebServiceTemplate;
import org.springframework.ws.soap.client.core.SoapActionCallback;
@Service
public class MyService {
private final WebServiceTemplate webServiceTemplate;
public MyService(WebServiceTemplateBuilder webServiceTemplateBuilder) {
this.webServiceTemplate = webServiceTemplateBuilder.build();
}
public SomeResponse someWsCall(SomeRequest detailsReq) {
return (SomeResponse) this.webServiceTemplate.marshalSendAndReceive(detailsReq,
new SoapActionCallback("https://ws.example.com/action"));
}
}
默认情况下,WebServiceTemplateBuilder
会检测合适的 WebServiceMessageSender
,WebServiceMessageSender
,在 classpath 中查找一个可用的 HTTP client,如下:
import java.time.Duration;
import org.springframework.boot.webservices.client.HttpWebServiceMessageSenderBuilder;
import org.springframework.boot.webservices.client.WebServiceTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ws.client.core.WebServiceTemplate;
import org.springframework.ws.transport.WebServiceMessageSender;
@Configuration(proxyBeanMethods = false)
public class MyWebServiceTemplateConfiguration {
@Bean
public WebServiceTemplate webServiceTemplate(WebServiceTemplateBuilder builder) {
WebServiceMessageSender sender = new HttpWebServiceMessageSenderBuilder()
.setConnectTimeout(Duration.ofSeconds(5))
.setReadTimeout(Duration.ofSeconds(2))
.build();
return builder.messageSenders(sender).build();
}
}
Spring Framework 提供了广泛的支持来集成消息系统,从简单使用 JMS API 如 JmsTemplate
到一个完整的架构来接收异步消息。Spring AMQP 提供了一个类似的功能来配置 Advanced Message Queuing Protocol,Spring Boot 也为 RabbitTemplate
和 RabbitMQ 提供了自动配置选项。Spring WebSocket 原生包括对 STOMP 消息传递的支持,Spring Boot 通过启动器和少量的自动配置来支持它。Spring Boot 还支持 Apache Kafka。
javax.jms.ConnectionFactory
接口提供一个标准方法来创建一个 javax.jms.Connection
,用于与 JMS 代理交互的标准方法。尽管 Spring 需要 ConnectionFactory
与 JMS 一起使用,但你通常不需要自己直接使用它,而是可以依赖更高级别的消息传递抽象。Spring Boot 还自动配置必要的基础设置来发送和接收消息。
当 ActiveMQ 在类路径中可用时,Spring Boot 也能配置一个 ConnectionFactory
,如果 broker 存在,则会自动启动和配置嵌入式代理。
ActiveMQ 配置由 spring.activemq.*
配置。
默认情况下,ActiveMQ 自动配置为使用 VM transport,它启动嵌入在同一 JVM 实例中的代理。
你可以使用 spring.activemq.in-memory
属性来禁用嵌入式代理,比如:
spring.activemq.in-memory=false
如果你配置代理 URL,嵌入式代理也会被禁用,如下:
spring.activemq.broker-url=tcp://192.168.1.210:9876
spring.activemq.user=admin
spring.activemq.password=secret
默认情况下,CachingConnectionFactory
使用合理的配置封装本地 ConnectionFactory
,你可以使用外部配置 spring.jms.*
来完全控制它,如下:
spring.jms.cache.session-cache-size=5
Spring Boot 能够自动配置 ConnectionFactory
,当它在 classpath 中发现 ActiveMQ Artemis 时。如果 broker 存在,一个嵌入式 broker 会被自动启动和配置。支持的模式有 embedded
的和 native
。当模式配置为后者时,Spring Boot 配置一个 ConnectionFactory
连接到本地机器上运行的代理的默认配置。
ActiveMQ Artemis 配置由 spring.artemis.*
指定,如下:
spring.artemis.mode=native
spring.artemis.broker-url=tcp://192.168.1.210:9876
spring.artemis.user=admin
spring.artemis.password=secret
如果你在一个应用服务器上运行你的应用程序,Spring Boot 尝试使用 JNDI 定位一个 JMS ConnectionFactory
。默认情况下,java:/JmsXA
和 java:/XAConnectionFactory
位置会被检查,你应该使用 spring.jms.jndi-name
属性,如果你需要指定一个可选的位置,比如:
spring.jms.jndi-name=java:/MyConnectionFactory
Spring 的 JmsTemplate
会自动配置,你可以将其自动装配到你自己的 bean 中,如下:
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Component;
@Component
public class MyBean {
private final JmsTemplate jmsTemplate;
public MyBean(JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
public void someMethod() {
this.jmsTemplate.convertAndSend("hello");
}
}
当 JMS 架构存在时,任何被标注了 @JmsListener
的 bean 会创建一个 listener endpoint。如果没有 JmsListenerContainerFactory
被定义,则会自动配置一个。如果 DestinationResolver
和 MessageConverter
或者 javax.jms.ExceptionListener
beans 被定义了,它们自动和默认的工厂相关联。
默认情况下,默认工厂是事务性的,如果存在一个 JtaTransactionManager
,它被关联到 listener container,如果没有,sessionTrasacted
标记会被启用。在后一种情况下,你可以通过添加 @Transactional
侦听器方法将本地数据存储事务与传入消息的处理相关联。这可确保在本地事务完成后确认传入消息。这还包括发送已在同一 JMS 会话上执行的响应消息。如下:
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
@Component
public class MyBean {
@JmsListener(destination = "someQueue")
public void processMessage(String content) {
// ...
}
}
如果你需要创建更多 JmsListenerContainerFactory
实例或者你想覆盖默认值,Spring Boot 提供了一个 DefaultJmsListenerContainerFactoryConfigurer
,你可以使用 DefaultJmsListenerContainerFactory
与自动配置相同的设置来初始化一个实例。
import javax.jms.ConnectionFactory;
import org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
@Configuration(proxyBeanMethods = false)
public class MyJmsConfiguration {
@Bean
public DefaultJmsListenerContainerFactory myFactory(DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
ConnectionFactory connectionFactory = getCustomConnectionFactory();
configurer.configure(factory, connectionFactory);
factory.setMessageConverter(new MyMessageConverter());
return factory;
}
private ConnectionFactory getCustomConnectionFactory() {
return ...
}
}
然后你可以在任何带注释的方法中使用工厂,如下:
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
@Component
public class MyBean {
@JmsListener(destination = "someQueue", containerFactory = "myFactory")
public void processMessage(String content) {
// ...
}
}
高级消息队列协议(AMQP)是面向消息中间件的平台中立、线路级协议。Spring AMQP 项目将核心 Spring 概念应用于基于 AMQP 的消息传递解决方案的开发。Spring Boot 为通过 RabbitMQ 使用 AMQP 提供了多种便利,包括 spring-boot-starter-amqp
Starter。
RabbitMQ 是基于 AMQP 协议的轻量级、可靠、可扩展、可移植的消息代理。Spring 使用 RabbitMQ
通过 AMQP 协议进行通信。RabbitMQ 的配置前缀为 spring.rabbitmq.*
,如下:
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=secret
或者,你可以使用如下 addresses
属性配置相同的连接:
spring.rabbitmq.addresses=amqp://admin:secret@localhost
Spring 的 AmqpTemplate
和 AmqpAdmin
是自动配置的,你可以将它们直接自动装配到你自己的 bean 中,如下列所示:
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.stereotype.Component;
@Component
public class MyBean {
private final AmqpAdmin amqpAdmin;
private final AmqpTemplate amqpTemplate;
public MyBean(AmqpAdmin amqpAdmin, AmqpTemplate amqpTemplate) {
this.amqpAdmin = amqpAdmin;
this.amqpTemplate = amqpTemplate;
}
public void someMethod() {
this.amqpAdmin.getQueueInfo("someQueue");
}
public void someOtherMethod() {
this.amqpTemplate.convertAndSend("hello");
}
}
如有必要,任何 org.springframework.amqp.core.Queue
定义为 bean 的内容都会自动用于 RabbitMQ 实例上声明相应的队列。要重试操作,你可以重启重试 AmqpTemplate
:
spring.rabbitmq.template.retry.enabled=true
spring.rabbitmq.template.retry.initial-interval=2s
发送消息到一个特定的流,指定流的名称,如下:
spring.rabbitmq.stream.name=my-stream
如果 MessageConverter
,StreamMessageConverter
或者 ProducerCustomizer
bean 被定义,它们会被自动关联到 RabbitStreamTemplate
。
如果你想要创建更多的 RabbitStreamTemplate
实例或者你想要覆盖默认的实例,Spring Boot 提供一个 RabbitStreamTemplateConfigurer
bean 来初始化一个 RabbitStreamTemplate
。
当 Rabbit 架构存在的时候,任何标注了 @RabbitListener
注解的类创建一个 listener endpoint,如果 RabbitListenerContainerFactory
没有被定义,将使用 SimpleRabbitListenerContainerFactory
,你也可以使用 spring.rabbitmq.listener.type
属性来自定义。如果 MessageConverter
或者 MessageRecoverer
bean 已经被定义,会自动和默认工厂关联。
下面的例子会创建一个 listener endpoint 在 someQueue
队列:
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class MyBean {
@RabbitListener(queues = "someQueue")
public void processMessage(String content) {
// ...
}
}
如果你需要创建更多的 RabbitListenerContainerFactory
实例或者你想要覆盖默认的,Spring Boot 提供了 SimpleRabbitListenerContainerFactoryConfigurer
和 DirectRabbitListenerContianerFactoryConfigurer
来初始化一个 SimpleRabbitListenerContainerFactory
和一个 DirectRabbitListenerContainerFactory
。
下面的配置类暴露另一个使用特定 MessageConverter
的工厂类:
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MyRabbitConfiguration {
@Bean
public SimpleRabbitListenerContainerFactory myFactory(SimpleRabbitListenerContainerFactoryConfigurer configurer) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
ConnectionFactory connectionFactory = getCustomConnectionFactory();
configurer.configure(factory, connectionFactory);
factory.setMessageConverter(new MyMessageConverter());
return factory;
}
private ConnectionFactory getCustomConnectionFactory() {
return ...
}
}
然后,你能在任何标注了 @RabbitListener
注解的方法中使用该工厂,如下:
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class MyBean {
@RabbitListener(queues = "someQueue", containerFactory = "myFactory")
public void processMessage(String content) {
// ...
}
}
Apache Kafka 被支持自动配置通过一个 spring-kafka
项目。
Kafka 配置的前缀为 spring.kafka.*
。
spring.kafka.bootstrap-servers=localhost:9092
spring.kafka.consumer.group-id=myGroup
Spring 的 KafkaTemplate
被自动配置,你可以将它注入到你自己的 bean 中,如下:
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Component;
@Component
public class MyBean {
private final KafkaTemplate<String, String> kafkaTemplate;
public MyBean(KafkaTemplate<String, String> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
public void someMethod() {
this.kafkaTemplate.send("someTopic", "Hello");
}
}
当 Apache Kafka 架构存在的时候,任何被标注了 @KafkaListener
的 bean 创建一个 listener endpoint。如果没有定义 KafkaListenerContainerFactory
,使用默认的工厂。
下面组件创建一个 listener endpoint 在 someTopic
topic 中:
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;
@Component
public class MyBean {
@KafkaListener(topics = "someTopic")
public void processMessage(String content) {
// ...
}
}
Spring Boot 包含很多附加的特性来帮助你监控和管理你的应用程序。你可以选择使用 HTTP 端点或者使用 JMX 来管理和监控你的应用程序。
spring-boot-actuator
模块提供了所有的 Spring Boot 的 Production-Ready Features。开启这个特性推荐的方法是添加 spring-boot-starter-actuator
Starter。
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
dependencies>
Actuator Endpoints 让你监控和与你的应用程序交互。Spring Boot 包括很多内建 Endpoints,你也可以添加你自己的 Endpoints。比如,health
Endpoint 提供了基础的应用程序健康信息。
你可以启动或者禁用每一个单独的 Endpoint 以及将它们暴露到 HTTP 或者 JMX 中。当一个 Endpoint 是 Enabled(启用) 和 Exposed(暴露) 时,才是可用的。内建 Endpoint 只有在可用的时候自动配置。
大多数应用程序选择 HTTP 协议暴露 Endpoint,其中 Endpoint 的 ID 和 /actuator
前缀映射到 URL。例如,默认情况下,health
端点映射到 /actuator/health
。
默认情况下,除了 shutdown
其他端点都被启用。配置 Endpoint 启用,使用 management.endpoint.
属性,下面例子启用了 shutdown
端点:
management.endpoint.shutdown.enabled=true
因为 Endpoint 可能包含敏感信息,你应该仔细考虑什么时候暴露它们,下表显示了内置 Endpoint 的默认暴露:
ID | JMX | Web |
---|---|---|
auditevents |
Yes | No |
beans |
Yes | No |
caches |
Yes | No |
conditions |
Yes | No |
configprops |
Yes | No |
env |
Yes | No |
flyway |
Yes | No |
health |
Yes | Yes |
heapdump |
N/A | No |
httptrace |
Yes | No |
info |
Yes | No |
integrationgraph |
Yes | No |
jolokia |
N/A | No |
logfile |
N/A | No |
loggers |
Yes | No |
liquibase |
Yes | No |
metrics |
Yes | No |
prometheus |
N/A | No |
quartz |
Yes | No |
scheduledtasks |
Yes | No |
sessions |
Yes | No |
shutdown |
Yes | No |
startup |
Yes | No |
threaddump |
Yes | No |
改变哪些端点暴露,使用下面特殊的 include
和 exclude
属性:
Property | Default |
---|---|
management.endpoints.jmx.exposure.exclude |
|
management.endpoints.jmx.exposure.include |
* |
management.endpoints.jmx.exposure.exclude |
|
management.endpoints.jmx.exposure.include |
health |
include
属性列出暴露端点的 ID,exclude
属性列出不被暴露端点的 ID。exclude
属性优先于 include
属性。你可以同时配置 exlude
和 include
属性。
比如,停止暴露 JMX 上的所有端点,只暴露 health
和 info
端点,使用下面的属性:
management.endpoints.jmx.exposure.include=health,info
*
表示选择所有端点。比如,暴露所有在 HTTP 上的端点,除了 env
和 beans
端点,使用下面配置:
management.endpoints.web.exposure.include=*
management.endpoints.web.exposure.exclude=env,beans