SpringBoot自动配置源码剖析

一,自动配置


在添加Jar包依赖的时候,自动的去完成一些组件的配置,减少主动配置。

SpringBoot 自动配置入口:@SpringBootApplication

二,@SpringBootApplication


@SpringBootApplication为一个组合注解。上面四个注解用于代表当前注解的元数据信息(适用范围,生命周期, 是否记录JavaDoc,是有可以被继承) 。接下来着重看下面的三个注解

@Target(ElementType.TYPE)    //注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中
@Retention(RetentionPolicy.RUNTIME) ///表示注解的生命周期,Runtime运行时
@Documented 表示注解可以记录在javadoc中
@Inherited   //表示可以被子类继承该注解

@SpringBootConfiguration  标明该类为配置类
@EnableAutoConfiguration  // 启动自动配置功能
@ComponentScan(excludeFilters = {   // 包扫描器 <context:component-scan base-package="com.xxx.xxx"/>
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

三,@SpringBootConfiguration


 @Configuration,这个注解如果有了解过Spring相关的源码会明白这个注解其实由Spring提供,到此@SpringBootCongfiguration内部组合了@Configuration,为标记配置的注解类。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented

@Configuration //配置类
public @interface SpringBootConfiguration {
}

四,@EnableAutoConfiguration


 该注解也是一个组合注解,用于启动自动配置。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited

@AutoConfigurationPackage		//自动配置包 : 会把@springbootApplication注解标注的类所在包名拿到,并且对该包及其子包进行扫描,将组件添加到容器中
@Import(AutoConfigurationImportSelector.class)  //可以帮助springboot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器(ApplicationContext)中
public @interface EnableAutoConfiguration {

@AutoConfigurationPackage

@Import也是Spring原生提供的注解,通过该注解讲Registrar类导入到容器中。

//spring框架的底层注解,它的作用就是给容器中导入某个组件类,
//例如@Import(AutoConfigurationPackages.Registrar.class),它就是将Registrar这个组件类导入到容器中
@Import(AutoConfigurationPackages.Registrar.class)  //  默认将主配置类(@SpringBootApplication)所在的包及其子包里面的所有组件扫描到Spring容器中
public @interface AutoConfigurationPackage {

}

Registrar.class

自动配置启动时,会自动的去调用registerBeanDefinitions方法,两个方法参数传入register方法中进行类注册到容器,第一个registry对象不用多说,为自身注册对象,第二个metaData是一个注解的元数据信息。会去获取@SpringBootApplication的元数据从中解析出使用该注解的类所在的包名。

点进register方法,先去判断了register对象中是否该类已经注册了过了,这里的if进不去,直接看else,这里了解过Spring源码的会觉得很熟悉,把获取的包名下的启动类和根目录下的其它类加载成BeanDefinition,添加配置之后在register工厂对象中进行注册。

注:为什么SpringBoot的启动类要写在项目的根目录下的原因: 加载BeanDefinition为根目录下的所有类注册,根目录以外的类无法注册进入。

/**
	 * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
	 * configuration.
	 */
	static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
		// 获取的是项目主程序启动类所在的目录
		//metadata:注解标注的元数据信息
		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			//默认将会扫描@SpringBootApplication标注的主配置类所在的包及其子包下所有组件
			register(registry, new PackageImport(metadata).getPackageName());
		}

		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImport(metadata));
		}
	}

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
	    // 如果已经存在该 BEAN ,则修改其包(package)属性
		// BEAN 就是 AutoConfigurationPackages,用于存储自动配置包以供稍后引用
		if (registry.containsBeanDefinition(BEAN)) {
			BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
			ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
			// 将构造函数的第一个参数设置为包名列表
			constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
        // 如果不存在该 BEAN ,则创建一个 Bean ,并进行注册
        } else { GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
			beanDefinition.setBeanClass(BasePackages.class);
			// 将beanClass设置为BasePackages
			beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
			// 将构造函数的第一个参数设置为包名列表,也就是BasePackages的构造函数
			beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			// 注册beanDefinition
			registry.registerBeanDefinition(BEAN, beanDefinition);
		}
	}

AutoConfigurationImportSelector.class

为容器中导入Selector,帮助Spring应用将所有的符合条件的@Configuration中的配置类加载到工厂中,告知其需要导入哪一些的组件,进入该类,有一些重要的方法需要解析

selectImports方法 

该方法中可以看到该注解是可以关闭的,关闭自动配置注入后那就需要去手动的去注入配置。

@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		//判断 enableautoconfiguration注解有没有开启,默认开启(是否进行自动装配)
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		//1. 加载配置文件META-INF/spring-autoconfigure-metadata.properties,从中获取所有支持自动配置类的条件
		//作用:SpringBoot使用一个Annotation的处理器来收集一些自动装配的条件,那么这些条件可以在META-INF/spring-autoconfigure-metadata.properties进行配置。
		// SpringBoot会将收集好的@Configuration进行一次过滤进而剔除不满足条件的配置类
		// 自动配置的类全名.条件=值
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

loadMetadata方法

然后一行一行的去看,第一行会传入一个类加载器到元数据配置信息加载类中去,loadMetaData方法,该方法跟进去为一个重载方法,会根据Url去判断路径下的spring-autoconfigure-metadata.properties文件,然后存入到Properties中加载成元数据返回一个AutoConfigurationMetadata对象。

 spring-autoconfigure-metadata.properties解读

文件很大这里只贴出出处和部分信息,这里说一下编写的规范org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration.ConditionalOnClass=com.couchbase.client.java.Cluster,com.couchbase.client.java.CouchbaseBucket

已这条为例,org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration为类的全名,ConditionalOnClass为加载的条件,等号后面的部分为条件内容。

即为在加载CouchbaseAutoConfiguration类的时候回去判断是否有加载CouchbaseBucket类。

 getAutoConfigurationEntry方法

同样方法的开始都会去做判断然后获取注解的一些属性信息,然后通过getCandidateConfigurations方法从配置文件中去获取所有的自动配置类。但是原生的自动配置类很多,不是所有的都适用,所以需要进行筛选,说是有一些类不需要去自动配置配置需要去去除,然后对自动加载配置类进行最后一次条件满足判断,最后将自动配置类加入到事件监听器中进行触发加载。这里着重讲getCandidateConfigurations 和filter方法。

	protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
	    // 1. 判断是否开启注解。如未开启,返回空串
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		// 2. 获得注解的属性
		AnnotationAttributes attributes = getAttributes(annotationMetadata);

		// 3. getCandidateConfigurations()用来获取默认支持的自动配置类名列表
		// spring Boot在启动的时候,使用内部工具类SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories,
		// 找出其中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的属性定义的工厂类名称,
		// 将这些值作为自动配置类导入到容器中,自动配置类就生效了
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);


		// 3.1 //去除重复的配置类,若我们自己写的starter 可能存在重复的
		configurations = removeDuplicates(configurations);
		// 4. 如果项目中某些自动配置类,我们不希望其自动配置,我们可以通过EnableAutoConfiguration的exclude或excludeName属性进行配置,
		// 或者也可以在配置文件里通过配置项“spring.autoconfigure.exclude”进行配置。
		//找到不希望自动配置的配置类(根据EnableAutoConfiguration注解的一个exclusions属性)
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		// 4.1 校验排除类(exclusions指定的类必须是自动配置类,否则抛出异常)
		checkExcludedClasses(configurations, exclusions);
		// 4.2 从 configurations 中,移除所有不希望自动配置的配置类
		configurations.removeAll(exclusions);

		// 5. 对所有候选的自动配置类进行筛选,根据项目pom.xml文件中加入的依赖文件筛选出最终符合当前项目运行环境对应的自动配置类

		//@ConditionalOnClass : 某个class位于类路径上,才会实例化这个Bean。
		//@ConditionalOnMissingClass : classpath中不存在该类时起效
		//@ConditionalOnBean : DI容器中存在该类型Bean时起效
		//@ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效
		//@ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效
		//@ConditionalOnExpression : SpEL表达式结果为true时
		//@ConditionalOnProperty : 参数设置或者值一致时起效
		//@ConditionalOnResource : 指定的文件存在时起效
		//@ConditionalOnJndi : 指定的JNDI存在时起效
		//@ConditionalOnJava : 指定的Java版本存在时起效
		//@ConditionalOnWebApplication : Web应用环境下起效
		//@ConditionalOnNotWebApplication : 非Web应用环境下起效

		//总结一下判断是否要加载某个类的两种方式:
		//根据spring-autoconfigure-metadata.properties进行判断。
		//要判断@Conditional是否满足
		// 如@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })表示需要在类路径中存在SqlSessionFactory.class、SqlSessionFactoryBean.class这两个类才能完成自动注册。
		configurations = filter(configurations, autoConfigurationMetadata);


		// 6. 将自动配置导入事件通知监听器
		//当AutoConfigurationImportSelector过滤完成后会自动加载类路径下Jar包中META-INF/spring.factories文件中 AutoConfigurationImportListener的实现类,
		// 并触发fireAutoConfigurationImportEvents事件。
		fireAutoConfigurationImportEvents(configurations, exclusions);
		// 7. 创建 AutoConfigurationEntry 对象
		return new AutoConfigurationEntry(configurations, exclusions);
	}

getCandidateConfigurations方法

还是和上面一样去加载原生配置文件中的类,然后加载后返回一个configurations对象。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
	    // 让SpringFactoryLoader去加载一些组件的名字
		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;
	}

 filter方法

 这里的过滤方法可能不是很直观,直接去文档中随便找一个自动配置类点进去。加载的配置类中是有加载先决条件存在的!而filter方法就是做这些事情。

 五,@ComponentScan


这个注解就是一个包扫描器,会对某一个包以及其子包中的类进行加载。 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值