Preface
Spring Boot作为当下最流行的微服务项目构建基础, 有的时候我们根本不需要额外的配置就能够干很多的事情, 这得益于它的一个核心理念: “习惯优于配置”. . .
说白的就是大部分的配置都已经按照
最佳实践的编程规范配置好了本文基于 Spring Boot 2的学习杂记, 还是与1.X版本还是有一定区别的
构建依赖版本管理工程
学习Demo: https://github.com/masteranthoneyd/spring-boot-learning
为什么要分开为两个工程?因为考虑到common工程也需要版本控制, 但parent工程中依赖了common工程, 所以common工程不能依赖parent工程(循环依赖), 故例外抽离出一个dependencies的工程, 专门用作依赖版本管理, 而parent工程用作其他子工程的公共依赖.
依赖版本管理工程
跟下面父工程一样只有一个pom.xml
https://github.com/masteranthoneyd/spring-boot-learning/tree/master/spring-boot-parent-dependencies
父工程
https://github.com/masteranthoneyd/spring-boot-learning/blob/master/spring-boot-parent/pom.xml
说明:
<packaging>
为pom
表示此会被打包成 pom 文件被其他子项目依赖.- 由于 Spring Boot 以及集成了
maven-surefire-plugin
插件, 跳过测试只需要在 properties中添加<maven.test.skip>true</maven.test.skip>
即可, 等同mvn package -Dmaven.test.skip=true
, 也可使用<skipTests>true</skipTests>
, 两者的区别在于<maven.test.skip>
标签连.class
文件都不会生成, 而<skipTests>
会编译生成.class
文件
- 子项目会继承父项目的
properties
, 若子项目重新定义属性, 则会覆盖父项目的属性. <dependencyManagement>
管理依赖版本, 不使用<parent>
来依赖 Spring Boot, 可以使用上面方式, 添加<type>
为pom
以及<scope>
为import
.<pluginManagement>
的功能类似于<dependencyManagement>
, 在父项目中设置好插件属性, 在子项目中直接依赖就可以, 不需要每个子项目都配置一遍, 当然了, 子项目也可以覆盖插件属性.
打包
打包成可执行的Jar
默认情况下Spring Boot打包出来的jar包是不可执行的, 需要这样配置:
1 | <plugins> |
打包之后会发现有两个jar, 一个是本身的代码, 一个是集成了Spring Boot的可运行jar:
打包依赖了Spring Boot的工具库
只需要在打包插件spring-boot-maven-plugin
中这样配置:
1 | <build> |
打包契约类
1 | <build> |
然后指定该pom文件构建:
1 | mvn -f pom_own.xml package |
参数说明:
1 | <plugin> |
executable
: 打包出来的Jar包是否可执行, 设置为true打包是会有额外的脚本使得Jar包可直接执行.goal:repackage
: 默认的 goal, 将 Spring Boot 再次打包成可执行的Jar
配置文件: Properties 和 YAML
配置文件的生效顺序, 会对值进行覆盖
- Devtools 全局配置:当 devtools 启用时,
$HOME/.config/spring-boot
- 测试类中的
@TestPropertySource
- 测试中的
properties
属性:在 @SpringBootTest 和 用来测试特定片段的测试注解 - 命令行参数
SPRING_APPLICATION_JSON
中的属性:内嵌在环境变量或系统属性中的 JSONServletConfig
初始化参数ServletContext
初始化参数java:comp/env
中的 JNDI 属性- Java 系统属性:
System.getProperties()
- 操作系统环境变量
- 随机值(
RandomValuePropertySource
):random.*
属性 - jar 包外的指定 profile 配置文件:
application-{profile}.properties
- jar 包内的指定 profile 配置文件:
application-{profile}.properties
- jar 包外的默认配置文件:
application.properties
- jar 包内的默认配置文件:
application.properties
- 代码内的
@PropertySource
注解:用于@Configuration
类上 - 默认属性:通过设置
SpringApplication.setDefaultProperties
指定
配置随机值
1 | roncoo.secret=${random.value} |
应用简单配置
1 | #端口配置: |
导入其他配置
1 | spring: |
此时在项目或其他JAR包中应该存在application-docker-log4j2.yml
.
通过编码导入配置文件
实现 SpringApplicationRunListener
(注意构造器, 不声明会报错, 因为Spring是通过反射调用构造器的):
1 | public class MvcProfileIncludeInitializer implements SpringApplicationRunListener { |
1 | public class SystemProfileAppender { |
在 resource/META-INF/spring-factories
中添加:
1 | org.springframework.boot.SpringApplicationRunListener=\ |
这样相当于 include 了 application-mvc.yml
了.
通过 PropertySource
1 | public class YamlPropertySourceFactory implements PropertySourceFactory { |
1 |
|
配置文件-多环境配置
多环境配置的好处
- 不同环境配置可以配置不同的参数
- 便于部署, 提高效率, 减少出错
Properties多环境配置
1 | 1. 配置激活选项 |
YAML多环境配置
1 | 1.配置激活选项 |
两种配置方式的比较
- Properties配置多环境, 需要添加多个配置文件, YAML只需要一个配件文件
- 书写格式的差异, yaml相对比较简洁, 优雅
- YAML的缺点: 不能通过
@PropertySource
注解加载. 如果需要使用@PropertySource
注解的方式加载值, 那就要使用properties文件.
启动时指定环境
通过命令行参数
1 | java -jar myapp.jar --spring.profiles.active=dev |
通过 Java 系统参数
1 | java -Dspring.profiles.active=dev -jar myapp.jar |
通过OS环境变量
Spring Boot 在启动时加载环境变量 SPRING_PROFILES_ACTIVE
并将其设置为配置文件:
1 | export SPRING_PROFILES_ACTIVE=dev |
热部署
pom.xml
添加依赖:
1 | <dependencies> |
application.yml
配置文件中添加:
1 | spring: |
关于DevTools的键值如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20# DEVTOOLS (DevToolsProperties)
spring.devtools.livereload.enabled=true # Enable a livereload.com compatible server.
spring.devtools.livereload.port=35729 # Server port.
spring.devtools.restart.additional-exclude= # Additional patterns that should be excluded from triggering a full restart.
spring.devtools.restart.additional-paths= # Additional paths to watch for changes.
spring.devtools.restart.enabled=true # Enable automatic restart.
spring.devtools.restart.exclude=META-INF/maven/**,META-INF/resources/**,resources/**,static/**,public/**,templates/**,**/*Test.class,**/*Tests.class,git.properties # Patterns that should be excluded from triggering a full restart.
spring.devtools.restart.poll-interval=1000 # Amount of time (in milliseconds) to wait between polling for classpath changes.
spring.devtools.restart.quiet-period=400 # Amount of quiet time (in milliseconds) required without any classpath changes before a restart is triggered.
spring.devtools.restart.trigger-file= # Name of a specific file that when changed will trigger the restart check. If not specified any classpath file change will trigger the restart.
# REMOTE DEVTOOLS (RemoteDevToolsProperties)
spring.devtools.remote.context-path=/.~~spring-boot!~ # Context path used to handle the remote connection.
spring.devtools.remote.debug.enabled=true # Enable remote debug support.
spring.devtools.remote.debug.local-port=8000 # Local remote debug server port.
spring.devtools.remote.proxy.host= # The host of the proxy to use to connect to the remote application.
spring.devtools.remote.proxy.port= # The port of the proxy to use to connect to the remote application.
spring.devtools.remote.restart.enabled=true # Enable remote restart.
spring.devtools.remote.secret= # A shared secret required to establish a connection (required to enable remote support).
spring.devtools.remote.secret-header-name=X-AUTH-TOKEN # HTTP header used to transfer the shared secret.
当我们修改了java类后, IDEA默认是不自动编译的, 而spring-boot-devtools
又是监测classpath
下的文件发生变化才会重启应用, 所以需要设置IDEA的自动编译:
(1)File-Settings-Compiler-Build Project automatically
(2)ctrl + shift + alt + /,选择Registry,勾上 Compiler autoMake allow when app running
OK了, 重启一下项目, 然后改一下类里面的内容, IDEA就会自动去make了.
热部署可能会牺牲一定的系统性能, 因为是动态的编译
使用为Undertow作为Web容器
Spring Boot内嵌容器支持Tomcat、Jetty、Undertow.
根据 Tomcat vs. Jetty vs. Undertow: Comparison of Spring Boot Embedded Servlet Containers 这篇文章统计, Undertow的综合性能更好.在Spring Boot 2中, 已经把netty作为webflux的默认容器
与Tomcat性能对比
以下是Undertow与Tomcat简单的性能测试(同样是默认配置)
Tomcat:
Undertow:
显然Undertow的吞吐量要比Tomcat高
Maven配置
1 | <dependency> |
监听多个端口与HTTP2支持
1 | // 在@Configuration的类中添加@bean |
Undertow相关配置
1 | # Undertow 日志存放目录 |
查看依赖树
如果引入了某些jar包带有logback
依赖, log4j2会失效, 需要通过IDEA或Maven查找排除依赖:
1 | mvn dependency:tree |
创建异步方法
启动异步
1 |
|
配置完这个就已经具备异步方法功能了, 只需要在方法上面添加@Async
即可
如果被@Async
注解的方法所在类是基于接口实现的, 想要直接注入实现类, 需要添加: @EnableAsync(proxyTargetClass = true)
以使用CGLIB代理
编写异步方法
1 |
|
配置线程池
在不配置线程池的情况下, Spring默认使用SimpleAsyncTaskExecutor
, 每一次的执行任务都会使用新的线程, 性能不太好, 所以我们可以自定义线程池
直接声明线程池
1 |
|
通过使用ThreadPoolTaskExecutor
创建了一个线程池, 同时设置了以下这些参数:
- 核心线程数10: 线程池创建时候初始化的线程数
- 最大线程数20: 线程池最大的线程数, 只有在缓冲队列满了之后才会申请超过核心线程数的线程
- 缓冲队列500: 用来缓冲执行任务的队列
- 允许线程的空闲时间60秒: 当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
- 线程池名的前缀: 设置好了之后可以方便我们定位处理任务所在的线程池
- 线程池对拒绝任务的处理策略: 这里采用了
CallerRunsPolicy
策略, 当线程池没有处理能力的时候, 该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭, 则会丢弃该任务
实现AsyncConfigurer
通过这种方式, 可以对异常进行处理
AsyncConfigurer
接口有两个方法:
getAsyncExecutor()
: 提供线程池getAsyncUncaughtExceptionHandler()
: 异步任务异常处理
1 |
|
优雅关闭线程池
有时候, 存在关闭程序但还有异步任务在执行的情况, 这时候, 我们需要优雅地关闭线程池, 只需要两个参数:
1 | executor.setWaitForTasksToCompleteOnShutdown(true); |
Async使用指定线程池
如果同时实现了AsyncConfigurer
以及配置线程池, 那么@Async
默认使用AsyncConfigurer.getAsyncExecutor
的线程池.
如果需要指定线程池可以这样
1 | "threadPoolTaskExecutor") ( |
获取异步执行结果
Service:
1 | "threadPoolTaskExecutor") ( |
Controller:
1 | "/hello") ( |
执行结果:
1 | wait... |
Spring启动后执行程序的几种方式
@PostConstruct 或 InitializingBean
通过@PostConstruct
或实现InitializingBean
实现初始化bean
的时候干一些事情, 两者区别在于InitializingBean
是在属性设置完之后执行的, 所以执行顺序是在@PostConstruct
之前
由于此接口的方法
afterPropertiesSet
是在对象的所有属性被初始化后才会调用. 当Spring的配置文件中设置类初始默认为”延迟初始”(default-lazy-init="true"
, 此值默认为false
)时,类对象如果不被使用, 则不会实例化该类对象. 所以
InitializingBean
子类不能用于在容器启动时进行初始化的工作, 则应使用Spring提供的ApplicationListener
接口来进行程序的初始化工作.另外, 如果需要
InitializingBean
子类对象在Spring容器启动时就初始化并则容器调用afterPropertiesSet
方法则需要在类上增加org.springframework.context.annotation.Lazy
注解并设置为false即可(也可通过spring配置bean时添加lazy-init="false"
).
监听ContextRefreshedEvent
通过监听ContextRefreshedEvent
事件:
1 | public class ApplicationContextRefreshedEventListener implements ApplicationListener<ContextRefreshedEvent> { |
Spring的事件处理是单线程的, 所以如果一个事件被触发, 除非所有的接收者得到消息, 否则这些进程被阻止, 流程将不会继续. 因此, 如果要使用事件处理, 在设计应用程序时应小心.
Spring内置事件
以下是Spring的内置事件
Spring 内置事件 | 描述 |
---|---|
ContextRefreshedEvent | ApplicationContext 被初始化或刷新时, 该事件被发布. 这也可以在ConfigurableApplicationContext 接口中使用refresh() 方法来发生. |
ContextStartedEvent | 当使用ConfigurableApplicationContext 接口中的start() 方法启动ApplicationContext 时, 该事件被触发. 你可以查询你的数据库, 或者你可以在接受到这个事件后重启任何停止的应用程序. |
ContextStoppedEvent | 当使用ConfigurableApplicationContext 接口中的stop() 方法停止ApplicationContext 时, 该事件被触发. 你可以在接受到这个事件后做必要的清理的工作. |
ContextClosedEvent | 当使用ConfigurableApplicationContext 接口中的close() 方法关闭ApplicationContext 时, 该事件被触发. 一个已关闭的上下文到达生命周期末端;它不能被刷新或重启. |
RequestHandledEvent | 这是一个web-specific 事件, 告诉所有bean HTTP请求已经被服务. |
Spring Boot 2.0新增事件
在Spring Boot 2.0中对事件模型做了一些增强, 主要就是增加了ApplicationStartedEvent
事件, 所以在2.0版本中所有的事件按执行的先后顺序如下:
ApplicationStartingEvent
ApplicationEnvironmentPreparedEvent
ApplicationPreparedEvent
ApplicationStartedEvent
<= 新增的事件ApplicationReadyEvent
ApplicationFailedEvent
ApplicationRunner 或 CommandLineRunner
实现ApplicationRunner
或CommandLineRunner
1 |
|
ApplicationRunner
比CommandLineRunner
先执行
总结: 以上三种方式的顺序跟其序号一样
onApplicationEvent执行两次问题
applicationontext
和使用MVC之后的webApplicationontext
会两次调用上面的方法, 如何区分这个两种容器呢?
但是这个时候, 会存在一个问题, 在web 项目中(spring mvc), 系统会存在两个容器, 一个是root application context
,另一个就是我们自己的 projectName-servlet context
(作为root application context的子容器).
这种情况下, 就会造成onApplicationEvent
方法被执行两次. 为了避免上面提到的问题, 我们可以只在root application context
初始化完成后调用逻辑代码, 其他的容器的初始化完成, 则不做任何处理, 修改后代码
1 |
|
后续发现加上以上判断还是能执行两次, 不加的话三次, 最终研究结果使用以下判断更加准确:
event.getApplicationContext().getDisplayName().equals("Root WebApplicationContext")
Spring应用停止前执行程序的几种方式
监听
ContextClosedEvent
实现
DisposableBean
或使用@PostConstruct
, 执行顺序:@PostConstruct
>DisposableBean
使用ShutdownHook:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class ShutdownHook {
public static void main(String[] args) throws InterruptedException {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try (FileWriter fw = new FileWriter("hook.log")) {
// 假设记录日志/或者发送消息
fw.write("完成销毁操作,回收内存! " + (new Date()).toString());
System.out.println("退出程序...");
} catch (IOException e) {
e.printStackTrace();
}
}));
IntStream.range(0, 10).forEach(i -> {
try {
System.out.println("正在工作...");
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
Spring Bean生命周期
1 | 实例化 |
元注解与组合注解
元注解
Spring4.0的许多注解都可以用作meta annotation(元注解). 元注解是一种使用在别的注解上的注解. 这意味着我们可以使用Spring的注解组合成一个我们自己的注解.
类似于: @Documented
, @Component
, @RequestMapping
, @Controller
, @ResponseBody
等等
对于元注解, 是Spring框架中定义的部分, 都有特定的含义. 我们并不能修改, 但是对于组合注解, 我们完全可以基于自己的定义进行实现.
组合注解
自定义注解或组合注解是从其他的Spring元注解创建的, 我们先看一下@SpringBootApplication
这个神奇的注解(去除注释):
1 | (ElementType.TYPE) |
发现这个注解中有含有大量其他注解, 并使用了@AliasFor
这个注解传递注解属性值.
自定义组合注解
1 | (ElementType.TYPE) |
使用:
1 | @Rest("/ex") |
Spring AOP
AOP为Aspect Oriented Programming的缩写, 意为: 面向切面编程, 通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术. AOP是Spring框架中的一个重要内容, 它通过对既有程序定义一个切入点, 然后在其前后切入不同的执行内容, 比如常见的有: 打开数据库连接/关闭数据库连接、打开事务/关闭事务、记录日志等. 基于AOP不会破坏原来程序逻辑, 因此它可以很好的对业务逻辑的各个部分进行隔离, 从而使得业务逻辑各部分之间的耦合度降低, 提高程序的可重用性, 同时提高了开发的效率.
注解说明
实现AOP的切面主要有以下几个要素:
- 使用
@Aspect
注解将一个java类定义为切面类 - 使用
@Pointcut
定义一个切入点, 可以是一个规则表达式, 比如下例中某个package下的所有函数, 也可以是一个注解等. - 根据需要在切入点不同位置的切入内容
- 使用
@Before
在切入点开始处切入内容 - 使用
@After
在切入点结尾处切入内容 - 使用
@AfterReturning
在切入点return内容之后切入内容(可以用来对处理返回值做一些加工处理) - 使用
@Around
在切入点前后切入内容, 并自己控制何时执行切入点自身的内容 - 使用
@AfterThrowing
用来处理当切入内容部分抛出异常之后的处理逻辑
- 使用
引入依赖
与其他模块一样, 使用需要引入pom依赖:
1 | <dependency> |
引入依赖程序将自动启用AOP, 只要引入了AOP依赖后, 默认已经增加了@EnableAspectJAutoProxy
, 并且默认启用Cglib代理:
AOP顺序
由于通过AOP实现, 程序得到了很好的解耦, 但是也会带来一些问题, 比如: 我们可能会对Web层做多个切面, 校验用户, 校验头信息等等, 这个时候经常会碰到切面的处理顺序问题.
所以, 我们需要定义每个切面的优先级, 我们需要@Order(i)
注解来标识切面的优先级. i的值越小, 优先级越高.
AOP记录Web访问日志用例
日志注解
1 | @Target(ElementType.METHOD) |
别忘了加上@Retention(RetentionPolicy.RUNTIME)
声明Pointcut
1 | "execution(public * com.yangbingdong.docker.controller..*.*(..))") ( |
然后这样使用:
1 | "path() && @annotation(reqLog)") ( |
如果要很方便地获取@ReqLog
的value
, 我们可以将其绑定为参数:
1 | "execution(public * com.yangbingdong.docker.controller..*.*(..))") ( |
Pointcut匹配表达式详解可以参考: https://blog.csdn.net/elim168/article/details/78150438
如果是使用@Around
, 则方法参数应该使用ProceedingJoinPoint,
因为ProceedingJoinPoint.proceed()
可获取方法返回值, 且必须返回Object
:
1 | "logHttp()") ( |
函数式方式动态注册 Bean
Spring 5 支持在应用程序上下文中以函数式方式注册 bean. 简单地说, 您可以通过在
GenericApplicationContext
类中定义的一个新registerBean()
方法重载来完成.
看一下有哪些方法重载:
注入GenericWebApplicationContext
:
1 |
|
注册并设置bean:
1 | String beanName = lowercaseInitial(handler.getClass().getSimpleName()) + "-" + j; |
上面的 registerBean
底层是用到了 DefaultListableBeanFactory# registerBeanDefinition
, 以前的用法是这样的:
1 | BeanDefinitionBuilder beanDefinitionBuilder = |
更多动态注册请看 https://zhuanlan.zhihu.com/p/30590254
自动配置的原理与自定义starter
在自定义starter之前, 先看一下Spring Boot的一些原理
Spring Boot实现自动配置的原理
入口注解类@EnableAutoConfiguration
@SpringBootApplication
注解中包含了自动配置的入口注解:
1 | (ElementType.TYPE) |
1 | "deprecation") ( |
这个注解的Javadoc内容还是不少, 所有就不贴在文章里面了, 概括一下:
- 自动配置基于应用的类路径以及你定义了什么Beans
- 如果使用了
@SpringBootApplication
注解, 那么自动就启用了自动配置 - 可以通过设置注解的
excludeName
属性或者通过spring.autoconfigure.exclude
配置项来指定不需要自动配置的项目 - 自动配置的发生时机在用户定义的Beans被注册之后
- 如果没有和
@SpringBootApplication
一同使用, 最好将@EnableAutoConfiguration
注解放在root package的类上, 这样就能够搜索到所有子packages中的类了 - 自动配置类就是普通的Spring
@Configuration
类, 通过SpringFactoriesLoader
机制完成加载, 实现上通常使用@Conditional
(比如@ConditionalOnClass
或者@ConditionalOnMissingBean
)
@AutoConfigurationPackage
1 | (ElementType.TYPE) |
这个注解的职责就是引入了另外一个配置类: AutoConfigurationPackages.Registrar
.
1 | /** |
这个注解实现的功能已经比较底层了, 调试看看上面的register方法什么会被调用:
调用参数中的packageNames
数组中仅包含一个值: com.example.demo
, 也就是项目的root package名.
从调用栈来看的话, 调用register
方法的时间在容器刷新期间:
refresh
-> invokeBeanFactoryPostProcessors
-> invokeBeanDefinitionRegistryPostProcessors
-> postProcessBeanDefinitionRegistry
-> processConfigBeanDefinitions
(开始处理配置Bean的定义) -> loadBeanDefinitions
-> loadBeanDefinitionsForConfigurationClass
(读取配置Class中的Bean定义) -> loadBeanDefinitionsFromRegistrars
(这里开始准备进入上面的register方法) -> registerBeanDefinitions
(即上述方法)
这个过程已经比较复杂了, 目前暂且不深入研究了. 它的功能简单说就是将应用的root package给注册到Spring容器中, 供后续使用.
相比而言, 下面要讨论的几个类型才是实现自动配置的关键.
@Import(EnableAutoConfigurationImportSelector.class)
@EnableAutoConfiguration
注解的另外一个作用就是引入了EnableAutoConfigurationImportSelector
:
它的类图如下所示:
可以发现它除了实现几个Aware类接口外, 最关键的就是实现了DeferredImportSelector
(继承自ImportSelector
)接口.
所以我们先来看看ImportSelector
以及DeferredImportSelector
接口的定义:
1 | public interface ImportSelector { |
这个接口的Javadoc比较长, 还是捡重点说明一下:
- 主要功能通过
selectImports
方法实现, 用于筛选需要引入的类名 - 实现了
ImportSelector
的类也可以实现一系列Aware接口, 这些Aware接口中的相应方法会在selectImports
方法之前被调用(这一点通过上面的类图也可以佐证,EnableAutoConfigurationImportSelector
确实实现了四个Aware类型的接口) ImportSelector
的实现和通常的@Import
在处理方式上是一致的, 然而还是可以在所有@Configuration
类都被处理后再进行引入筛选(具体看下面即将介绍的DeferredImportSelector
)
1 | public interface DeferredImportSelector extends ImportSelector { |
这个接口是一个标记接口, 它本身没有定义任何方法. 那么这个接口的含义是什么呢:
- 它是
ImportSelector
接口的一个变体, 在所有的@Configuration
被处理之后才会执行. 在需要筛选的引入类型具备@Conditional
注解的时候非常有用 - 实现类同样也可以实现
Ordered
接口, 来定义多个DeferredImportSelector
的优先级别(同样地,EnableAutoConfigurationImportSelector
也实现了Ordered
接口)
明确了这两个接口的意义, 下面来看看是如何实现的:
1 |
|
很明显, 核心就在于上面的步骤3:
1 | protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, |
它将实现委托给了SpringFactoriesLoader
的loadFactoryNames
方法:
1 | // 传入的factoryClass: org.springframework.boot.autoconfigure.EnableAutoConfiguration |
这段代码的意图很明确, 在第一篇文章讨论Spring Boot启动过程的时候就已经接触到了. 它会从类路径中拿到所有名为META-INF/spring.factories
的配置文件, 然后按照factoryClass
的名称取到对应的值. 那么我们就来找一个META-INF/spring.factories
配置文件看看.
META-INF/spring.factories
比如spring-boot-autoconfigure
包:
1 | # Auto Configure |
列举了非常多的自动配置候选项, 挑一个AOP相关的AopAutoConfiguration
看看究竟:
1 | // 如果设置了spring.aop.auto=false, 那么AOP不会被配置 |
这个自动配置类的作用是判断是否存在配置项:
1 | spring.aop.proxy-target-class=true |
如果存在并且值为true
的话使用基于CGLIB字节码操作的动态代理方案, 否则使用JDK自带的动态代理机制.
下面列举所有由Spring Boot提供的条件注解:
@ConditionalOnBean
@ConditionalOnClass
@ConditionalOnCloudPlatform
@ConditionalOnExpression
@ConditionalOnJava
@ConditionalOnJndi
@ConditionalOnMissingBean
@ConditionalOnMissingClass
@ConditionalOnNotWebApplication
@ConditionalOnProperty
@ConditionalOnResource
@ConditionalOnSingleCandidate
@ConditionalOnWebApplication
一般的模式, 就是一个条件注解对应一个继承自SpringBootCondition
的具体实现类.
自定义starter
看完上面描述之后, 应该不难发现, 自定义starter的关键就是META-INF/spring.factories
了, Spring Boot会在启动时加载这个文件中声明的第三方类.
自定义properties
为了给可配置的bean属性生成元数据, 我们需要引入如下jar包:
1 | <!-- 将被@ConfigurationProperties注解的类的属性注入到元数据 --> |
application.properties
:
1 | ybd.datasource.driver-class-name=com.mysql.jdbc.Driver |
生成的元数据位于jar文件中的
META-INF/spring-configurationmetadata. json
. 元数据本身并不会修改被@ConfigurationProperties
修饰的类属性, 在我的理解里元数据仅仅只是表示配置类的默认值以及java doc, 供调用者便利的了解默认配置有哪些以及默认配置的含义, 在idea里面如果有元数据则可以提供良好的代码提示功能以方便了解默认的配置.
properties接收类
1 |
|
@ConfigurationProperties
会将application.properties
中指定的前缀的属性注入到bean中
Config类
1 |
|
@Import
引入其他配置类@ConditionalOnClass
在指定类存在时该配置类生效@EnableConfigurationProperties
启用配置接受类, 通过Spring字段注入或构造器注入properties配置Bean
使Spring Boot可以自动加载配置类
在/resource
目录创建META-INF/spring.factories
:
1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ |
然后打包成Jar, 第三方Spring Boot系统通过引入这个Jar包, 会自动加载该类.
如果有需要, 可以配合@AutoConfigureAfter
, @ConditionalOnBean
, @ConditionalOnProperty
等注解控制配置是否需要加载以及加载顺序.
需要更灵活的配置可以实现Condition
或SpringBootCondition
通过@Conditional(XXXCondition.class)
实现类加载判断.
自定义Banner
新建一个banner.txt
到resources
目录下:
1 | ${AnsiColor.BRIGHT_GREEN} |
自定义favico
将自己的favicon.ico
放到src/main/resources
即可.
发送邮件
各个厂商的STMP服务以及端口请自行搜索. . .
主要pom依赖:
1 | <dependency> |
邮件模板:
1 |
|
yml:
1 | spring: |
发送邮件:
1 | .class) (SpringRunner |
手动停机
1 |
|
优雅停机
Spring Boot 2.3.0 之后支持配置方式实现优雅停机
通过配置:
1 | server: |