• spring.factories配置文件的工厂模式


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

    1 # Initializers
    2 org.springframework.context.ApplicationContextInitializer=
    3 org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,
    4 org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

    我们看到,这个文件配置了一个key:value格式的数据

    1)key是:org.springframework.context.ApplicationContextInitializer

    2)value是:org.springframework.context.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,org.springframework.context.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

    key声明的是一个接口,value则是这个接口对应的实现类,如果有多个则以","符号分割。

    简单来说,spring.factories文件包含了一些接口相对应的实现类的配置,我们通过这些配置就可以知道接口有哪些可选的实现类,并通过反射获取对应的实例对象。就像是简单工厂模式一样,也因此spring将这个文件定义为spring.factories这个名字。

    代码实例

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

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

     1 private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
     2     // 先试着取缓存
     3     MultiValueMap<String, String> result = cache.get(classLoader);
     4     if (result != null) {
     5         return result;
     6     }
     7 
     8     try {
     9         // 获取所有spring.factories的URL
    10         Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
    11         result = new LinkedMultiValueMap<>();
    12         // 遍历URL
    13         while (urls.hasMoreElements()) {
    14             URL url = urls.nextElement();
    15             UrlResource resource = new UrlResource(url);
    16             // 加载每个URL中的properties配置
    17             Properties properties = PropertiesLoaderUtils.loadProperties(resource);
    18             // 遍历每个配置
    19             for (Map.Entry<?, ?> entry : properties.entrySet()) {
    20                 String factoryClassName = ((String) entry.getKey()).trim();
    21                 // 将实现类的配置按照","符号分割开
    22                 for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
    23                     // 逐个添加到接口对应的集合当中
    24                     result.add(factoryClassName, factoryName.trim());
    25                 }
    26             }
    27         }
    28         // 加入缓存
    29         cache.put(classLoader, result);
    30         return result;
    31     } catch (IOException ex) {
    32         // ...
    33     }
    34 }

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

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

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

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

     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     // 遍历实例对象的全限定名
     5     for (String name : names) {
     6         try {
     7             // 加载该类
     8             Class<?> instanceClass = ClassUtils.forName(name, classLoader);
     9             // 断言是否为该接口的实现类
    10             Assert.isAssignable(type, instanceClass);
    11             // 获取构造方法
    12             Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
    13             // 实例化该类
    14             T instance = (T) BeanUtils.instantiateClass(constructor, args);
    15             // 添加到结果集当中
    16             instances.add(instance);
    17         }
    18         catch (Throwable ex) {
    19             throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
    20         }
    21     }
    22     return instances;
    23 }

    总结

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

  • 相关阅读:
    jquery operate
    ujs
    图标站
    rails foreign key
    feedback product from uservoice
    秒杀网
    short url
    rails nil blank
    paperclip imagemagic api &paperclip relevent
    类似优米网
  • 原文地址:https://www.cnblogs.com/lay2017/p/11415661.html
Copyright © 2020-2023  润新知