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

网友投稿 1073 2022-08-01


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

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都能自动识别,如下图所示。

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> cache = new ConcurrentReferenceHashMap<>();

private SpringFactoriesLoader() {

}

/**

* 根据给定的类型加载并实例化工厂的实现类

*/

public static List loadFactories(Class factoryType, @Nullable ClassLoader classLoader) {

Assert.notNull(factoryType, "'factoryType' must not be null");

// 获取类加载器

ClassLoader classLoaderToUse = classLoader;

if (classLoaderToUse == null) {

classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();

}

// 加载类的全限定名

List factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);

if (logger.isTraceEnabled()) {

logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);

}

List result = new ArrayList<>(factoryImplementationNames.size());

for (Stringhttp:// factoryImplementationName : factoryImplementationNames) {

// 实例化Bean,并将Bean放入到List集合中

result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));

}

AnnotationAwareOrderComparator.sort(result);

return result;

}

/**

* 根据给定的类型加载类路径的全限定类名

*/

public static List 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> loadSpringFactories(@Nullable ClassLoader classLoader) {

// 根据类加载器从缓存中获取,如果缓存中存在,就直接返回,如果不存在就去加载

MultiValueMap result = cache.get(classLoader);

if (result != null) {

return result;

}

try {

// 获取所有jar中classpath路径下的META-INF/spring.factories

Enumeration urls = (classLoader != null ?

classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :

ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));

result = new LinkedMultiValueMap<>();

// 遍历所有的META-INF/spring.factories

while (urlsxUqBrMy.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.getxUqBrMyValue())) {

// 以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 instantiateFactory(String factoryImplementationName, Class 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


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Java详细分析梳理垃圾回收机制(关于java的垃圾回收机制,下面哪些结论)
下一篇:Java超详细讲解设计模式中的命令模式(Java中常用的设计模式)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~