自动装配是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每次都会重新生成;
@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系注解
-
ConditionalOnBean:是否存在某个某类或某个名字的Bean
-
ConditionalOnMissingBean:是否缺失某个某类或某个名字的Bean
-
ConditionalOnSingleCandidate:是否符合指定类型的Bean只有一个
-
ConditionalOnClass:是否存在某个类
-
ConditionalOnMissingClass:是否缺失某个类
-
ConditionalOnExpression:指定的表达式返回的是true还是false
-
ConditionalOnJava:判断Java版本
-
ConditionalOnJndi:JNDI指定的资源是否存在
-
ConditionalOnWebApplication:当前应用是一个Web应用
-
ConditionalOnNotWebApplication:当前应用不是一个Web应用
-
ConditionalOnProperty:Environment中是否存在某个属性
-
ConditionalOnResource:指定的资源是否存在
-
ConditionalOnWarDeployment:当前项目是不是以War包部署的方式运行
-
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!