SpringBoot加载spring.factories的价值

SpringBoot加载spring.factories的价值

在springboot的各个依赖包下,我们经常看到META-INF/spring.factories这个文件。spring.factories文件的内容基本上都是这样的格式

# Initializers
org.springframework.context.ApplicationContextInitializer=
\org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,
\org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

通过spring.factories内容我们知道,这个里面其实就是应用上下文初始化。那么这个配置文件有什么作用呢?

通过源码我们可以看出来。

org.springframework.context.ApplicationContextInitializer是一个接口。而,
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
这两个就是接口的实现类。那么从这个逻辑看我们知道。spring.factories这个配置件,就是加载
ApplicationContextInitializer接口的实现类。实现初始化的作用、那么我们知道大部分封装的功能获取对象都是通过。
反射获取对应的实例对象。就像是简单工厂模式一样,也因此spring将这个文件定义为spring.factories这个名字

下面以ApplicationContextInitializer接口为示例,我们看看springboot是怎么使用spring.factories的。

首先会用classLoader加载类路径下的所有spring.factories的配置内容,SpringFactoriesLoader#loadSpringFactories方法将返回一个key=接口名,value=实现类集合的Map结构

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

    try {
        // 获取所有spring.factories的URL
        Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        result = new LinkedMultiValueMap<>();
        // 遍历URL
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            // 加载每个URL中的properties配置
            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.add(factoryClassName, factoryName.trim());
                }
            }
        }
        // 加入缓存
        cache.put(classLoader, result);
        return result;
    } catch (IOException ex) {
        // ...
    }
}

有了以上这个Map结构,就可以拿到对应接口的实现类集合

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
     return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
 }

SpringApplication#getSpringFactoriesInstances首先会用classLoader加载类路径下的所有spring.factories的配置内容如上,然后通过反射实例化对象

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

SpringApplication#createSpringFactoriesInstances这里的factoryClass是接口,通过getName()方法获取全限定名,然后根据该全限定名从Map结构中get出对应的实现类全限定名的集合。

到这里我们得到了一个实现类的集合,要获取实现类具体的实例对象只需要通过反射得到实例对象即可

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

因此spring.factories就像是工厂一样配置了大量的接口对应的实现类,我们通过这些配置 + 反射处理就可以拿到相应的实现类。这种类似于插件式的设计方式,只要引入对应的jar包,那么对应的spring.factories就会被扫描到,对应的实现类也就会被实例化,如果不需要的时候,直接把jar包移除即可。

这也是我们说为什么要加上spring.factories配置文件的作用。实现的热插法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值