亲宝软件园·资讯

展开

Spring Boot自动化装配机制

麦神-mirson 人气:0

1. 自动化装配介绍

Spring Boot针对mvc做了大量封装,简化开发者的使用,内部是如何管理资源配置,Bean配置,环境变量配置以及启动配置等? 实质是SpringBoot做了大量的注解封装,比如@SpringBootApplication, 同时采用Spring 4框架的新特性@Conditional基于条件的Bean创建管理机制来实现;

实际的工作场景中是复杂多样的, 有些项目需要不同的组件, 比如REDIS、MONGODB作缓存; RABBITMQ、KAFKA作消息队列; 有些项目运行环境不同, 比如JDK7、JDK8不同版本,面对众多复杂的需求, 又要做到最大化支持, Spring Boot是如何管理实现的, 这就依赖Conditional功能,基于条件的自动化配置。

2. Spring Boot 自动化配置UML图解

SpringBootApplication是我们所常用熟知的注解, 它是一个组合注解, 依赖多个注解,共同实现Spring Boot应用功能, 以下为所有依赖的UML图解,我们围绕这些注解深入研究,看下具体的实现。

3. Spring Boot 自动化配置核心注解分析

SpringBootApplication注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
@ConfigurationPropertiesScan
public @interface SpringBootApplication {
	/**
	 * 需要排除的自动化配置, 根据类名进行排除,  比如MongoAutoConfiguration, JpaRepositoriesAutoConfiguration等
	 */
	@AliasFor(annotation = EnableAutoConfiguration.class)
	Class<?>[] exclude() default {};
	/**
	 * 需要排除的自动化配置, 根据名称进行排除
	 */
	@AliasFor(annotation = EnableAutoConfiguration.class)
	String[] excludeName() default {};
	/**
	 * 指定需要扫描的包路径,参数填写包名
	 */
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
	String[] scanBasePackages() default {};
	/**
	 * 指定需要扫描的包路径
	 */
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default {};
	/**
	 * Bean方法的动态代理配置, 如果没有采用工厂方法, 可以标记为false, 采用cglib代理。	
	 */
	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;
}

3.1 @Inherited

java.lang.annotation.@Inherited 注解,从包名可以看出为JDK自带注解, 作用是让子类能够继承父类中引用Inherited的注解, 但需注意的是, 该注解作用范围只在类声明中有效; 如果是接口与接口的继承, 类与接口的继承, 是不会生效。

3.2 @SpringBootConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration(proxyBeanMethods = false)
public @interface SpringBootConfiguration {
	/**
	 * Bean方法的动态代理配置
	 */
	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;
}

这是配置型处理注解, 可以看到内部源码引用了@Configuration注解,

自身没有太多的实现, 那为什么还需要再包装?官方给出的解释是对Spring的@Configuration的扩展,

用于实现SpringBoot的自动化配置。proxyBeanMethods属性默认为true, 作用是对bean的方法是否开启代理方式调用, 默认为true, 如果没有采用工厂方法,可以设为false, 通过cglib作动态代理。

3.3 @EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    // 设置注解支持重载的标识
	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
	/**
	 * 排除自动化配置的组件, 如MongoAutoConfiguration, JpaRepositoriesAutoConfiguration等
	 */
	Class<?>[] exclude() default {};
	/**
	 * 排除自动化配置的组件, 根据名称设置
	 */
	String[] excludeName() default {};
}

用于管理开启Spring Boot的各种自动化配置注解, 如datasource, mongodb, redis等,也是spring-boot-autoconfigure工程的核心注解。

AutoConfigurationPackage

它的主要作用是扫描主程序同级及下级的包路径所有Bean与组件注册到Spring Ioc容器中。

Import

它可以把没有声明配置的类注册到Spring Ioc容器中管理引用。 导入的AutoConfigurationImportSelector类实现BeanClassLoaderAware、ResourceLoaderAware、EnvironmentAware等接口, 管理类装载器, 资源装载器及环境配置等, 是一个负责处理自动化配置导入的选择管理器。在下面【@AutoConfigurationImportSelector剖析】进行详解。

3.4 @ComponentScan

这是我们在Spring下面常用的一个注解,它可以扫描Spring定义的注解, 如@Componment, @Service等, 常用的属性有basePackages扫描路径,includeFilters包含路径过滤器, excludeFilters排除路径过滤器,lazyInit是否懒加载等,能够非常灵活的扫描管理需要注册组件。

3.5 @ConfigurationPropertiesScan

作用是扫描指定包及子包路径下面的ConfigurationProperties注解,管理工程配置属性信息。主要属性为basePackages扫描路径, 支持多个路径,数组形式;basePackageClasses属性也可以具体到包下面的类,

支持多个配置。

3.6 @AutoConfigurationImportSelector

AutoConfigurationImportSelector 实现 DeferredImportSelector、BeanClassLoaderAware、ResourceLoaderAware、BeanFactoryAware、EnvironmentAware、Ordered 接口, 为自动化配置的核心处理类, 主要负责自动化配置规则的一系列处理逻辑:

/**
 * {@link DeferredImportSelector} to handle {@link EnableAutoConfiguration
 * auto-configuration}. This class can also be subclassed if a custom variant of
 * {@link EnableAutoConfiguration @EnableAutoConfiguration} is needed.
 *
 * @author Phillip Webb
 * @author Andy Wilkinson
 * @author Stephane Nicoll
 * @author Madhura Bhave
 * @since 1.3.0
 * @see EnableAutoConfiguration
 */
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
            ...
        }
}

讲解几个技术点:

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 Boot声明定义的自动化配置类。

看下具体有哪些信息:

这些实际是配置在Spring-boot-autoconfigure工程下的META-INF/spring.factories文件中:

看到这里, 我们应该可以明白,为什么AOP,RABBIT,DATASOURCE, REIDS等组件SPRING BOOT都能帮我们快速配置实现,其实它内部遵循SPI机制, 已经把自动化配置做好了封装。

AutoConfigurationGroup类

它是AutoConfigurationImportSelector的内部类,实现了DeferredImportSelector.Group、BeanClassLoaderAware、BeanFactoryAware、ResourceLoaderAware接口,是一个重要的核心类。主要作用是负责自动化配置条目信息的记录, 排序,元数据处理等。它通过getImportGroup方法获取返回,该方法实现DeferredImportSelector的接口。

private static class AutoConfigurationGroup
			implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
    	// 记录注解的元数据
		private final Map<String, AnnotationMetadata> entries = new LinkedHashMap<>();
		// 记录自动化配置条目,放入集合
		private final List<AutoConfigurationEntry> autoConfigurationEntries = new ArrayList<>();
        // 设置bean的类加载器
		private ClassLoader beanClassLoader;
        // 设置bean工厂信息
		private BeanFactory beanFactory;
        // 设置资源加载器信息
		private ResourceLoader resourceLoader;
        // 设置自动化配置的元数据记录
		private AutoConfigurationMetadata autoConfigurationMetadata;
		...
	}

属性主要定义了一些自动化配置类目信息、BEAN工厂、类和资源加载器信息。entries条目有22条, 具体内容如下:

里面是主要的自动化配置类的元数据信息,autoConfigurationEntries属性就是具体的自动化配置条目。这些主要自动化类配置是Spring boot帮助我们实现mvc的核心功能,如请求分发,文件上传,参数验证,编码转换等功能。还有一部分是定制条件自动化配置类,

autoConfigurationMetadata元数据内容较多, 包含各种组件, 根据环境配置和版本不同, 这里可以看到共有705个:

由于Spring Boot支持众多插件,功能丰富, 数量较多; 这里存在些疑问, 这里面的元数据和上面的entries条目都是AutoConfiguration自动化配置类, 那有什么区别? 其实这里面的, 都是基于条件的自动化配置。

我们就拿KafkaAutoConfiguration来看:

可以看到注解ConditionalOnClass,意思是KafkaAutoConfiguration生效的前提是基于KafkaTemplate类的初始化成功,这就是定制条件,也就是基于条件的自动化配置类,虽然有七百多个,但其实是根据工程实际用到的组件,才会触发加载对应的配置。 有关Conditional基于条件的自动化配置实现原理, 在下面我们再作深入研究。

继续看AutoConfigurationImportSelector内部类的selectImports方法:

@Override
public Iterable<Entry> selectImports() {
    if (this.autoConfigurationEntries.isEmpty()) {
        return Collections.emptyList();
    }
    // 将所有自动化条目根据配置的Exclusion条件作过滤, 并转换为SET集合
    Set<String> allExclusions = this.autoConfigurationEntries.stream()
        .map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
    // SET集合, 记录所有需要处理的自动化配置
    Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
        .map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
        .collect(Collectors.toCollection(LinkedHashSet::new));
    // 两个SET, 做交集过滤, 排除不需要的配置
    processedConfigurations.removeAll(allExclusions);
    // 最后进行排序处理
    return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
        .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
        .collect(Collectors.toList());
}

该方法是针对autoConfigurationEntries自动化配置条目做过滤,根据指定的排除规则处理;再根据设置的启动的优先级做排序整理。从代码中可以看到,先获取所有的allExclusions排除配置信息,再获取所有需要处理的processedConfigurations配置信息,然后做过滤处理,最后再调用sortAutoConfigurations方法,根据order顺序做排序整理。

AutoConfigurationImportSelector内部类的process方法:

@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
    Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
                 () -> String.format("Only %s implementations are supported, got %s",
                                     AutoConfigurationImportSelector.class.getSimpleName(),
                                     deferredImportSelector.getClass().getName()));
    // 获取自动化配置条目
    AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
        .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
    // 记录获取的条目
    this.autoConfigurationEntries.add(autoConfigurationEntry);
    for (String importClassName : autoConfigurationEntry.getConfigurations()) {
        // 放入成员变量entries中
        this.entries.putIfAbsent(importClassName, annotationMetadata);
    }
}

该方法是扫描获取autoConfigurationEntries自动化配置条目信息。

annotationMetadata参数:

为注解元数据,有也就是被@SpringBootApplication修饰的类信息,在这里就是我们的启动入口类信息。

deferredImportSelector参数:

通过@EnableAutoConfiguration注解定义的 @Import 的类,也就是AutoConfigurationImportSelector对象。根据配置,会加载指定的beanFactory、classLoader、resourceLoader和environment对象。

AutoConfigurationImportSelector内部类的getAutoConfigurationEntry方法:

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
        // 1、判断是否开对应注解
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
        // 2、获取注解定义的属性
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
        // 3、获取符合规则的Spring Boot 内置的自动化配置类, 并做去重处理
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
        // 4、做排除规则匹配, 过滤处理
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = filter(configurations, autoConfigurationMetadata);
        // 5、触发自动导入处理完成事件
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

该方法主要作用是获取Spring Boot 内置的自动化条目, 例AopAutoConfiguration等,该方法会调用上面讲解的getCandidateConfigurations方法。 主要步骤逻辑如下:

3.7 @AutoConfigurationPackages

AutoConfigurationPackages是EnableAutoConfiguration上的另一个核心注解类, 官方解释为:

Indicates that the package containing the annotated class should be registered

意思是包含该注解的类,所在包下面的class, 都会注册到Spring Ioc容器中。对应源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
...
}

Import注解, 导入AutoConfigurationPackages抽象类下面的内部静态类Registrar,研究Registrar实现原理:

Registrar实现 ImportBeanDefinitionRegistrar、DeterminableImports 接口,它负责存储从@AutoConfigurationPackage注解扫描到的信息。 源码如下:

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        // 注册BEAN的定义信息
    	@Override
    	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    		register(registry, new PackageImport(metadata).getPackageName());
    	}
        // 决定是否导入注解中的配置内容
    	@Override
    	public Set<Object> determineImports(AnnotationMetadata metadata) {
    		return Collections.singleton(new PackageImport(metadata));
    	}
    }

这里面主要涉及到PackageImport类, 它是AutoConfigurationPackages的内部私有静态类,主要是记录导入的 报名信息, 源码如下:

    /**
	 * Wrapper for a package import.
	 */
	private static final class PackageImport {
		private final String packageName;
		// 构造方法, 记录注解内容	
		PackageImport(AnnotationMetadata metadata) {
			this.packageName = ClassUtils.getPackageName(metadata.getClassName());
		}
		// 获取指定包名称
		public String getPackageName() {
			return this.packageName;
		}
	    // 重载父类比较逻辑, 根据包名判断
		@Override
		public boolean equals(Object obj) {
			if (obj == null || getClass() != obj.getClass()) {
				return false;
			}
			return this.packageName.equals(((PackageImport) obj).packageName);
		}
		// 重载hash标识, 以包名的HASH值为准
		@Override
		public int hashCode() {
			return this.packageName.hashCode();
		}
	    // 重载toString, 打印内容
		@Override
		public String toString() {
			return "Package Import " + this.packageName;
		}
	}

内部断点跟踪的话, 可以看到它记录的是我们启动类所在的包名。这也就是为什么不需要指定扫描包路径, 也会加载启动类所在包下面的JavaConfig配置信息。

回到上面Registrar的registerBeanDefinitions方法, 内部调用的是register方法:

它是处理记录AutoConfigurationPackages扫描包信息,源码如下:

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
        // 判断是否包含BEAN定义信息, 如果包含, 更新packageNames信息
		if (registry.containsBeanDefinition(BEAN)) {
			BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
			ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
			constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
		}
        // 如果registry中不包含BEAN定义, 重新构造GenericBeanDefinition对象, 记录相关信息
		else {            
			GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
			beanDefinition.setBeanClass(BasePackages.class);
			beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
			beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			registry.registerBeanDefinition(BEAN, beanDefinition);
		}
	}

先判断AutoConfigurationPackages注解, 记录对应的扫描包信息;如果不存在,则自行创建基于BasePackages的BEAN定义信息, 并进行注册。再看下addBasePackages方法:

private static String[] addBasePackages(ConstructorArgumentValues constructorArguments, String[] packageNames) {
        // 获取已经存在的Bean定义信息
		String[] existing = (String[]) constructorArguments.getIndexedArgumentValue(0, String[].class).getValue();
        // 创建合并集合, 过滤重复的Bean定义
		Set<String> merged = new LinkedHashSet<>();
        // 根据Set特性, 自动合并去重
		merged.addAll(Arrays.asList(existing));
		merged.addAll(Arrays.asList(packageNames));        
		return StringUtils.toStringArray(merged);
	}

获取已经存在的定义信息,再和packageNames合并, 过滤重复的扫描包。

自动化配置到此就不再对其他代码进行深入跟踪分析,Spring Boot整个框架代码还是较多, 大家可以按这种思路, 逐个层级去剖析,深入挖掘更多技术点。

4. 总结

我们研究了Spring Boot的自动化配置原理,逐层研究剖析,从@SpringBootApplication启动注解开始,到下面的@SpringBootConfiguration, @ConfigurationPropertiesScan, @ComponentScan以及核心@EnableAutoConfiguration。我们对@EnableAutoConfiguration下面的@AutoConfigurationPackage和@Import(AutoConfigurationImportSelector.class)两个重要注解作了深入研究,从中可以看到Spring Boot针对自动化配置, 是分为两部分, 一部分是核心注解,来支撑服务的正常运行; 另一部分是非核心的各种自动化组件注解,做了大量封装,便于我们集成使用。

加载全部内容

相关教程
猜你喜欢
用户评论