SpringBoot源码初学者(一):SpringBoot功能扩展接口的使用与源码分析
2020/3/30 17:02:18
本文主要是介绍SpringBoot源码初学者(一):SpringBoot功能扩展接口的使用与源码分析,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
ps: 在长期的源码学习中,我一直没有找到一个讲解SpringBoot源码的文章,适合刚开始阅读源码的新手,包括我之前的写的一些文章,说实话SpringBoot的源码的确不适合新手,跳转多、结构复杂混乱,很容易迷乱其中。 长时间的学习当中,也总结出这些文章的部分问题:
- 没有说明用法,直接讲解源码,其实很多新手对SpringBoot了解不够多,还不明白怎么用,更不要想能理解源码了
- 源码阅读跳跃大,没有说清楚来龙去脉,只列出重要的部分源码
- 不够有趣,让人很难耐心的看下去,我也不是一个非常幽默的人,当我会尽可能的把这个文章写的有趣一些,也是对自己的配音
- 不成体系,SpringBoot是一个庞大的项目,类与类之间的关系错综复杂。如果不能很好的说明类之间的协作配合,就很难理解相互的流程
- 没有说明方法的参数,我最初阅读源码的时候就很纠结,这到底传了什么过来,我都不知道传参传了什么,更不要提搞清楚做了什么
不管如何,我决定尝试克服这些问题,写一份真正适合阅读源码的新手来看的SpringBoot源码讲解,如果你真的想读懂SpringBoot源码,可以按照以下推荐的方式来阅读文章
- 打开ide,打开SpringBoot源码,跟着文章一起写注释,写自己的注释
- 不要过于纠结没讲到的地方,毕竟SpringBoot源码那么多,想全讲完是不可能的,只要跟着文章认真阅读,SpringBoot是如何运行的一定可以有一个较为深刻的理解
- 文章适合通篇阅读,不适合跳读,跳跃性的阅读很容易错过重要的东西
- 同样的如果之前的文章没有读过,还是最好先去看之前的文章
- 阅读源码必然少不了大段大段的源码,一定要耐心,不要翻翻了事,往往是那些最长的方法中才是真正需要学习的
- 如果断更了请用点赞、收藏、评论的方式激励我 @TOC
一、ApplicationContextInitializer接口
ApplicationContextInitializer,直译过来就是“应用程序上下文初始值设定接口”,在官方文档中描述其作用为“Spring容器刷新前执行的一个回调函数”,如果是对SpringBoot了解较深的老司机一定知道在SpringBoot初始化的时候有两个极为重要的步骤:准备上下文(prepareContext)和刷新上下文(refreshContext),那么ApplicationContextInitializer接口就在刷新上下文(refreshContext)前期准备工作的时候起作用
,如果你还是一个“萌新”SpringBoot的初学者不知道这两个步骤也不要着急,我们后面细细道来。
废话这么多,那么ApplicationContextInitializer接口到底是用来做什么的呢??又需要怎么使用它呢??
ApplicationContextInitializer接口主要的作用是向SpringBoot的容器中注入属性,我们一共有3中方式来使用它,姿势很多直接开干。
1、将属性注入到容器
(1)spring.factories文件注入
步骤1:新建一个类并实现ApplicationContextInitializer接口
@Order(1) public class TestInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext applicationContext) { //从上下文中获取到程序环境 ConfigurableEnvironment environment = applicationContext.getEnvironment(); //创建需要注入的属性 HashMap<String, Object> map = new HashMap<>(); map.put("key1","value1"); //将属性封装成配置项 MapPropertySource testPropertySource = new MapPropertySource("testInitializer", map); //添加到项目环境配置的最末端 environment.getPropertySources().addLast(testPropertySource); //完成控制台输出提示 System.out.println("TestInitializer执行完成"); } } 复制代码
步骤2:将编写好的类注入到容器中,在resources文件夹下新建META-INF文件夹,META-INF文件夹中创建一个spring.factories的文件,里面填写以下内容
#等号后面是需要加载的类,就是我们实现了ApplicationContextInitializer的类的全路径 org.springframework.context.ApplicationContextInitializer=com.gyx.test.TestInitializer 复制代码
(2) SpringApplication手动注入
步骤1:新建一个类并实现ApplicationContextInitializer接口(与之前的完全一样) 步骤2:修改SpringBoot启动类
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication springApplication = new SpringApplication(Application.class); //添加到初始化配置项中 springApplication.addInitializers(new TestInitializer()); springApplication.run(args); } } 复制代码
(3) 配置文件(application.properties)注册
步骤1:新建一个类并实现ApplicationContextInitializer接口(与之前的完全一样) 步骤2:修改SpringBoot的配置文件(Application.properties)
#等号后面是需要加载的类,就是我们实现了ApplicationContextInitializer的类的全路径 context.initializer.classes=com.gyx.test.TestInitializer 复制代码
(4)注意事项
- 多个ApplicationContextInitializer可以使用@Order注解指定执行优先级,Order值越小越有限执行
- 配置文件(application.properties)的注册方式优先于其他的方式(具体原因,接着往下看)
2、将属性从容器中读出
@Component public class TestRead implements ApplicationContextAware { private ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext=applicationContext; } public String test(){ return applicationContext.getEnvironment().getProperty("key1"); } } 复制代码
那么ApplicationContextInitializer是如何被注册到SpringBoot容器中的呢??有请第二位嘉宾闪亮登场!!伴随着音乐向我们走来的是“SpringFactoriesLoader”!
二、SpringFactoriesLoader抽象类
SpringFactoriesLoader是个内敛害羞的汉子,是springboot框架中通用工厂加载机制的一种实现,会从classpath下多个jar包指定的位置读取文件并实例化类
,读取的文件内容必须是key-value形式的,key是接口或者抽象的权限定名,value必须是对应的实现,可以用“,”分割。
SpringFactoriesLoader是SpringBoot门派的守门大将,几乎是SpringBoot初始化最先接触到的类,让我们从SpringBoot的启动类跟着运行顺序看看能在哪里逮到SpringFactoriesLoader。
1、工厂加载机制源码解析
通过这次阅读会了解SpringFactoriesLoader完整的调用过程,以及SpringFactoriesLoader与ApplicationContextInitializer接口是怎样互动的
步骤1:查看SpringBoot启动类
@SpringBootApplication public class Application { public static void main(String[] args) { //进入run方法的源码,按着ctrl戳它 SpringApplication.run(Application.class, args); } } 复制代码
步骤2:这里可以看到一层简单的调用
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) { //进入这个同名方法,继续戳run方法,两个参数分别是传入的启动类和java的启动配置 return run(new Class<?>[] { primarySource }, args); } 复制代码
步骤3:
这里就比较有意思了,注意一下注释
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { //这里将SpringBoot的启动分成了两个重要的部分 //创建一个SpringApplication对象,这个构造方法是在配置SpringBoot自己的一些基本参数,做一些基本数据的获取 //run方法,正式的启动,开始管理SpringBoot的生命周期 //点这个SpringApplication构造方法 return new SpringApplication(primarySources).run(args); } 复制代码
步骤4:没有什么用的封装,对构成函数复用
public SpringApplication(Class<?>... primarySources) { //点this,查看最重要的构造函数 this(null, primarySources); } 复制代码
步骤5:
这里我们可以看到两个熟悉的名字getSpringFactoriesInstances方法和ApplicationContextInitializer接口
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); this.webApplicationType = WebApplicationType.deduceFromClasspath(); //目光聚焦到这里,上面的部分先不要管他,就是在做简单的赋值 //setInitializers方法:设置初始化项 //getSpringFactoriesInstances方法:怎么获取需要的初始化项,当然是通过SpringFactoriesInstances来获取,所以我们先要获取到SpringFactoriesInstances //我们要告诉SpringFactoriesInstances这些初始化项都要哪些特征啊,哦!原来他们都是ApplicationContextInitializer接口的实现类 //点进 getSpringFactoriesInstances方法 setInitializers((Collection) getSpringFactoriesInstances( ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); } 复制代码
步骤6:又是一层同名封装,SpringBoot难看懂有一大半原因就是他喜欢叠千层饼
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) { //不读多说直接戳这个getSpringFactoriesInstances方法 return getSpringFactoriesInstances(type, new Class<?>[] {}); } 复制代码
步骤7:
这里正式出现了SpringFactoriesLoader,仔细阅读,先来看一下这3个参数,
Class type:是之前传进来的“ApplicationContextInitializer.class”
Class<?>[] parameterTypes:是空的数组
Object... args:干脆啥都没有传
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { //获取类加载器,不需要特别在意 ClassLoader classLoader = getClassLoader(); // 这个set里面是所有的SpringFactoriesLoader方法的实现的全限定名 Set<String> names = new LinkedHashSet<>( //查找所有的SpringFactoriesLoader方法的实现的全限定名 //进入loadFactoryNames,跳至步骤8 SpringFactoriesLoader.loadFactoryNames(type, classLoader)); //将这些实现类全部创建出来,我们获得到了一群“内敛害羞的汉子” //进入createSpringFactoriesInstances,跳至 步骤10 List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); //在根据Order对实例进行排序(@Order注解或者Order接口) AnnotationAwareOrderComparator.sort(instances); //返回排序后的结果,SpringFactoriesInstances的源码告一段落,再往后就是另一位嘉宾的故事了 return instances; } 复制代码
步骤8:
这里我们终于初次窥见SpringFactoriesLoader的真面目,这里开始正式的进入SpringFactoriesLoader类的内部
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { //还记factoryClass是什么吗?没错!是ApplicationContextInitializer.class //获取它的类名,这里是ApplicationContextInitializer接口和SpringFactoriesLoader第一次牵手 String factoryClassName = factoryClass.getName(); //点击loadSpringFactories进入 步骤9 //getOrDefault方法:找到key是ApplicationContextInitializer接口的集合,有就返回这个集合,如果没有就返回一个空集合 //返回到 步骤7,到这里就走出了SpringFactoriesLoader return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); } 复制代码
步骤9:
看起来loadSpringFactories是个很复杂的方法,其实不然,loadSpringFactories非常简单,仔仔细细的一起来读一下
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { //SpringFactoriesLoader内部的缓存,第一次进来的时候为空,后面进来就可以直接返回结果,优化加载速度 MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) { return result; } try { //在步骤7中获取到了classLoader,所以classLoader不为空 //用类加载器读取资源 //FACTORIES_RESOURCE_LOCATION这常量是啥呢??如果小伙伴们已经迫不及待的点进去看了,可以很惊奇的看到这不就是"META-INF/spring.factories"! //文章往上翻(或者搜索)找到“spring.factories文件注入”的步骤2,我们是不是创建的就是这个文件,路径和文件名完全一样,我们似乎要接近真相了 Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap<>(); //这里把所有找到的资源循环 //聪明的小朋友可能就有疑惑了!为什么这里要用循环啊,我们不是只有一个spring.factories文件吗? //相信所有的小伙伴们都有开发经验,当我们引入一个叫做“XXX-stater”的jar包时,jar包里面就会有一个spring.factories文件,并且SpringBoot自己也有一个spring.factories文件 //别着急,spring.factories文件的功能可不止注入ApplicationContextInitializer的实现类,我们以后还会和他见面 while (urls.hasMoreElements()) { //获取一条数据(这里占时也只能拿到2条数据,一个我们写的,一个是SpringBoot自身的) URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); //借助Properties类型的文件读取工具,读取spring.factories文件中配置的ApplicationContextInitializer的实现类,当然现在他们还只是一条条可怜的全限定名 Properties properties = PropertiesLoaderUtils.loadProperties(resource); //循环所有的获取到的内容 for (Map.Entry<?, ?> entry : properties.entrySet()) { //先获取key,就是接口或抽象类的全限定名(就是我们写在spring.factories文件中的“org.springframework.context.ApplicationContextInitializer”等号前面的这段) String factoryClassName = ((String) entry.getKey()).trim(); //使用工具根据“,”将value进行切割成字符串集合(不知道原因的小伙伴在文章中搜索“value必须是对应的实现,可以用“,”分割”,然后仔细读几遍) for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { //逐一的添加到结果集中 result.add(factoryClassName, factoryName.trim()); } } } //将结果集放入缓存 cache.put(classLoader, result); //返回 return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } } 复制代码
步骤10:
经过步骤9,获取到所有的指定的ApplicationContextInitializer实现类的全限定名,这这一步中把它们逐一创建出来
回顾一下参数:
Class type:是之前传进来的“ApplicationContextInitializer.class”
Class<?>[] parameterTypes:是空的数组
Object[] args:干脆啥都没有传是个null
names:ApplicationContextInitializer所有实现类的全限定名
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) { //结果集合,放实例对象的 List<T> instances = new ArrayList<>(names.size()); //把全限定名,一个个的循环处理 for (String name : names) { try { //根据全限定名获取到类对象 Class<?> instanceClass = ClassUtils.forName(name, classLoader); //这是一个检测,判断获取到的类型是否与需要的类一样 Assert.isAssignable(type, instanceClass); //根据参数获取构造方法,实际上获取到的就是默认的无参构造方法 Constructor<?> constructor = instanceClass .getDeclaredConstructor(parameterTypes); //使用反射,通过构造方法实例化对象 T instance = (T) BeanUtils.instantiateClass(constructor, args); //将对象放入结果集 instances.add(instance); } catch (Throwable ex) { throw new IllegalArgumentException( "Cannot instantiate " + type + " : " + name, ex); } } //最后返回结果集,回到 步骤7 return instances; } 复制代码
三、系统初始化器源码解析
说明ApplicationContextInitializer使用的时候,我们采用了3种方式,逐一说明,各个击破
1、方法一(spring.factories文件注入)
之前说过ApplicationContextInitializer接口在刷新上下文(refreshContext)之前起作用,我们看一下在哪里起作用了。
步骤1:还是一样查看SpringBoot启动类,从头开始理的清楚
@SpringBootApplication public class Application { public static void main(String[] args) { //进入run方法 SpringApplication.run(Application.class, args); } } 复制代码
步骤2:这里直接进入同名方法
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) { //继续戳run方法 return run(new Class<?>[] { primarySource }, args); } 复制代码
步骤3:
这里开始有区别了,我们现在需要进入run方法,之前是进入SpringApplication的构造函数
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { //这里将SpringBoot的启动分成了两个重要的部分 //创建一个SpringApplication对象,这个构造方法是在配置SpringBoot自己的一些基本参数,做一些基本数据的获取 //run方法,正式的启动,开始管理SpringBoot的生命周期 //点这个run方法 return new SpringApplication(primarySources).run(args); } 复制代码
步骤4:
这是SpringBoot最重要的一个方法!!!超级重要
public ConfigurableApplicationContext run(String... args) { //创建一个计时器,用来记录SpringBoot的运行时间 StopWatch stopWatch = new StopWatch(); //启动计时器 stopWatch.start(); //配置应用上下文的控制器,可进行一些基础配置的操作,设置上下文ID,设置父应用上下文,添加监听器和刷新容器相关的操作等 //目前只需要知道SpringBoot有这么一个管理者就完全OK了,具体作用不用过于在意,实际上SpringBoot有完整的一条管理者体系,这些我们放在以后了解 ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); // 配置属性 configureHeadlessProperty(); //获取监听器 从路径MEAT-INF/spring.factories中找到所有的SpringApplicationRunListener //是不是和之前说的SpringFactoriesLoader有些相似,可谓是一招鲜吃遍天 SpringApplicationRunListeners listeners = getRunListeners(args); //监视器启动! listeners.starting(); try { //封装一下环境 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // 准备环境 不管他,跳过 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); //打印banner 就是每次启动输出的SpringBoot的图标 Banner printedBanner = printBanner(environment); //创建应用程序上下文,常用的有2种 web环境的和普通环境的,根据条件不同,生成的结果不一样 context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); //准备上下文,下面那个就是刷新上下文 //回忆下之前是怎么说的 //ApplicationContextInitializer接口在刷新上下文(refreshContext)之前起作用 //就是这里没跑了,点进去 prepareContext(context, environment, listeners, applicationArguments, printedBanner); //刷新上下文 refreshContext(context); //2次刷新 afterRefresh(context, applicationArguments); //计时器停止 stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } listeners.started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; } 复制代码
步骤5:这里的重点只有一句话
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { context.setEnvironment(environment); postProcessApplicationContext(context); //注意力集中过来 //方法名直译过来“应用初始值设定” //传入的参数就是之前获取的web环境的应用程序上下文,进入!! //ApplicationContextInitializer也就是从这里要开始起作用了 applyInitializers(context); listeners.contextPrepared(context); if (this.logStartupInfo) { logStartupInfo(context.getParent() == null); logStartupProfileInfo(context); } // Add boot specific singleton beans ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); beanFactory.registerSingleton("springApplicationArguments", applicationArguments); if (printedBanner != null) { beanFactory.registerSingleton("springBootBanner", printedBanner); } if (beanFactory instanceof DefaultListableBeanFactory) { ((DefaultListableBeanFactory) beanFactory) .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } // Load the sources Set<Object> sources = getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); load(context, sources.toArray(new Object[0])); listeners.contextLoaded(context); } 复制代码
步骤6
:敲黑板!!注意记笔记 重点来了
protected void applyInitializers(ConfigurableApplicationContext context) { //getInitializers() 就是简单的拿出initializers属性,然后根据order属性进行排序 //initializers属性在代码中是一个list,有兴趣可以点进去看一看,如下 //"private List<ApplicationContextInitializer<?>> initializers;" //重点是list元素是如何添加的进去的 //大家都是天才程序员 //工厂加载机制解析步骤10中,生成的结果集合最终就被赋值给了Initializers //赋值的方法,在工厂加载机制解析步骤5中的setInitializers方法(全文搜索setInitializers快速查看),具体的实现也是简单的赋值,和我们写的getset方法基本上一样 for (ApplicationContextInitializer initializer : getInitializers()) { //这里会获取实现ApplicationContextInitializer接口时填写的泛型 //如果记不得了 ,全文搜索一下“TestInitializer”,可以看到我们实现ApplicationContextInitializer时填写的泛型正是“ConfigurableApplicationContext” Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), ApplicationContextInitializer.class); //判断填入的泛型是否为ConfigurableApplicationContext类或者是它的子类,如果不是停止运行,并报错 //工作过一段时间的话,可能见过这个提示"Unable to call initializer." , //现在透彻理解了报错原因,以后就可以愉快的装逼了 Assert.isInstanceOf(requiredType, context, "Unable to call initializer."); //考大家一个问题,面向对象的3大基本特征是什么? //封装、继承、多态,这里就是使用的多态 //这里调用了ApplicationContextInitializer接口强制实现的initialize方法 //如果遗忘了,全文搜索一下“TestInitializer”,实现接口的同时,重写了initialize方法 initializer.initialize(context); } } 复制代码
2、方法二(SpringApplication手动注入)
步骤1:一样从启动类开始
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication springApplication = new SpringApplication(Application.class); //添加到初始化配置项中 //不同似乎只有这里,此事必有蹊跷,前去一看究竟 springApplication.addInitializers(new TestInitializer()); springApplication.run(args); } } 复制代码
步骤2:手工将实例化的Initializers存入集合
//第一次看到这里的时候,感觉智商收到了侮辱 //编程往往就是这么简单,手动把Initializers存入集合,把步骤1中的TestInitializer的实现放入集合 //没有理解的小朋友,回看2个地方 //1、工厂加载机制解析-》步骤5中的setInitializers方法(全文搜索setInitializers快速查看) //2、系统初始化器解析-》方法一的步骤6中的getInitializers方法(全文搜索getInitializers快速查看) public void addInitializers(ApplicationContextInitializer<?>... initializers) { this.initializers.addAll(Arrays.asList(initializers)); } 复制代码
之后的操作和方法一就没有区别了
3、方法三(配置文件注册)
配置文件注册的方式是通过DelegatingApplicationContextInitializer类来实现的
public class DelegatingApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered 复制代码
可看到DelegatingApplicationContextInitializer也实现了ApplicationContextInitializer接口,这个类被SpringBoot预先写入到spring.factories文件中,大家都是认真的宝宝,认认真真的阅读了“方法一”的解析,知道DelegatingApplicationContextInitializer会被调用initialize方法,跳转至initialize方法代码,走你 步骤1:initialize方法
@Override public void initialize(ConfigurableApplicationContext context) { //获取上下文环境变量,就是配置文件里面的数据 ConfigurableEnvironment environment = context.getEnvironment(); //环境变量中查找initializer所有类的类型,跳转到步骤2 List<Class<?>> initializerClasses = getInitializerClasses(environment); if (!initializerClasses.isEmpty()) { //跳转到步骤3 applyInitializerClasses(context, initializerClasses); } } 复制代码
步骤2:获取配置中设置的Initializer
private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) { //从环境变量(我们的配置文件)中查找一个叫PROPERTY_NAME的属性 //PROPERTY_NAME是个常量,值为“context.initializer.classes” //全局搜索context.initializer.classes,看看出现在哪了,就是配置文件中的属性 String classNames = env.getProperty(PROPERTY_NAME); List<Class<?>> classes = new ArrayList<>(); if (StringUtils.hasLength(classNames)) { //将设置的值根据“,”切割 所以我们配置多个ApplicationContextInitializer实现类的时候需要用“,”隔开 for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) { //getInitializerClass同名方法,方法的重载,根据字符查找对应的类,并判断是否为ApplicationContextInitializer的实现类 //然后添加到返回对象中 classes.add(getInitializerClass(className)); } } //回到步骤1 return classes; } 复制代码
步骤3:实例化ApplicationContextInitializer实现类
private void applyInitializerClasses(ConfigurableApplicationContext context, List<Class<?>> initializerClasses) { Class<?> contextClass = context.getClass(); List<ApplicationContextInitializer<?>> initializers = new ArrayList<>(); for (Class<?> initializerClass : initializerClasses) { //instantiateInitializer方法是使用BeanUtils工具类实例化ApplicationContextInitializer的实现类 //将实例到集合中 initializers.add(instantiateInitializer(contextClass, initializerClass)); } //调用这些实例 跳转到步骤4 applyInitializers(context, initializers); } 复制代码
步骤4:将找到的ApplicationContextInitializer运用起来
private void applyInitializers(ConfigurableApplicationContext context, List<ApplicationContextInitializer<?>> initializers) { //根据order属性进行排序 initializers.sort(new AnnotationAwareOrderComparator()); //循环刚刚的实例集合 for (ApplicationContextInitializer initializer : initializers) { //调用ApplicationContextInitializer的接口强制实现的initialize方法 //完成ApplicationContextInitializer实现类的起作用 initializer.initialize(context); } } 复制代码
了解了所有的运行原理之后,回头思考一下为什么配置文件的方式会优先于其他的方式执行,自己打开源码的仔细阅读的小伙伴们有没有注意到DelegatingApplicationContextInitializer类中定义的order属性为0,而我们自定义的TestInitializer的order属性为1,所以通过配置文件设置的Initializer会优先执行,如果想要优先于配置文件的方式执行只需要将order属性设置的小于0就可以了
这篇关于SpringBoot源码初学者(一):SpringBoot功能扩展接口的使用与源码分析的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-26大厂数据结构与算法教程:入门级详解
- 2024-12-26大厂算法与数据结构教程:新手入门指南
- 2024-12-26Python编程入门指南
- 2024-12-26数据结构高级教程:新手入门及初级提升指南
- 2024-12-26并查集入门教程:从零开始学会并查集
- 2024-12-26大厂数据结构与算法入门指南
- 2024-12-26大厂算法与数据结构入门教程
- 2024-12-26二叉树入门教程:轻松掌握基础概念与操作
- 2024-12-26初学者指南:轻松掌握链表
- 2024-12-26平衡树入门教程:轻松理解与应用