SpringBoot借助spring.factories文件跨模块实例化Bean

作者:CoderJ 时间:2021-12-01 18:22:41 

1. 前言

SpringBoot在包扫描时,并不会扫描子模块下的内容,这样就使得我们的子模块中的Bean无法注入到Spring容器中。SpringBoot就为我们提供了spring.factories这个文件,让我们可以轻松的将子模块的Bean注入到我们的Spring容器中,本篇文章我们就一起探究一下spring.factories 跨模块实例化Bean的原理。

我们在SpringBoot项目为何引入大量的starter?如何自定义starter?文章中也讲到构建自己构建starter,其中spring.factories就起到重要的作用,我们是通过spring.factories让starer项目中的Bean注入到Web模块的Spring容器中。本篇文章就来探究一下spring.factories文件,更深层次的东西,以及我们是如何借助该文件实例化Bean的。

2. 配置

spring.factories文件一般都是配置在src/main/resources/META-INF/ 目录下。

也就是说我们在IDEA新建的SpringBoot项目或者Maven项目的资源文件resources目录下新建一个META-INF文件夹,再建一个spring.factories文件即可,新建的文件没有问题的化,一般IDEA都能自动识别,如下图所示。

SpringBoot借助spring.factories文件跨模块实例化Bean

spring.factories 的文件内容就是接口对应其实现类,实现类可以有多个

文件内容必须是kv形式,即properties类型

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.zhj.config.AutoConfiguration

如其一个接口有多个实现,如下配置:

org.springframework.boot.logging.LoggingSystemFactory=\
org.springframework.boot.logging.logback.LogbackLoggingSystem.Factory,\
org.springframework.boot.logging.log4j2.Log4J2LoggingSystem.Factory,\
org.springframework.boot.logging.java.JavaLoggingSystem.Factory

3. 原理

spring -core 中定义了SpringFactoriesLoader 类,这个类就是让spring.factories文件发挥作用的类。SpringFactoriesLoader类的作用就是检索META-INF/spring.factories文件,并获取指定接口将其实现实例化。 在这个类中定义了两个对外的方法:

  • loadFactories 根据给定的接口类获取其实现类的实例,这个方法返回的是对象列表

  • loadFactoryNames 根据给定的类型加载类路径的全限定类名,这个方法返回的是全限定类名的列表。

源码如下:

public final class SpringFactoriesLoader {

   // 文件位置
   public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

   // 缓存
   private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();


   private SpringFactoriesLoader() {
   }


   /**
    * 根据给定的类型加载并实例化工厂的实现类
    */
   public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
       Assert.notNull(factoryType, "'factoryType' must not be null");
       // 获取类加载器
       ClassLoader classLoaderToUse = classLoader;
       if (classLoaderToUse == null) {
           classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
       }
       // 加载类的全限定名
       List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
       if (logger.isTraceEnabled()) {
           logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
       }
       List<T> result = new ArrayList<>(factoryImplementationNames.size());
       for (String factoryImplementationName : factoryImplementationNames) {
           // 实例化Bean,并将Bean放入到List集合中
           result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
       }
       AnnotationAwareOrderComparator.sort(result);
       return result;
   }

/**
    * 根据给定的类型加载类路径的全限定类名
    */
   public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {

       // 获取工厂类型名称
       String factoryTypeName = factoryType.getName();
       // 加载所有META-INF/spring.factories中的value
       return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
   }

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
       // 根据类加载器从缓存中获取,如果缓存中存在,就直接返回,如果不存在就去加载
       MultiValueMap<String, String> result = cache.get(classLoader);
       if (result != null) {
           return result;
       }

try {
           // 获取所有jar中classpath路径下的META-INF/spring.factories
           Enumeration<URL> urls = (classLoader != null ?
                   classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                   ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
           result = new LinkedMultiValueMap<>();
           // 遍历所有的META-INF/spring.factories
           while (urls.hasMoreElements()) {
               URL url = urls.nextElement();
               UrlResource resource = new UrlResource(url);
               // 将META-INF/spring.factories中的key value加载为Prpperties对象
               Properties properties = PropertiesLoaderUtils.loadProperties(resource);
               for (Map.Entry<?, ?> entry : properties.entrySet()) {
                   // key就是接口的类名称
                   String factoryTypeName = ((String) entry.getKey()).trim();
                   for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                       // 以factoryTypeName为key,实现类为value放入map集合中
                       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);
       }
   }

// 通过反射实例化Bean对象
   @SuppressWarnings("unchecked")
   private static <T> T instantiateFactory(String factoryImplementationName, Class<T> factoryType, ClassLoader classLoader) {
       try {
           Class<?> factoryImplementationClass = ClassUtils.forName(factoryImplementationName, classLoader);
           if (!factoryType.isAssignableFrom(factoryImplementationClass)) {
               throw new IllegalArgumentException(
                       "Class [" + factoryImplementationName + "] is not assignable to factory type [" + factoryType.getName() + "]");
           }
           return (T) ReflectionUtils.accessibleConstructor(factoryImplementationClass).newInstance();
       }
       catch (Throwable ex) {
           throw new IllegalArgumentException(
               "Unable to instantiate factory class [" + factoryImplementationName + "] for factory type [" + factoryType.getName() + "]",
               ex);
       }
   }

}

4. 总结

Spring通过SpringFactoriesLoader实例化Bean的过程

  • 获取SpringFactoriesLoader对应的类加载器

  • 查找缓存,查看缓存中是否已经读取到所有jar中classpath路径下的META-INF/spring.factories的内容

  • 如果缓存已经存在,根据/spring.factories文件中配置的全限定类名通过反射实例化Bean

  • 如果缓存中没有值,则扫描所有jar中的这个META-INF/spring.factories文件,并将其以读取到缓存中,并返回这个配置列表

  • 然后根据这个全限定类名的列表再通过反射实例化Bean

来源:https://juejin.cn/post/7029683137865056292

标签:SpringBoot,Bean,spring.factories,文件
0
投稿

猜你喜欢

  • Java高并发之CyclicBarrier的用法详解

    2023-11-17 16:27:57
  • Android应用APP自动更新功能的代码实现

    2022-09-17 09:48:25
  • Java语言一元运算符实例解析

    2023-01-14 15:31:22
  • 解决Mybatis-plus和pagehelper依赖冲突的方法示例

    2022-06-28 16:52:59
  • C#跨PC远程调用程序并显示UI界面

    2022-06-22 13:42:38
  • asp.net core 使用 tensorflowjs实现 face recognition的源代码

    2022-11-16 22:14:38
  • java数字转汉字工具类详解

    2023-04-28 02:00:26
  • JavaWeb项目Servlet无法访问问题解决

    2022-10-10 03:33:52
  • java获取系统路径字体、得到某个目录下的所有文件名、获取当前路径

    2023-11-29 14:39:23
  • 用Java设计模式中的观察者模式开发微信公众号的例子

    2023-01-17 05:30:58
  • SpringBoot+WebSocket实现即时通讯的方法详解

    2021-07-24 15:48:49
  • feign GET请求不支持对象传参的坑及解决

    2023-07-29 18:10:25
  • 深入了解SparkSQL的运用及方法

    2023-06-08 13:43:27
  • java isInterrupted()判断线程的实例讲解

    2023-07-21 01:45:53
  • 使用Java设置字型和颜色的方法详解

    2023-04-14 05:09:23
  • Java中闭包简单代码示例

    2023-11-08 23:09:48
  • 为什么mybatis中的SqlSession一定要关闭

    2022-02-24 07:57:11
  • Java中的Static class详解及实例代码

    2023-12-24 18:19:40
  • IntelliJ IDEA修改新建文件自动生成注释的user名

    2021-12-29 04:27:38
  • 浅谈C#六大设计原则

    2023-05-02 16:29:58
  • asp之家 软件编程 m.aspxhome.com