springboot使用ContextLoaderListener springboot的condition_spring boot

在前面一文中,我们讲了SpringBoot启动的几个阶段和自动装配的原理,我们会发现,在Spring中,使用了大量的@ConditionXXX注解,本文就来介绍下@Condition注解的原理。

问题:

  1. ConditionXX注解是做什么的?如何使用?
  2. 如何自定义Condition?
  3. Condition实现原理是什么?

一、@ConditionXXX注解的作用

springboot使用ContextLoaderListener springboot的condition_spring boot_02

常见的一些@Condition相关的注解如上。

该类注解的作用就是在满足某个条件的时候才将某个类进行实例化并加入到Spring容器中来。

如:

  • @ConditionalOnBean:当容器中有某个Bean的时候才调用当前被修饰的方法、或类,并加入到容器管理
@Configuration
public class MyConfiguration {

    @Bean(name = "haha")
    public People people(){
        return new People();
    }

    @Bean
    @ConditionalOnBean(name = "haha")
    public  People onPeople(){
        System.out.println("上下文有名为haha的bean");
        return new People();
    }

}

springboot使用ContextLoaderListener springboot的condition_List_03

启动时就加载到了onPeople方法。如果把上面注入的名为haha的bean去掉,则当前方法就不会被调用。

  • ConditionalOnClass:当工程中有某个类时才调用
@Bean
    @ConditionalOnClass(name = "com.wml.config.User")
    public People onClass(){
        System.out.println("有User类");
        return new People();
    }
  • 同样的还有对应的Missing注解,与上面两个相反:
@Bean("missingPeople")
    @ConditionalOnMissingClass(value = "com.wml.pojo.Test")
    public People onMissingClass(){
        System.out.println("上下文没有Test类");
        return new People();
    }

    @Bean
    @ConditionalOnMissingBean(name = "didi")
    public  People missingPeople(){
        System.out.println("上下文无名为didi的bean");
        return new People();
    }

在没有对应的类和Bean的时候才生效。

  • ConditionalOnExpression:表达式成立才生效
@Bean
    @ConditionalOnExpression(value = "${server.port}==8080")
    public People onExpression(){
        System.out.println("端口为8080");
        return new People();
    }

springboot使用ContextLoaderListener springboot的condition_List_04

  • @ConditionalOnProperty:配置文件的属性匹配时才生效:
@Bean
    @ConditionalOnProperty(prefix = "spring.application",name = "name",havingValue = "test-application")
    public People prop(){
        System.out.println("应用名为test-application");
        return new People();
    }

springboot使用ContextLoaderListener springboot的condition_List_05

暂且列举这么多。

二、自定义Condition注解

观察上面一些Condition注解,以ConditionalOnMissingBean为例:

@Conditional(OnBeanCondition.class)
public @interface ConditionalOnMissingBean {
}

其上都使用了一个@Conditional(OnBeanCondition.class)注解,当括号中的类返回true时才会生效。而里面的类,跟进去,最终都是实现了@Condition接口:

@FunctionalInterface
public interface Condition {

	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}

而我们要实现自己的Condition类,就需要实现该接口,并在matches方法中定义匹配规则。

如:

public class MyCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
      	String age=context.getEnvironment().getProperty("my.age");
        return age != null && (Integer.valueOf(age) == 22);
    }
}

获取配置文件中的my.age属性,如果没配置或者值不是22,则返回false,否则返回true。

my:
  age: 22
@Bean
    @Conditional(MyCondition.class)
    public People cusCondition(){
        System.out.println("自定义Condition生效");
        return new People();
    }

springboot使用ContextLoaderListener springboot的condition_System_06

可以看到自定义的Condition生效了。

而如果改成其他的年龄或不写,则不会生效。

三、Condition原理

在看源码前先猜测下可能是如何实现的?

首先,处理@Configuration、@Import等注解,都是在前面说的那个ConfigurationClassPostProcessor类中进行集中处理的,而我们的Condition注解,那么可能就需要判断某个类上是否有该条件注解,如果有的话,那么肯定会去拿它的条件,如果条件满足,则正常实例化,如果不满足,则不进行实例化。

那么进来具体看看:

具体处理是在这个类的parser.parse方法里处理:

一直跟进去,配置类会在下面这个方法处理:

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
		if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
			return;
		}
  .............

}

在方法第一行,首先调用了一个shouldSkip方法,该方法就是用来判断当前类是否需要跳过,如果需要跳过则不装载到容器中,否则正常装载。

public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
   //1.如果没有被Conditional注解,则直接返回false,不需要跳过
		if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
			return false;
		}

		......

		List<Condition> conditions = new ArrayList<>();
   //2.收集当前类中所有Conditional注解的value值,如我们自定义的MyCondition类名,并通过getCondition获取对应的Condition(实现类),即我们的MyCondition类,加入到conditions集合中
		for (String[] conditionClasses : getConditionClasses(metadata)) {
			for (String conditionClass : conditionClasses) {
				Condition condition = getCondition(conditionClass, this.context.getClassLoader());
				conditions.add(condition);
			}
		}

		AnnotationAwareOrderComparator.sort(conditions);

		for (Condition condition : conditions) {
			ConfigurationPhase requiredPhase = null;
			if (condition instanceof ConfigurationCondition) {
				requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
			}
      //这里就会调用接口的matches方法,如果匹配就返回false,不匹配就返回true,不需要加载到容器中
			if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
				return true;
			}
		}

		return false;
	}

但是可以看到matches实现类只有下面四个:

springboot使用ContextLoaderListener springboot的condition_自定义_07

这里只看SpringBootCondition,为啥呢?因为那几个Condition类都继承了该抽象类。

@ConditionalOnClass的条件类OnClassCondition:

springboot使用ContextLoaderListener springboot的condition_List_08

来到SpringBootConditionmatches方法:

@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();
   }
   ..............
}

其中ConditionOutcome中有一个布尔类型的match字段,该字段就是匹配的结果。这里的getMatchOutcome是一个模板方法,实现交给了具体的子类,我们就以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> 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));
		}
		......
	}

这里核心方法就是filter,会反射定义的需要存在的类,如@ConditionalOnClass(name = "com.wml.config.User")中的User,如果出现异常,说明不存在该类,则添加到集合中返回,如果missing集合不为空,则就是目标类不存在,因此构造一个noMath,里面的match的值就是false。

protected final List<String> filter(Collection<String> classNames, ClassNameFilter classNameFilter,
			ClassLoader classLoader) {
		if (CollectionUtils.isEmpty(classNames)) {
			return Collections.emptyList();
		}
		List<String> matches = new ArrayList<>(classNames.size());
		for (String candidate : classNames) {
     
			if (classNameFilter.matches(candidate, classLoader)) {
				matches.add(candidate);
			}
		}
		return matches;
	}

这里会调用传入的ClassNameFilter的matches方法进行匹配,因为传入的是MISSING类型的,因此进入下面的MISSING类型的枚举:

protected enum ClassNameFilter {

		PRESENT {

			@Override
			public boolean matches(String className, ClassLoader classLoader) {
				return isPresent(className, classLoader);
			}

		},

		MISSING {

			@Override
			public boolean matches(String className, ClassLoader classLoader) {
				return !isPresent(className, classLoader);
			}

		};

		abstract boolean matches(String className, ClassLoader classLoader);

		static boolean isPresent(String className, ClassLoader classLoader) {
			.....
			try {
        //这里就是通过反射调用className
				resolve(className, classLoader);
        //反射成功,说明存在,返回true
				return true;
			}catch (Throwable ex) {
        //反射失败,说明不存在,返回false
				return false;
			}
		}

	}

如果返回false,那么就会添加到filter方法的matches集合中,最终返回,有一个类不存在, 集合就非空,那么就会在getMatchOutcome返回ConditionOutcome.noMatch,最终会在shouldSkip方法返回true,这样就会跳过将该类或方法构造成BeanDefinition加入到容器中。

其他几个ConditionXXX注解的原理都类似,就不赘述了,如@ConditionalOnBean会从beanFactory中拿目标bean,如果拿不到说明不存在,如果拿到就说明匹配,返回true,就会将被修饰的类或方法加入到容器。

OK,到这开头的三个问题就都解决了。