springboot学习(二)系统初始化器

一:系统初始化器(ApplicationContextInitializer)介绍

  系统初始化器是spring容器刷新之前执行的一个回调函数,它的作用是向springboot容器中注册属性。

二:系统初始化器的三种实现方式

1.springboot SPI 扩展机制向 META-INF/spring.factories中配置我们自定义的系统初始化器

  • 实现ApplicationContextInItializer接口
  • 在spring.factories内填写接口实现,key值为 org.springframework.context.ApplicationContextInitializer
​@Order(1)
public class FirstInitializer implements
    ApplicationContextInitializer<ConfigurableApplicationContext> {

  @Override
  public void initialize(ConfigurableApplicationContext applicationContext) {
    //获取环境
    ConfigurableEnvironment environment = applicationContext.getEnvironment();
    Map<String, Object> map = new HashMap<>(16);
    //放入环境属性
    map.put("aaa", "bbb");
    MapPropertySource mapPropertySource = new MapPropertySource("firstInitializer", map);
    //放入environment中
    environment.getPropertySources().addLast(mapPropertySource);
    System.out.println("执行第一个系统初始化器");
  }
}

​
org.springframework.context.ApplicationContextInitializer = com.yangle.springboot.initializer.FirstInitializer

可以看到在启动springboot的时候执行了我们的第一个系统初始化器 

 2.在Application类的main方法中通过addInitializers()方法添加

  • 实现ApplicationContextInitializer
  • SpringApplication类初始化后通过addInitializers()设置进去
@Order(2)
public class SecondInitializer implements
    ApplicationContextInitializer<ConfigurableApplicationContext> {

  @Override
  public void initialize(ConfigurableApplicationContext applicationContext) {
    ConfigurableEnvironment environment = applicationContext.getEnvironment();
    Map<String, Object> map = new HashMap<>(16);
    map.put("ccc", "ddd");
    MapPropertySource mapPropertySource = new MapPropertySource("secondInitializer", map);
    environment.getPropertySources().addLast(mapPropertySource);
    System.out.println("执行第二个系统初始化器");
  }
}

 

​
  public static void main(String[] args) {
//    SpringApplication.run(SpringbootApplication.class, args);
    SpringApplication springApplication = new SpringApplication(SpringbootApplication.class);
    springApplication.addInitializers(new SecondInitializer());
    springApplication.run(args);
  }

​

可以看到第二个系统初始化器也加入进来了,因为我们在类上定义了Order(2)注解,所以第二个系统初始化器是在第一个系统初始化器之后加载进来的。

3.在配置文件中进行配置

  • 实现ApplicationContextInitializer接口
  • 在application.yml中填写接口实现,key值为 context.initializer.classes
@Order(3)
public class ThirdInitializer implements
    ApplicationContextInitializer<ConfigurableApplicationContext> {

  @Override
  public void initialize(ConfigurableApplicationContext applicationContext) {
    ConfigurableEnvironment environment = applicationContext.getEnvironment();
    Map<String, Object> map = new HashMap<>(16);
    map.put("fff", "ggg");
    MapPropertySource mapPropertySource = new MapPropertySource("thirdInitializer", map);
    environment.getPropertySources().addLast(mapPropertySource);
    System.out.println("执行第三个系统初始化器");
  }
}

 

 可以看到第三个系统初始化器也加入进来的,但是它的Order为3却优先级最高,是因为springboot在加载系统初始化器时会先加载  DelegatingApplicationContextInitializer类,因为它的order是0。而它所做的事情就是去加载context.initializer.classes下面的实现类。所以第三个的order失效了。可以看下源码:

  private List<Class<?>> getInitializerClasses(ConfigurableEnvironment env) {
    String classNames = env.getProperty("context.initializer.classes");
    List<Class<?>> classes = new ArrayList();
    if (StringUtils.hasLength(classNames)) {
      String[] var4 = StringUtils.tokenizeToStringArray(classNames, ",");
      int var5 = var4.length;

      for(int var6 = 0; var6 < var5; ++var6) {
        String className = var4[var6];
        classes.add(this.getInitializerClass(className));
      }
    }

    return classes;
  }

通过上面三种实现方式可以看出

  • 都要实现ApplicationContextInitializer接口
  • Order值越小越先执行
  • application.yml中定义的优先于其它方式

实现原理总结:

  • 定义在spring.factories文件中被SpringFactoriesLoader发现注册(第一种)
  • SpringApplication初始化完成后手动添加(第二种)
  • 定义成环境变量被DelegatingApplicationContextInitializer发现注册(第三种)

这就是ApplicationContextInitializer的三种实现方式,那么spring容器是怎么识别并注入它们的呢?是通过SpringFactoriesLoader

下面就介绍下这个类

三:SpringFactoriesLoader介绍

首先我们来看下官方是怎么描述它的:该类是一个框架内部使用的通用工厂装载机制,SpringFactoriesLoader 加载和实例化给定类型的工厂“META-INF / spring.factories”文件,其可存在于在类路径多个JAR文件。该spring.factories文件必须为Properties格式,其中的关键是接口或抽象类的的名称必须是全限定名并且值是一个逗号分隔(多个的情况)的实现类名的列表。(英文不是很好,翻译可能读起来有点怪不准确)

总结一下

  • SpringFactoriesLoader是框架内部使用的的通用工厂加载机制
  • 从从classpath下多个jar包特定位置读取文件并初始化类
  • 文件内容必学是kv形式(properties类型)
  • key是全限定名(抽象类或者接口),value是实现类名,如果多个实现类,用逗号分隔

在上篇文章中提到过类初始化器是在框架初始化中完成的。我们可以点进去看一下源码:

  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;
  }
	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        //先去缓存中看存不存在
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
            //如果存在直接返回
			return result;
		}

		try {
            //获取所有jar包META-INF/spring.factories下的实现类
			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 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;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}
	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		String factoryTypeName = factoryType.getName();
        //getOrDefault去寻找有没有这个key对应的集合,如果没有返回一个新的集合
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
	}

 

  private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) {
    //创建一个跟name大小的实例化集合
    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;
  }

由以上源码可以看出SpringFactories的工作流程是:首先在缓存中进行查找,如果缓存中存在,则直接返回缓存中的全路径类名,完成实例化排序。如果不存在,先读取指定资源,构建Properties,获取kv值,并将value值按逗号分隔,把结果保存到缓存中,后一次实例化这些对象,对实例化后的对象进行排序。

四:ApplicationContextInitializer调用

上面讲了是springboot是如何识别并注册系统初始化器的,那么它是在什么时候调用系统初始化器呢?因为系统初始化器是spring容器刷新之前执行的一个回调函数。结合上一章springboot的启动流程可知,它应该是在框架启动中,刷新上下文之前执行的。可以看下源码

 public Set<ApplicationContextInitializer<?>> getInitializers() {
    //获取之前加载到容器的系统初始化器
    return asUnmodifiableOrderedSet(this.initializers);
  }
 
 protected void applyInitializers(ConfigurableApplicationContext context) {
    Iterator var2 = this.getInitializers().iterator();

    while(var2.hasNext()) {
      ApplicationContextInitializer initializer = (ApplicationContextInitializer)var2.next();
      //获取初始化器的泛型
      Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), ApplicationContextInitializer.class);
      //判断该泛型是不是ConfigurableApplicationContext的子类
      Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
      initializer.initialize(context);
    }

  }

ApplicationContextInitializer调用总结

  • run框架启动
  • prepareContext方法中调用applyInitializers方法
  • 遍历调用初始化器进行判断
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值