SpringBoot3.x中spring.factories?SPI?服務(wù)發(fā)現(xiàn)機(jī)制的改變問題小結(jié)
一、基礎(chǔ)背景
以Spring Boot 2.x與Spring Boot 3.x為背景做變化描述,順帶勾勒啟動(dòng)與注冊(cè)流程;
二、服務(wù)發(fā)現(xiàn)接口
1.@SpringBootApplication啟用@EnableAutoConfiguration
2.@EnableAutoConfiguration引入并初始化@Import(AutoConfigurationImportSelector.class)
AutoConfigurationImportSelector類就此被加載并初始化,它的核心加載方法在哪被調(diào)用呢?
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = new ArrayList<>( SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader())); ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you " + "are using a custom packaging, make sure that file is correct."); return configurations; }
spring.factories
spring.factories文件被SpringFactoriesLoader加載
spring.factories其實(shí)是SpringBoot提供的SPI機(jī)制,底層實(shí)現(xiàn)是基于SpringFactoriesLoader檢索ClassLoader中所有jar(包括ClassPath下的所有模塊)引入的META-INF/spring.factories文件。
基于文件中的接口(或者注解)加載對(duì)應(yīng)的實(shí)現(xiàn)類并且注冊(cè)到IOC容器。
這種方式對(duì)于@ComponentScan不能掃描到的并且想自動(dòng)注冊(cè)到IOC容器的使用場景十分合適,基本上絕大多數(shù)第三方組件甚至部分spring-projects中編寫的組件都是使用這種方案。
三、服務(wù)發(fā)現(xiàn)機(jī)制調(diào)用
1.啟動(dòng)SpringApplication
作為SpringBoot啟動(dòng)入口類,位于Spring-boot-project->spring-boot。
常見啟動(dòng)類編寫如下:
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
2.加載SpringApplication.run
SpringApplication的靜態(tài)方法run被調(diào)用,開始啟動(dòng)Spring Boot應(yīng)用程序。
public ConfigurableApplicationContext run(String... args) { long startTime = System.nanoTime(); DefaultBootstrapContext bootstrapContext = createBootstrapContext(); ConfigurableApplicationContext context = null; configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(bootstrapContext, this.mainApplicationClass); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments); Banner printedBanner = printBanner(environment); context = createApplicationContext(); //1展開說明 context.setApplicationStartup(this.applicationStartup); //2展開說明 prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); //3展開說明 refreshContext(context); afterRefresh(context, applicationArguments); Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup); } listeners.started(context, timeTakenToStartup); callRunners(context, applicationArguments); } catch (Throwable ex) { if (ex instanceof AbandonedRunException) { throw ex; } handleRunFailure(context, ex, listeners); throw new IllegalStateException(ex); } try { if (context.isRunning()) { Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime); listeners.ready(context, timeTakenToReady); } } catch (Throwable ex) { if (ex instanceof AbandonedRunException) { throw ex; } handleRunFailure(context, ex, null); throw new IllegalStateException(ex); } return context; }
1.SpringApplication.createApplicationContext
創(chuàng)建Context上下文,加載SPI配置
SpringApplication中的createApplicationContext方法被調(diào)用,創(chuàng)建一個(gè)ApplicationContext實(shí)例。
通常未做拓展或者配置的情況下為ApplicationContextFactory接口中的
ApplicationContextFactory DEFAULT = new DefaultApplicationContextFactory(); protected ConfigurableApplicationContext createApplicationContext() { //WebApplicationType是一個(gè)枚舉,在SpringApplication構(gòu)造方法中 //通過WebApplicationType.deduceFromClasspath確定應(yīng)用是servlet亦或reactive return this.applicationContextFactory.create(this.webApplicationType); } class DefaultApplicationContextFactory implements ApplicationContextFactory { ... @Override public ConfigurableApplicationContext create(WebApplicationType webApplicationType) { try { return getFromSpringFactories(webApplicationType, ApplicationContextFactory::create, this::createDefaultApplicationContext); } catch (Exception ex) { throw new IllegalStateException("Unable create a default ApplicationContext instance, " + "you may need a custom ApplicationContextFactory", ex); } } private <T> T getFromSpringFactories(WebApplicationType webApplicationType, BiFunction<ApplicationContextFactory, WebApplicationType, T> action, Supplier<T> defaultResult) { //SpringFactoriesLoader在spring-context中,用于加載spring.factories指定工廠類在classpath中所有可用的實(shí)現(xiàn)類的實(shí)例列表。 for (ApplicationContextFactory candidate : SpringFactoriesLoader.loadFactories(ApplicationContextFactory.class, getClass().getClassLoader())) { T result = action.apply(candidate, webApplicationType); if (result != null) { return result; } } return (defaultResult != null) ? defaultResult.get() : null; } ... }
2.SpringApplication.prepareContext
為應(yīng)用程序上下文準(zhǔn)備必要的配置信息,并將自動(dòng)配置的組件注冊(cè)到上下文中,以完成應(yīng)用程序的初始化工作。
在prepareContext中l(wèi)oad方法繼續(xù)執(zhí)行,加載所有的ApplicationListener實(shí)例,并注冊(cè)到ApplicationContext中。
說到這里肯定會(huì)有人問:什么是上下文?
GenericWebApplicationContext類實(shí)現(xiàn):ConfigurableWebApplicationContext接口
ServletWebServerApplicationContext類繼承:GenericWebApplicationContext類
具體實(shí)現(xiàn)類有:
ServletWebServerApplicationContext、ReactiveWebServerApplicationContext、XmlServletWebServerApplicationContext等。
ServletWebServerApplicationContext封裝了WebServer、ServletConfig,對(duì)外暴露統(tǒng)一的配置工廠注冊(cè)接口,屏蔽從Servlet獲取資源信息的復(fù)雜性
適配對(duì)接不同的WebServer對(duì)象比如netty、jetty、tomcat、unbertow
3.SpringApplication.refreshContext
刷新應(yīng)用程序上下文,以完成 Bean 的加載、依賴解析、實(shí)例化等一系列初始化操作,并執(zhí)行一些后置處理操作,如注冊(cè) ShutdownHook 鉤子、輸出 Banner 等。
SpringApplication中的run方法繼續(xù)執(zhí)行,調(diào)用refreshContext方法,啟動(dòng)ApplicationContext上下文并刷新應(yīng)用程序。
在refreshContext方法中,會(huì)調(diào)用load方法,加載所有的自動(dòng)配置類。
4.AutoConfigurationImportSelector在什么時(shí)候被調(diào)用呢?
通過AbstractApplicationContext#refresh()
↓
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors
↓
(ConfigurationClassPostProcessor)registryProcessor.postProcessBeanDefinitionRegistry(registry)
↓
ConfigurationClassPostProcessor.processConfigBeanDefinitions();
↓
ConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment,this.resourceLoader, this.componentScanBeanNameGenerator, registry);
↓
parser.parse(candidates);
↓
ConfigurationClassParser. processConfigurationClass()
↓
ConfigurationClassParser.doProcessConfigurationClass
↓
ConfigurationClassParser.processImports
↓
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,this.environment, this.resourceLoader, this.registry);
selector.selectImports(currentSourceClass.getMetadata());
↓
spring-boot-autoconfigure-> AutoConfigurationImportSelector.fireAutoConfigurationImportEvents()
↓
AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions); for (AutoConfigurationImportListener listener : listeners) { invokeAwareMethods(listener); listener.onAutoConfigurationImportEvent(event); }
ConditionEvaluationReportAutoConfigurationImportListener.onAutoConfigurationImportEvent public void onAutoConfigurationImportEvent(AutoConfigurationImportEvent event) { if (this.beanFactory != null) { ConditionEvaluationReport report = ConditionEvaluationReport.get(this.beanFactory); report.recordEvaluationCandidates(event.getCandidateConfigurations()); report.recordExclusions(event.getExclusions()); } }
AutoConfigurationImportSelector.getCandidateConfigurations protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct."); return configurations; }
三、服務(wù)發(fā)現(xiàn)實(shí)現(xiàn)核心
spring-core包中
public class SpringFactoriesLoader { private final Map<String, List<String>> factories; //構(gòu)造方法被保護(hù),被公開的靜態(tài)方法forResourceLocation調(diào)用 protected SpringFactoriesLoader(@Nullable ClassLoader classLoader, Map<String, List<String>> factories) { this.classLoader = classLoader; this.factories = factories; } ... //初始化factories public static SpringFactoriesLoader forResourceLocation(String resourceLocation, @Nullable ClassLoader classLoader) { Assert.hasText(resourceLocation, "'resourceLocation' must not be empty"); ClassLoader resourceClassLoader = (classLoader != null ? classLoader : SpringFactoriesLoader.class.getClassLoader()); Map<String, SpringFactoriesLoader> loaders = cache.computeIfAbsent( resourceClassLoader, key -> new ConcurrentReferenceHashMap<>()); return loaders.computeIfAbsent(resourceLocation, key -> new SpringFactoriesLoader(classLoader, loadFactoriesResource(resourceClassLoader, resourceLocation))); } protected static Map<String, List<String>> loadFactoriesResource(ClassLoader classLoader, String resourceLocation) { Map<String, List<String>> result = new LinkedHashMap<>(); try { Enumeration<URL> urls = classLoader.getResources(resourceLocation); while (urls.hasMoreElements()) { UrlResource resource = new UrlResource(urls.nextElement()); Properties properties = PropertiesLoaderUtils.loadProperties(resource); properties.forEach((name, value) -> { List<String> implementations = result.computeIfAbsent(((String) name).trim(), key -> new ArrayList<>()); Arrays.stream(StringUtils.commaDelimitedListToStringArray((String) value)) .map(String::trim).forEach(implementations::add); }); } result.replaceAll(SpringFactoriesLoader::toDistinctUnmodifiableList); } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + resourceLocation + "]", ex); } return Collections.unmodifiableMap(result); } private static List<String> toDistinctUnmodifiableList(String factoryType, List<String> implementations) { return implementations.stream().distinct().toList(); } ... //服務(wù)構(gòu)建構(gòu)建層做事 private List<String> loadFactoryNames(Class<?> factoryType) { return this.factories.getOrDefault(factoryType.getName(), Collections.emptyList()); } }
四、服務(wù)發(fā)現(xiàn)變化
AutoConfigurationImportSelector.getCandidateConfigurations //原本通過spring-core->SpringFactoriesLoader 去加載META-INF/spring.factories //現(xiàn)在改用META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()) .getCandidates(); Assert.notEmpty(configurations, "No auto configuration classes found in " + "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you " + "are using a custom packaging, make sure that file is correct."); return configurations; }
五、周邊生態(tài)支持適配變化
Spring Boot 2.x升級(jí)到Spring Boot 3.0其實(shí)是一個(gè)"破壞性"升級(jí),目前來看相對(duì)較大的影響是:
- 必須使用JDK17
- Jakarta EE的引入,導(dǎo)致很多舊的類包名稱改變
- 部分類被徹底移除
- spring-data模塊的所有配置屬性必須使用spring.data前綴,例如spring.redis.host必須更變?yōu)閟pring.data.redis.host
- spring.factories功能在Spring Boot 2.7已經(jīng)廢棄,在Spring Boot 3.0徹底移除
1.替代方案
替代方案比較簡單,就是在類路徑下創(chuàng)建META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件
文件的內(nèi)容是:
每個(gè)實(shí)現(xiàn)類的全類名單獨(dú)一行。例如:
對(duì)于使用了(低版本還沒適配Spring Boot 3.0)mybatis-plus、dynamic-datasource組件的場景,可以在項(xiàng)目某個(gè)模塊的resources目錄下建立META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,輸入以下內(nèi)容:
com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration com.baomidou.mybatisplus.autoconfigure.MybatisPlusLanguageDriverAutoConfiguration com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration
到此這篇關(guān)于SpringBoot3.x中spring.factories SPI 服務(wù)發(fā)現(xiàn)機(jī)制的改變的文章就介紹到這了,更多相關(guān)spring.factories SPI 服務(wù)發(fā)現(xiàn)機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Spring中的spring.factories文件用法(Spring如何加載第三方Bean)
- springboot自動(dòng)配置原理以及spring.factories文件的作用詳解
- springboot 加載 META-INF/spring.factories方式
- SpringBoot借助spring.factories文件跨模塊實(shí)例化Bean
- 關(guān)于SpringBoot3.x中spring.factories功能被移除的解決方案
- SpringBoot?spring.factories加載時(shí)機(jī)分析
- springBoot?之spring.factories擴(kuò)展機(jī)制示例解析
- SpringBoot 自動(dòng)掃描第三方包及spring.factories失效的問題解決
- SpringBoot之spring.factories的使用方式
- 在SpringBoot3中spring.factories配置不起作用的原因和解決方法
- 淺談spring.factories文件的作用
相關(guān)文章
Java發(fā)送郵箱驗(yàn)證碼、session校驗(yàn)功能
本篇主要描述“發(fā)送郵箱驗(yàn)證碼、session校驗(yàn)”相關(guān)前(html\js)后(java)臺(tái)代碼,業(yè)務(wù)邏輯示例,需要的朋友可以參考下2018-02-02vue+springboot+shiro+jwt實(shí)現(xiàn)登錄功能
這篇文章主要介紹了vue+springboot+shiro+jwt實(shí)現(xiàn)登錄功能,本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-04-04Spring中Websocket身份驗(yàn)證和授權(quán)的實(shí)現(xiàn)
在Web應(yīng)用開發(fā)中,安全一直是非常重要的一個(gè)方面,本文主要介紹了Spring中Websocket身份驗(yàn)證和授權(quán)的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-08-08java+jdbc+mysql+socket搭建局域網(wǎng)聊天室
這篇文章主要為大家詳細(xì)介紹了java+jdbc+mysql+socket搭建局域網(wǎng)聊天室,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-01-01Java中Map實(shí)現(xiàn)線程安全的3種方式
本文主要介紹了Java中Map實(shí)現(xiàn)線程安全的3種方式,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03圖數(shù)據(jù)庫NebulaGraph的Java 數(shù)據(jù)解析實(shí)踐與指導(dǎo)詳解
這篇文章主要介紹了圖數(shù)據(jù)庫NebulaGraph的Java 數(shù)據(jù)解析實(shí)踐與指導(dǎo)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04Spring框架應(yīng)用的權(quán)限控制系統(tǒng)詳解
在本篇文章里小編給大家整理的是關(guān)于基于Spring框架應(yīng)用的權(quán)限控制系統(tǒng)的研究和實(shí)現(xiàn),需要的朋友們可以學(xué)習(xí)下。2019-08-08