欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot3.x中spring.factories?SPI?服務發(fā)現(xiàn)機制的改變問題小結

 更新時間:2023年05月22日 09:05:00   作者:潦草白紙  
spring.factories其實是SpringBoot提供的SPI機制,底層實現(xiàn)是基于SpringFactoriesLoader檢索ClassLoader中所有jar引入的META-INF/spring.factories文件,這篇文章主要介紹了SpringBoot3.x中spring.factories?SPI?服務發(fā)現(xiàn)機制的改變,需要的朋友可以參考下

一、基礎背景

以Spring Boot 2.x與Spring Boot 3.x為背景做變化描述,順帶勾勒啟動與注冊流程;

二、服務發(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其實是SpringBoot提供的SPI機制,底層實現(xiàn)是基于SpringFactoriesLoader檢索ClassLoader中所有jar(包括ClassPath下的所有模塊)引入的META-INF/spring.factories文件。

基于文件中的接口(或者注解)加載對應的實現(xiàn)類并且注冊到IOC容器。

這種方式對于@ComponentScan不能掃描到的并且想自動注冊到IOC容器的使用場景十分合適,基本上絕大多數(shù)第三方組件甚至部分spring-projects中編寫的組件都是使用這種方案。

三、服務發(fā)現(xiàn)機制調(diào)用

1.啟動SpringApplication

作為SpringBoot啟動入口類,位于Spring-boot-project->spring-boot。

常見啟動類編寫如下:

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)用,開始啟動Spring Boot應用程序。

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)建一個ApplicationContext實例。

通常未做拓展或者配置的情況下為ApplicationContextFactory接口中的

ApplicationContextFactory DEFAULT = new DefaultApplicationContextFactory();
protected ConfigurableApplicationContext createApplicationContext() {
    //WebApplicationType是一個枚舉,在SpringApplication構造方法中
    //通過WebApplicationType.deduceFromClasspath確定應用是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中所有可用的實現(xiàn)類的實例列表。
    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

為應用程序上下文準備必要的配置信息,并將自動配置的組件注冊到上下文中,以完成應用程序的初始化工作。

在prepareContext中l(wèi)oad方法繼續(xù)執(zhí)行,加載所有的ApplicationListener實例,并注冊到ApplicationContext中。

說到這里肯定會有人問:什么是上下文?

GenericWebApplicationContext類實現(xiàn):ConfigurableWebApplicationContext接口
ServletWebServerApplicationContext類繼承:GenericWebApplicationContext類
具體實現(xiàn)類有:
ServletWebServerApplicationContext、ReactiveWebServerApplicationContext、XmlServletWebServerApplicationContext等。

ServletWebServerApplicationContext封裝了WebServer、ServletConfig,對外暴露統(tǒng)一的配置工廠注冊接口,屏蔽從Servlet獲取資源信息的復雜性
適配對接不同的WebServer對象比如netty、jetty、tomcat、unbertow

3.SpringApplication.refreshContext

刷新應用程序上下文,以完成 Bean 的加載、依賴解析、實例化等一系列初始化操作,并執(zhí)行一些后置處理操作,如注冊 ShutdownHook 鉤子、輸出 Banner 等。

SpringApplication中的run方法繼續(xù)執(zhí)行,調(diào)用refreshContext方法,啟動ApplicationContext上下文并刷新應用程序。

在refreshContext方法中,會調(diào)用load方法,加載所有的自動配置類。

4.AutoConfigurationImportSelector在什么時候被調(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;
}

三、服務發(fā)現(xiàn)實現(xiàn)核心

spring-core包中

public class SpringFactoriesLoader {
private final Map<String, List<String>> factories;
//構造方法被保護,被公開的靜態(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();
}
...
//服務構建構建層做事
private List<String> loadFactoryNames(Class<?> factoryType) {
    return this.factories.getOrDefault(factoryType.getName(), Collections.emptyList());
}
}

四、服務發(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升級到Spring Boot 3.0其實是一個"破壞性"升級,目前來看相對較大的影響是:

  • 必須使用JDK17
  • Jakarta EE的引入,導致很多舊的類包名稱改變
  • 部分類被徹底移除
  • 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)容是:
每個實現(xiàn)類的全類名單獨一行。例如:
對于使用了(低版本還沒適配Spring Boot 3.0)mybatis-plus、dynamic-datasource組件的場景,可以在項目某個模塊的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

到此這篇關于SpringBoot3.x中spring.factories SPI 服務發(fā)現(xiàn)機制的改變的文章就介紹到這了,更多相關spring.factories SPI 服務發(fā)現(xiàn)機制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論