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

607 阅读8分钟

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 简短概括

  1. 程序启动,注册配置类处理器
  2. spring刷新上下文,执行配置类处理器
  3. 扫描spring.factories将得到的BeanDefinition注册到容器
  4. spring实例化/初始化这些BeanDefinition

2.2 源码跟踪

  1. 启动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) {
            ...
        }
    }
}
  1. 注册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<BeanDefinitionHolderregisterAnnotationConfigProcessors(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;
    }
}
  1. 刷新应用上下文,执行注册的后置处理器

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) {
            ...
        }
    }
}
  1. 执行配置类后置处理器后置处理器(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<BeanDefinitionHolder> 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<String> filter) {
        // 处理配置类的@PropertySource注解
        ...
        processPropertySource(propertySource);
        ...
        // 处理配置类的@ComponentScan注解
        ...
        Set<BeanDefinitionHolder> 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<MethodMetadata> 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<SourceClass> 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<BeanDefinitionHolder> configCandidates) {
        ...
        // 这里发起真正处理Selector的逻辑
        this.deferredImportSelectorHandler.process();
    }
}
  • 处理AutoConfigurationImportSelector的具体实现步骤如下:

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

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

class ConfigurationClassParser {
    public Iterable<Group.EntrygetImports() {
        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<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        ...
    }
    protected List<StringgetCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        // 读取key为EnableAutoConfiguration.class的所有自动配置类
        List<String> 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());
    }
}
  1. 最后就是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