原创不易,转载请注明出处
前言
在没有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 代码
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)) {
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);
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);
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);
}
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 实例化,加载。
|