Springboot启动分析(springboot启动过程详解)
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的自动装配和启动就说到这里了。
关于博客这件事,我是把它当做我的笔记,里面有很多的内容反映了我思考的过程,因为思维有限,不免有些内容有出入,如果有问题,欢迎指出。一同探讨。谢谢。
作者:liuxiaocheng
链接:https://juejin.cn/post/7068244957911482375