首页 > 编程语言 > 详细

spring boot启动原理一

时间:2021-06-27 09:51:56      阅读:23      评论:0      收藏:0      [点我收藏+]

说明:本文使用的SpringBoot版本为较新的2.3.5的版本;集成开发工具选择ideal旗舰版最新版本2020.2;

SpringBoot启动总体上分为两个步骤:创建SpringApplication对象 和 执行run()方法过程。

首先来看第一部分:创建SpringApplication对象

 

一、new SpringApplication

 

代码如下:

 

 技术分享图片

 

 

 

 

其中,重点关注如下两行代码:

 

this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));

 

这两行代码都是调用了getSpringFactoriesInstances(Class<T> type)这个方法,

这个方法最终调用的是

 

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = this.getClassLoader();
    Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

 

上面这个方法中有一个SpringFactoriesLoader.loadFactoryNames(type, classLoader)方法,

返回的是List<String>

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    String factoryTypeName = factoryType.getName();
    return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

 

这个静态方法调用loadSpringFactories(classLoader)返回一个Map<String, List<String>>集合,而这个map集合中的数据就是从META-INF/spring.factories中获得的

 

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            try {
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryImplementationName = var9[var11];
                            // 把每个键值对添加到Map<String, List<String>>中
                            result.add(factoryTypeName, factoryImplementationName.trim());
                        }
                    }
                }
                // 所有的处理完成之后,还会把这些数据缓存起来
                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }

 

如下是运行时部分代码的结果截图

 

 技术分享图片

 

 

获取到的url有:

jar:file:/Users/zhangyan/Applications/apache/maven-repository/default-maven-repository/org/springframework/boot/spring-boot/2.3.5.RELEASE/spring-boot-2.3.5.RELEASE.jar!/META-INF/spring.factories

 

从这个url中拿到的properties中有8个数据

技术分享图片

 

 

 

而这8个键值对对应的就是spring-boot-2.3.5.RELEASE.jar包下META-INF/spring.factories中对应的键值对

技术分享图片

 

 技术分享图片

 

 

 

而这个方法中的result结果如下:

技术分享图片

 

 技术分享图片

 

 

 

result是Map<String, List<String>>结构,所以map中的key实际上就是spring.factories中的等号前面的值,而map中的value这个List就是等号后面的值用逗号分隔之后获取的list。实际上完成的工作就是把spring.factories中的数据内容转换成Map<String, List<String>>这个结构。

 

除了上面这个url之外,还有一个

jar:file:/Users/zhangyan/Applications/apache/maven-repository/default-maven-repository/org/springframework/boot/spring-boot-autoconfigure/2.3.5.RELEASE/spring-boot-autoconfigure-2.3.5.RELEASE.jar!/META-INF/spring.factories

 

 技术分享图片

 

 

从这个路径下可以拿到7个键值对属性,这个是从自动配置包中获取的。其中有一个很重要的就是key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的这个键值对,其值就指定了很多个自动配置的类

 

技术分享图片

 

 技术分享图片

 

 

 

除此之外,还有jar:file:/Users/zhangyan/Applications/apache/maven-repository/default-maven-repository/org/springframework/spring-beans/5.2.10.RELEASE/spring-beans-5.2.10.RELEASE.jar!/META-INF/spring.factories这个路径

 技术分享图片

 

 

 

 

这个路径下properties中只有一个属性

 

 技术分享图片

 

 

最终拿到的结果如下,一共有13个List<String>集合,每个集合中的数据数量不等,具体如下

技术分享图片

 

 技术分享图片

 

 

 

 

 

这个result结果数据会被缓存到cache属性中,这个cache是SpringFactoriesLoader这个类的Map<ClassLoader, MultiValueMap<String, String>>类型的变量,下次再使用loadSpringFactories(@Nullable ClassLoader classLoader)方法的时候就会直接从这个cache中去那,不必要在重新加载一次。

 

拿到map集合之后,使用getOrDefault()方法获取指定的key的value值

技术分享图片

 

 技术分享图片

 

 

在这里实际上就是拿org.springframework.context.ApplicationContextInitializer这个key的list集合。

 

然后对拿到的list集合的数据调用createSpringFactoriesInstances进行实例化

 

 

 技术分享图片

 

 

方法体具体如下:

 

private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) {
        List<T> instances = new ArrayList(names.size());
        Iterator var7 = names.iterator();

        while(var7.hasNext()) {
            String name = (String)var7.next();

            try {
                Class<?> instanceClass = ClassUtils.forName(name, classLoader);
                Assert.isAssignable(type, instanceClass);
                Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
                T instance = BeanUtils.instantiateClass(constructor, args);
                instances.add(instance);
            } catch (Throwable var12) {
                throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, var12);
            }
        }

        return instances;
    }

 

通过上面的方法体,知道遍历list集合中的每一个全限定类名,使用反射的方法加载并实例化的过程,把实例化的对象放入list中返回。

 

例如:ApplicationContextInitializer对应的7个类的实例化对象

 

技术分享图片

 

 技术分享图片

 

 

 

至此, 下图中的setInitializers()代码括号中的代码就执行完毕,然后把对象的list设置到SpringApplication这个对象的initializers属性中;

技术分享图片

 

 技术分享图片

 

 

下面一行的this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));执行情况和上一行的执行情况差不多,主要区别在于这一次可以直接从之前保存的cache中拿类名集合,而不需要在从spring.factories中加载。

 

设置这两个属性的目的是为了后面进行使用,目前先知道有这两个设置就可以了。

 

最终设置完成initializers属性和listeners属性如下:

技术分享图片

 

 技术分享图片

 

 

 

最后一个设置主类,完成后SpringApplication对象就创建完成,SpringApplication对象属性内容如下。

 

技术分享图片

 

 技术分享图片

 

 

 

第一步就全部执行完成结束。

 

二、在第一步的基础上执行run方法

 

这一步就包括了,各种应用上下文(IOC容器)、组件扫描实例化、自动配置等等各种过程,相较于第一步的操作会更加复杂。

 

执行run方法的代码如下:

 

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting();

        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
            this.handleRunFailure(context, var10, exceptionReporters, listeners);
            throw new IllegalStateException(var10);
        }

        try {
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
            this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }

 

通过这个方法可以发现,run方法最终返回的是一个ConfigurableApplicationContext类型的实例对象,而这个就是我们重点关注的IOC容器。

 

上面这个run方法中有一个getRunListeners的方法,这个方法代码如下:

 private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class[]{SpringApplication.class, String[].class};
        return new SpringApplicationRunListeners(logger, this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
    }

 

方法体中有我们很熟悉的getSpringFactoriesInstances这个方法,就是从spring.factories中取SpringApplicationRunListener.class的全限定类名指定的类,然后逐一实例化保存在SpringApplicationRunListeners实例对象的属性中(实际代码运行出来就只有一个对象EventPublishingRunListener);注意,这里和前面的区别,这里是SpringApplicationRunListeners,而第一步中是SpringApplicationListeners,注意两者的区别;

 

而这里在实例化EventPublishingRunListener的时候,有一个重要的事情,这个事情会在后面的代码中用到,我们来看看这个重要的事情是什么

 

我们跟着调用链进入到创建实例的这一步,代码执行过程到下面这一步,这一步实际上只有一个就是EventPublishingRunListener需要实例化

 

技术分享图片

 

 技术分享图片

 

 

 

我们重点关注这个的实例化过程,代码中的实现是通过反射来实例化,但是这个实例化中会涉及到前面我们在第一步中设置的listeners,我们来看看,反射代码如下:

 

技术分享图片

 

 技术分享图片

 

 

 

我们一步一步执行看看

 

首先拿到指定参数类型的构造器

 

技术分享图片

 

 技术分享图片

 

 

 

然后,利用这个工具类通过这个构造器实例化

 

技术分享图片

 

 技术分享图片

 

 

 

进入这个类的构造方法,我们的重点来了,我们之前所说的重要的事情就是在这里出现

 

技术分享图片

 

 技术分享图片

 

 

 

我们注意到这个构造方法的入参有两个,一个就是我们之前创建的SpringApplication对象,一个是字符串数组,我们重要关注的就是前面这个SpringApplication对象,我们既然把这个对象传递给来这个构造方法,那么在这里构造方法里就可以访问我们SpringApplication中提供的方法以及属性。

 技术分享图片

 

 

 

 

如上可以看到,首先这个对象就把入参保存起来,然后注意这里执行了一个application.getListeners()的方法,这个方法可以实现什么?很明显,这个方法会把之前在SpringApplication对象中保存的listenres对象拿到,就是我们之前保存的11个listener都可以拿到了。

 

然后,执行了 this.initialMulticaster.addApplicationListener(listener);这行代码,就经过while循环把11个listener都添加进EventPublishingRunListener这个对象的initialMulticaster这个属性中的defaultRetriver中的applicationListeners中保存起来了。这个保存至关重要,因为在后面调用staring()方法以及环境准备好通知listener进行回调的时候会用到这些个listener。

 

技术分享图片

 

 技术分享图片

 

 

 

我们看一个添加的过程

 

技术分享图片

 

我们看最终添加完成之后,前面保存的11个listener对象都添加到这个事件广播器中了

 

技术分享图片

 

至此,这个RunListener实例化过程也就接近结束

技术分享图片

 

其实,也就是说,这个EventPublishingRunListener中是有个SimpleApplicationEventMulticaster类型的广播器,这个广播器保存了之前创建的11个listener。

 

技术分享图片

 

然后,在后面调用这个RunListener执行一些方法的时候,可能会对这些个listener也做一些操作。实际上,接下来的staring()方法就已经可以体现这一点,我们继续往下来看。

 

接下来就是调用starting()方法了。

 

技术分享图片

 

调用listeners中的staring()方法,这个方法内部会遍历执行listener(这里的listener还是指的上面那个SpringApplicationRunListeners内部的List<SpringApplicationRunListener>集合中的每一个listener)的starting()方法

 

技术分享图片

 

其实,我们这里实际上就只有一个listener,就是EventPublishingRunListener,我们进入这个类的starting()方法看一下

 

技术分享图片

 

我们可以从上面这个属性的截图中看到了我们前面在分析这个EventPublishingRunListener创建实例的时候创建的事件广播器SimpleApplicationEventMulticaster initialMulticaster,而这个广播器中还保存了我们之前创建的那11个listener。还记得么?不记得往前翻一翻!!!

 

我们看到实际上就是利用这个广播器广播了ApplicationStartingEvent类型的这个事件,我们看看这个广播方法做了哪些事

 

技术分享图片

 

我们再进入

 

技术分享图片

 

我们看到这个方法有一个很重要的代码就是this.getApplicationListeners(event, type)这个方法,我们通过计算器看下这个方法拿到的数据是什么样的

 

技术分享图片

 

可以看到,这个数据就是好像就是我们之前在第一步创建SpringApplication的时候设置listeners时拿到的实例对象,那我们看看这个方法到底是干了啥

 

技术分享图片

 

上面这个就是这个方法的方法体,这个方法还是比较复杂的;

 

方法进来之后就调用了event的getSource()这个方法,这个方法其实就是拿到event中的SpringApplication对象

 

技术分享图片

 

然后,剩下的其实就是对AbstractApplicationEventMulticaster的内部类做了一些声明等的操作;

 

比如接下来就new了一个AbstractApplicationEventMulticaster.ListenerCacheKey 内部类,名字是cacheKey,这个里面封装了eventType和sourceType

 

技术分享图片

 

然后下面就声明了一个newRetriver,然后声明了一个existingRetriver,然后从自己的缓存中那cacheKey指定的Retriver,此时缓存中是没有的,所以拿到的必定是个null

 

技术分享图片

 

然后接下来就遇到了这个条件的判断,这个判断比较不容易看,它实际上是两个结果的&&,仔细看看就知道这个结果最终是true(当然,也可以用计算器算一下),所以会进到下面的方法体执行

 

进入方法体执行之后,我们可以看到,首先new了一个对象,然后把这个对象以k-v键值对的形式放进了retrieverCache中,注意,这个putfAbsent()方法最终是掉了map中的put方法,就是说如果这个里面已经存在这个key的值后,会把这个key对应的value返回,很明显这里之前缓存是空的,所以并没有返回,所以existinRetriver最终还是null

 

技术分享图片

 

然后下面两个判断结果都为false,所以最终都跳过了没有执行

 

最终执行到了最后这一行至关重要的代码

 

技术分享图片

 

而这行代码进去之后会发现,代码比较长,我们一点点的来分析

 

首先进来,声明了几个局部变量使用allListeners、filterListeners、retriever、listeners、listenerBeans等等,比较简单;然后我们看到这里有个同步代码块,

 

技术分享图片

 

在这个同步代码块中,以广播器中this.defaultRetriever.applicationListeners这个创建了LinkedHashSet,而这个我们还记得this.defaultRetriever.applicationListeners这个里面保存了什么么?是的,这个里面保存了我们之前的那11个listener

 

技术分享图片

 

我们继续往下,拿到这个11个listener的集合之后,下面代码做了遍历处理,判断每个listener是否有对这里的eventType支持的响应处理,如果支持,添加进filteredListeners以及addListeners集合中;

 

技术分享图片

 

我们以其中一个支持的listener来看看执行过程

LoggingApplicationListener这个对象支持ApplicationStartEvent这个事件

 

技术分享图片

 

最终遍历完成,发现其实有4个listener是支持对这个事件的响应的

 

技术分享图片

 

我们继续往下看

技术分享图片

 

在上面这部分代码中,对retriver这个对象属性也做了一些填充,然后就最终把这个allListeners返回了,这个返回的allListeners其实就是是刚才支持响应这个事件的4个listener的集合。

 

技术分享图片

 

所以说了这么多都是为了解释这一行代码做了什么

 

技术分享图片

 

最终返回的就是4个支持ApplicationStartEvent事件响应的listener集合。

 

这个广播器拿到这个集合之后,就遍历这个集合,触发这个listener对这个事件的响应。

 

技术分享图片

 

我们以LoggingApplicationListener这个listener对事件的响应为例,来看看这个listener做了什么事情。

 

技术分享图片

 

调用链进入

 

技术分享图片

 

继续进入

技术分享图片

 

我们可以看到,是调用了这个LoggingApplicationListener中的onApplicationEvent()方法,我们看看这个方法做了什么事情

技术分享图片

 

我们看到,通过if判断,找到这个事件对应的响应方法是this.onApplicationStartingEvent((ApplicationStartingEvent)event);

 

然后我们进入这个方法

 

技术分享图片

 

我们发现这个方法是得到日志系统,然后调用了下面这个方法

 

技术分享图片

 

我们一步步进入看下

技术分享图片

 

再进入

技术分享图片

 

继续执行,其实就是做一些日志相关的操作,实际上就是启动日志系统

技术分享图片

 

技术分享图片

 

我们看到这里其实也就差不多了。

 

所以,实际上这个starting()方法做了很多事情,我们也可以看看其它三个listener对这个事件的响应是什么,大致了解一下。这一步就到此为止,我们继续回到启动主流程。

 

技术分享图片

 

在run方法中还有一个prepareEnvironment(listeners, applicationArguments)这个方法,这个方法返回一个ConfigurableEnvironment对象实例。我们进入方法体,看到代码如下:

 

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {        ConfigurableEnvironment environment = this.getOrCreateEnvironment();        this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());        ConfigurationPropertySources.attach((Environment)environment);        listeners.environmentPrepared((ConfigurableEnvironment)environment);        this.bindToSpringApplication((ConfigurableEnvironment)environment);        if (!this.isCustomEnvironment) {            environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());        }
ConfigurationPropertySources.attach((Environment)environment); return (ConfigurableEnvironment)environment; }

方法体第一步获取或者创建一个环境

技术分享图片

 

然后对这个环境进行设置,方法体中用到了在第一步创建SpringApplication对象的时候设置的一个属性addConversionService,当时设置的是true,其实就是在这里使用,作用是获取一个共享的转换服务,然后设置到环境中去。

 

    protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {        if (this.addConversionService) {            ConversionService conversionService = ApplicationConversionService.getSharedInstance();            environment.setConversionService((ConfigurableConversionService)conversionService);        }
this.configurePropertySources(environment, args); this.configureProfiles(environment, args); }

上面这个方法中的getSharedInstance()方法体如下:

 

public static ConversionService getSharedInstance() {        // 首先从静态属性中去取        ApplicationConversionService sharedInstance = ApplicationConversionService.sharedInstance;        // 如果没取到为null,那接下来就新建出一个来并设置到这个静态属性中去        if (sharedInstance == null) {            Class var1 = ApplicationConversionService.class;            // 注意,这里使用来同步代码块的方式,并且使用来二次检验,保证单例            synchronized (ApplicationConversionService.class) {                sharedInstance = ApplicationConversionService.sharedInstance;                if (sharedInstance == null) {                    sharedInstance = new ApplicationConversionService();                    // 把新建出来的对象再设置给静态属性                    ApplicationConversionService.sharedInstance = sharedInstance;                }            }        }        // 最后返回        return sharedInstance;    }

 

 

获得之后设置到环境中

 

 

 

技术分享图片

 

设置环境中还剩下两个,一个是设置configurePropertySources,一个是设置configureProfiles

 

protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {        MutablePropertySources sources = environment.getPropertySources();        if (this.defaultProperties != null && !this.defaultProperties.isEmpty())             sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));        }
if (this.addCommandLineProperties && args.length > 0) { String name = "commandLineArgs"; if (sources.contains(name)) { PropertySource<?> source = sources.get(name); CompositePropertySource composite = new CompositePropertySource(name); composite.addPropertySource(new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args)); composite.addPropertySource(source); sources.replace(name, composite); } else { sources.addFirst(new SimpleCommandLinePropertySource(args)); } } }

 

这里就是设置PropertySources,这里会用到之前在创建SpringApplication时使用的addCommandLineProperties属性。当时这个属性设置的是true,这里就会用到。但是这里this.addCommandLineProperties && args.length > 0这个并且判断,会根据命令行参数有无执行不同的代码,如果没有命令行参数即args的长度为0时会判断为false,也就是实际上这个判断内部的代码并没有执行;当存在命令行参数时,内部的代码就会执行;

 

配置Profiles的方法体如下

protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {        Set<String> profiles = new LinkedHashSet(this.additionalProfiles);        profiles.addAll(Arrays.asList(environment.getActiveProfiles()));        environment.setActiveProfiles(StringUtils.toStringArray(profiles));    }

这个方法体就是把SpringApplication中的additionalProfiles添加到environment的activeProfiles属性中;

 

然后 ConfigurationPropertySources.attach((Environment)environment);这行代码实际上只执行了最后那个addFirst方法,这个方法项enviroment中的propertySources中添加了一条记录

技术分享图片

 

接下来 listeners.environmentPrepared((ConfigurableEnvironment)environment);

 

这行代码就是告诉SpringApplicationRunListeners中的listener环境信息已经准备好,这个listener就会发布一个环境信息准备好的事件,然后其它的SpringApplication中的Listener中对这个事件有相应的方法就会执行。具体比较复杂,我们以一个为例分析一下:(这个过程实际上就和之前的调用staring()哪里是一致的逻辑)

 

进入方法体之后,listners中只有一个EventPublishingRunListener

 

技术分享图片

 

方法体中对集合遍历,进入关键的方法

技术分享图片

上面这一行代码进入后,我们看见了属性的广播器广播方法,这里就和之前方法逻辑一致,我们再来认识一边

技术分享图片

 

上面这个方法中构造了一个ApplicationEnvironmentPreparedEvent类型的对象实例(这是类型和之前的不同),这个事件就是上面所说的环境信息准备好了的事件,我们要广播的就是这个事件。下面这个代码截图就是这个事件的构造方法,这个事件对象中包含了应用上下文信息、环境信息。

 

技术分享图片

 

我们来看看这个广播器广播事件的过程,进入广播器广播的方法

技术分享图片

 

上面这个方法就是对事件进行广播,我们来看看这个广播的方法中做了什么(其实,通过之前那个starting()方法我们可以猜测到就是从那11个listener中找到支持这个事件响应的listener,然后逐个触发响应)

 

技术分享图片

 

上面这个方法体中就是广播事件方法的代码,其中有一个很明显的熟悉方法就是this.getApplicationListeners(event, type)这个方法

技术分享图片

 

这一次我们就简单说一下,上面这个代码走到的肯定也是最后一行重要的代码,具体做的内容就是检查在第一步创建SpringApplication实例的时候保存的listeners中的11个listener中哪些是支持对此事件做出响应处理的,并最终放在集合中返回。而最终是一共有7个Listener支持对此事件进行处理,执行过程截图如下:

 

技术分享图片

 

拿到这个集合中的7个listener之后,遍历每一个,触发listener对这个事件的处理,以ConfigFileApplicationListener这个为例,我们来分析一下

 

技术分享图片

 

方法执行过程如下:

技术分享图片

 

技术分享图片

 

最终执行到这个listener自己内部定义的方法

 

技术分享图片

 

继续

技术分享图片

 

从上面这个方法体看是一些后置处理器,在环境准备好之后需要执行的一些代码

 

技术分享图片

 

进到这个方法中发现似乎和之前在获取Initializers和listeners方法相似(但是,实际上不是相同的),我们来看一下

技术分享图片

 

这个方法体中有一个我们之前经常使用的loadFactoryNames(factoryType, classLoaderToUse)这个方法,就是从spring.factories中拿指定类型的全限定类名(这里的指定类型是EnvironmentPostProcessor,环境后置处理器)。我们来看看拿到的结果:

 

技术分享图片

 

最终拿到的就是这个4个结果,而这四个结果就是之前已经加载进缓存cache中的

技术分享图片

 

拿到这些之后,会对这些全限定类名代表的类进行实例化

 

技术分享图片

 

我们看看这个实例化代码是怎么样的

 

技术分享图片

 

通过上面可以发现,实际上也是使用反射的方式进行实例化,实例化的最终结果

技术分享图片

 

最后,将这些实例放进list中返回

 

回到上层这个方法

 

技术分享图片

 

看到,还将这个listener自己添加进去,然后针对每个后置处理调用postProcessEnvironment()方法

 

 

 

技术分享图片

 

以其中一个为例

 

技术分享图片

 

比如,这里后置处理器就是对环境中的一些PropertySource进行替换

以上就是环境准备完成事件的通知,其它的后置处理器也是按照上面的逻辑进行处理。

 

我们在回到程序继续执行的地方

 

技术分享图片

下一步就是执行这个绑定

 

技术分享图片

spring boot启动原理一

原文:https://www.cnblogs.com/chaojibaidu/p/14939587.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!