Spring源码分析十:Springboot 自动化配置原理

一、前言

本文是笔者阅读Spring源码的记录文章,由于本人技术水平有限,在文章中难免出现错误,如有发现,感谢各位指正。在阅读过程中也创建了一些衍生文章,衍生文章的意义是因为自己在看源码的过程中,部分知识点并不了解或者对某些知识点产生了兴趣,所以为了更好的阅读源码,所以开设了衍生篇的文章来更好的对这些知识点进行进一步的学习。


在使用SpringBoot的时候,我们引入其他功能的时候通过一个简单的@EnableXXX 注解就可实现比如

  • @EnableAspectJAutoProxy(proxyTargetClass = true) : 启用 AOP。实际上只要引入了aop的包就会启动aop功能,这是因为aop默认引入了该注解。
  • @EnableTransactionManagement : 启用事务

本文就是来分析Springboot自动装配的源码实现。


1. ImportSelector

之所以需要了解这个接口是因为下面的讲解离不开这个接口。
ImportSelector 见名知意,是一种引入选择器。其中selectImports 方法返回的String[] 数组的元素是类的全路径名,Spring 会调用 selectImports, 并按照其返回的结果数组的元素指向的类加载到Spring容器中。

public interface ImportSelector {
    
    

	/**
	 * Select and return the names of which class(es) should be imported based on
	 * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
	 * @return the class names, or an empty array if none
	 */
	String[] selectImports(AnnotationMetadata importingClassMetadata);

	/**
	 * Return a predicate for excluding classes from the import candidates, to be
	 * transitively applied to all classes found through this selector's imports.
	 * <p>If this predicate returns {@code true} for a given fully-qualified
	 * class name, said class will not be considered as an imported configuration
	 * class, bypassing class file loading as well as metadata introspection.
	 * @return the filter predicate for fully-qualified candidate class names
	 * of transitively imported configuration classes, or {@code null} if none
	 * @since 5.2.4
	 */
	@Nullable
	default Predicate<String> getExclusionFilter() {
    
    
		return null;
	}

}

具体的调用链路比较多,这里就不具体分析,调用逻辑在 ConfigurationClassParser#processImports 中,见下图。我们只需要知道Springboot 在启动时候会加载引入(使用 @Import 注解引入)的 ImportSelector 实现类。但是需要注意的是,Spring自动装配的类AutoConfigurationImportSelector 实现的接口并不是ImportSelector 接口,而是DeferredImportSelector接口。
在这里插入图片描述
注: 关于 ImportSelector 的调用处理实际上是在 ConfigurationClassPostProcessor 中。关于 ConfigurationClassPostProcessor 的分析已经开设了文章,具体请看:
Spring 源码分析衍生篇七 :ConfigurationClassPostProcessor 上篇

2. DeferredImportSelector

AutoConfigurationImportSelector 实现的是 DeferredImportSelector 接口。
DeferredImportSelectorImportSelector 接口的子接口。

DeferredImportSelector 有两个特点:

  • 继承该接口的 ImportSelector会在最后执行
  • 如果定义了一个以上的DeferredImportSelector则使用Order接口来进行排序

3. spring.factories

这个仅需要知道,Springboot 强调约定大于配置,其中有一个约定就是 springboot启动的时候会加载 META-INF/spring.factories 文件,至于有什么用,后面会讲到。

三、源码解析

1. 原理概述

要分析Springboot自动化配置的原理,首先我们需要看他的启动方式如下。下面的启动方式基本就是Springboot的固定启动方式。

@SpringBootApplication
public class DemoApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(DemoApplication.class, args);
    }
}

1.1 @EnableAutoConfiguration

这时候我们进入 @SpringBootApplication 注解,发现 @SpringBootApplication 注解上赫然写着 @EnableAutoConfiguration 注解,我们再进去 @EnableAutoConfiguration 查看。这里我们发现 @EnableAutoConfiguration 注解通过 @Import(AutoConfigurationImportSelector.class) 引入了一个类AutoConfigurationImportSelector
在这里插入图片描述

1.2 AutoConfigurationImportSelector

AutoConfigurationImportSelector 实现了 DeferredImportSelector 接口,DeferredImportSelector又是ImportSelector的子接口,这里需要注意的是 AutoConfigurationImportSelector 由于实现的是 DeferredImportSelector 接口,所以在调用逻辑上并不会调用 AutoConfigurationImportSelector.selectImports 方法(后面会具体讲解)。下面我们先来详细分析一下 getAutoConfigurationEntry 方法。

我们来看 AutoConfigurationImportSelector 的部分代码如下:

	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
    
    
		if (!isEnabled(annotationMetadata)) {
    
    
			return NO_IMPORTS;
		}
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
				annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

	/**
	 * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
	 * of the importing {@link Configuration @Configuration} class.
	 * @param autoConfigurationMetadata the auto-configuration metadata
	 * @param annotationMetadata the annotation metadata of the configuration class
	 * @return the auto-configurations that should be imported
	 */
	 
	 /**
	 * 我们主要看这个方法
	 */
	protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
    
    
		// 判断是否启动自动装配
		if (!isEnabled(annotationMetadata)) {
    
    
			return EMPTY_ENTRY;
		}
		// 获取 @SpringBootApplication 的注解属性exclude、excludeName。
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		// 获取未筛选前的配置类
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		// 去除重复的配置类
		configurations = removeDuplicates(configurations);
		// 获取 需要排除的 配置类
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		// 检查需要排除的配置类
		checkExcludedClasses(configurations, exclusions);
		// 移除需要排除的配置类
		configurations.removeAll(exclusions);
		configurations = filter(configurations, autoConfigurationMetadata);
		// 发布一个事件
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}
	
	// 这里我们看到可以通过设置 EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY 属性来 控制是否开启自动化配置
	protected boolean isEnabled(AnnotationMetadata metadata) {
    
    
		if (getClass() == AutoConfigurationImportSelector.class) {
    
    
			return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
		}
		return true;
	}

上面的代码中 在 getAutoConfigurationEntry 方法中 还有一个关键方法 getCandidateConfigurations。我们再来看一下这个方法。这个方法我们仅仅通过他的断言我们就可以发现,这个方法是去加载 META-INF/spring.factories 文件的。另外我们又发现 getSpringFactoriesLoaderFactoryClass 返回的就是 EnableAutoConfiguration.class

	/**
	 * Return the auto-configuration class names that should be considered. By default
	 * this method will load candidates using {@link SpringFactoriesLoader} with
	 * {@link #getSpringFactoriesLoaderFactoryClass()}.
	 * @param metadata the source metadata
	 * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
	 * attributes}
	 * @return a list of candidate configurations
	 */
	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;
	}

	/**
	 * Return the class used by {@link SpringFactoriesLoader} to load configuration
	 * candidates.
	 * @return the factory class
	 */
	protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    
    
		return EnableAutoConfiguration.class;
	}

再看一下 SpringFactoriesLoader.loadFactoryNames 方法的实现。这里可以看到loadFactoryNames 方法其实上是根据类的全路径类名去 spring.factories 中获取值。这里更细致的代码就不具体分析了

	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    
    
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
	}

在这里插入图片描述

1.3 META-INF/spring.factories

我们这里找到 META-INF/spring.factories 看一下。可以看到都是 key-value形式,并且可以是一些注解的全路径名,value是需要加载的配置类。
在这里插入图片描述

1.4 DeferredImportSelector 的问题

经过上面的分析,我们可以确定 getAutoConfigurationEntry 方法就是整个解析的核心。那么上面为什么说并不会调用 AutoConfigurationImportSelector.selectImports方法呢 ?

这里我们着重观察一下 ConfigurationClassParser#processImports 方法,

调用链路如下图
在这里插入图片描述
代码分析如下(这里代码分析源自 https://www.jianshu.com/p/480ebb1ecc8b):

    private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
            Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
    
    
        //检查包含有Import注解的集合是不是空的,空的则表示没有
        if (importCandidates.isEmpty()) {
    
    
            return;
        }
        //检查是否存在循环引入,通过deque的方式来检查
        if (checkForCircularImports && isChainedImportOnStack(configClass)) {
    
    
            this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
        }
        else {
    
    
            //将当前bean放到importStack中,用于检查循环引入
            this.importStack.push(configClass);
            try {
    
    
                for (SourceClass candidate : importCandidates) {
    
    
                    //import指定的Bean是ImportSelector类型
                    if (candidate.isAssignable(ImportSelector.class)) {
    
    
                        //实例化指定的ImportSelector子类
                        Class<?> candidateClass = candidate.loadClass();
                        ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
                                this.environment, this.resourceLoader, this.registry);
                        //如果是ImportSelector的子类DeferredImportSelector的子类则按照DeferredImportSelectorHandler逻辑进行处理
                        if (selector instanceof DeferredImportSelector) {
    
    
                            this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
                        }
                        else {
    
    //如果不是则获取指定的需要引入的class的ClassNames
                            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                            //根据ClassNames获取并封装成一个SourceClass
                            Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
                            //继续调用processImports处理
                            processImports(configClass, currentSourceClass, importSourceClasses, false);
                        }
                    }//如果是ImportBeanDefinitionRegistrar的子类
                    else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
    
    
                        // Candidate class is an ImportBeanDefinitionRegistrar -> delegate to it to register additional bean definitions
                        //如果对应的ImportBeanDefinitionRegistrar子类对象,并放到configClass,这个是用来注册额外的bean的
                        Class<?> candidateClass = candidate.loadClass();
                        ImportBeanDefinitionRegistrar registrar =
                                ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                                        this.environment, this.resourceLoader, this.registry);
                        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                    }
                    else {
    
    //不是上面任何类的子类就可以进行处理了,将指定的需要引入的bean转化为ConfigurationClass,然后到processConfigurationClass方法中处理
                        this.importStack.registerImport(
                                currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                        processConfigurationClass(candidate.asConfigClass(configClass));
                    }
                }
            }
            catch (BeanDefinitionStoreException ex) {
    
    
                throw ex;
            }
            catch (Throwable ex) {
    
    
                throw new BeanDefinitionStoreException(
                        "Failed to process import candidates for configuration class [" +
                        configClass.getMetadata().getClassName() + "]", ex);
            }
            finally {
    
    
                this.importStack.pop();
            }
        }
    }

这里我们发现一个问题
在这里插入图片描述
我们发现,上面在讲ImportSelector 接口的时候,我们明明说他们只有调用了580行的 selector.selectImports(currentSourceClass.getMetadata()); 语句才能而调用 ImportSelector 接口的 selectImports 方法。
AutoConfigurationImportSelector 实现的接口是 DeferredImportSelector 接口,所以这里他会执行if分支的内容,也就是 this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector); 语句。

this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) 里面的代码就不具体贴出来了,在handle方法中关键代码是 handler.processGroupImports(); 再继续追溯 发现 grouping.getImports() 。下面贴出 getImports() 代码如下:

		public Iterable<Group.Entry> getImports() {
    
    
			for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
    
    
				this.group.process(deferredImport.getConfigurationClass().getMetadata(),
						deferredImport.getImportSelector());
			}
			return this.group.selectImports();
		}

这里我们可以看到group调用了两个方法 process和 selectImports(这两个方法都是 AutoConfigurationImportSelector.AutoConfigurationGroup 的方法),如下:

  1. 我们可以看到 process 方法中调用了getAutoConfigurationEntry 方法,通过上面的解释我们知道 getAutoConfigurationEntry 方法就是用来解析spring.factories 文件的。另外getAutoConfigurationEntry 方法的返回值保存在了 autoConfigurationEntries中。
  2. selectImports 方法则是对 autoConfigurationEntries 结果进行进一步筛选排序,最终返回。
		@Override
		public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
    
    
		// 省略断言,在这里我们可以看到他调用了 getAutoConfigurationEntry 方法。通过上面的解释我们知道 getAutoConfigurationEntry 方法就是用来解析spring.factories 文件的。
						AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
					.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
			this.autoConfigurationEntries.add(autoConfigurationEntry);
			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
    
    
				this.entries.putIfAbsent(importClassName, annotationMetadata);
			}
		}

		@Override
		public Iterable<Entry> selectImports() {
    
    
			if (this.autoConfigurationEntries.isEmpty()) {
    
    
				return Collections.emptyList();
			}
			Set<String> allExclusions = this.autoConfigurationEntries.stream()
					.map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
			Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
					.map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
					.collect(Collectors.toCollection(LinkedHashSet::new));
			processedConfigurations.removeAll(allExclusions);

			return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
					.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
					.collect(Collectors.toList());
		}

所以AutoConfigurationImportSelector 并不会调用方法
AutoConfigurationImportSelector.#selectImports。而是会调用AutoConfigurationImportSelector.AutoConfigurationGroup#selectImports。注意二者的区别, AutoConfigurationGroupAutoConfigurationImportSelector 一个静态内部类。

2. 总结

结合上述:

  1. 首先需要创建 META-INF/spring.factories 文件,文件中的存储是key-value形式
  2. @SpringBootApplication 注解 继承了 @EnableAutoConfiguration 注解。而 @EnableAutoConfiguration 注解中引入了 AutoConfigurationImportSelector 类。
  3. AutoConfigurationImportSelector 类会根据 @EnableAutoConfiguration 注解的全路径类名作为 key值加载 META-INF/spring.factories 文件 的value值,value值是预先配置好的,即当 使用 @EnableAutoConfiguration 注解开启自动装配功能时就会加载对应value 所指向的配置类。(如果我们想定义扫描别的key,就可以模仿 AutoConfigurationImportSelector 来实现 )
  4. 将value值对应的配置类信息返回,并作为 AutoConfigurationImportSelector # selectImports 方法的返回值返回。
  5. Springboot会根据 selectImports 的返回值中的配置类全路径名加载对应的配置类。这些配置类再完成相应的工作实现自动装配功能。

简单来说,Spring通过 @EnableXXX 注解来启用某项功能的原理是通过 @EnableXXX 上的@Import 注解来引入 自定义的ImportSelector实现类,并在其 selectImports 方法中将该功能需要使用的类注入到Spring容器中,从而达到启用某项功能。

额外的,如果想要在Spring引入 jar 时就启用功能(比如MyBatis)。可以在 spring.factories 文件中以org.springframework.boot.autoconfigure.EnableAutoConfiguration 为key,以需要引入的类的全路径名作为value。
因为Spring 启动时会扫描所有的spring.factories 文件中的key为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的值并将其value通过反射注入到容器中。

下图为MyBatis 的spring.factories文件内容。
在这里插入图片描述

四、关于ImportSelector的简陋Demo

1. EnableDemo

自定义注解 @EnableDemo,作为启用注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MyImportSelector.class)
public @interface EnableDemo {
    
    
}

2. DemoImportSelector

看注释看注释。。。

public class DemoImportSelector implements ImportSelector {
    
    
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
    
    
        // 也可以在 META-INF/spring.factories 配置加载类并获取返回
        // 返回类的全路径地址,会被Spring按照路径加载到容器中
        return new String[]{
    
    "com.kingfish.auto.Demo"};
    }
}

3. Demo

public class Demo {
    
    
    public void getmsg(){
    
    
        System.out.println("Demo.getmsg");
    }
}

4. DemoSpringRunner

@Component
public class DemoSpringRunner  implements ApplicationRunner {
    
    
    @Autowired
    private Demo demo;
    @Override
    public void run(ApplicationArguments args) throws Exception {
    
    
        demo.getmsg();
    }
}

4. 测试

上面可以看到,我们并没有使用常规的注入方式将 Demo类注入到容器中。我们启动测试。可以看到Demo正常加载并输出。大功告成!
在这里插入图片描述


以上:内容部分参考
《Spring源码深度解析》、
https://blog.csdn.net/boling_cavalry/article/details/82555352
https://www.jianshu.com/p/480ebb1ecc8b
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正

猜你喜欢

转载自blog.csdn.net/qq_36882793/article/details/105327732