Spring Boot 之主启动类
主启动类
文章目录
@SpringBootApplication
作用
-
SpringBoot 项目中都有主启动类用来启动项目
- 启动类上使用
@SpringBootApplication
注解 - 标注这是 SpringBoot 应用
- 启动类上使用
-
@SpringBootApplication
- 注解在主类上,复合注解:由多个注解组成
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
- 主类定义在主包中,和各模块同级
- 可以自动创建主类所在包及子包的 bean 对象
- 注解在主类上,复合注解:由多个注解组成
-
主类作用
- 判断应用类型:普通项目 或 Web 项目
- 查找并加载所有可用初始化器,设置到
initializers
属性 - 加载所有监听器程序,设置到
listeners
属性 - 判断并设置
main()
方法定义类,找到运行主类
-
Spring Boot 自动装配流程
package li_maven;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 主类,启动项目;包括 Tomcat 服务器
* @SpringBootApplication 注解中包含多个注解
* 可以将本类当作配置文件使用。 使用 @Bean 注解声明对象并放入容器
* 可以自动配置对象并放到容器:例如 MyBatis 需要的对象
* 扫描注解创建对象:默认扫描范围 此注解所在类所在的包及子包
*/
@SpringBootApplication
public class SpringBootDemoApplication {
/**
* 执行程序启动:执行 SpringApplication.run() 方法
* 方法内传参:本类 Class 对象,args
* 固定执行写法
*/
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
}
@SpringBootConfiguration
- 包含
@Configuration
注解- 可以作为配置类、配置文件使用
- 可使用
@Bean
声明对象注入到容器 - 包含
@Component
注解- 说明此类也是 spring 组件
@EnableAutoConfiguration
-
启用自动配置
- 将 Java 对象配置好注入到 Spring 容器中
- 例如:自动创建 MyBatis 对象放到容器
-
复合注解,包含以下注解:实现自动装配
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
-
加载自动配置类的时候,并非将
spring.factories
的配置全部加载进来- 而是通过
@Conditional
等注解的判断进行动态加载
- 而是通过
@AutoConfigurationPackage
-
包含
@AutoConfigurationPackage
:自动导入配置包- 将主配置类所在的包下面所有组件都扫描注冊到 spring 容器
-
包含
@Import({AutoConfigurationPackages.Registrar.class})
@Import
为 spring 的注解:导入一个配置文件- 在 SpringBoot 中是给容器导入一个组件
- 导入的组件由
AutoConfigurationPackages.class
的内部类Registrar.class
执行逻辑来决定如何导入
Registrar
实现了ImportBeanDefinitionRegistrar
类- 可以被注解
@Import
导入到 spring 容器
- 可以被注解
@Import
@Import({AutoConfigurationImportSelector.class})
- 开启自动配置类的导包的选择器
- 即:带入哪些类,有选择性的导入
@Import
支持导入的三种方式- 带有
@Configuration
注解的配置类 ImportSelector
的实现ImportBeanDefinitionRegistrar
的实现
- 带有
自动装配流程
自动装配在 AutoConfigurationImportSelector
类中进行以下流程
-
selectImports(AnnotationMetadata annotationMetadata)
:选择需要导入的组件- 调用
getAutoConfigurationEntry()
方法
- 调用
-
getAutoConfigurationEntry(AnnotationMetadata annotationMetadata)
- 根据导入的配置类的
AnnotationMetadata
返回AutoConfigurationImportSelector.AutoConfigurationEntry
- 调用
getCandidateConfigurations()
方法找到所有候选的配置类
- 根据导入的配置类的
-
getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes)
-
找到所有候选的配置类
- 经过去重,去除需要的排除的依赖
- 最终的组件才是环境需要的所有组件
-
有自动配置,不需要手写配置的值;配置类有默认值
-
调用
SpringFactoriesLoader.loadFactoryNames()
方法找到需要配置的组件 -
断言判断调用方法返回的结果
loadFactoryNames()
方法找到自动的配置类返回才不会报错
-
-
loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader)
-
传入参数:
this.getSpringFactoriesLoaderFactoryClass()
-
返回值:
EnableAutoConfiguration.class
- 和
@SpringBootApplication
注解下标识的同一个注解
- 和
-
即获取一个能加载自动配置类的类
- SpringBoot 默认自动配置类为
EnableAutoConfiguration
- SpringBoot 默认自动配置类为
-
this.getBeanClassLoader()
- 返回值:
this.beanClassLoader
- 返回值:
-
-
String factoryTypeName = factoryType.getName();
factoryType
是EnableAutoConfiguration.class
-
ClassLoader classLoaderToUse = classLoader
- 当传入类加载器为空时:
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader()
- 当传入类加载器为空时:
-
返回值:调用
loadSpringFactories()
转为 List 集合返回- 方法返回值 Map 集合
- 调用
getOrDefault(factoryTypeName, Collections.emptyList())
factoryTypeName
值是EnableAutoConfiguration
- 获取对应的 value
META-INF/spring.factories
文件下key
为org.....EnableAutoConfiguration
得值对应的 value
- value 不存在时返回默认值空数组
-
-
loadSpringFactories(ClassLoader classLoader)
:返回 Map 集合- 先从缓存中根据传进来的类加载器加载组件得到 Map result
- result 不为 null 时直接返回
- 为 null 时创建新的 HashMap result
Enumeration urls = classLoader.getResources("META-INF/spring.factories")
- 固定资源路径:
META-INF/spring.factories
- 加载引入 jar 包当前类路径下的
META-INF/spring.factories
文件 - 将所有资源加载到 Propeties 配置文件中
- 从其中获取对应的自动装配类
- 获取到组件后放入 result 集合中(此时强转为 List)
- key:文件中定义的一些标识工厂类
- value:能自动配置的一些工厂实现的类
- value 用 List 保存并去重
- 将 classLoader 作为 key,result 作为 value 放入缓存
- 下一次加载时可以直接使用
- 返回 result
- 固定资源路径:
- 方法作用
- 加载所有依赖的路径
META-INF/spring.factories
文件 - 通过 map 结构保存
- key:文件中定义的一些标识工厂类
- value:能自动配置的一些工厂实现的类
- value 用 list 保存并去重
- 加载所有依赖的路径
SpringFactoriesLoader
工厂加载机制- Spring内部提供的一个约定俗成的加载方式
- 只需要在模块的
META-INF/spring.factories
文件- Properties 格式的文件中
- key:接口、注解、或抽象类的全名
- value:以
,
分隔的实现类
- 使用
SpringFactoriesLoader
实现相应的实现类注入 Spirng 容器
- Properties 格式的文件中
- 加载所有 jar 包下的
classpath
路径下的META-INF/spring.factories
文件- 这样文件不止一个
- 先从缓存中根据传进来的类加载器加载组件得到 Map result
自动配置原理
-
SpringBoot 启动的时候加载主配置类
@EnableAutoConfiguration
注解开启自动配置功能
-
@EnableAutoConfiguration
作用- 利用
EnableAutoConfigurationImportSelector
给容器中导入一些组件 - 查看
selectImports()
方法的内容List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
- 获取候选的配置
SpringFactoriesLoader.loadFactoryNames()
- 扫描所有jar包类路径下
META-INF/spring.factories
- 把扫描到的文件的内容包装成
properties
对象 - 从
properties
中获取EnableAutoConfiguration.class
类(类名)对应的值 - 然后把他们添加在容器中
- 扫描所有jar包类路径下
- 将类路径
META-INF/spring.factories
里面配置的所有EnableAutoConfiguration
的值加入到了容器 - 这样的
xxxAutoConfiguration
类都是容器中的一个组件,都加入到容器中,用他们来做自动配置
- 利用
-
对每一个自动配置类进行自动配置功能
- 以
HttpEncodingAutoConfiguration
为例解释自动配置原理- Http 编码自动配置
- 所有配置文件中能配置的属性都是在
xxxxProperties
类中封装- 配置文件的配置可以参照某个功能对应的属性类
- 在配置文件中设置属性之后会自动装配到配置类中
- 实现对配置文件的设置
@Configuration // 表示这是一个配置类,和配置文件一样,也可以给容器中添加组件 /* 启动指定类的 ConfigurationProperties 功能; 将配置文件中对应的值和 HttpEncodingProperties 绑定起来; 并把 HttpEncodingProperties 加入到ioc容器中 */ @EnableConfigurationProperties(HttpEncodingProperties.class) /* Spring 底层 @Conditional 注解(Spring注解版), 如果满足指定的条件,整个配置类里面的配置就会生效; 判断当前应用是否是 web 应用,如果是,当前配置类生效 */ @ConditionalOnWebApplication // 判断当前项目有没有 CharacterEncodingFilter:SpringMVC中进行乱码解决的过滤器; @ConditionalOnClass(CharacterEncodingFilter.class) /* 判断配置文件中是否存在配置:spring.http.encoding.enabled,如果不存在,判断也成立 即使配置文件中不配置 pring.http.encoding.enabled=true,也默认生效的 */ @ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true) public class HttpEncodingAutoConfiguration { // 和 SpringBoot 的配置文件映射 private final HttpEncodingProperties properties; // 只有一个有参构造器的情况下,参数的值就会从容器中拿 public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) { this.properties = properties; } @Bean // 给容器中添加一个组件,组件的某些值需要从 properties 中获取 @ConditionalOnMissingBean(CharacterEncodingFilter.class) // 判断容器是否有这个组件 public CharacterEncodingFilter characterEncodingFilter() { CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter(); filter.setEncoding(this.properties.getCharset().name()); filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST)); filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE)); return filter; } }
- 以
- 当需要其他的配置时
- 如监听相关配置:listenter
- 传入不同的参数,获取相关的 listenter 配置
- 只有自动配置类进入到容器中以后,这个自动配置类才开始进行启动
- 通过
@Conditional
等注解的判断进行动态加载- 加载自动配置类的时候,并非将 spring.factories 的配置全部加载进来
@ComponentScan
- 组件扫描器,配置用于 Configuration 类的组件扫描指令
- 类似
<context:component-scan>
basePackageClasses
或basePackages
来定义要扫描的特定包
- 类似
- 默认扫描 @ComponentScan 所在类的同级包和子包
- 没有定义特定的包,将从声明该注解的类的包开始扫描
run()
- SpringApplication.run() 方法启动流程
- 作用
- 判断应用类型:普通项目 或 Web 项目
- 查找并加载所有可用初始化器,设置到
initializers
属性 - 加载所有监听器程序,设置到
listeners
属性 - 判断并设置 main() 方法定义类,找到运行主类
ApplicationContext
-
手动获取容器对象
-
SpringApplication.run()
方法返回 Spring 容器对象- 返回值类型:
ConfigurableApplicationContext
- 此接口继承
ApplicationContext
接口
- 返回值类型:
-
再获取业务 bean 进行调用
-
//持久层类,注解创建对象放到容器中
@Repository
class Demo{
//类定义略
}
//主类,启动时扫描同级、下级目录注解对象放到容器
@SpringBootApplication
public class DemoApplication{
public static void main(String[] args){
//获取容器对象
ApplicationContext context = SpringApplication.run( DemoApplication.class, args);
//从容器中的得到 bean 对象
Demo demo = (Demo)context.getBean("demo");
System.out.println(demo);
}
}
启动后执行
- 开发中可能需要在容器启动后执行一些内容
- 比如:读取配置文件、数据库连接之类
- 四种实现方式
- 注解:
@PostConstruct
- 实现
CommandLineRunner
接口 - 实现
ApplicationRunner
接口 - 实现
ApplicationListener
接口
- 注解:
- 执行顺序
- 注解
@PostConstruct
始终最先执行 - 监听
ApplicationStartedEvent
事件 ApplicationRuner
默认优先执行CommandLineRunner
默认后执行- 若两个接口都指定了
@Order
则按数字大小执行 - 数字越小优先级越高
- 若两个接口都指定了
- 监听
ApplicationReadyEvent
事件
- 注解
@PostConstruct
- Java 自带注解,注解在方法上会在项目启动时执行该方法
- Spring 容器初始化时执行
- 执行顺序
- 构造函数
@Autowired
@PostConstruct
- 注意事项
- 方法不能有参数
- 方法返回值为 void
- 方法不能抛出已检查异常
- 方法是非静态方法
- 方法只会被执行一次
- 耗时长的逻辑应放到独立线程,减少容器初始化时间
- 容器加载过程
- SpringBoot 将标记 Bean 的相关注解的类或接口自动初始化全局单一实例
- 当未标记初始化顺序时按照默认顺序进行初始化
- 初始化过程中执行一个 Bean 的构造方法后会执行该类存在的
@PostConstruce
方法,之后再初始化下一个 Bean
- 则
@PostConstruct
方法中逻辑处理时间过长时会增加 SpringBoot 初始化 Bean 时间,增加应用启动时间- 只有 Bean 初始化完成后才会打开端口服务,所以初始化完成前应用不可访问
- SpringBoot 将标记 Bean 的相关注解的类或接口自动初始化全局单一实例
两个接口
-
SpringBoot 提供两个接口实现这种需求,容器启动成功后的最后一步回调
CommandLineRunner
- run 方法参数:String 数组
ApplicationRunner
- run 方法参数:ApplicationArguments
- 参数格式:K=V
- 实现接口中重写 run() 方法实现即可
- 两个接口的 run 方法参数不同,效果相同
- 容器对象创建之后自动执行 run 方法
- 完成容器对象创建之后的自定义操作
- 容器对象创建的同时会将容器中的对象创建
- run 方法中可以获取容器中的对象使用
- 控制执行顺序
- 通过
@Order
注解指定优先级- 数字越小优先级越高
- 通过
Ordered
接口控制执行顺序
- 通过
- 方法执行时项目已初始化完毕,可以正常提供服务
- 完成容器对象创建之后的自定义操作
-
示例
-
package com.example.demo2; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * 实现 CommandLineRunner 接口,Order 设置为 1 使优先级最高 */ @SpringBootApplication @Order(value = 1) public class Demo2Application implements CommandLineRunner { public static void main(String[] args) { SpringApplication.run(Demo2Application.class, args); } // 覆写接口的 run 方法,在容器创建后自动执行 @Override public void run(String... args) throws Exception { } } package li_maven.springboot; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * 实现 ApplicationRunner 接口,Order 设置为 2 优先级次高 */ @Order(value = 2) @SpringBootApplication public class Application implements ApplicationRunner { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Override public void run(ApplicationArguments args) throws Exception { System.out.println("启动后自动执行"); } }
-
ApplicationListener
-
和另两个接口实现一样都不影响服务,可以正常提供服务
-
监听事件通常是
ApplicationStartedEvent
或ApplicationReadyEnvent
- 其他事件可能无法注入 Bean
-
使用
-
package li_maven.springboot; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; @SpringBootApplication public class Application implements ApplicationListener { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Override public void onApplicationEvent(ApplicationEvent event) { } }
-