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

目录
  • 1. 前言
  • 2. 配置
  • 3. 原理
  • 4. 总结

1. 前言

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

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

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

到此这篇关于SpringBoot借助spring.factories文件跨模块实例化Bean的文章就介绍到这了,更多相关SpringBoot实例化Bean内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • springboot1.X和2.X中如何解决Bean名字相同时覆盖

    目录 如何解决Bean名字相同时覆盖 覆盖重写 原有Spring Bean几种方法 方法1 直接在自己工程中建同包同类名的类进行替换 方法3 排除需要替换的jar包中的类 方法4 @Bean 覆盖 方法5 使用BeanDefinitionRegistryPostProcessor 如何解决Bean名字相同时覆盖 在2版本之前的版本,项目中有两个相同名字的bean是可以启动成功的,但是会有覆盖问题 但是在2.X版本的时候会报错: could not be registered. A bean wi

  • springboot-启动bean冲突的解决

    目录 启动bean冲突 启动提示bean重复问题 先说结论 原理 启动bean冲突 在一次启动中遇到了bean冲突的问题,提示存在两个名称重复的bean org.springframework.beans.factory.BeanDefinitionStoreException: Failed to parse configuration class [com.test.api.Application]; nested exception is org.springframework.conte

  • springboot自动配置原理以及spring.factories文件的作用详解

    目录 一.springboot 自动配置原理 二.spring.factories文件的作用 spring.factories 的妙用 什么是 SPI 机制? Spring Boot 中的 SPI 机制 Spring Factories 实现原理是什么? Spring Factories 在 Spring Boot 中的应用 一.springboot 自动配置原理 先说说我们自己的应用程序中Bean加入容器的办法: package com.ynunicom.dc.dingdingcontract

  • springboot 加载 META-INF/spring.factories方式

    目录 springboot 加载 META-INF/spring.factories 用户应用程序Application 建立META-INF/spring.factories文件的意义何在 平常我们如何将Bean注入到容器当中 springboot 加载 META-INF/spring.factories 用户应用程序Application ConfigurableApplicationContext context = SpringApplication.run(NacosSpringBoo

  • springboot中如何判断某个bean是否存在

    目录 如何判断某个bean是否存在 使用@Bean的好处与坏处 如何判断某个bean是否存在 ApplicationContext ctx = SpringUtil.getContext(); String[] beanNames = ctx.getBeanDefinitionNames(); Arrays.sort(beanNames); for (String beanName : beanNames) { //全部bean System.out.println(beanName); } /

  • SpringBoot配置@Configuration注解和@bean注解

    目录 1.@Configuration注解 2.@bean注解 3.单实例 4.配置类也是容器的组件 5.直接调用配置类里面的person1()方法 6.proxyBeanMethods——代理bean的方法 1.@Configuration注解 用法:作用在类上面 作用:告诉SpringBoot这是一个配置类,相当于Spring中的xml配置文件. @Configuration //告诉SpringBoot这是一个配置类 == 配置文件 public class Config { } 2.@b

  • 解决springboot生成bean名称冲突(AnnotationBeanNameGenerator)

    目录 springboot生成bean名称冲突 问题描述 解决问题 自定义bean对象重名问题 springboot生成bean名称冲突 问题描述 我们再使用springboot的时候,在不同的文件目录下,可能存在相同名称的java类,这个时候会报bean name冲突错误 首先我们来了解下,springboot生成bean名称的原理 当Component,Respository,Service,Controller注解的value树形没有自定义时,会根据类的名称生成一个短的bean name.

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

    目录 1. 前言 2. 配置 3. 原理 4. 总结 1. 前言 SpringBoot在包扫描时,并不会扫描子模块下的内容,这样就使得我们的子模块中的Bean无法注入到Spring容器中.SpringBoot就为我们提供了spring.factories这个文件,让我们可以轻松的将子模块的Bean注入到我们的Spring容器中,本篇文章我们就一起探究一下spring.factories 跨模块实例化Bean的原理. 我们在上篇文章中也讲到构建自己构建starter,其中spring.factor

  • Spring中的spring.factories文件用法(Spring如何加载第三方Bean)

    目录 Spring的spring.factories文件用法 问题 解决 SpringBoot的扩展机制之Spring Factories 什么是 SPI机制 Spring Boot中的SPI机制 Spring Factories实现原理是什么 Spring Factories在Spring Boot中的应用 Spring的spring.factories文件用法 在springBoot中,它自动扫描包的时候,只会扫描自己模块下的类. 问题 如果我们不想被Spring容器管理的Bean的路径下不

  • spring在IoC容器中装配Bean详解

    1.Spring配置概述 1.1.概述 Spring容器从xml配置.java注解.spring注解中读取bean配置信息,形成bean定义注册表: 根据bean定义注册表实例化bean: 将bean实例放入bean缓存池: 应用程序使用bean. 1.2.基于xml的配置 (1)xml文件概述 xmlns------默认命名空间 xmlns:xsi-------标准命名空间,用于指定自定义命名空间的schema文件 xmlns:xxx="aaaaa"-------自定义命名空间,xx

  • SpringBoot 如何优雅的实现跨服务器上传文件的示例

    项目完整代码链接:代码链接 跨服务上传文件示意图 一.创建项目 springboot:2.2.6 JDK:1.8 由于资源有限,就用不同端口表示不同服务器了. 1.1 上传文件的项目 首先idea快速搭建工具创建一个springboot项目,名字为fileupload,作为上传文件的服务端. 选择spring web模块即可 配置相关参数 spring.servlet.multipart.enabled=true spring.servlet.multipart.max-file-size=30

  • 关于springboot 中使用httpclient或RestTemplate做MultipartFile文件跨服务传输的问题

    大家好,因为近期做需求中遇到了文件上传这个东西,而且我这个还是跨服务去传输文件的所以我这边使用了httpclient和RestTemplate去做,但是最后还是用的httpclient.feign和RestTemplate在超大文件下会OOM所以适用于小文件传输我这边测试的在1G以下.httpclient好像是无限哈哈哈.(具体多少大家有时间可以去测一下) 1.被调用服务的Controller 1.这块使用@RequestParam("file")或者@RequestPart(&quo

  • Spring如何按业务模块输出日志到不同的文件详解

    目录 一.背景 二.需求 三.技术实现 四.代码实现 1.编写xml日志文件 2.编写QQ模块的代码 3.编写login模块的代码 五.运行结果 六.完整代码 七.一个小知识点 总结 一.背景 在我们开发的过程中,可能存在如下情况: 1.有些时候我们需要调用第三方的接口,一般情况下,调用接口,我们都会记录请求的入参和响应的.如果我们自己系统的日志和第三方的日志混合到一个日志文件中,那么可能查找日志就比较麻烦了.那么我们是否可以将第三方系统的日志单独放到另外的文件中呢? 2.或者有些时候我们系统需

  • SpringBoot整合Spring Security的详细教程

    好好学习,天天向上 本文已收录至我的Github仓库DayDayUP:github.com/RobodLee/DayDayUP,欢迎Star,更多文章请前往:目录导航 前言 Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架.提供了完善的认证机制和方法级的授权功能.是一款非常优秀的权限管理框架.它的核心是一组过滤器链,不同的功能经由不同的过滤器.这篇文章就是想通过一个小案例将Spring Security整合到SpringBoot中去.要实现的功能就是在认证服务器上

  • springboot 无法扫描到父类模块中Bean的原因及解决

    目录 springboot 无法扫描到父类模块中Bean 现象: 如何解决 解决方案 spring boot 启动就自动关闭 之 找不到bean 解决方法: 原因: 以下收集别的解释: 所以有两种解决办法: springboot 无法扫描到父类模块中Bean 现象: 我定义了两个模块 A 和 B .B模块依赖A模块 A模块中我定义了一个@Component 却发现在B模块中我无法扫描到这个Bean导入注入失败 如何解决 查阅得知,在springboot中的bean扫描是扫描同级目录或者下级目录,

随机推荐