持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第30天,点击查看活动详情
一. 理解SpringBoot自动装配
1.1 @SpringBootApplication其实包含来下面三个注解
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
@EnableAutoConfiguration可以激活自动装配特性,示例:
@Controller
@EnableAutoConfiguration
public class SampleController {
@RequestMapping("/")
@ResponseBody
public String home(){
return "hello";
}
public static void main(String[] args) {
SpringApplication.run(SampleController.class,args);
}
}
@SpringBootApplication并非SpringBoot必须要的注解,不过使用它可以减少配置.
1.2 失效自动装配
- 代码配置方式
- 配置类型安全的属性方法 @EnableAutoConfiguration.exclude()
- 配置排除类名的属性方法@EnableAutoConfiguration.excludeName()
- 外部化配置方式
- 配置属性: spring.autoconfigure.exclude
总结:AutoConfigurationImportSelector读取自动装配class的流出为:
- 通过SpringFactoriesLoader#loadFactoryNames()方法读取所有META-INF/spring.factories资源中@EnableAutoConfiguration所关联的自动装配Class集合
- 读取当前配置类所标注的@EnableAutoConfiguration属性exclude和excludeName,并与spring.autoconfigure.exclude配置属性合并为自动装配Class排除集合
- 检查自动装配Class排除集合是否合法
- 排除候选自动装配Class集合中的排除名单
- 再次过滤候选自动装配Class集合中Class不存在的成员
311页
1.3 自动装配事件
(1) 实现AutoConfigurationImportListener接口
public class DefaultAutoConfigurationImportListener implements AutoConfigurationImportListener {
@Override
public void onAutoConfigurationImportEvent(AutoConfigurationImportEvent event) {
ClassLoader classLoader = event.getClass().getClassLoader();
List<String> candidates = SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, classLoader);
List<String> configurations = event.getCandidateConfigurations();
Set<String> exclusions = event.getExclusions();
System.out.printf("自动装配Class名单-候选数量:%d,实际数量:%d,排除数量:%s\n",candidates.size(),configurations.size(),exclusions.size());
}
}
(2) 新建META-INF/spring.factories并配置
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=thinking.in.spring.boot.listener.DefaultAutoConfigurationImportListener
1.4 自定义Starter ✨
(1) 新建项目 项目名:formatter-spring-boot-starter
(2) 定义pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>first-app-by-gui</artifactId>
<groupId>thinking-in-spring-boot</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>formatter-spring-boot-starter</artifactId>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.3.4.RELEASE</version>
<!-- 细节,防止依赖传递,避免其他项目引用该项目之后版本冲突-->
<optional>true</optional>
</dependency>
</dependencies>
</project>
(3) 写一个接口和实现
public interface Formatter {
String format(Object object);
}
public class DefaultFormatter implements Formatter{
@Override
public String format(Object object) {
return String.valueOf(object);
}
}
(4) 配置类
@Configuration
public class FormatterAutoConfiguration {
@Bean
public Formatter defaultFormatter(){
return new DefaultFormatter();
}
}
(5) 配置文件 写一个META-INF/spring.factories文件,内容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=thinking.in.spring.boot.config.FormatterAutoConfiguration
1.5 条件装配
1.5.1 class条件装配:
public class FormatterAutoConfiguration {
@Bean
@ConditionalOnMissingClass(value = "com.fasterxml.jackson.databind.ObjectMapper")
public Formatter defaultFormatter(){
return new DefaultFormatter();
}
@Bean
@ConditionalOnClass(name = "com.fasterxml.jackson.databind.ObjectMapper")
public Formatter jacksonFormatter(){
return new JacksonFormatter();
}
}
1.5.2 bean条件装配:
因为ObjectMapper会被JacksonAutoConfiguration自动装配,所以我们写的starter不能声明该bean,否则影响JacksonAutoConfiguration装配.
改造如下:
@Configuration
public class FormatterAutoConfiguration {
@Bean
@ConditionalOnMissingClass(value = "com.fasterxml.jackson.databind.ObjectMapper")
public Formatter defaultFormatter(){
return new DefaultFormatter();
}
@Bean
@ConditionalOnClass(name = "com.fasterxml.jackson.databind.ObjectMapper")
@ConditionalOnMissingBean(type = "com.fasterxml.jackson.databind.ObjectMapper")
public Formatter jacksonFormatter(){
return new JacksonFormatter();
}
@Bean
@ConditionalOnBean(ObjectMapper.class)
public Formatter objectMapperFormatter(ObjectMapper objectMapper){
return new JacksonFormatter(objectMapper);
}
}
1.5.3 属性条件装配
@Configuration
@ConditionalOnProperty(prefix = "formatter",name = "enabled",havingValue = "true")
public class FormatterAutoConfiguration {
}
在配置文件application.properties中配置formatter.enabled=true即可满足条件
属性方法 | 说明 |
---|---|
prefix() | 配置属性名称前缀 |
value() | name()的别名 |
name() | 如果prefix()不为空,则完整配置属性为prefix()+name(),否则为name() |
havingValue() | 表示期望的配置属性值,并且禁止使用false |
matchIfMissing() | 用于判断当前属性值不存在时是否匹配 |
1.5.4 resource条件装配
@ConditionalOnResource(resources = "META-INF/spring.factories")
public class FormatterAutoConfiguration {
}
1.5.5 Web应用条件注解 @ConditionalOnWebApplication
1.5.6 表达式条件注解 @ConditionalOnExpression("${formatter.enabled:true}")
二. 理解SpringApplication
2.1 构造阶段
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
//引导类
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//推断Web应用类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//加载Spring应用上下文初始化器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//加载应用事件监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//推断应用引导类
this.mainApplicationClass = deduceMainApplicationClass();
}
2.2 SpringApplication配置阶段
配置阶段位于构造阶段和运行阶段之间,该阶段是可选的.主要用于调整SpringApplication的行为.可以通过new SpringApplication的方式或者new SpringApplicationBuilder的方式进行额外配置
2.3 SpringApplication运行阶段
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
//SpringAPplicationRunListener监听器,这个是SpringBoot运行时监听器,不是SpringBoot事件监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
//装配参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//准备ConfigurableEnvironment
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
//根据类型创建应用上下文
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//应用上下文运行前准备
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//started监听
listeners.started(context);
//调用callRunners方法
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
//监听应用启动完成
try {
listeners.running(context);
}
catch (Throwable ex) {
//异常处理
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
2.3.1 Spring应用上下文准备prepareContext
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
//设置Environment
context.setEnvironment(environment);
postProcessApplicationContext(context);
//执行Initializers
applyInitializers(context);
//监听上下文准备
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//注册SpringBoot Bean
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// 合并上下文配置源
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//加载上下文配置源
load(context, sources.toArray(new Object[0]));
//监听ApplicationPreparedEvent事件
listeners.contextLoaded(context);
}
2.3.2 Spring应用上下文启动
private void refreshContext(ConfigurableApplicationContext context) {
//执行ApplicationContext启动
refresh((ApplicationContext) context);
//注册shutdownHook线程,实现优雅的SpringBean生命周期回调
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
SpringApplicationRunListener的监听方法
监听方法 | 说明 |
---|---|
starting | Spring应用刚启动 |
environmentPrepared | ConfigurableEnbironment准备妥当,允许将其调整 |
contextPrepared | ConfigurableApplicationContext准备妥当,允许将其调整 |
contextLoaded | ConfigurableApplicationContext以装载,但仍未启动 |
started | ConfigurableApplicationContext已启动,此时SpringBean已初始化完成 |
running | Spring应用正在运行 |
failed | Spring应用运行失败 |
==Spring事件监听机制:(事件/监听器模式)==
ApplicationListener和ApplicationEvent来实现.
在Spring3.0之前,ApplicationListener必须监听所有但ApplicationContext,如果要过滤不同类型但事件要通过instanceof方式进行筛选.从3.0之后开始支持泛型监听,仅监听具体但ApplicationEvent实现.
但是这样也有一个问题,泛型化后无法监听不同类型但ApplicationEvent,为此引入来SmartApplicationListener,通过实现其中但supportsEventType方法来监听支持的类型.
==SimpleApplicationEventMulticaster既是Spring事件广播的实现也是SpringBoot事件广播的实现==
Spring内建事件
事件 | 说明 |
---|---|
ContextRefreshedEvent | Spring应用上下文就绪事件 |
ContextStartedEvent | Spring应用上下文启动事件 |
ContextStoppedEvent | Spring应用上下文停止事件 |
ContextClosedEvent | Spring应用上下文关闭事件 |
(1) 监听内建事件
public class ListenerDemo {
public static void main(String[] args) {
ConfigurableApplicationContext context=new GenericApplicationContext();
context.addApplicationListener(new ApplicationListener<ApplicationEvent>() {
@Override
public void onApplicationEvent(ApplicationEvent event) {
System.out.println("触发事件:"+event.getClass().getSimpleName());
}
});
System.out.println("应用上下文准备初始化");
context.refresh();
System.out.println("应用上下文已初始化");
System.out.println("应用上下文准备停止启动");
context.stop();
System.out.println("应用上下文停止启动");
System.out.println("应用上下文准备启动");
context.start();
System.out.println("应用上下文启动");
System.out.println("应用上下文准备关闭");
context.close();
System.out.println("应用上下文关闭");
}
}
(2) 自定义监听实现
public class CustomListener {
public static void main(String[] args) {
GenericApplicationContext context=new GenericApplicationContext();
context.registerBean(MyApplicationListener.class);
context.refresh();
context.publishEvent(new MyApplicationEvent("hello world"));
context.close();
//关闭之后事件无法执行,是因为close()中的destroyBeans()方法将ApplicationListenerBean从ApplicationEventMulticaster关联缓存移除.见ApplicationListenerDetector
context.publishEvent(new MyApplicationEvent("hello world again"));
}
public static class MyApplicationEvent extends ApplicationEvent{
public MyApplicationEvent(Object source) {
super(source);
}
}
public static class MyApplicationListener implements ApplicationListener<MyApplicationEvent>{
@Override
public void onApplicationEvent(MyApplicationEvent event) {
System.out.println("执行事件:"+event.getClass().getSimpleName());
}
}
}
(3) @EventListener方法监听
@EventListener方法必须是Spring Bean中的public方法,并支持返回类型为非void的情况.当他监听一个或多个ApplicationEvent时,其参数可为零到1个参数.
- 加上@Async还可以异步执行.
- 加上@Order可以指定顺序
@Component
public class AnnotatedEventListener {
@EventListener(ContextRefreshedEvent.class)
public void onEvent(ApplicationContextEvent event){
System.out.println("事件监听:"+event.getClass().getSimpleName());
}
}
2.4 准备阶段 凡是使用Spring工厂加载机制的场景,建议被加载实现类覆盖hashCode和equals方法,以免重复执行带来的隐患.
示例:
public class HelloWorldApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("hello world");
}
@Override
public int hashCode() {
return getClass().hashCode();
}
@Override
public boolean equals(Object obj) {
return getClass().equals(obj.getClass());
}
}
如果我不覆写hashCode和equals方法,那么此自定义初始化方法会被重复执行
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(FirstAppByGuiApplication.class);
springApplication.addInitializers(new HelloWorldApplicationContextInitializer());
springApplication.addInitializers(new HelloWorldApplicationContextInitializer());
springApplication.run(args);
}
2.5 自定义FailureAnalyzer和FailureAnalysisReporter
public class UnknownErrorFailureAnalyzer implements FailureAnalyzer {
@Override
public FailureAnalysis analyze(Throwable failure) {
if(failure instanceof UnknownError){
return new FailureAnalysis("未知错误","请重新尝试",failure);
}
return null;
}
}
public class ConsoleFailureAnalysisReporter implements FailureAnalysisReporter {
@Override
public void report(FailureAnalysis analysis) {
System.out.printf("故障描述:%s \n执行动作:%s \n 异常堆栈:%s \n",analysis.getDescription()
,analysis.getAction(),analysis.getCause());
}
}
并在META-INF/spring.factories中添加
org.springframework.boot.diagnostics.FailureAnalyzer=thinking.in.spring.boot.error.UnknownErrorFailureAnalyzer
org.springframework.boot.diagnostics.FailureAnalysisReporter=thinking.in.spring.boot.error.ConsoleFailureAnalysisReporter