SpringBoot中借助spring.factories文件跨模块实例化Bean的原理

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

1. 前言

我们在上篇文章中也讲到构建自己构建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都能自动识别,如下图所示。

image.png

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
目录
相关文章
|
3月前
|
XML Java 测试技术
《深入理解Spring》:IoC容器核心原理与实战
Spring IoC通过控制反转与依赖注入实现对象间的解耦,由容器统一管理Bean的生命周期与依赖关系。支持XML、注解和Java配置三种方式,结合作用域、条件化配置与循环依赖处理等机制,提升应用的可维护性与可测试性,是现代Java开发的核心基石。
|
3月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
504 2
|
4月前
|
人工智能 Java 机器人
基于Spring AI Alibaba + Spring Boot + Ollama搭建本地AI对话机器人API
Spring AI Alibaba集成Ollama,基于Java构建本地大模型应用,支持流式对话、knife4j接口可视化,实现高隐私、免API密钥的离线AI服务。
3821 2
基于Spring AI Alibaba + Spring Boot + Ollama搭建本地AI对话机器人API
存储 JSON Java
634 0
|
4月前
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
1713 0
|
4月前
|
缓存 Java 开发者
【Spring】原理:Bean的作用域与生命周期
本文将围绕 Spring Bean 的作用域与生命周期展开深度剖析,系统梳理作用域的类型与应用场景、生命周期的关键阶段与扩展点,并结合实际案例揭示其底层实现原理,为开发者提供从理论到实践的完整指导。
604 22
|
5月前
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
|
5月前
|
监控 Java API
Spring Boot 3.2 结合 Spring Cloud 微服务架构实操指南 现代分布式应用系统构建实战教程
Spring Boot 3.2 + Spring Cloud 2023.0 微服务架构实践摘要 本文基于Spring Boot 3.2.5和Spring Cloud 2023.0.1最新稳定版本,演示现代微服务架构的构建过程。主要内容包括: 技术栈选择:采用Spring Cloud Netflix Eureka 4.1.0作为服务注册中心,Resilience4j 2.1.0替代Hystrix实现熔断机制,配合OpenFeign和Gateway等组件。 核心实操步骤: 搭建Eureka注册中心服务 构建商品
971 3