SpringBoot深入浅出分析初始化器

 更新时间:2022年07月15日 09:55:39   作者:lhf2112  
这篇文章主要介绍了SpringBoot初始化器的分析,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

如有错误,望指正;

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值会被以‘,’为分界线进行分割,来获得每个所需的系统初始化器的全限定名,并通过BeanUtis工具来进行初始化;

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

到此这篇关于SpringBoot深入浅出分析初始化器的文章就介绍到这了,更多相关SpringBoot初始化器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 解析本地方法映射Java层的数据类型

    解析本地方法映射Java层的数据类型

    这篇文章给大家介绍了本地方法映射Java层的数据类型,包括基础类型映射,引用类型映射等等,对java层数据类型映射相关知识,感兴趣的朋友跟随脚本之家小编一起看看吧
    2018-03-03
  • eclipse下整合springboot和mybatis的方法步骤

    eclipse下整合springboot和mybatis的方法步骤

    这篇文章主要介绍了eclipse下整合springboot和mybatis的方法步骤,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-03-03
  • solr在java中的使用实例代码

    solr在java中的使用实例代码

    本篇文章主要介绍了solr在java中的使用实例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • Scala异常处理的方法深入分析

    Scala异常处理的方法深入分析

    Scala是一种多范式的编程语言,支持面向对象和函数式编程。Scala也支持异常处理,即在程序运行过程中发生意外或错误时,采取相应的措施
    2023-04-04
  • Java CountDownLatch完成异步回调实例详解

    Java CountDownLatch完成异步回调实例详解

    这篇文章主要介绍了Java CountDownLatch完成异步回调实例详解的相关资料,需要的朋友可以参考下
    2017-03-03
  • idea导入项目爆红问题记录以及解决

    idea导入项目爆红问题记录以及解决

    这篇文章主要介绍了idea导入项目爆红问题记录以及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-07-07
  • 一篇文章带你了解初始Spring

    一篇文章带你了解初始Spring

    这篇文章主要给大家介绍了一个简单的Spring容器初始化流程的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-08-08
  • 聊聊java 过滤器、监听器、拦截器的区别(终结篇)

    聊聊java 过滤器、监听器、拦截器的区别(终结篇)

    这篇文章主要介绍了聊聊java 过滤器、监听器、拦截器的区别(终结篇),具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-02-02
  • SpringMvc微信支付回调示例代码

    SpringMvc微信支付回调示例代码

    微信一直是一个比较热门的词汇,今天这篇文章主要介绍的是SpringMvc微信支付回调的示例代码,对大家开发微信支付具有一定的参考借鉴价值,下面来一起看看吧。
    2016-09-09
  • 图解Java中插入排序算法的原理与实现

    图解Java中插入排序算法的原理与实现

    插入排序的算法描述是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。本文将通过图片详解插入排序的原理及实现,需要的可以参考一下
    2022-08-08

最新评论