spring-boot自动装配源码解析

128 阅读5分钟

自动装配是springboot的核心,一般提到自动装配就会和springboot联系在一起。实际上 Spring Framework 早就实现了这个功能。Spring Boot 只是在其基础上,通过 SPI 的方式,做了进一步优化。

SpringBoot 定义了一套接口规范,这套规范规定:SpringBoot在启动时会扫描外部引用 jar包中的META-INF/spring.factories文件,将文件中配置的类型信息加载到Spring 容器,并执行类中定义的各种操作。对于外部 jar 来说,只需要按照 SpringBoot 定义的标准,就能将自己的功能装置进 SpringBoot。

我们使用自动装配

1.根据属性配置文件,自动装配组件,如DataSourceAutoConfiguration,根据连接信息,自动装配数据源

2.装载各种Bean,如Mybatis中的MapperFactoryBean, 自动将接口生成bean,放置到容器中

1.Spring Boot自动装配的过程

1.1 @SpringBootApplication

@SpringBootApplication是核心注解,我们根据它去思考springboot的自动装配过程

@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) })
public @interface SpringBootApplication {

    // 去除指定的自动装配类
    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};

    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};

	// 指定扫描包路径,并将标注了@Component注解的类加入到环境中
    // 默认扫描根路径
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};

    // 可以指定多个类或接口的class,扫描时会在这些指定的类和接口所属的包进行扫面。
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};

    // bean名称生成器,默认AnnotationBeanNameGenerator=>首字母小写
    @AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

    // 是否代理bean方法
	boolean proxyBeanMethods() default true;

}

ExcludeFilter

1.TypeExcludeFilter 内部存在 delegate,可由开发者定义排除那些类型

2.AutoConfigurationExcludeFilter 当前类有@Configuration注解,并且在spring.factories是EnableAutoConfiguration的,那么则排除; 防止自动装配类的再次加载

你应该能轻松理解这块的源码!

proxyBeanMethod

关于 proxyBeanMethods,若为true,则所有@Bean注解配置的方法,都是会通过CGLIB代理;最终此通过方法生成的bean均为共享单例;

允许代理,则@Bean方法生成的bean均为共享单例;

不允许代理,则@Bean方法生成的bean每次都会重新生成;

blog.csdn.net/gp_911014/a…

@SpringBootApplication最主要是由三个注解组成

@ComponentScan 默认扫描当前主类路径下的所有包

@SpringBootConfiguration 实质上是 @Configuration 的复合注解,标注主类为配置类

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;
}

@EnableAutoConfiguration则是实现自动装配的核心类;

1.2 @EnableAutoConfiguration

开启@EnableAutoConfiguration,即意味着同意spring帮助我们自动寻找到需要配置的bean,将其注册到容器中;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    // 在SpringBootApplication已解释过
	Class<?>[] exclude() default {};

	String[] excludeName() default {};

}

@AutoConfigurationPackage

当你没有主动指定basePackage 或者 backagePageClass;自动将主类的包路径认为是扫描路径!

这一块代码思路 是通过注册(修改)BasePackage类的bean定义来实现的;

AutoConfigurationPackages#register

	public static void register(BeanDefinitionRegistry registry, String... packageNames) {
		// 非常经典的修改bean定义的方式
        if (registry.containsBeanDefinition(BEAN)) {
			BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
			ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
			// 存在bean定义,则覆盖之
            constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
		}
		else {
            // 没有则创建bean定义
			GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
			beanDefinition.setBeanClass(BasePackages.class);
			beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
			beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			registry.registerBeanDefinition(BEAN, beanDefinition);
		}
	}

一些自动装配的配置,有时需要获取 basePackage,如mybatis需要扫描包路径,获取Mapper接口

则可以配合如下方法,去获取basePackage

AutoConfigurationPackages.get(this.beanFactory); 通过此方法也能寻找到扫描路径!

@Import(AutoConfigurationImportSelector.class)

AutoConfigurationImportSelector 是实现了 DeferredImportSelector;在bean定义注册时,会将ImportSelector相关的bean定义加载至容器中;

AutoConfigurationImportSelector#getAutoConfigurationEntry

	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

	protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
        // 这一步方法,会从spring.factories中加载所有配置列表 <1>
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
        // 别忘记了,之前还有需要去除的自动装配类
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
        // 还要经过conditional的筛选过滤 <2>
		configurations = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

<1> 加载spring.factories中配置的核心方法!用classLoader去加载类路径下所有的spring.factories

SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());

<2> 加载完成后,还有进行conditional条件的筛选

2.@Conditional系注解

  1. ConditionalOnBean:是否存在某个某类或某个名字的Bean

  2. ConditionalOnMissingBean:是否缺失某个某类或某个名字的Bean

  3. ConditionalOnSingleCandidate:是否符合指定类型的Bean只有一个

  4. ConditionalOnClass:是否存在某个类

  5. ConditionalOnMissingClass:是否缺失某个类

  6. ConditionalOnExpression:指定的表达式返回的是true还是false

  7. ConditionalOnJava:判断Java版本

  8. ConditionalOnJndi:JNDI指定的资源是否存在

  9. ConditionalOnWebApplication:当前应用是一个Web应用

  10. ConditionalOnNotWebApplication:当前应用不是一个Web应用

  11. ConditionalOnProperty:Environment中是否存在某个属性

  12. ConditionalOnResource:指定的资源是否存在

  13. ConditionalOnWarDeployment:当前项目是不是以War包部署的方式运行

  14. ConditionalOnCloudPlatform:是不是在某个云平台上

2.1 ConditionalOnClass

我们主要研究@ConditionalOnClass

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {

	Class<?>[] value() default {};

	String[] name() default {};

}

使用@conditionOnClass
当我们自己主动使用此注解配置了一个不存在的类时,由于java的源文件需要编译,此时项目无法运行,抛出编译错误; (我们可以将此类所在jar包放到编译期)
而如果引用的是jar包中的类使用@conditionOnClass引用另外一个jar包的类,则是因为jar包经过了编译,已经打包成功了,故不存在问题。(已经经过了编译器)

重点方法在于 OnClassCondition.class

SpringBootCondition#matches

	@Override
	public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		// 获取当前解析的类名或方法名
        String classOrMethodName = getClassOrMethodName(metadata);
		try {
            // 进行具体的条件匹配,ConditionOutcome表示匹配结果
			ConditionOutcome outcome = getMatchOutcome(context, metadata);
			logOutcome(classOrMethodName, outcome);
            // 日志记录匹配结果
			recordEvaluation(context, classOrMethodName, outcome);
			return outcome.isMatch();
		}
		catch (NoClassDefFoundError ex) {
			// ...
		}
		catch (RuntimeException ex) {
			// ...
		}
	}

OnClassCondition#getMatchOutcome

	@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
		ClassLoader classLoader = context.getClassLoader();
		ConditionMessage matchMessage = ConditionMessage.empty();
        
        // 拿到ConditionalOnClass中的候选者
		List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
		if (onClasses != null) {
            // 判断是否缺失候选者
			List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);
			if (!missing.isEmpty()) {
				return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
						.didNotFind("required class", "required classes").items(Style.QUOTE, missing));
			}
			matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
					.found("required class", "required classes")
					.items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader));
		}

        // 这里是判断ConditionalOnMissingClass注解,暂时忽略
        List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);
    	// ...
		return ConditionOutcome.match(matchMessage);
	}

如何判断是否缺失class?
使用Class.forName(""); 尝试加载类!
那为什么之前能获取到元数据信息,此处是通过MetadataReader(asm)获取的;请自行查阅MetadataReader!