当前位置:   article > 正文

SpringBoot的系统初始化器分析_springboot自定义初始化器

springboot自定义初始化器

这里我们尝试从SpringBoot的源码出发来学习下SpringBoot的初始化器;

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

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

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

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

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

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

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

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

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

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

继续进入这个run方法:

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

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

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

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

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

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

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

loadSpringFactories的重点部分代码如下:

  1. try {
  2. Enumeration<URL> urls = (classLoader != null ?
  3. classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
  4. ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
  5. result = new LinkedMultiValueMap<>();
  6. while (urls.hasMoreElements()) {
  7. URL url = urls.nextElement();
  8. UrlResource resource = new UrlResource(url);
  9. Properties properties = PropertiesLoaderUtils.loadProperties(resource);
  10. for (Map.Entry<?, ?> entry : properties.entrySet()) {
  11. String factoryTypeName = ((String) entry.getKey()).trim();
  12. for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
  13. result.add(factoryTypeName, factoryImplementationName.trim());
  14. }
  15. }
  16. }
  17. cache.put(classLoader, result);
  18. return result;
  19. }

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

"META-INF/spring.factories"

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

在获取到了初始化器的全限定名后,我们继续看createSpringFactoriesInstances方法:

  1. private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
  2. ClassLoader classLoader, Object[] args, Set<String> names) {
  3. List<T> instances = new ArrayList<>(names.size());
  4. for (String name : names) {
  5. try {
  6. Class<?> instanceClass = ClassUtils.forName(name, classLoader);
  7. Assert.isAssignable(type, instanceClass);
  8. Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
  9. T instance = (T) BeanUtils.instantiateClass(constructor, args);
  10. instances.add(instance);
  11. }
  12. catch (Throwable ex) {
  13. throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
  14. }
  15. }
  16. return instances;
  17. }

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

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

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

  1. @Override
  2. public void initialize(ConfigurableApplicationContext context) {
  3. ConfigurableEnvironment environment = context.getEnvironment();
  4. List<Class<?>> initializerClasses = getInitializerClasses(environment);
  5. if (!initializerClasses.isEmpty()) {
  6. applyInitializerClasses(context, initializerClasses);
  7. }
  8. }

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

  1. private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) {
  2. String classNames = env.getProperty(PROPERTY_NAME);
  3. List<Class<?>> classes = new ArrayList<>();
  4. if (StringUtils.hasLength(classNames)) {
  5. for (String className : StringUtils.tokenizeToStringArray(classNames, ",")) {
  6. classes.add(getInitializerClass(className));
  7. }
  8. }
  9. return classes;
  10. }

这里的常量:

PROPERTY_NAME= "context.initializer.classes";

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/672140
推荐阅读
相关标签
  

闽ICP备14008679号