SpringBoot深入浅出分析初始化器

作者:lhf2112 时间:2022-07-06 09:05:59 

如有错误,望指正;

SpringBoot可以有三种方式定义初始化器,来为容器中增加自定义的对象,具体如下:

1、定义在spring.factories文件中,被SpringFactoriesLoader发现注册;

在resources下建立META-INF文件夹,新建spring.factories文件,添加自定义的初始化器:
org.springframework.context.ApplicationContextInitializer=com.mooc.sb2.initializer.InitializerOne

2、SpringApplication初始化完成后手动添加;

SpringApplication springApplication = new SpringApplication(SbApplication.class);
springApplication.addInitializers(new InitializerTwo());
springApplication.run(args)

3、定义成环境变量,被DelegatingApplicationContextInitializer所发现注册(优先级最高)

application.properties中增加一项配置:
context.initializer.classes=com.mooc.sb2.initializer.ThirdInitializer

下面从代码的角度,来看下这三种方式是如何加载的

对于第一种方法,可以跟进

SpringApplication.run(SpringBootApplication.class, args);
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}

继续进入这个run方法:

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}

还有一个run方法,我们继续进入:

这个run方法代码比较长,这里我们关注的是这部分:

exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);

我们进入这个getSpringFactoriesInstances方法,如下:

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}

我们先看这个loadFactoryNames方法,它调用了loadSpringFactories方法来获取固定位置的配置信息:

loadSpringFactories的重点部分代码如下:

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;
}

我们可以发现一个常量的定义:FACTORIES_RESOURCE_LOCATION,而它的值,就是

"META-INF/spring.factories"

这里就解释了我们为什么要自己建一个配置文件在固定的目录下,这样springboot就成功读取到了自定义的初始化器,加载到了cache中;下面我们回到SpringApplication.java中,继续跟进getSpringFactoriesInstances方法。

在获取到了初始化器的全限定名后,我们继续看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());
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);
}
}
return instances;
}

主要使用了反射手段,来完成初始化器的实例化工作;在获取了实例列表之AnnotationAwareOrderComparator.sort(instances);会将实例按照@Order的注解顺序进行排序,在这里不进行详述;

第二种初始化器是通过手动添加

第三种是通过DelegatingApplicationContextInitializer的initialize方法来调用的,首先,我们先看这个方法的代码:

@Override
public void initialize(ConfigurableApplicationContext context) {
ConfigurableEnvironment environment = context.getEnvironment();
List<Class<?>> initializerClasses = getInitializerClasses(environment);
if (!initializerClasses.isEmpty()) {
applyInitializerClasses(context, initializerClasses);
}
}

其中,我们重点关注getInitializerClasses()方法

private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) {
String classNames = env.getProperty(PROPERTY_NAME);
List<Class<?>> classes = new ArrayList<>();
if (StringUtils.hasLength(classNames)) {
for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) {
classes.add(getInitializerClass(className));
}
}
return classes;
}

这里的常量:

PROPERTY_NAME= "context.initializer.classes";

也就是我们配置文件的key,value值会被以&lsquo;,&rsquo;为分界线进行分割,来获得每个所需的系统初始化器的全限定名,并通过BeanUtis工具来进行初始化;

另:为什么DelegatingApplicationContextInitializer加载的初始化器是优先于其他方式执行呢?这是因为SpringApplication的run方法中的prepareContext方法会调用applyInitializers方法,applyInitializers方法的for循环会调用getInitializers方法来加载所有的初始化器,而DelegatingApplicationContextInitializer的Order=0,因此优先级最高,会被最先加载;

来源:https://blog.csdn.net/lhf2112/article/details/103787563

标签:SpringBoot,初始化器
0
投稿

猜你喜欢

  • 掌握Android Handler消息机制核心代码

    2023-11-29 18:29:23
  • DataGridView控件显示行号的正确代码及分析

    2022-07-26 22:05:50
  • Android 管理Activity中的fragments

    2022-06-30 16:41:37
  • c#典型工厂化实现实例

    2022-01-04 19:41:08
  • Android垂直切换的圆角Banner与垂直指示器相关介绍与应用详解

    2023-01-09 02:59:15
  • Java操作FTP实现上传下载功能

    2021-12-07 18:35:04
  • Android实现自定义曲线图

    2023-08-22 07:05:01
  • Android编程之滑动按钮事件实例详解

    2022-03-20 23:03:13
  • Java 远程调用失败重试的操作方法

    2021-08-26 21:42:58
  • Java中接收键盘输入的三种方法

    2023-11-13 16:11:29
  • Spring实战之属性占位符配置器用法示例

    2021-05-30 22:58:04
  • Android开发中解析xml文件XmlUtils工具类与用法示例

    2023-03-26 01:05:45
  • C#修改MAC地址类的实例

    2023-09-11 06:51:14
  • 使用String类型小数值转换为Long类型

    2023-04-14 10:34:56
  • Java实现马踏棋盘算法

    2023-03-05 04:30:46
  • 老生常谈java中的fail-fast机制

    2022-01-27 15:31:50
  • Android AlertDialog六种创建方式案例详解

    2022-08-30 01:55:47
  • Android TimePicker 直接输入的问题解决方案

    2023-08-03 03:11:55
  • springboot 自定义权限标签(tld),在freemarker引用操作

    2023-11-23 06:20:15
  • c#中Invoke与BeginInvoke的用法及说明

    2023-06-10 12:39:49
  • asp之家 软件编程 m.aspxhome.com