SpringBoot借助spring.factories文件跨模塊實例化Bean
1. 前言
SpringBoot在包掃描時,并不會掃描子模塊下的內(nèi)容,這樣就使得我們的子模塊中的Bean無法注入到Spring容器中。SpringBoot
就為我們提供了spring.factories
這個文件,讓我們可以輕松的將子模塊的Bean注入到我們的Spring容器中,本篇文章我們就一起探究一下spring.factories
跨模塊實例化Bean的原理。
我們在SpringBoot項目為何引入大量的starter?如何自定義starter?文章中也講到構(gòu)建自己構(gòu)建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 的文件內(nèi)容就是接口對應(yīng)其實現(xiàn)類,實現(xiàn)類可以有多個
文件內(nèi)容必須是kv形式,即properties類型
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.zhj.config.AutoConfiguration
如其一個接口有多個實現(xiàn),如下配置:
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文件發(fā)揮作用的類。SpringFactoriesLoader類的作用就是檢索META-INF/spring.factories文件,并獲取指定接口將其實現(xiàn)實例化。 在這個類中定義了兩個對外的方法:
- loadFactories 根據(jù)給定的接口類獲取其實現(xiàn)類的實例,這個方法返回的是對象列表
- loadFactoryNames 根據(jù)給定的類型加載類路徑的全限定類名,這個方法返回的是全限定類名的列表。
源碼如下:
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() { } ? ? /** * 根據(jù)給定的類型加載并實例化工廠的實現(xiàn)類 */ 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; } /** * 根據(jù)給定的類型加載類路徑的全限定類名 */ 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) { // 根據(jù)類加載器從緩存中獲取,如果緩存中存在,就直接返回,如果不存在就去加載 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,實現(xiàn)類為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. 總結(jié)
Spring通過SpringFactoriesLoader
實例化Bean的過程
- 獲取
SpringFactoriesLoader
對應(yīng)的類加載器 - 查找緩存,查看緩存中是否已經(jīng)讀取到所有jar中classpath路徑下的META-INF/spring.factories的內(nèi)容
- 如果緩存已經(jīng)存在,根據(jù)/spring.factories文件中配置的全限定類名通過反射實例化Bean
- 如果緩存中沒有值,則掃描所有jar中的這個META-INF/spring.factories文件,并將其以讀取到緩存中,并返回這個配置列表
- 然后根據(jù)這個全限定類名的列表再通過反射實例化Bean
到此這篇關(guān)于SpringBoot借助spring.factories文件跨模塊實例化Bean的文章就介紹到這了,更多相關(guān)SpringBoot實例化Bean內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot中json對象中對Long類型和String類型相互轉(zhuǎn)換
與前端聯(lián)調(diào)接口時,后端一些字段設(shè)計為Long類型,這樣就有可能導(dǎo)致前端缺失精度,這時候我們就需要將Long類型返回給前端時做數(shù)據(jù)類型轉(zhuǎn)換,本文主要介紹了springboot中json對象中對Long類型和String類型相互轉(zhuǎn)換,感興趣的可以了解一下2023-11-11Spring Cache和EhCache實現(xiàn)緩存管理方式
這篇文章主要介紹了Spring Cache和EhCache實現(xiàn)緩存管理方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06Spring框架事務(wù)屬性中事務(wù)隔離級別與傳播行為全面講解
這篇文章主要介紹了Spring框架聲明式事務(wù)的事務(wù)隔離級別和事務(wù)傳播行為,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2022-11-11Mybatis基礎(chǔ)概念與高級應(yīng)用小結(jié)
這篇文章主要介紹了Mybatis基礎(chǔ)回顧與高級應(yīng)用,本文內(nèi)容有點小長,希望大家耐心閱讀,此文結(jié)合實例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-06-06Java設(shè)計模式之中介者模式(Mediator Pattern)簡介
這篇文章主要介紹了Java設(shè)計模式之中介者模式(Mediator Pattern),需要的朋友可以參考下2014-07-07