Springboot启动分析
建议先看# Spring-@ComponentScan分析 SpringBoot的例子就不写了,最熟悉的代码如下:
@SpringBootApplication
public class SimpleApplication {
public static void main(String[] args) {
SpringApplication.run(SimpleApplication.class,args);
}
}
SpringApplication的作用是什么?@SpringBootApplication注解有什么功能?Springboot怎么做自动装配是怎么做的?这篇文章就回答了相关的问题。
SpringApplication
作用
通过它来创建加载SpringApplication
在Spring的时候知道,得先创建Application,在启动的得把配置类,或者指定扫描的包,这类的定义信息告诉Spring,它才可以启动。比如下面的代码
public class TestAopApplication {
public static void main(String[] args) {
try {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 告诉扫描的路径
context.scan(TestAopApplication.class.getPackage().getName());
// 显性调用refresh方法,启动容器
context.refresh();
TestBean bean = context.getBean(TestBean.class);
bean.sayHello();
}catch (Exception e){
e.printStackTrace();
}
}
}
那么对于SpringApplication来说,它也是上面类似的逻辑,创建Application,创建好Environment,在增加一系列的监听方法,比如application创建好了的回调,环境创建好了的回调等等,通过这些监听就可以进一步的定制化对应的对象。
上面说的只是一个思想,实际上它大体的做法是,创建一个BootStrapApplication用来它引导创建ApplicationContext和Environment,并且创建一直到启动环节中,都有对应的监听方法,来对增强操作(比如,日志的选择,配置文件的解析(Application.yaml)等等),此外还会设置创建好的ApplicationContext加载资源的路径,设置一些属性(增加BeanFactoryPostProcess)。在调用ConfigurableApplicationContext#refresh()方法之前,会将BootStrapApplication关闭掉。在容器跑起来之后,会发布事件,还会调用ApplicationRunner和CommandLineRunner。
详细
继续上面的说,在SpringApplication类里面利用BootStrap来创建Application的时候,会有一系列的监听类。但是这些监听类要怎么创建呢?
- 代码里面写死,new出来,放里面去。(但这很不好,作为一个框架来说,没有拓展性了)。
- 像Spring那样,搞一个注解扫描。(不可取,想想Spring就知道,那一套得多烦。作为一个引导程序没必要这么搞)
- 写成配置文件,全类名。读取配置文件,实例化它。(SPI)。
- SpringBoot采取的就是这个。只不过和Java标准的SPI不一样,它指定文件名称,文件的key是要实现接口的全类名,v是具体的实现类的全限定类名,多个实现用逗号分开。这个文件叫做META-INF/spring.factories。
有这样的机制,如果想要那个接口需要拓展,直接写在里面,并且会将之前已经加载好的配置文件缓存起来,用的时候获取一下,并且实例化,就好了。回到Springboot中,看看那些接口需要拓展。
-
BootstrapRegistryInitializer (他是在BootstrapRegistry创建好了之后,在使用之前的一个初始化回调)
-
ApplicationContextInitializer (在refresh方法之前的回调,用来初始化ApplicationContext)
-
ApplicationListener(这是典型的Spring里面的事件监听,指定事件来监听)
-
SpringApplicationRunListener(在SpringBoot启动的时候提供的监听类,在各个环节都有方法)。
需要注意的是,它的实现类在Springboot中就一个
EventPublishingRunListener,但是EventPublishingRunListener里面聚合了SimpleApplicationEventMulticaster,通过它来做发布事件,会在创建EventPublishingRunListener的时候将ApplicationListener传递给SimpleApplicationEventMulticaster。一般来说,这个玩意基本不会动的。
-
SpringBootExceptionReporter(在启动报错的时候回调)
-
EnvironmentPostProcessor
这个是重点:
它是做
Environment对象的增强处理的。具体做法如下:
EnvironmentPostProcessorApplicationListener监听了它感兴趣的事件,SpringApplication创建好Environment之后,会通过EventPublishingRunListener发布对应的事件,EnvironmentPostProcessorApplicationListener来处理,它再从处理的时候又会从spring.factories中加载EnvironmentPostProcessor的实现类,来做Environment的后置处理。其中就包括解析Application.yaml文件。 -
AutoConfigurationImportListener(在自动配置类导入的时候回调)
-
EnableAutoConfiguration(自动配置类)
这个是重点:
还记得之前说配置类中@Import注解嘛?有两个特殊的类,
ImportSelector和ImportBeanDefinitionRegistrar。其中ImportSelector可以返回一个String数组,它里面包含的是类的全类名,Spring拿到这个之后,会加载对应类,继续解析一遍。这其实就是自动装配,这个功能不是Springboot搞出来的,之前就存在这样的支持。 那么key是EnableAutoConfiguration,value是对应的实现类,用逗号分隔,在
ImportSelector的实现类里面从spring.factories里面读取到,返回给Spring,Spring就会继续把他们解析一遍。可以在自动配置类上面写那些之前在@Configuration类上面写的注解,Spring会解析他们,将Bean注册到Spring中。...... 因为涉及的地方太多了,这里就列举几个有经典的。
加载 META-INF/spring.factories的代码长什么样(怎么加载的)
对应的代码在 SpringFactoriesLoader#loadFactories(Class , @Nullable ClassLoader )
这里的代码不难理解,总体就是利用classLoader.getResource加载配置文件,读取配置文件,将v逗号分割。
public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
Assert.notNull(factoryType, "'factoryType' must not be null");
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
// 通过classload和指定需要的lei找到对应的实现类的集合了,
List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
if (logger.isTraceEnabled()) {
logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
}
List<T> result = new ArrayList<>(factoryImplementationNames.size());
for (String factoryImplementationName : factoryImplementationNames) {
// 实例化
result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
}
// order接口排序
AnnotationAwareOrderComparator.sort(result);
return result;
}
// 真正做加载的地方
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// 先从缓存中拿,缓存的key是对应的classloader,v是属于它的数据
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
// 加载配置文件
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
// 去掉左右的空格,逗号分割,
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
当然,SpringApplication绝不是我说的这么简单,这里只是说了一些大体的思路,具体的实现的细节这里不展开说了
@SpringBootApplication
他是一个复合注解,
而它下面的属性对应的都是元注解里面的属性。主要分析如下:
@SpringBootConfiguration
就是一个配置类。没有什么可说的了。
@EnableAutoConfiguration
可以看到它导入了一个ImportSelector,可以用它来读取spring.factories里面的自动装配的全类名,经过处理返回给Spring来解析。处理其实就是它的属性excludeName了,拿到所有的自动装配的类的全类名之后,从集合中将指定的移除就好了,所以,上面的两个属性是一个意思,并且exclude最后还是要变为String的。调用的是Class.getName()。
此外,还可以在这里可以做一些Condition的判断,比如AutoConfigureAfter,AutoConfigureBefore,关于这个判断,之后专门说说
还得注意,还有一个注解AutoConfigurationPackage
AutoConfigurationPackage
他会往Spring容器中注册一个BasePackages,bean的名字叫做 AutoConfigurationPackages.class.getName(),它里面保存了 属性所指定的包名,如果属性为空,就是当前注解所在的类。放在容器里面之后就可以通过AutoConfigurationPackages来操作,存储自动注入的包是为了延迟引用,比如说JPA的实体扫描。
@ComponentScan
这注解之前已经分析过了,相当于调用context.scan(String);方法,但是它搞了两个过滤器。还都是excludeFilters
-
TypeExcludeFilter
可以从Spring容器里面获取TypeExcludeFilter的实现类,循环调用。
-
AutoConfigurationExcludeFilter
在做@ComponentScan的时候排除自动配置类
详细的SpringApplication类的运行过程,就不展开说了,Springboot的自动装配和启动就说到这里了。
关于博客这件事,我是把它当做我的笔记,里面有很多的内容反映了我思考的过程,因为思维有限,不免有些内容有出入,如果有问题,欢迎指出。一同探讨。谢谢。