SpringBoot的SPI机制与自动装配

“这篇对Spring SPI和自动装配原理的分析,来源于一次自己开发spring boot starter的经历,带着对这种扩展机制的好奇来看相关的源码的时候越发觉得Spring或SpringBoot虽然天天用,但真的是所谓灯下黑,作为一个胶水式的基础整合框架,Spring要做到足够通用和抽象以及强扩展性,所以用了大量设计模式做所谓解耦,类的数量巨多,Spring的原理和作用说说可能谁都会,但是源码真的是笔者觉得最难读的之一,相比一些专注于某个特定功能的框架要难多了。本文是笔者硬着头皮看了源码和debug之后的一些理解,难免有不当的地方,而且一些个细节笔者也没有去深究,只是把主要的一些个点和思路串起来了留了一些印象。所以本文仅供大家参考。”

Java SPI机制介绍 前文中我们知道了Java中自带了所谓SPI机制,按照约定去META-INF/services目录里找各个接口的配置文件,找到接口的实现类,然后使用当前线程上线文类加载器定位到实现类加载器,通过其加载实现类,然后再反射newInstance得到实现类的实例。

Spring里也有类似的SPI,思路根上面类似,从classpath下所有jar包的META-INF/spring.factories 配置文件中加载标识为EnableAutoConfiguration的配置类,然后将其中定义的bean注入到Spring容器。

笔者认为,跟Java的SPI更多的是为了面向接口编程和克服双亲委派局限不同,Spring的这种SPI可能更多的是体现一种框架的可扩展性:在springboot工程中我们都知道,默认是会加载主类所在目录及其所有子目录下的自动注入bean的,比如主类在com.wangan,则com.wangan.controller, com.wangan.service等等都会加载并注入;但如果第三方开发的jar包、大概率情况下目录是跟工程目录不同的,比如wangan公司的合作伙伴lb公司开发了一个组件用的是com.lb.*,这个组件的类就没法自动的注入到spring,而通过上面讲的SPI机制就可以解决这个问题。

按照上面的思路,我们从spring boot工程的主类开始分析一下相关的源代码:

源代码走读

@SpringBootApplication实际上由三个注解组成:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
                                                                  @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

@SpringBootConfiguration查看代码就是个@Configuration,所以上面的3注解相当于就是@EnableAutoConfiguration@Configuration@ComponentScan

先研究下@EnableAutoConfiguration

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    Class[] exclude() default {};
    String[] excludeName() default {};

}

@AutoConfigurationPackage里边是@Import(AutoConfigurationPackages.Registrar.class)
@Import(AutoConfigurationImportSelector.class)

"这里有点跳跃",从主类main方法怎么执行,然后走到注解这的,又是怎么执行到的AutoConfigurationImportSelector.getCandidateConfigurations()方法。

这里涉及到springboot的自动装配原理,最终是springboot启动时,是将主类作为一个配置类,ConfigurationClassPostProcessor.processConfigBeanDefinitions,然后通过ConfigrationClassParser类parse()、getImports()解析到@Import注解、从而获取到AutoConfigurationImportSelector这个类的,最终会调用到getCandidateConfiguration()方法。 完整的调用可以看下面的线程栈:

Thread [main] (Suspended)   
owns: Object  (id=69)   
SpringFactoriesLoader.loadSpringFactories(ClassLoader) line: 128    
SpringFactoriesLoader.loadFactoryNames(Class, ClassLoader) line: 122 
AutoConfigurationImportSelector.getCandidateConfigurations(AnnotationMetadata, AnnotationAttributes) line: 171  
AutoConfigurationImportSelector.getAutoConfigurationEntry(AutoConfigurationMetadata, AnnotationMetadata) line: 116  
AutoConfigurationImportSelector$AutoConfigurationGroup.process(AnnotationMetadata, DeferredImportSelector) line: 396    
ConfigurationClassParser$DeferredImportSelectorGrouping.getImports() line: 869  
ConfigurationClassParser$DeferredImportSelectorGroupingHandler.processGroupImports() line: 798  
ConfigurationClassParser$DeferredImportSelectorHandler.process() line: 770  
ConfigurationClassParser.parse(Set) line: 185 
ConfigurationClassPostProcessor.processConfigBeanDefinitions(BeanDefinitionRegistry) line: 315  
ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(BeanDefinitionRegistry) line: 232 
PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(Collection, BeanDefinitionRegistry) line: 275 
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, List) line: 95 
AnnotationConfigApplicationContext(AbstractApplicationContext).invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory) line: 705   
AnnotationConfigApplicationContext(AbstractApplicationContext).refresh() line: 531  
SpringApplication.refresh(ApplicationContext) line: 744 
SpringApplication.refreshContext(ConfigurableApplicationContext) line: 391  
SpringApplication.run(String...) line: 312  
SpringApplication.run(Class[], String[]) line: 1215  
SpringApplication.run(Class, String...) line: 1204   
TestRestfullApplication.main(String[]) line: 18 

总之,我们来到了getCandidateConfigurations方法:

protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List 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;
}

SpringFactoriesLoader的方法,

loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader())

两个参数,前一个就是EnableAutoConfiguration.class, 后一个getBeanClassLoader()是spring bean的classLoader(这个跟整个工程deploy有关,最后通过gradle打出来的jar包解压出来,有个目录专门放的就是classLoader,应该是一个自定义的classLoader)

ps:eclipse里边debug看是sun.misc.Launcher$AppClassLoader

loadFactoryNames调的是loadSpringFactories,

/*
从loadSpringFactories返回的 Map>里边,
按照name=EnableAutoConfiguration获取list,如果没有值就返回个空的list。
*/
loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());

loadSpringFactories方法很有意思,从spring.factories文件加载里边的k-v,然后一个key可能会有多个逗号分隔的value,所以这里最后返回的是个LinkedMultiValueMap。加载的时候会建立一个cache,下次就不用重复从文件里加载了直接读cache:

private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) {
    //根据classLoader查cache这个map如果查到了说明factories已经加载过了,直接从缓存返回就行了
    MultiValueMap result = cache.get(classLoader);
    if (result != null) {
        return result;
    }
    //所以下面的逻辑就是怎么填充这个cache
    try {
        //用自定义classloader从Resource加载,没有就用system ClassLoader
        Enumeration urls = (classLoader != null ?
                classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        result = new LinkedMultiValueMap<>();
        while (urls.hasMoreElements()) { //遍历从spring.factories查到的k-v,文件里可能是一个key多个v,逗号分隔
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry entry : properties.entrySet()) {
                String factoryClassName = ((String) entry.getKey()).trim();
                for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                    result.add(factoryClassName, factoryName.trim());
                }
            }
        }
        cache.put(classLoader, result);
        return result;
    }
    catch (IOException ex) { //抛异常,说从META-INF/spring.factories加载不了
        throw new IllegalArgumentException("Unable to load factories from location [" +
                FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

好了,到此我们分析完了getCandidateConfigurations这条线的关键代码逻辑了,总结一下就是从spring.factories里边加载配置的那些key-values,然后找到里边key名字是是EnableAutoConfiguration的那些。

嗯,现在知道哪些类要(作为springboot插件)自动装配了,但是什么时候装配的呢?

getCandidateConfigurations所在的类AutoConfigurationImportSelector实现了DeferredImportSelector接口,然后在ConfigurationClassPostProcessor这条线中,解析parse我们的各个@Configuration注解的类的时候,会去processDeferredImportSelectors(),在这个方法里进行的bean的生成。

从实验结果上来看是spring.factories配置了EnableAutoConfiguration类型的那些个类,无论用的注解是@Component、@Configuration还是@RibbonClients都给加载了,然后生成了spring bean注入到了上下文里。进一步实验可知,即使把@Component去掉也是可以注入的。

从META-INF/spring.factories文件里读取配置的自动装配Bean,EnableAutoConfiguration=xxx,然后这些Bean实例化的也是在springboot启动的时候完成的。对应main线程的完整线程栈:

Thread [main] (Suspended)   
owns: ConcurrentHashMap  (id=203)  
owns: Object  (id=34)   
BeanUtils.instantiateClass(Constructor, Object...) line: 171 
CglibSubclassingInstantiationStrategy(SimpleInstantiationStrategy).instantiate(RootBeanDefinition, String, BeanFactory) line: 87    
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).instantiateBean(String, RootBeanDefinition) line: 1294   
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).createBeanInstance(String, RootBeanDefinition, Object[]) line: 1196  
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).doCreateBean(String, RootBeanDefinition, Object[]) line: 555 
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).createBean(String, RootBeanDefinition, Object[]) line: 515   
DefaultListableBeanFactory(AbstractBeanFactory).lambda$doGetBean$0(String, RootBeanDefinition, Object[]) line: 320  
1932470703.getObject() line: not available  
DefaultListableBeanFactory(DefaultSingletonBeanRegistry).getSingleton(String, ObjectFactory) line: 222   
DefaultListableBeanFactory(AbstractBeanFactory).doGetBean(String, Class, Object[], boolean) line: 318    
DefaultListableBeanFactory(AbstractBeanFactory).getBean(String) line: 199   
DefaultListableBeanFactory.preInstantiateSingletons() line: 847 
AnnotationConfigApplicationContext(AbstractApplicationContext).finishBeanFactoryInitialization(ConfigurableListableBeanFactory) line: 877   
AnnotationConfigApplicationContext(AbstractApplicationContext).refresh() line: 549  
SpringApplication.refresh(ApplicationContext) line: 744 
SpringApplication.refreshContext(ConfigurableApplicationContext) line: 391  
SpringApplication.run(String...) line: 312  
SpringApplication.run(Class[], String[]) line: 1215  
SpringApplication.run(Class, String...) line: 1204   
TestRestfullApplication.main(String[]) line: 18 

最终是来到BeanUtils.instantiateClass(Constructor, Object...)

public static  T instantiateClass(Constructor ctor, Object... args) throws BeanInstantiationException {
    Assert.notNull(ctor, "Constructor must not be null");
    try {
        ReflectionUtils.makeAccessible(ctor);
        return (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass()) ?
                KotlinDelegate.instantiateClass(ctor, args) : ctor.newInstance(args));
    }
    catch (InstantiationException ex) {
        throw new BeanInstantiationException(ctor, "Is it an abstract class?", ex);
    }
    catch (IllegalAccessException ex) {
        throw new BeanInstantiationException(ctor, "Is the constructor accessible?", ex);
    }
    catch (IllegalArgumentException ex) {
        throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", ex);
    }
    catch (InvocationTargetException ex) {
        throw new BeanInstantiationException(ctor, "Constructor threw exception", ex.getTargetException());
    }
}

注意上面代码里ctor.newInstance(args)就是利用Java反射实例化Bean对象了。

我们比较一下上面两个线程栈的信息可以发现都会走到AbstractApplicationContext.refresh()这个方法里,所不同的是接下在这个方法内又去调用两个不同的方法:加载spring.factories调用的是invokeBeanFactoryPostProcessors,实例化Bean调用的是finishBeanFactoryInitialization,所以有必要来看一下这个refresh()方法,有种说法是读懂了refresh()方法就掌握了Spring容器的启动逻辑。

传说中的refresh()方法

org.springframework.context.support包下的

AbstractApplicationContext类的refresh()方法是整个spring框架启动逻辑的“大纲”,refresh里边调的15个方法体现了容器中Bean的整个创建过程。比如前面starter里边配置的Bean,SpringBoot如何读取的spring.factories文件拿到类名,然后创建Defination,到最后使用反射进行实例化Bean,都在refresh方法中有所体现。

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // Prepare this context for refreshing.
        prepareRefresh();

        // Tell the subclass to refresh the internal bean factory.
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // Prepare the bean factory for use in this context.
        prepareBeanFactory(beanFactory);

        try {
            // Allows post-processing of the bean factory in context subclasses.
            postProcessBeanFactory(beanFactory);

            // Invoke factory processors registered as beans in the context.
            invokeBeanFactoryPostProcessors(beanFactory);  // spring.factories文件里的配置是在这加载的

            // Register bean processors that intercept bean creation.
            registerBeanPostProcessors(beanFactory);

            // Initialize message source for this context.
            initMessageSource();

            // Initialize event multicaster for this context.
            initApplicationEventMulticaster();

            // Initialize other special beans in specific context subclasses.
            onRefresh();

            // Check for listener beans and register them.
            registerListeners();

            // Instantiate all remaining (non-lazy-init) singletons.
            finishBeanFactoryInitialization(beanFactory);  //对配置的bean进行反射实例化

            // Last step: publish corresponding event.
            finishRefresh();
        }

        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                        "cancelling refresh attempt: " + ex);
            }

            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();

            // Reset 'active' flag.
            cancelRefresh(ex);

            // Propagate exception to caller.
            throw ex;
        }

        finally {
            // Reset common introspection caches in Spring's core, since we
            // might not ever need metadata for singleton beans anymore...
            resetCommonCaches();
        }
    }
}
总结

springboot是基于Spring框架开发的,利用引导主类作为总的配置入口和启动入口,使用它自己上边的注解,然后处理@Import注解、拿到里边的AutoConfigurationImportSelector、调用它的相应的方法来加载SPI插件的配置spring.factories,之后使用反射来实例化插件中的Bean供使用。

参考

https://www.cnblogs.com/niechen/p/9027804.html

你可能感兴趣的:(SpringBoot的SPI机制与自动装配)