1.Springboot 启动注解分析
springboot启动类
//SpringBootApplication注解用来标注一个主程序类,说明是一个springboot应用
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
查看@SpringBootApplication 注解:
/**
@ComponentScan:自动扫描并加载符合条件的组件或者bean,将这个bean定义加载到IOC容器中
@SpringBootConfiguration:标注在某个类上,表示这是一个springboot的配置类。
@EnableAutoConfiguration:开启自动配置功能,之前在使用springboot的时候,springboot可以自动帮我们完成配置功能,@EnableAutoConfiguration告诉springboot开启自动配置功能,这样自动配置才能生效
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
//
}
查看@EnableAutoConfiguration 注解:
/*
@AutoConfigurationPackage:自动配置包
@Import(AutoConfigurationImportSelector.class):导入哪些组件的选择器,它将所有需要导入的组件以全类名的方式返回,这些组件就会被添加到容器中,它会给容器中导入非常多的自动配置类,就是给容器中导入这个场景需要的所有组件,并配置好这些组件
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
//
}
查看@AutoConfigurationPackage 注解:
/*
给容器导入一个组件,导入的组件由AutoConfigurationPackages.Registrar.class将主配置类(@SpringBootApplication标注的类)的所在包及包下面所有子包里面的所有组件扫描到spring容器
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
//
}
2.自动装配源码解析
org.springframework.boot.SpringApplication#run(java.lang.String...)
/**
* 运行Spring application, 创建并且更新ApplicationContext.
* @param args 命令行参数
* @return ApplicationContext
*/
public ConfigurableApplicationContext run(String... args) {
// 用来记录当前springboot启动耗时
StopWatch stopWatch = new StopWatch();
// 记录启动开始时间
stopWatch.start();
// spring上下文的接口, 接收ApplicationContext实现
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// 通过spring.factroies中读取了SpringApplicationRunListener 的组件,用来发布事件或者运行监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
// 触发ApplicationStartingEvent的监听者执行onApplicationEvent
listeners.starting();
try {
// 根据命令行参数 实例化一个ApplicationArguments
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 预初始化环境: 读取环境变量,读取配置文件信息(基于监听器)
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 忽略beaninfo的bean
configureIgnoreBeanInfo(environment);
// 打印Banner
Banner printedBanner = printBanner(environment);
// 根据webApplicationType创建Spring上下文
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 准备应用上下文,该步骤包含一个非常关键的操作,将启动类注入容器,为后续开启自动化配置提供基础
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 加载spring ioc 容器 **相当重要 由于是使用AnnotationConfigServletWebServerApplicationContext 启动的spring容器所以springboot对它做了扩展:
// 加载自动配置类:invokeBeanFactoryPostProcessors , 创建servlet容器onRefresh
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 发布ApplicationStartedEvent事件
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 发布ApplicationReadyEvent事件
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry
/**
* 获取符合条件的自动配置类,避免加载不必要的自动配置类从而造成内存浪费
*/
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
// 获取是否有配置spring.boot.enableautoconfiguration属性,默认返回true
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 获得@Congiguration标注的Configuration类即被审视introspectedClass的注解数据,
// 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
// 将会获取到exclude =和excludeName=""的注解数据
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 得到spring.factories文件配置的所有自动配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
// 利用LinkedHashSet移除重复的配置类
configurations = removeDuplicates(configurations);
// 得到要排除的自动配置类,比如注解属性exclude的配置类
// 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
// 将会获取到exclude = FreeMarkerAutoConfiguration.class的注解数据
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 检查要被排除的配置类,因为有些不是自动配置类,故要抛出异常
checkExcludedClasses(configurations, exclusions);
// 将要排除的配置类移除
configurations.removeAll(exclusions);
// 因为从spring.factories文件获取的自动配置类太多,如果有些不必要的自动配置类都加载进内存,会造成内存浪费,因此这里需要进行过滤
// 注意这里会调用AutoConfigurationImportFilter的match方法来判断是否符合@ConditionalOnBean,@ConditionalOnClass或@ConditionalOnWebApplication
configurations = filter(configurations, autoConfigurationMetadata);
// 获取了符合条件的自动配置类后,此时触发AutoConfigurationImportEvent事件,
// 目的是告诉ConditionEvaluationReport条件评估报告器对象来记录符合条件的自动配置类
// 该事件什么时候会被触发?--> 在刷新容器时调用invokeBeanFactoryPostProcessors后置处理器时触发
fireAutoConfigurationImportEvents(configurations, exclusions);
// 将符合条件和要排除的自动配置类封装进AutoConfigurationEntry对象,并返回
return new AutoConfigurationEntry(configurations, exclusions);
}
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getCandidateConfigurations
/**
将类路径下 META-INF/spring.factories 里面配置的所有EnableAutoConfiguration的值加入到了容器中
*/
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//将类路径下 META-INF/spring.factories 里面配置的所有EnableAutoConfiguration的值取入集合中
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
3.自定义自动配置类
1)新建一个springboot工程 xxx-spring-boot-starter 自定义配置类 建议命名
2)新建一个配置类 在配置类中创建一个Bean 对象
//必须声明为一个配置类
@Configuration
//可以定义配置生成条件
//@ConditionalOnWebApplication //web应该生效
@ConditionalOnJava(value = JavaVersion.THIRTEEN)
//可以引入自定义参数配置
@EnableConfigurationProperties(TestProperties.class)
public class TestServiceAutoConfiguration {
@Autowired
TestProperties testProperties;
@Bean
public TestService testService() {
TestService service = new TestService();
service.setTestProperties(testProperties);
return service;
}
}
参数配置类:
//定义参数配置前缀
@Data
@ConfigurationProperties(prefix = "demo")
public class TestProperties {
private String prefix;
private String suffix;
}
3)在 在resources目录下按照SpringBoot自动配置目录、文件名称的规则,如下图,新建一个自己的spring.factories文件。
并在 spring.factories文件 中添将自定义的自动配置类声明为名称为org.springframework.boot.autoconfigure.EnableAutoConfiguration的值,“\”的作用:换行,如果我们有多个自定义的自动配置类,这里可以用逗号分隔
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.isvmspace.demo.mystarter.TestServiceAutoConfiguration
自定义的自动配置类就创建好了
4)目标项目中只要引入对应自定义配置模块
<!-- 引入自动配置模块 -->
<dependency>
<groupId>com.isvmspace.demo.mystarter</groupId>
<artifactId>test-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
附录-常用注解:
@Conditional:自动配置类在一定条件下才能生效
注解 | 作用 |
---|---|
@ConditionalOnJava | 系统的java版本是否符合要求 |
@ConditionalOnBean | 容器中存在指定Bean |
@ConditionalOnMissingBean | 容器中不存在指定Bean |
@ConditionalOnExpression | 满足SpEL表达式 |
@ConditionalOnClass | 系统中有指定的类 |
@ConditionalOnMissingClass | 系统中没有指定的类 |
@ConditionalOnSingleCandidate | 容器中只有一个指定的Bean,或者是首选Bean |
@ConditionalOnProperty | 系统中指定的属性是否有指定的值 |
@ConditionalOnResource | 类路径下是否存在指定资源文件 |
@ConditionOnWebApplication | 当前是web环境 |
@ConditionalOnNotWebApplication | 当前不是web环境 |
@ConditionalOnJndi | JNDI存在指定项 |