这一篇我们要梳理的主要是关于注入Bean
的条件判断,只有满足对应的条件才注入对应的BeanDefinition
。关于注入的条件判断目前我们知道是有3个地方,其中两个都是使用的ConditionEvaluator
处理,另外一个是我们的上一篇提到的AutoConfigurationImportFilter
。下面我们就按顺序来依次梳理。建议看下前两篇
一、@Conditional
1、结构
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
首先我们需要了解这个注解,这个注解就是用来条件判断的,只有满足条件(通过value
导入的继承Condition
接口的类来判断),才导入这个Bean
。下面我们通过一个spring
源码中的demo来看下这个注解的具体配套使用。
2、demo1
@Configuration
@Conditional(NoBeanOneCondition.class)
static class BeanTwoConfiguration {
@Bean
public ExampleBean bean2() {
return new ExampleBean();
}
}
这个配置类BeanTwoConfiguration
,我们可以看到其本身是要注入bean2()
(ExampleBean
)的,但其加了一个@Conditional(NoBeanOneCondition.class)
,就是说这个配置类能不能注入,需要看NoBeanOneCondition
的判断。
static class NoBeanOneCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return !context.getBeanFactory().containsBeanDefinition("bean1");
}
}
这个判断就是看当前Bean
工厂中有没有名称为bean1
的BeanDefiniton
,如果没有就返回true
,这样就会注入BeanTwoConfiguration
以此来注入其中的ExampleBean
。
@Test
public void conditionalOnMissingBeanNoMatch() throws Exception {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(BeanTwoConfiguration.class);
ctx.refresh();
assertFalse(ctx.containsBean("bean1"));
assertTrue(ctx.containsBean("bean2"));
assertTrue(ctx.containsBean("configurationClassWithConditionTests.BeanTwoConfiguration"));
}
这个就是测试类,可以看到其只ctx.register(BeanTwoConfiguration.class)
,即只注入了BeanTwoConfiguration
,就还没有bean1
,所以这里就会成功注入bean2
(ExampleBean
)。
3、demo2
在上面的demo1基础上,我们来看下另一种情况:
@Configuration
static class BeanOneConfiguration {
@Bean
public ExampleBean bean1() {
return new ExampleBean();
}
}
这个BeanOneConfiguration
就是注解注入bean1
,然后看测试类:
@Test
public void conditionalOnMissingBeanMatch() throws Exception {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(BeanOneConfiguration.class, BeanTwoConfiguration.class);
ctx.refresh();
assertTrue(ctx.containsBean("bean1"));
assertFalse(ctx.containsBean("bean2"));
assertFalse(ctx.containsBean("configurationClassWithConditionTests.BeanTwoConfiguration"));
}
这样就没有注入configurationClassWithConditionTests.BeanTwoConfiguration
了,所以也不会注入bean2
。
二、@Conditional在SpringBoot中的使用
上面这个是简单使用。下面我们就来其在SpringBoot
中的使用,这个其实我们上一篇文章有简单提到。下面这些注解都是用在SpringBoot的自动配置。
1、@ConditionalOnBean
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
Class<?>[] value() default {};
String[] type() default {};
...........
这个注解@ConditionalOnBean
有点类似上面demo的NoBeanOneCondition
这种,只不过NoBeanOneCondition
是类,还要自动注入,这个@ConditionalOnBean
只要注解标注就可以了。不过两者的核心都是通过@Conditional
来导入Condition
接口的实现类。这里是导入的OnBeanCondition
。
然后这个@ConditionalOnBean
只要是用来判断有没有Bean
,只有存在的话才注入。下面我们来看下使用demo
1)、demo
@Configuration
@ConditionalOnBean(name = "foo")
protected static class OnBeanNameConfiguration {
@Bean
public String bar() {
return "bar";
}
}
@Configuration
protected static class FooConfiguration {
@Bean
public String foo() {
return "foo";
}
}
@Test
public void testNameOnBeanCondition() {
this.contextRunner.withUserConfiguration(FooConfiguration.class, OnBeanNameConfiguration.class)
.run(this::hasBarBean);
}
private void hasBarBean(AssertableApplicationContext context) {
assertThat(context).hasBean("bar");
assertThat(context.getBean("bar")).isEqualTo("bar");
}
这样就完成了OnBeanNameConfiguration
的注入,当然这里还可以指定这个Bean的类似,可以通过value
、或type
:
@Configuration
@ConditionalOnBean(name = "foo", value = Date.class)
protected static class OnBeanNameAndTypeConfiguration {
@Bean
public String bar() {
return "bar";
}
}
@Test
public void testNameAndTypeOnBeanCondition() {
this.contextRunner.withUserConfiguration(FooConfiguration.class, OnBeanNameAndTypeConfiguration.class)
.run((context) -> assertThat(context).doesNotHaveBean("bar"));
}
这样由于类型不匹配,就不能注入了。
当然SpringBoot
还有很多的这种注解,来帮助自动配置与注解。
看名称也大致知道其的作用了。我们下面就来看下OnBeanCondition
具体逻辑。
三、OnBeanCondition
class OnBeanCondition extends SpringBootCondition implements ConfigurationCondition {
它有继承SpringBootCondition
,然后实现ConfigurationCondition
。
1、ConfigurationCondition
public interface ConfigurationCondition extends Condition {
ConfigurationPhase getConfigurationPhase();
enum ConfigurationPhase {
PARSE_CONFIGURATION,
REGISTER_BEAN
}
}
这个接口主要是为了颗粒化控制Condition
的作用,要对应匹配才会返回true
。PARSE_CONFIGURATION
表面这个是Configuration
即配置类,REGISTER_BEAN
表示这个是一个常规Bean
。
2、SpringBootCondition
public abstract class SpringBootCondition implements Condition {
SpringBootCondition是SpringBoot
关于Condition
的基础接口,也就是说,SpringBoot
所有拓展Condition
的都是通过SpringBootCondition
来实现Condition
接口。下面我们就来具体看下其的方法。
1)、matches
@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String classOrMethodName = getClassOrMethodName(metadata);
try {
ConditionOutcome outcome = getMatchOutcome(context, metadata);
logOutcome(classOrMethodName, outcome);
recordEvaluation(context, classOrMethodName, outcome);
return outcome.isMatch();
}
catch (NoClassDefFoundError ex) {
throw new IllegalStateException("Could not evaluate condition on " ....
+ "put a @ComponentScan in the default package by mistake)", ex);
}
catch (RuntimeException ex) {
throw new IllegalStateException("Error processing condition on " + getName(metadata), ex);
}
}
这里首先是通过getMatchOutcome
方法来获取匹配结果,然后再通过recordEvaluation
来记录评估提交的详细结果,最后返回是否匹配。这里主要是getMatchOutcome
方法,因为整个逻辑判断是否匹配都是这个方法,其他方法主要是记录一些信息,我们就略过了。
public class ConditionOutcome {
private final boolean match;
private final ConditionMessage message;
2)、getMatchOutcome
public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);
这个方法是抽象方法,要让子类来处理是否匹配。
下面我们就来具体看下这个SpringBootCondition
的一个子类OnBeanCondition
的实现逻辑。
3、getMatchOutcome(SpringBootCondition)
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ConditionMessage matchMessage = ConditionMessage.empty();
if (metadata.isAnnotated(ConditionalOnBean.class.getName())) {
..........
}
if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
.........
}
if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
........
}
return ConditionOutcome.match(matchMessage);
}
这个我们可以看到其是会判断三种不同的注解ConditionalOnBean
、ConditionalOnSingleCandidate
、ConditionalOnMissingBean
。其中ConditionalOnSingleCandidate
是用来判断是不是传入的类是不是有单例Bean
、ConditionalOnMissingBean
是判断有没有缺失这个Bean
。
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnMissingBean {
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnSingleCandidate {
这里面的具体判断我们就先不深入探究了。
四、在注入的时候使用Conditional判断
下面我们就来看下其是怎样使用的,我们在前面的两篇文章是有初步提到过得。
1、以PARSE_CONFIGURATION的方式(ConditionEvaluator源码调用)
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
..........
// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass = asSourceClass(configClass);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);
this.configurationClasses.put(configClass, configClass);
}
这里就是在配置类的解析的前面以PARSE_CONFIGURATION
也就是解析配置类的方式。
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
if (phase == null) {
.......
}
List<Condition> conditions = new ArrayList<>();
for (String[] conditionClasses : getConditionClasses(metadata)) {
.........
}
AnnotationAwareOrderComparator.sort(conditions);
for (Condition condition : conditions) {
......
}
return false;
}
下面我们就以前面的demo1
为例,来看下这个过程。
1)
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
首先是判断这个配置类有没有@Conditional
注解,如果没有就返回false
,主要在processConfigurationClass
就不会直接返回,是继续往下解析。
2)
if (phase == null) {
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}
下一步是类型phase
判断,如果为null
,就需要设置参数,这里的ConfigurationClassUtils.isConfigurationCandidate
我们前面文章讲过,就是判断有没有@Component
、@Bean
这些
static {
candidateIndicators.add(Component.class.getName());
candidateIndicators.add(ComponentScan.class.getName());
candidateIndicators.add(Import.class.getName());
candidateIndicators.add(ImportResource.class.getName());
}
public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
....
// Any of the typical annotations found?
for (String indicator : candidateIndicators) {
if (metadata.isAnnotated(indicator)) {
return true;
}
}
// Finally, let's look for @Bean methods...
try {
return metadata.hasAnnotatedMethods(Bean.class.getName());
}
return false;
}
}
是的话,就返回true
表面其是PARSE_CONFIGURATION
,不然的话就是一个普通的REGISTER_BEAN
然后再重新进入shouldSkip
方法,就能继续往下走了。
3)
List<Condition> conditions = new ArrayList<>();
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
}
}
这里就是获取@Conditional
的值,也就是实现Condition
接口的类获取,然后通过getCondition
去实例化创建添加到conditions
中。例如我们的demo
中,这里获取的就是NoBeanOneCondition
4)
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
}
return false;
这里就是最后一步了,如果是ConfigurationCondition
类型,由于其本身是有getConfigurationPhase()
返回ConfigurationPhase
的,所以这里会判断,然后就上课关键的condition.matches
,就是调用Condition
的方法,如果不匹配就会true
,也就是注解return
,不就行后续解析了。不然就最后返回false
来继续解析。
五、在@ComponentScan的使用。
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
.........
// Process any @ComponentScan annotations
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
............
}
}
}
.....
}
这个方法我们前面文章也就说明。这里之所以会用,就是因为@ComponentScans
会主动引入一些Bean
提过包扫描,而这些事前面提过processConfigurationClass
方法没有引入到的,所以对于这些另外引入的也会进行判断。
六、SpringBoot中AutoConfigurationImportSelector的类似判断
我们在上篇文章中,梳理自动配置的时候,是有遇到上面的判断能不能注入的内容的:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
.........
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
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 StringUtils.toStringArray(configurations);
}
就是这里的filter(configurations, autoConfigurationMetadata)
。
private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
long startTime = System.nanoTime();
String[] candidates = StringUtils.toStringArray(configurations);
boolean[] skip = new boolean[candidates.length];
boolean skipped = false;
for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
invokeAwareMethods(filter);
boolean[] match = filter.match(candidates, autoConfigurationMetadata);
for (int i = 0; i < match.length; i++) {
if (!match[i]) {
skip[i] = true;
skipped = true;
}
}
}
if (!skipped) {
return configurations;
}
List<String> result = new ArrayList<>(candidates.length);
for (int i = 0; i < candidates.length; i++) {
if (!skip[i]) {
result.add(candidates[i]);
}
}
return new ArrayList<>(result);
}
这里通过filter.match
筛选,所有都满足的话,就!skipped
注解返回了,如果有些不能不满足,就往下筛选添加到result
中。
上面的getAutoConfigurationImportFilters()
方法也是从spring.factorias
中获取:
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
}
然后会返回这3个:
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
这3个我最开始看到的时候以为是与上面的OnBeanCondition
一样,但其实是不同的。这个是实现的AutoConfigurationImportFilter
,虽然也是判断是否注入,但其也是用到了ConfigurationCondition
,也是实现的Condition
。
@Order(Ordered.LOWEST_PRECEDENCE)
class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition {
public interface ConfigurationCondition extends Condition {
@FunctionalInterface
public interface AutoConfigurationImportFilter {
boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata);
}
这里的AutoConfigurationImportFilter
其是返回的boolean[]
(入参也是多个autoConfigurationClasses
),但Condition
是直接单个返回。所以内部是具体通过Condition
来匹配每个的。
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
七、FilteringSpringBootCondition
然后AutoConfigurationImportFilter
的初步实现是FilteringSpringBootCondition
。
1、结构方法
abstract class FilteringSpringBootCondition extends SpringBootCondition
implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {
private BeanFactory beanFactory;
private ClassLoader beanClassLoader;
@Override
public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);
ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);
boolean[] match = new boolean[outcomes.length];
for (int i = 0; i < outcomes.length; i++) {
match[i] = (outcomes[i] == null || outcomes[i].isMatch());
if (!match[i] && outcomes[i] != null) {
logOutcome(autoConfigurationClasses[i], outcomes[i]);
if (report != null) {
report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
}
}
}
return match;
}
然后这里也会获取ConditionEvaluationReport
来记录信息。通过getOutcomes
数组来记录每个具体的匹配关系。我们以OnBeanCondition
为FilteringSpringBootCondition
具体实现,本身有3个实现返回:
class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition {
@Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
}
@Override
protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
for (int i = 0; i < outcomes.length; i++) {
String autoConfigurationClass = autoConfigurationClasses[i];
if (autoConfigurationClass != null) {
Set<String> onBeanTypes = autoConfigurationMetadata.getSet(autoConfigurationClass, "ConditionalOnBean");
outcomes[i] = getOutcome(onBeanTypes, ConditionalOnBean.class);
if (outcomes[i] == null) {
Set<String> onSingleCandidateTypes = autoConfigurationMetadata.getSet(autoConfigurationClass,
"ConditionalOnSingleCandidate");
outcomes[i] = getOutcome(onSingleCandidateTypes, ConditionalOnSingleCandidate.class);
}
}
}
return outcomes;
}
这里下面也是遍历然后通过@ConditionalOnBean
、@ConditionalOnSingleCandidate
来具体判断每个匹配。上面是OnBeanCondition
,然后另外一个实现OnClassCondition
也是类似的。
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ClassLoader classLoader = context.getClassLoader();
ConditionMessage matchMessage = ConditionMessage.empty();
List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
if (onClasses != null) {
....
}
List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);
if (onMissingClasses != null) {
.......
matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
.didNotFind("unwanted class", "unwanted classes")
.items(Style.QUOTE, filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));
}
return ConditionOutcome.match(matchMessage);
}
这里是使用ConditionalOnMissingClass
、ConditionalOnClass
来判断,这里之使用是这样匹配,是因为通过AutoConfigurationImportFilter
从spring.factorios
获取的所有内容,都是通过只这些@ConditionalOnMissingClass
、@ConditionalOnClass
来注入我们通过srping-start
引入的内容的。例如返回的其中一个valueorg.springframework.boot.autoconfigure.aop.AopAutoConfiguration
:
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Advice.class)
static class AspectJAutoProxyingConfiguration {
@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
matchIfMissing = false)
static class JdkDynamicAutoProxyConfiguration {
}
@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
static class CglibAutoProxyConfiguration {
}
}
.........
}
我们现在还没有引入spring-boot-starter-aop
,所以是没有Advice.class
,由于ConditionalOnClass
就不会注入AspectJAutoProxyingConfiguration
这些内容。
这里现在是没有Advice
,当我们引入spring-boot-starter-aop
。
就能注入需要的内容了。