解讀spring.factories文件配置詳情
使用場(chǎng)景
在程序開(kāi)發(fā)中,可能會(huì)出現(xiàn)包名不一樣的情況(如:pom 依賴的很多的 jar),如何解決Spring Boot不能被默認(rèn)路徑掃描呢?
- 方法一:在 Spring Boot Application 主類上使用 @Import 注解。
- 方法二:使用 spring.factories 文件
方法一比較簡(jiǎn)單,在此就不做過(guò)多介紹,主要談?wù)?spring.factories 使用。
作用
spring.factories 的作用是使用外部 jar 時(shí)不用再寫配置,會(huì)自動(dòng)加入已經(jīng)配置好的配置。
內(nèi)部原理機(jī)制
spring.factories 這種機(jī)制實(shí)際上是仿照 java 中的 SPI 擴(kuò)展機(jī)制實(shí)現(xiàn)的。
SPI機(jī)制
SPI全稱Service Provider Interface,是 Java 提供的一套用來(lái)被第三方實(shí)現(xiàn)或者擴(kuò)展的接口,其意義在于為某個(gè)接口尋找服務(wù)的實(shí)現(xiàn),主要應(yīng)用在框架中用來(lái)尋找組件,提高擴(kuò)展性。
面向的對(duì)象設(shè)計(jì)里,我們一般推薦模塊之間基于接口編程,模塊之間不對(duì)實(shí)現(xiàn)類進(jìn)行硬編碼。一旦代碼里涉及了具體的實(shí)現(xiàn)類,就違反了可插拔的原則,如果需要替換一種實(shí)現(xiàn),就需要修改代碼。為了實(shí)現(xiàn)在模塊裝配的時(shí)候能不在程序里動(dòng)態(tài)指明,這就需要一種服務(wù)發(fā)現(xiàn)機(jī)制。
- Java 中的 SPI 機(jī)制:為某個(gè)接口尋找服務(wù)的實(shí)現(xiàn)的機(jī)制,有點(diǎn)類似 IOC 的思想,就是將裝配的控制權(quán)移到程序之外,在模塊化設(shè)計(jì)中這個(gè)機(jī)制很重要。
- Spring Boot 中的 SPI 機(jī)制:在 META-INFO/spring.factories 文件中配置接口的實(shí)現(xiàn)類名稱,然后在程序中讀取這些配置文件并實(shí)例化。這種自定義的 SPI 機(jī)制就是 Spring Boot Starter 實(shí)現(xiàn)的基礎(chǔ)。
Spring Factories 實(shí)現(xiàn)原理
spring.factories 實(shí)現(xiàn)是依賴 spring-core 包里的 SpringFactoriesLoader 類,這個(gè)類實(shí)現(xiàn)了檢索 META-INF/spring.factories 文件,并獲取指定接口的配置的功能。
這個(gè)類中定義了兩個(gè)對(duì)外的方法:
loadFactories
:根據(jù)接口類獲取其實(shí)現(xiàn)類的實(shí)例,這個(gè)方法返回的是對(duì)象列表loadFactoryNames
:根據(jù)接口獲取其接口類的名稱,這個(gè)方法返回的是類名的列表
上面兩個(gè)方法的關(guān)鍵都是從指定的 ClassLoader 中獲取 spring.factories 文件,并解析得到類名列表,具體代碼如下:
public final class SpringFactoriesLoader { public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class); 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 (classLoader == 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()); Iterator var5 = factoryImplementationNames.iterator(); while(var5.hasNext()) { String factoryImplementationName = (String)var5.next(); 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(); return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); } private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader); if (result != null) { return result; } else { try { Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories"); LinkedMultiValueMap result = new LinkedMultiValueMap(); while(urls.hasMoreElements()) { URL url = (URL)urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); Iterator var6 = properties.entrySet().iterator(); while(var6.hasNext()) { Entry<?, ?> entry = (Entry)var6.next(); String factoryTypeName = ((String)entry.getKey()).trim(); String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue()); int var10 = var9.length; for(int var11 = 0; var11 < var10; ++var11) { String factoryImplementationName = var9[var11]; result.add(factoryTypeName, factoryImplementationName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException var13) { throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13); } } } 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() + "]"); } else { return ReflectionUtils.accessibleConstructor(factoryImplementationClass, new Class[0]).newInstance(); } } catch (Throwable var4) { throw new IllegalArgumentException("Unable to instantiate factory class [" + factoryImplementationName + "] for factory type [" + factoryType.getName() + "]", var4); } } }
從上述代碼中可以看到,在這個(gè)方法中會(huì)遍歷整個(gè) ClassLoader 中所有 jar 包下的 spring.factories 文件,文件之間不會(huì)相互影響配置,也不回被別人的配置覆蓋。
spring.factories 的是通過(guò) Properties 解析得到的,所以在寫文件中的內(nèi)容都是按照下面這種方式配置的。
用法及配置
spring.factories 文件必須放在 resources 目錄下的 META-INF 的目錄下,否則不會(huì)生效。如果一個(gè)接口希望配置多個(gè)實(shí)現(xiàn)類,可以用","分割。
spring.factories 各種配置的具體含義
ApplicationContextInitializer
- 該配置項(xiàng)用來(lái)配置實(shí)現(xiàn)了 ApplicationContextInitializer 接口的類,這些類用來(lái)實(shí)現(xiàn)上下文初始化。
- 配置如下:
org.springframework.context.ApplicationContextInitializer=\ com.xh.config.MyApplicationContextInitializer
public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext applicationContext) { System.out.println("MyApplicationContextInitializer.initialize() " + applicationContext); } }
ApplicationListener
- 配置應(yīng)用程序監(jiān)聽(tīng)器,該監(jiān)聽(tīng)器必須實(shí)現(xiàn) ApplicationListener 接口。它可以用來(lái)監(jiān)聽(tīng) ApplicationEvent 事件。
- 配置如下:
org.springframework.context.ApplicationListener=\ com.xh.factories.listener.EmailApplicationListener
@Slf4j public class EmailApplicationListener implements ApplicationListener<EmailMessageEvent> { @Override public void onApplicationEvent(EmailMessageEvent event) { log.info("模擬發(fā)送郵件... "); log.info("EmailApplicationListener 接受到的消息:{}", event.getContent()); } }
AutoConfigurationImportListener
- 該配置項(xiàng)用來(lái)配置自動(dòng)配置導(dǎo)入監(jiān)聽(tīng)器,監(jiān)聽(tīng)器必須實(shí)現(xiàn) AutoConfigurationImportListener 接口。
- 該監(jiān)聽(tīng)器可以監(jiān)聽(tīng) AutoConfigurationImportEvent 事件。
- 配置如下:
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\ com.xh.config.MyAutoConfigurationImportListener
public class MyAutoConfigurationImportListener implements AutoConfigurationImportListener { @Override public void onAutoConfigurationImportEvent(AutoConfigurationImportEvent event) { System.out.println("MyAutoConfigurationImportListener.onAutoConfigurationImportEvent() " + event); } }
AutoConfigurationImportFilter
- 配置自動(dòng)配置導(dǎo)入過(guò)濾器,過(guò)濾器必須實(shí)現(xiàn) AutoConfigurationImportFilter 接口。
- 該過(guò)濾器用來(lái)過(guò)濾那些自動(dòng)配置類可用。
- 配置如下:
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\ com.xh.config.MyConfigurationCondition
public class MyConfigurationCondition implements AutoConfigurationImportFilter { @Override public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) { System.out.println("MyConfigurationCondition.match() autoConfigurationClasses=" + Arrays.toString(autoConfigurationClasses) + ", autoConfigurationMetadata=" + autoConfigurationMetadata); return new boolean[0]; } }
EnableAutoConfiguration
- 配置自動(dòng)配置類。這些配置類需要添加 @Configuration 注解。
- 配置如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.xh.config.MyConfiguration
@Configuration public class MyConfiguration { public MyConfiguration() { System.out.println("MyConfiguration()"); } }
FailureAnalyzer
- 配置自定的錯(cuò)誤分析類,該分析器需要實(shí)現(xiàn) FailureAnalyzer 接口。
- 配置如下:
org.springframework.boot.diagnostics.FailureAnalyzer=\ com.huangx.springboot.autoconfig.MyFailureAnalyzer
/** * 自定義錯(cuò)誤分析器 */ public class MyFailureAnalyzer implements FailureAnalyzer { @Override public FailureAnalysis analyze(Throwable failure) { System.out.println("MyFailureAnalyzer.analyze() failure=" + failure); return new FailureAnalysis("MyFailureAnalyzer execute", "test spring.factories", failure); } }
TemplateAvailabilityProvider
- 配置模板的可用性提供者,提供者需要實(shí)現(xiàn) TemplateAvailabilityProvider 接口。
- 配置如下:
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\ com.huangx.springboot.autoconfig.MyTemplateAvailabilityProvider
/** * 驗(yàn)證指定的模板是否支持 */ public class MyTemplateAvailabilityProvider implements TemplateAvailabilityProvider { @Override public boolean isTemplateAvailable(String view, Environment environment, ClassLoader classLoader, ResourceLoader resourceLoader) { System.out.println("MyTemplateAvailabilityProvider.isTemplateAvailable() view=" + view + ", environment=" + environment + ", classLoader=" + classLoader + "resourceLoader=" + resourceLoader); return false; } }
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
JAVA中的函數(shù)式接口Function和BiFunction詳解
這篇文章主要介紹了JAVA中的函數(shù)式接口Function和BiFunction詳解,JDK的函數(shù)式接口都加上了@FunctionalInterface注解進(jìn)行標(biāo)識(shí),但是無(wú)論是否加上該注解只要接口中只有一個(gè)抽象方法,都是函數(shù)式接口,需要的朋友可以參考下2024-01-01Spring Boot 參數(shù)校驗(yàn)的具體實(shí)現(xiàn)方式
這篇文章主要介紹了Spring Boot 參數(shù)校驗(yàn)的具體實(shí)現(xiàn)方式,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-06-06java通過(guò)Arrays.sort(int[] a)實(shí)現(xiàn)由大到小排序的方法實(shí)現(xiàn)
Java中的Arrays.sort()方法是一種內(nèi)置的排序方法,用于對(duì)數(shù)組進(jìn)行排序,本文就來(lái)介紹一下java中的Arrays.sort()排序方法的用法,具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12springcloud下hibernate本地化方言配置方式
這篇文章主要介紹了springcloud下hibernate本地化方言配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09Springboot 如何實(shí)現(xiàn)filter攔截token驗(yàn)證和跨域
這篇文章主要介紹了Springboot 如何實(shí)現(xiàn)filter攔截token驗(yàn)證和跨域操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08