SpringBoot(二):springboot自动装配之SPI机制

2023年 8月 23日 61.3k 0

SpringBoot(二):springboot自动装配之SPI机制

上篇文章我们介绍了springboot启动过程中涉及的核心类及其功能,我们知道springboot相较于spring的一大特性就是自动装配,那么自动装配是怎么具体实现的呢? 其实在实现自动装配上springboot采用了多种方案结合的,比如基于spring的扩展点的自动属性注入等,还有提供了一套SPI机制让程序自动可插拔的装配。 本文我带大家重点 了解一下SPI机制的实现原理。

1 什么是SPI?

SPI(Service Provider Interface)机制是一种服务发现和加载机制。它允许开发者编写一个服务接口,然后通过在项目中使用服务提供者实现该接口的方式,实现对应的服务功能。

1.1 JDK中的SPI

JDK的SPI机制通过在Classpath中的META-INF/services目录下,创建以服务接口全限定名命名的文件, 文件的内容为实现该接口的具体实现类。当应用程序需要使用该服务时,JDK会自动加载并实例化配置文件中列出的实现类,并提供给应用程序使用。

1.2 Springboot中的SPI

在Spring Boot中,SPI机制允许开发者通过定义接口和实现类的方式,实现对应的功能扩展。通过在META-INF/spring.factories配置文件中列出实现类, Spring Boot能够自动加载并使用这些扩展点,提供了灵活的定制和扩展能力。

Tip:我本人在多年的开发经验中也积累了很多通用的starter,比如说通过引入我的starter修改配置文件就可以快速搭建一个web资源认证授权服务器/客户端, 当然这个也是在spring-security+oauth2上进一步的封装,需要了解的可以去Git自己获取

// Git代码
https://gitee.com/yeeevip/yeee-memo/tree/master/memo-parent/memo-common/common-auth/common-platform-auth-server
https://github.com/yeeevip/yeee-memo/tree/master/memo-parent/memo-common/common-auth/common-platform-auth-server

2 Springboot的SPI机制是怎么实现的?

2.1 简短概括

  • 程序启动,注册配置类处理器
  • spring刷新上下文,执行配置类处理器
  • 扫描spring.factories将得到的BeanDefinition注册到容器
  • spring实例化/初始化这些BeanDefinition
  • 2.2 源码跟踪

  • 启动SpringBoot程序,创建应用上下文ApplicationContext
  • 我们会调用SpringApplication.run启动程序,通常情况下我们会在程序的主启动类上加一个 @SpringBootApplication 注解,追踪这个注解会发现它是一个复杂的聚合注解, 其中内部包含了@Configuration、 @EnableAutoConfiguration ,而 @EnableAutoConfiguration 有集成了 @Import 注解,这个注解对于springboot非常重要!

    // 用户定义的程序主启动类,启动程序
    @SpringBootApplication
    public class SpringbootExampleApplication {
        public static void main(String[] args) {
            SpringApplication.run(SpringbootExampleApplication.class, args);
        }
    }
    // Springboot的主启动类,创建spring上下文
    public class SpringApplication {
        public ConfigurableApplicationContext run(String... args) {
            ConfigurableApplicationContext context = null;
            try {
                ...
                context = createApplicationContext();
                ...
            } catch (Throwable ex) {
                ...
            }
        }
    }
    
  • 注册BeanFactory后置处理器用于处理配置类上的注解
  • springboot启动创建应用上下文后,会注册一系列的内置的后置处理器,如ConfigurationClassPostProcessor、AutowiredAnnotationBeanPostProcessor、 CommonAnnotationBeanPostProcessor,其中 ConfigurationClassPostProcessor 实现了BeanFactoryPostProcessor是一个BeanFactory级别的处理器用于处理配置类

    注册PostProcessor代码如下:

    public class AnnotatedBeanDefinitionReader {
        public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
            ...
            // 注册各种后置处理器
            AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
        } 
    }
    // 注册各种后置处理器
    public abstract class AnnotationConfigUtils {
        public static Set registerAnnotationConfigProcessors(BeanDefinitionRegistry registry, @Nullable Object source) {
            ...
            if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
                // 注册处理器
                RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
                def.setSource(source);
                beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
            }
            ...
            return beanDefs;
        }
    }
    
  • 刷新应用上下文,执行注册的后置处理器
  • springboot执行refreshContext刷新上下文,本质还是spring上下文的refresh方法,这个方法是spring生命周期的关键核心代码, spring生命周期的大部分扩展点都是在这里执行的。其中在执行容器中未实例化初始化的bean定义之前会执行内置的、用户自定义的所有注册的beanFactory后置处理器 ,这里会执行到ConfigurationClassPostProcessor这个配置类处理器

    Tip:关于Spring的扩展点大概有13个,其中包括一些后置处理器,具体怎么使用需要了解的可以去Git自己获取,这里不再细说

    // Git代码
    https://gitee.com/yeeevip/yeee-memo/tree/master/learn-example/spring-boot-example/src/main/java/vip/yeee/memo/demo/springboot/extpoint
    https://github.com/yeeevip/yeee-memo/tree/master/learn-example/spring-boot-example/src/main/java/vip/yeee/memo/demo/springboot/extpoint
    

    刷新上下文代码如下:

    // Springboot调用refreshContext来执行父类的refresh方法
    public class SpringApplication {
        public ConfigurableApplicationContext run(String... args) {
            ...
            try {
                ...
                // 刷新上下文
                refreshContext(context);
                ...
            } catch (Throwable ex) {
                throw new IllegalStateException(ex);
            }
            ...
            return context;
        }
    }
    // 调用spring的AbstractApplicationContext的refresh()方法刷新
    public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
        public void refresh() throws BeansException, IllegalStateException {
            ...
            try {
                ...
                // 执行所有注册的BeanFactory后置处理器
                invokeBeanFactoryPostProcessors(beanFactory);
                ...
                // 实例化、初始化所有的单例Bean
                finishBeanFactoryInitialization(beanFactory);
            } catch (BeansException ex) {
                ...
            }
        }
    }
    
  • 执行配置类后置处理器后置处理器(SPI核心逻辑)
  • spring在执行到BeanFactory处理器的生命周期时会执行到ConfigurationClassPostProcessor这个处理器, 执行具体处理逻辑,如配置类上的@PropertySource、@ComponentScan、@Import、@ImportResource、@Bean等注解都会有相应的处理,目的就是将定义的BeanDefinition注册到BeanFactory容器中以便于最终的初始化。

    由于用@SpringBootApplication这个注解标记的类即SpringbootExampleApplication.class本身也是个配置类, 这里的话就会处理这个主启动类。

    处理配置类的代码:

    public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor {
        public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
            // 这里会有我们程序的主启动配置类,也就是SpringbootExampleApplication.class
            List configCandidates = new ArrayList();
            ...
            // 转换每个配置类
            ConfigurationClassParser parser = new ConfigurationClassParser();
            ...
            do {
                ...
                // 处理配置类
                parser.parse(candidates);
                ...
            } while (!candidates.isEmpty());
        }
    }
    // 调用parse方法后会执行到doProcessConfigurationClass
    class ConfigurationClassParser {
        protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicate filter) {
            // 处理配置类的@PropertySource注解
            ...
            processPropertySource(propertySource);
            ...
            // 处理配置类的@ComponentScan注解
            ...
            Set scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
            ...
            // 处理配置类的@Import注解,这里就是SPI机制核心实现逻辑了
            processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
            // 处理配置类的@ImportResource注解
            ...
            String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
            configClass.addImportedResource(resolvedResource, readerClass);
            ...
            // 处理配置类的@Bean注解
            ...
            Set beanMethods = retrieveBeanMethodMetadata(sourceClass);
            ...
        }
    }
    

    processImports这个方法会专门处理 @Import 这个注解, 进入方法发现会执行getImports方法获得@Import导入的类AutoConfigurationImportSelector.class,由于它实现了DeferredImportSelector,所以程序会执行 deferredImportSelectorHandler.handle去处理这个Selector;其实这里也还没有真正开始处理,最终的处理是在调用deferredImportSelectorHandler.process()发起的。

    看代码:

    class ConfigurationClassParser {
        private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection importCandidates,...) {
            ...
            for (SourceClass candidate : importCandidates) {
                if (candidate.isAssignable(ImportSelector.class)) {
                    ...
                    // 判断当前的导入的类是不是DeferredImportSelector的子类
                    if (selector instanceof DeferredImportSelector) {
                        // 是DeferredImportSelector的子类的话由deferredImportSelectorHandler去处理
                        this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
                    } 
                    ...
                }
                ...
            }
        }
        public void parse(Set configCandidates) {
            ...
            // 这里发起真正处理Selector的逻辑
            this.deferredImportSelectorHandler.process();
        }
    }
    
    • 处理AutoConfigurationImportSelector的具体实现步骤如下:

    4.1 读取项目路径下所有依赖中的spring.factories文件,加载需要自动配置的类

    跟踪代码发现,执行process()后会执行handler.processGroupImports(),进而会执行grouping.getImports(),这个方法会用类加载器扫码程序classpath下所有jar中的spring.factories 文件,然后会得到所有用EnableAutoConfiguration属性标记的自动配置类

    class ConfigurationClassParser {
        public Iterable getImports() {
            for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
                // 会扫描jar下的spring.factories中的自动配置类
                this.group.process(deferredImport.getConfigurationClass().getMetadata(),
                        deferredImport.getImportSelector());
            }
            return this.group.selectImports();
        }
    }
    // 具体扫描spring.factories的实现在AutoConfigurationImportSelector.process
    public class AutoConfigurationImportSelector implements DeferredImportSelector,... {
        @Override
        public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
            // getAutoConfigurationEntry这个方法执行
            AutoConfigurationEntry autoConfigurationEntry = (deferredImportSelector).getAutoConfigurationEntry(annotationMetadata);
            ...
        }
        protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
            ...
            // 这里会调用SpringFactoriesLoader.loadFactoryNames读取spring.factories文件
            List configurations = getCandidateConfigurations(annotationMetadata, attributes);
            ...
        }
        protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
            // 读取key为EnableAutoConfiguration.class的所有自动配置类
            List configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader());
        }
        protected Class getSpringFactoriesLoaderFactoryClass() {
            return EnableAutoConfiguration.class;
        }
    }
    

    4.2 针对所有扫描到的自动配置类再次执行配置类处理逻辑

    上面已经从spring.factories中扫描到了所有配置类,紧接着程序同样会执行配置类处理逻辑,具体处理逻辑同第4节开始,同样也是处理这个配置类中的各种注解/方法, 目的就是为了将这些BeanDefinition加入到spring容器中以便于最终初始化。

    4.3 将所有通过配置类处理过符合条件的BeanDefinition注册到spring容器中

    执行完针对配置类的处理转换后,也就是执行完parse方法后,会再调用reader.loadBeanDefinitions用来把配置类处理后符合条件的BeanDefinition注册到spring容器

    看代码:

    public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor {
        public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
            ...
            do {
                ...
                // 处理配置类
                parser.parse(candidates);
                ...
                // parse转换处理完配置类后,然后会把所有处理过符合条件的注册到spring容器
                this.reader.loadBeanDefinitions(configClasses);
                ...
            } while (!candidates.isEmpty());
        }
    }
    
  • 最后就是spring将所有单例BeanDefinition进行实例化、初始化了
  • 这里spring会把通过各种方式注册的单例BeanDefinition,如通过注解@Component、@Service标记的Bean,以及我们本文探讨的重点通过SPI机制的导入的Bean进行最终的实例化/初始化。 具体的处理过程也是spring的核心之一,有兴趣的小伙伴可以看一下源码,这里不详细介绍了。

    public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
        public void refresh() throws BeansException, IllegalStateException {
            ...
            try {
                ...
                // 执行所有注册的BeanFactory后置处理器
                invokeBeanFactoryPostProcessors(beanFactory);
                ...
                // 实例化、初始化所有的单例Bean
                finishBeanFactoryInitialization(beanFactory);
            } catch (BeansException ex) {
                ...
            }
        }
    }
    

    2. 总结

    好啦,今天的内容就到这里了,通过以上代码分析,带大家了解了springboot的SPI机制实现的原理, 当然本文分析的可能还是比较片面,毕竟springboot在实现自动装配上还不止是SPI一个方案,它还涉及了很多复杂的处理。

    文章中如果有问题疑问欢迎小伙伴们提问或者给我指点一下哦,大家一起探讨一下!

    下面这个是我多年经验积累的一套脚手架, 里面集成了各种通用的自定义starter,同时也提供了互联网项目中各种中间件/框架的使用demo, 有兴趣的小伙伴可以git看看哦,并且欢迎大佬们指点!!!

    // Git代码
    https://gitee.com/yeeevip/yeee-memo
    https://github.com/yeeevip/yeee-memo
    

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论