PDS-ApplicationContextInitializer

1.介绍下SpringFactoriesLoader

  • 框架内部使用的通用加载机制
  • 加载并实例化类路径下(包含jar包)META-INF/spring.factories指定类型的类型;
  • 可以自定义实例化方式,只使用加载功能,返回List<String>,值为类的全限定类名;
  • META-INF/spring.factories文件的格式为Properties格式,即K/V格式。其中key一般为接口或抽象类的全限定类名,value为实现类,多个实现逗号分隔

Spring Factories Loader

2.SpringFactoriesLoader如何加载工厂类?

对外暴露了两个 public方法,具体如下:

loadFactories

public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
      //实际的FactoryClass为 META-INF/spring.factories文件中的某个key
        Assert.notNull(factoryClass, "'factoryClass' must not be null");
        ClassLoader classLoaderToUse = classLoader;
        if (classLoaderToUse == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
      //根据名字和参数就能知道,获取名字;这个方法比较核心,跟进去看一下;
        List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
        if (logger.isTraceEnabled()) {
            logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
        }
        List<T> result = new ArrayList<>(factoryNames.size());
      //for 循环反射实例化对象;
        for (String factoryName : factoryNames) {
            result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
        }
      //如果根据@Order注解中的值进行排序
        AnnotationAwareOrderComparator.sort(result);
        return result;
    }

loadFactoryNames

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
      //loadSpringFactories的返回是一个map,然后调用getOrDefault方法,具体api可以参加guava.
        return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }

loadSpringFactories

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        //老套路,尝试从缓存取
        MultiValueMap<String, String> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }


        try {
            //通过classLoader加载对应的META-INF/spring.factories文件。
            Enumeration<URL> urls = (classLoader != null ?
                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));

            result = new LinkedMultiValueMap<>();
            //迭代,放在map中。这里存在一k多v的情况,所以用的是LinkedMultiValueMap
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                //借助工具类转换为properties文件,其实就是一个k/v都为string的map
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    String factoryClassName = ((String) entry.getKey()).trim();
                    for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        //result类型是LinkedMultiValueMap,允许一K多V。
                        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);
        }
    }

根据上面的调用流程可以知道,可以选择加载&实例化,对应<T> List<T> loadFactories方法;也可以选择只加载,拿到全限定类名,自己想干什么都可以,对应 List<String> loadFactoryNames

3.系统初始化器的作用?

  • 回调接口,用于初始化 Spring Context,调用时机在reresh方法之前;
  • 通过实现该接口,可以在手动给应用上下文注入一些内容,如给Environment注入自定义属性;
  • 可以根据实现类上的 @Order注解进行排序;

4.系统初始化器调用时机?

org.springframework.boot.SpringApplication#run(java.lang.String...)#prepareContext方法处调用:

5.如何自定义实现系统初始化器?

方式一

  • 实现ApplicationContextInitializer接口
  • resources/META-INF/spring.factories内填写接口实现
  • key值为org.springframework.context.ApplicationContextlnitializer
@Order(1)
public class FirstInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        environment.setRequiredProperties("mooc");
        Map<String, Object> map = new HashMap<>();
        map.put("key1", "value1");
        MapPropertySource mapPropertySource = new MapPropertySource("firstInitializer", map);
        environment.getPropertySources().addLast(mapPropertySource);
        System.out.println("run firstInitializer");
    }
}
#配置
org.springframework.context.ApplicationContextInitializer=com.mooc.sb2.initializer.FirstInitializer

方式二

  • 实现ApplicationContextInitializer接口
  • SpringApplication类初始后设置进去
SpringApplication application = new SpringApplication(Sb2Application.class);
SecondInitializer initializer = new SecondInitializer();
application.addInitializers(initializer);
application.run(args);

方式三

  • 实现ApplicationContextInitializer接口
  • application.properties内填写接口实现
  • key值为context.initializer.classes
#application.properties
context.listener.classes=com.mooc.sb2.listener.ThirdListener

6.自定义实现系统初始化器有哪些注意事项?

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




网络知识搬运/梳理小工
原文地址:https://www.cnblogs.com/aibilim/p/12107349a587cdc9587c45684659d2e0.html