阅读 163

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关闭掉。在容器跑起来之后,会发布事件,还会调用ApplicationRunnerCommandLineRunner

详细

继续上面的说,在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注解嘛?有两个特殊的类,ImportSelectorImportBeanDefinitionRegistrar。其中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

他是一个复合注解,

image-20220224190756861.png

而它下面的属性对应的都是元注解里面的属性。主要分析如下:

@SpringBootConfiguration

image-20220224190929208.png

就是一个配置类。没有什么可说的了。

@EnableAutoConfiguration

image-20220224191258882.png

可以看到它导入了一个ImportSelector,可以用它来读取spring.factories里面的自动装配的全类名,经过处理返回给Spring来解析。处理其实就是它的属性excludeName了,拿到所有的自动装配的类的全类名之后,从集合中将指定的移除就好了,所以,上面的两个属性是一个意思,并且exclude最后还是要变为String的。调用的是Class.getName()。

此外,还可以在这里可以做一些Condition的判断,比如AutoConfigureAfter,AutoConfigureBefore,关于这个判断,之后专门说说

还得注意,还有一个注解AutoConfigurationPackage

AutoConfigurationPackage

image-20220224191711905.png

他会往Spring容器中注册一个BasePackages,bean的名字叫做 AutoConfigurationPackages.class.getName(),它里面保存了 属性所指定的包名,如果属性为空,就是当前注解所在的类。放在容器里面之后就可以通过AutoConfigurationPackages来操作,存储自动注入的包是为了延迟引用,比如说JPA的实体扫描。

image-20220224193744171.png

@ComponentScan

这注解之前已经分析过了,相当于调用context.scan(String);方法,但是它搞了两个过滤器。还都是excludeFilters

  1. TypeExcludeFilter

    可以从Spring容器里面获取TypeExcludeFilter的实现类,循环调用。

  2. AutoConfigurationExcludeFilter

    在做@ComponentScan的时候排除自动配置类

image-20220224194314883.png

详细的SpringApplication类的运行过程,就不展开说了,Springboot的自动装配和启动就说到这里了。


关于博客这件事,我是把它当做我的笔记,里面有很多的内容反映了我思考的过程,因为思维有限,不免有些内容有出入,如果有问题,欢迎指出。一同探讨。谢谢。


作者:liuxiaocheng
链接:https://juejin.cn/post/7068244957911482375

文章分类
代码人生
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐