spring.factories 的妙用
現象
在閱讀 Spring-Boot 相關源碼時,常常見到 spring.factories 文件,里面寫了自動配置(AutoConfiguration)相關的類名,因此產生了一個疑問:“明明自動配置的類已經打上了 @Configuration 的注解,為什么還要寫 spring.factories 文件?
用過 Spring Boot 的都知道
@ComponentScan 注解的作用是掃描 @SpringBootApplication 所在的 Application 類所在的包(basepackage)下所有的 @Component 注解(或拓展了 @Component 的注解)標記的 bean,并注冊到 spring 容器中。
那么問題來了
在 Spring Boot 項目中,如果你想要被 Spring 容器管理的 bean 不在 Spring Boot 包掃描路徑下,怎么辦?
解決 Spring Boot 中不能被默認路徑掃描的配置類的方式,有 2 種:
(1)在 Spring Boot 主類上使用 @Import 注解
(2)使用 spring.factories 文件
以下是對 使用 spring.factories 文件的簡單理解
Spring Boot 的擴展機制之 Spring Factories
Spring Boot 中有一種非常解耦的擴展機制:Spring Factories。這種擴展機制實際上是仿照Java中的SPI擴展機制來實現的。
什么是 SPI 機制?SPI 的全名為 Service Provider Interface.大多數開發人員可能不熟悉,因為這個是針對廠商或者插件的。在java.util.ServiceLoader的文檔里有比較詳細的介紹。
簡單的總結下 java SPI 機制的思想。我們系統里抽象的各個模塊,往往有很多不同的實現方案,比如日志模塊的方案,xml解析模塊、jdbc模塊的方案等。面向的對象的設計里,我們一般推薦模塊之間基于接口編程,模塊之間不對實現類進行硬編碼。一旦代碼里涉及具體的實現類,就違反了可拔插的原則,如果需要替換一種實現,就需要修改代碼。為了實現在模塊裝配的時候能不在程序里動態指明,這就需要一種服務發現機制。
java SPI 就是提供這樣的一個機制:為某個接口尋找服務實現的機制。有點類似IOC的思想,就是將裝配的控制權移到程序之外,在模塊化設計中這個機制尤其重要。
Spring Boot 中的 SPI 機制在 Spring 中也有一種類似與 Java SPI 的加載機制。它在 resources/META-INF/spring.factories 文件中配置接口的實現類名稱,然后在程序中讀取這些配置文件并實例化。
這種自定義的SPI機制是 Spring Boot Starter 實現的基礎。
Spring Factories 實現原理是什么?spring-core 包里定義了 SpringFactoriesLoader 類,這個類實現了檢索 META-INF/spring.factories 文件,并獲取指定接口的配置的功能。在這個類中定義了兩個對外的方法:
loadFactories 根據接口類獲取其實現類的實例,這個方法返回的是對象列表。
loadFactoryNames 根據接口獲取其接口類的名稱,這個方法返回的是類名的列表。
上面的兩個方法的關鍵都是從指定的 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> factoryClass, @Nullable ClassLoader classLoader) {Assert.notNull(factoryClass, "'factoryClass' must not be null");ClassLoader classLoaderToUse = classLoader;if (classLoader == null) {classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();}List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);if (logger.isTraceEnabled()) {logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);}List<T> result = new ArrayList(factoryNames.size());Iterator var5 = factoryNames.iterator();while(var5.hasNext()) {String factoryName = (String)var5.next();result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));}AnnotationAwareOrderComparator.sort(result);return result;}public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {String factoryClassName = factoryClass.getName();return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, 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 factoryClassName = ((String)entry.getKey()).trim();String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());int var10 = var9.length;for(int var11 = 0; var11 < var10; ++var11) {String factoryName = var9[var11];result.add(factoryClassName, factoryName.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 instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {try {Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader);if (!factoryClass.isAssignableFrom(instanceClass)) {throw new IllegalArgumentException("Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");} else {return ReflectionUtils.accessibleConstructor(instanceClass, new Class[0]).newInstance();}} catch (Throwable var4) {throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), var4);}} }從代碼中我們可以知道,在這個方法中會遍歷整個 spring-boot 項目的 classpath 下 ClassLoader 中所有 jar 包下的 spring.factories文件。也就是說我們可以在自己的 jar 中配置 spring.factories 文件,不會影響到其它地方的配置,也不會被別人的配置覆蓋。
Spring Factories 在 Spring Boot 中的應用在 Spring Boot 的很多包中都能夠找到 spring.factories 文件,接下來我們以 spring-boot-autoconfigure 包為例進行介紹
# Initializers org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener# Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.boot.autoconfigure.BackgroundPreinitializer# Auto Configuration Import Listeners org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\ org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener# Auto Configuration Import Filters org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\ org.springframework.boot.autoconfigure.condition.OnBeanCondition,\ org.springframework.boot.autoconfigure.condition.OnClassCondition,\ org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration結合前面的內容,可以看出 spring.factories 文件可以將 spring-boot 項目包以外的 bean(即在 pom 文件中添加依賴中的 bean)注冊到 spring-boot 項目的 spring 容器。由于@ComponentScan 注解只能掃描 spring-boot 項目包內的 bean 并注冊到 spring 容器中,因此需要 @EnableAutoConfiguration 注解來注冊項目包外的bean。而 spring.factories 文件,則是用來記錄項目包外需要注冊的bean類名。
總結
以上是生活随笔為你收集整理的spring.factories 的妙用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Mysql的select in会自动过滤
- 下一篇: erlang精要(14)-列表(1)