Spring boot的启动原理
一.我们开发任何spring boot的项目,都要用到如下类:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
从上面的代码中可以看到
Application 类上面的注解 @SpringBootApplication
和main方法中的 SpringApplication.run(Application.class, args);
这两位最显眼,我们可以从这两入手,从而了解springboot
二、 @SpringBootApplication原理解析
- @SpringBootApplication组合注解剖析
首先,我们直接追踪@SpringBootApplication的源码,可以看到其实@SpringBootApplication是一个组合注解,他分别是由底下这些注解组成。
@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) })
这些注解虽然看起来很多,但是除去元注解,真正起作用的注解只有以下三个注解:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
那这三个注解是有啥用?其实在Spring Boot 1.2版之前,或者我们初学者刚开始接触springboot时,都还没开始使用@SpringBootApplication这个注解,而是使用以上三个注解启动项目。如果有兴趣的,也可以手动敲敲代码,就会发现这样也可以正常启动项目!
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public class SpringBoot {
public static void main(String[] args) {
SpringApplication.run(SpringBoot.class, args);
}
}
所以说这三个注解才是背后的大佬,@SpringBootApplication只是个空壳子。接下来,我来说明下这三个注解各自的作用。
2. @SpringBootConfiguration
同样,我们跟踪下@SpringBootConfiguration的源代码,看下他由哪些注解组成
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
可以看到,除去元注解,剩下的@Configuration注解我相信大家应该都很熟了!我们springboot为什么可以去除xml配置,靠的就是@Configuration这个注解。所以,它的作用就是将当前类申明为配置类,同时还可以使用@bean注解将类以方法的形式实例化到spring容器,而方法名就是实例名,看下代码你就懂了!
@Configuration
public class TokenAutoConfiguration {
@Bean
public TokenService tokenService() {
return new TokenService();
}
}
作用等同于xml配置文件的
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:cache="http://www.springframework.org/schema/cache" xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd ">
<!--实例化bean-->
<bean id="tokenService" class="TokenService"></bean>
</beans>
3. @ComponentScan
我们先说下@ComponentScan作用。他的作用就是扫描当前包以及子包,将有@Component,@Controller,@Service,@Repository等注解的类注册到容器中,以便调用。
注:大家第一眼见到@ComponentScan这个注解的时候是否有点眼熟?之前,一些传统框架用xml配置文件配置的时候,一般都会使用context:component-scan来扫描包。以下两中写法的效果是相同的`
@Configuration
@ComponentScan(basePackages="XXX")
public class SpringBoot {
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:cache="http://www.springframework.org/schema/cache" xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd ">
<!-- 扫描需要被调用的注解文件包 -->
<context:component-scan base-package="XXX"></context:component-scan>
</beans>
注:如果@ComponentScan不指定basePackages,那么默认扫描当前包以及其子包,而@SpringBootApplication里的@ComponentScan就是默认扫描,所以我们一般都是把springboot启动类放在最外层,以便扫描所有的类。
4. @EnableAutoConfiguration
这里先总结下@EnableAutoConfiguration的工作原理,大家后面看的应该会更清晰:
它主要就是通过内部的方法,扫描classpath的META-INF/spring.factories配置文件(key-value),将其中的
org.springframework.boot.autoconfigure.EnableAutoConfiguration 对应的配置项实例化并且注册到spring容器。
ok,我们同样打开@EnableAutoConfiguration源码,可以发现他是由以下几个注解组成的
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
除去元注解,主要注解就是@AutoConfigurationPackage和@Import(AutoConfigurationImportSelector.class)
我们springboot项目为什么可以自动载入应用程序所需的bean?就是因为这个神奇的注解@Import。那么这个@Import怎么这么牛皮?没关系!我们一步一步的看下去!
首先我们先进入AutoConfigurationImportSelector类,可以看到他有一个方法selectImports(),
继续跟踪,进入getAutoConfigurationEntry()方法
List configurations = getCandidateConfigurations(annotationMetadata, attributes);
可以看到这里有个List集合,那这个List集合又是干嘛的?没事,我们继续跟踪getCandidateConfigurations()方法!
可以看到这里有个方法
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader());
1
这个方法的作用就是读取classpath下的META-INF/spring.factories文件的配置,将key为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 对应的配置项读取出来,通过反射机制实例化为配置文件,然后注入spring容器。
注:假如你想要实例化一堆bean,可以通过配置文件先将这些bean实例化到容器,等其他项目调用时,在spring.factories中写入这个配置文件的路径即可!我前面的文章有这个例子https://blog.csdn.net/weixin_40496191/article/details/109065430
主要是实现自己创建的starter依赖包,然后由其他项目引入使用,这是我的spring.factories文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=zzk.config.TokenAutoConfiguration
然后直接在SpringFactoriesLoader.loadFactoryNames;这个方法后面打个断点,可以在返回的集合里找到我们自定义的配置文件路径!
说明成功引入我们自定义的依赖包!
三、 SpringApplication.run()原理解析
SpringApplication.run()原理相对于前面注解的原理,会稍微麻烦点,为了方便我会适当贴出一些注解代码。
首先我们点击查看run方法的源码
可以看出,其实SpringApplication.run()包括两个部分,一部分就是创建SpringApplicaiton实例,另一部分就是调用run()方法,那他们又是怎么运行的?
- 创建SpringApplicaiton
继续跟踪SpringApplication实例的源码
继续跟踪进入,到如下这个方法中
@SuppressWarnings({
"unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));