SpringBoot3.x中spring.factories SPI 服务发现机制的改变问题小结
作者:潦草白纸
一、基础背景
以Spring Boot 2.x与Spring Boot 3.x为背景做变化描述,顺带勾勒启动与注册流程;
二、服务发现接口
1.@SpringBootApplication启用@EnableAutoConfiguration
2.@EnableAutoConfiguration引入并初始化@Import(AutoConfigurationImportSelector.class)
AutoConfigurationImportSelector类就此被加载并初始化,它的核心加载方法在哪被调用呢?
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机制,底层实现是基于SpringFactoriesLoader检索ClassLoader中所有jar(包括ClassPath下的所有模块)引入的META-INF/spring.factories文件。
基于文件中的接口(或者注解)加载对应的实现类并且注册到IOC容器。
这种方式对于@ComponentScan不能扫描到的并且想自动注册到IOC容器的使用场景十分合适,基本上绝大多数第三方组件甚至部分spring-projects中编写的组件都是使用这种方案。
三、服务发现机制调用
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的静态方法run被调用,开始启动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
创建Context上下文,加载SPI配置
SpringApplication中的createApplicationContext方法被调用,创建一个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中所有可用的实现类的实例列表。 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中load方法继续执行,加载所有的ApplicationListener实例,并注册到ApplicationContext中。
说到这里肯定会有人问:什么是上下文?
GenericWebApplicationContext类实现:ConfigurableWebApplicationContext接口
ServletWebServerApplicationContext类继承:GenericWebApplicationContext类
具体实现类有:
ServletWebServerApplicationContext、ReactiveWebServerApplicationContext、XmlServletWebServerApplicationContext等。
ServletWebServerApplicationContext封装了WebServer、ServletConfig,对外暴露统一的配置工厂注册接口,屏蔽从Servlet获取资源信息的复杂性
适配对接不同的WebServer对象比如netty、jetty、tomcat、unbertow
3.SpringApplication.refreshContext
刷新应用程序上下文,以完成 Bean 的加载、依赖解析、实例化等一系列初始化操作,并执行一些后置处理操作,如注册 ShutdownHook 钩子、输出 Banner 等。
SpringApplication中的run方法继续执行,调用refreshContext方法,启动ApplicationContext上下文并刷新应用程序。
在refreshContext方法中,会调用load方法,加载所有的自动配置类。
4.AutoConfigurationImportSelector在什么时候被调用呢?
通过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; }
三、服务发现实现核心
spring-core包中
public class SpringFactoriesLoader { private final Map<String, List<String>> factories; //构造方法被保护,被公开的静态方法forResourceLocation调用 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()); } }
四、服务发现变化
AutoConfigurationImportSelector.getCandidateConfigurations //原本通过spring-core->SpringFactoriesLoader 去加载META-INF/spring.factories //现在改用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; }
五、周边生态支持适配变化
Spring Boot 2.x升级到Spring Boot 3.0其实是一个"破坏性"升级,目前来看相对较大的影响是:
- 必须使用JDK17
- Jakarta EE的引入,导致很多旧的类包名称改变
- 部分类被彻底移除
- spring-data模块的所有配置属性必须使用spring.data前缀,例如spring.redis.host必须更变为spring.data.redis.host
- spring.factories功能在Spring Boot 2.7已经废弃,在Spring Boot 3.0彻底移除
1.替代方案
替代方案比较简单,就是在类路径下创建META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件
文件的内容是:
每个实现类的全类名单独一行。例如:
对于使用了(低版本还没适配Spring Boot 3.0)mybatis-plus、dynamic-datasource组件的场景,可以在项目某个模块的resources目录下建立META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,输入以下内容:
com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration com.baomidou.mybatisplus.autoconfigure.MybatisPlusLanguageDriverAutoConfiguration com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration
到此这篇关于SpringBoot3.x中spring.factories SPI 服务发现机制的改变的文章就介绍到这了,更多相关spring.factories SPI 服务发现机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
您可能感兴趣的文章:
- Spring中的spring.factories文件用法(Spring如何加载第三方Bean)
- springboot自动配置原理以及spring.factories文件的作用详解
- springboot 加载 META-INF/spring.factories方式
- SpringBoot借助spring.factories文件跨模块实例化Bean
- 关于SpringBoot3.x中spring.factories功能被移除的解决方案
- SpringBoot spring.factories加载时机分析
- springBoot 之spring.factories扩展机制示例解析
- SpringBoot 自动扫描第三方包及spring.factories失效的问题解决
- SpringBoot之spring.factories的使用方式
- 在SpringBoot3中spring.factories配置不起作用的原因和解决方法
- 浅谈spring.factories文件的作用