spring boot自动装配源码分析

原创不易,转载请注明出处


前言

在没有spring boot的年代,有幸体验到了ssm 整合配置的痛苦,那个时候,要想将springmvc ,spring ,mybatis整合起来,需要配置一堆的xml文件,说不痛苦那是假的。spring boot 面市之后,你还别说,是真的香,想用啥组件的时候,找找它的starter,引入starter依赖,就能整合在一起了,也没有那堆xml配置文件了,其实依赖starter就能整合,得益于spring boot 自动装配特性,starter中存在自动配置类,项目启动的时候,能够自动装配对应组件的一些对象啥的,本文主要就是看看spring boot 自动装配到底是怎么搞得。

1. @SpringBootApplication

我们都知道,创建一个spring boot项目的时候,都会有一个主启动类,该类上面需要有一个@SpringBootApplication 注解,就像下面这个启动类一样

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

那么我们需要看看这个 @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}
)}
)
public @interface SpringBootApplication {}

由三个注解组成的
@SpringBootConfiguration:spring boot 配置类,里面有个@Configuration 注解,这个注解大家应该都熟悉,spring的配置类注解
@EnableAutoConfiguration: 自动装配的注解,今天主角儿。
@ComponentScan:组件扫描注解
接下来我们看看@EnableAutoConfiguration注解

2.@EnableAutoConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
	Class<?>[] exclude() default {};
	String[] excludeName() default {};
}

由@AutoConfigurationPackage 注解和@Import(AutoConfigurationImportSelector.class) 注解组成。里面还有2个属性,都是指定需要排除的类。
@AutoConfigurationPackage 从名字上看是自动配置package
@Import 这个注解更牛逼,可以导入4种类型的类, 可以导入 配置类,ImportSelector实现类,ImportBeanDefinitionRegistrar实现类,普通类。
我们挨个看看这两个注解

2.1 @AutoConfigurationPackage

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

有个@Import注解,这里导入的是个registrar类型的
我们看下AutoConfigurationPackages.Registrar 代码

/**
 * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
 * configuration.
 */
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
	@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));
	}
}

看下上面的注释,说是为了存储 基本的package
看下registerBeanDefinitions 方法,从名字上就是注册bean的。
new PackageImport(metadata).getPackageName() 就是为了获取 你主启动类所在的包。

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
	if (registry.containsBeanDefinition(BEAN)) {// AutoConfigurationPackages.class.getName()
		BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
		ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
		constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
	}
	else {
		GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
		beanDefinition.setBeanClass(BasePackages.class);
		beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
		beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
		registry.registerBeanDefinition(BEAN, beanDefinition);
	}
}

判断,如果注册表中存在AutoConfigurationPackages 这个bean的话,就向设置一下它构造的第一参数值是 主启动类所在的包。
如果不存在的话,先创建BasePackages这个类对应beanDefinition ,在设置构造第一个参数值是主启动类所有在包。
默认是不存在。
我们看下这个BasePackages

static final class BasePackages {
	private final List<String> packages;
	private boolean loggedBasePackageInfo;
	BasePackages(String... names) {
		List<String> packages = new ArrayList<>();
		for (String name : names) {
			if (StringUtils.hasText(name)) {
				packages.add(name);
			}
		}
		this.packages = packages;
	}
}

其实就是将主启动类的包,塞到它packages 这个集合中,BasePackages 还提供了一个get方法,用来获取packages 里面的值的。
说白了,这个BasePackages 就是存储主启动类对应包的,如果后续有哪些组件想获取base package 就可以找到BasePackages bean,获取存储的主启动类package。这也是spring boot 主启动类要放在外层的原因,因为一些组件默认是扫面主启动类所在包下面的类。
好了,了解了@AutoConfigurationPackage 注解的作用,我们在来看下@Import(AutoConfigurationImportSelector.class)

2.2 @Import(AutoConfigurationImportSelector.class)

主要就是看下AutoConfigurationImportSelector

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return NO_IMPORTS;
	}
	// 加载元数据
	AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
			.loadMetadata(this.beanClassLoader);
	//spi找出那些EnableAutoConfiguration 对应的类 / 也就是自动配置类
	AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
			autoConfigurationMetadata, annotationMetadata);
	//将需要加载的自动配置类返回
	return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

这里selectImports方法核心作用就是找到你那些自动配置类 ,返回给spring,让spring给你实例化,加载配置。
我们直接看下getAutoConfigurationEntry 方法,该方法就是找自动配置类的。

protected AutoConfigurationEntry getAutoConfigurationEntry(
		AutoConfigurationMetadata autoConfigurationMetadata,
		AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return EMPTY_ENTRY;
	}
	// 获取注解下的属性值
	AnnotationAttributes attributes = getAttributes(annotationMetadata);
	// 到spring.factories中找出所有的EnableAutoConfiguration 实现类,就是那些自动配置类
	List<String> configurations = getCandidateConfigurations(annotationMetadata,
			attributes);
	// 去重
	configurations = removeDuplicates(configurations);
	// 找出那些需要排除掉的
	Set<String> exclusions = getExclusions(annotationMetadata, attributes);
	//校验
	checkExcludedClasses(configurations, exclusions);
	// 移除那些排除掉的 configuration
	configurations.removeAll(exclusions);
	// 拿 AutoConfigurationImportFilter 进行过滤
	configurations = filter(configurations, autoConfigurationMetadata);
	// 通知 AutoConfigurationImportListener
	fireAutoConfigurationImportEvents(configurations, exclusions);
	// 封装AutoConfigurationEntry 对象返回
	return new AutoConfigurationEntry(configurations, exclusions);
}

getAutoConfigurationEntry 方法的每行代码都做了注释,最核心的还是调用getCandidateConfigurations 方法找到自动装配类。
找到之后,先排除掉你在注解中配置的那些需要排除的自动装配类,接着使用你自定义的AutoConfigurationImportFilter 过滤器过滤一下,最后才将剩下的封装 AutoConfigurationEntry 对象返回,到上一个方法中也是取得AutoConfigurationEntry 的configurations 属性返回的。
接下来我们看下 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 spi 来加载的,spring spi 原理与jdk 提供spi是一样的,都是根据接口 去文件中找对应的实现。
这里简单说下spi 的原理:
就拿jdbc 来说吧,jdk 提供了一个Driver驱动接口,然后由各大数据库厂商 做响应的实现,mysql 提供的MysqlDriver,orcale提供的OrcaleDriver(这里不一定真实存在),比如说我项目中 使用了mysql的驱动,jdbc 创建连接的时候需要我的驱动,但是它不知道我用的是哪家的驱动,这个时候 就需要在项目中配置一个文件,告诉一下jdbc 我用的是MysqlDriver
所以在mysql的驱动jar 的META-INF/services文件夹下面就有一个java.sql.Driver 文件, 这个文件名就是jdbc driver接口的全类名,表示是里面内容是java.sql.Driver 接口的实现,里面内容就是com.mysql.jdbc.Driver ,表示mysql的驱动实现是com.mysql.jdbc.Driver 这个类。

spring spi不一样的是路径是META-INF,然后文件名也是统一的spring.factories ,里面内容采用的是 接口全类名=接口实现类全类名 的格式。
它这边就是加载的EnableAutoConfiguration 全类名org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的实现。
在这里插入图片描述
多个实现类就用英文逗号隔开就行了。
可以看到它的实现类都是AutoConfiguration 结尾的,这个是一个约定俗成的,如果你要写一个自动配置类,最好是以AutoConfiguration 结尾,见名知意。
随便拿出来一个看下。

@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class,
		AnnotatedElement.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {}

都是带有@Configuration 注解的,这个spring 就能够知道了,他是个配置类。

总结

@EnableAutoConfiguration 自动装配注解由两个注解组成, @AutoConfigurationPackage 这个注解主要是为了将主启动类对应包 存储到BasePackages 这个bean对象中,@Import(AutoConfigurationImportSelector.class) 主要就是通过spi找出那些自动装配配置类,交个spring 实例化,加载。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

$码出未来

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值