SpringBoot学习(三)--@Conditional按条件注册

@Conditional按条件注册

  • 一、SpringBoot怎么判断自动配置需要注册?
  • 二、SpringBoot中@Conditional简单使用
  • 三、自定义@Conditional注解DEMO
  • 四、 @Conditional注解解析
  • 五、SpringBoot启动自动注册配置类解析
    • 1、SpringBoot中的Condition
    • 2、SpringBoot注册自动配置类
      • 2.1 getConfigurationClassFilter获取过滤器
      • 2.2 filter(configurations)执行过滤判断
      • 2.3 最终注册

一、SpringBoot怎么判断自动配置需要注册?

SpringBoot在扫描到META-INFO下的spring.factories下的AutoConfiguration类的时候是怎么去判断是否需要注册这个类的呢?这里就有一个很重要的注解@Conditional。在SpringBoot中存在大量的条件注解@ConditionOnXXX,这些注解在自动配置类的上面都能见到,比如:
在这里插入图片描述
SpringBoot学习(三)--@Conditional按条件注册_第1张图片
它们是怎么工作的呢?

先看一下SpringBoot中的常用Condition注解:

条件化注解 配置生效条件
@ConditionalOnBean 配置了某个特定Bean
@ConditionalOnMissingBean 没有配置特定的Bean
@ConditionalOnClass Classpath里有指定的类
@ConditionalOnMissingClass Classpath里没有指定的类
@ConditionalOnExpression 给定的SpEL表达式的计算结果为true
@ConditionalOnJava Java的版本匹配特定值或者一个范围值
@ConditionalOnJndi 参数中给定的JNDI位置必须存在一个,如果没有给定参数,则要有JNDI InitialContext
@ConditionalOnProperty 指定的配置属性要有一个明确的值
@ConditionalOnResource Classpath里有指定的资源
@ConditionalOnWebApplication 是一个Web应用程序
@ConditionalOnNotWebApplication 不是Web应用程序

二、SpringBoot中@Conditional简单使用

SpringBoot中利用以上注解设置的简单示例:
自定义的自动配置类:MyAutoConfigurationPerson

@Configuration
@EnableConfigurationProperties(MyPersonProperties.class)
@ConditionalOnClass(PersonService.class)
//该属性为true时,配置文件中缺少对应的value或name的对应的属性值,也会注入成功
@ConditionalOnProperty(prefix = "test.person", matchIfMissing = true, name = {"name","address"})
public class MyAutoConfigurationPerson {

    private MyPersonProperties myPersonProperties;

    public MyAutoConfigurationPerson (MyPersonProperties myPersonProperties) {
        this.myPersonProperties = myPersonProperties;
    }

    @Bean
    @ConditionalOnMissingBean(OrderService.class)
    public PersonService personService () {
        PersonService service = new PersonService();
        System.err.println("service在自动配置类被初始化");
        System.err.println(myPersonProperties.toString());
        return service;
    }

}

配置的属性类:

@ConfigurationProperties(prefix = "test.person")
public class MyPersonProperties {
    private String name="alen";
    private String address="SHANGHAI";
/*省略getter、setter和toString*/
}

待注入的服务类两个

public class PersonService {
  public PersonService () {
        System.err.println("PersonService被初始化。。。。");
    }
}
public class OrderService{   
   public OrderService () {
         System.out.println("OrderService被初始化。。。。");
    }
}

application.properties配置:

test.person.address=BEIJING
test.person.name=lucy

测试的话,可以尝试下更改注解属性值、application.properties文件删除给定的配置、OrderService加上@Component注解、@ConditionalOnMissingBean改为@ConditionalOnBean等查看类加载情况。

三、自定义@Conditional注解DEMO

简单的自定义一个@Conditional注解,看他是如何工作的:
自定义的注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Conditional(TestConditional.class)
public @interface ConditionOnTest {
    String environment () default "dev";
}

匹配用的Condition接口实现类

public class TestConditional implements Condition {
    @Override
    public boolean matches (ConditionContext context, AnnotatedTypeMetadata metadata) {
        Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionOnTest.class.getName());
        String environment =(String) attributes.get("environment");
        //如果environment.equals("test")返回true,则表示注册该类
        return environment.equals("test");
    }
}

测试类

@Component
@ConditionOnTest(environment = "test")
public class PersonService {
    public PersonService () {
        System.err.println("PersonService被初始化。。。。");
    }
}

测试类上的注解值如果environment = “test”,将会在控制台“打印PersonService被初始化。。。。”。
如果将此值修改,则在启动时将不会看到构造函数输出被打印。

四、 @Conditional注解解析

以自定义的@Conditional注解Demo为例查看@Conditional注解的作用逻辑。
此类起作用肯定是TestConditional类在其中起了作用,进入debug模式,在matchs方法打上断点,简单查看下它的工作流程:
1.从Debug可以看出,是从解析配置类的包扫描,扫描到该组件。
SpringBoot学习(三)--@Conditional按条件注册_第2张图片
然后再去判断该组件是否需要跳过注册,可以看到最终调用到了org.springframework.context.annotation.ConditionEvaluator#shouldSkip(org.springframework.core.type.AnnotatedTypeMetadata)方法
大致逻辑为先判断是否存在@Conditional注解,如果存在,则获取conditionClasses 并实例化Condition 实现类在调用matchs方法进行判断。

public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
     //判断元数据是否存在Conditional注解
		if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
			return false;
		}
省略.....
       //获取Condition类,demo此处为TestConditional类
		List<Condition> conditions = new ArrayList<>();
		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();
			}
			//此处调用了matchs方法判断
			if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
				return true;
			}
		}

		return false;
	}

如果返回false,后续将不会放入候选的集合中,则Spring将不会注册该类。
第一个Demo中的配置也是如此,可以看下@ConditionalOnClass(PersonService.class)注解,此注解底层依旧是@Conditional注解

@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {

所以可以直接在OnClassCondition类的getOutcome方法上打上断点去查看。

五、SpringBoot启动自动注册配置类解析

SpringBoot的大致处理逻辑也是一样,简单看一下SpringBoot启动时去怎么选择哪些自动配置项的。

1、SpringBoot中的Condition

1.先看一下Condition接口在SpringBoot中的应用,Condition接口在SpringBoot中的基础类为SpringBootCondition类,在其上注释为:

/**
 * Base of all {@link Condition} implementations used with Spring Boot. Provides sensible
 * logging to help the user diagnose what classes are loaded.
 * */
  public abstract class SpringBootCondition implements Condition {
  /*。。。。*/

SpringBootCondition有很多子类,此处只展示本处演示所在意的类的类图,其他再说。。。(此处只关注AutoConfigurationImportFilter类,因为下面会用到
SpringBoot学习(三)--@Conditional按条件注册_第3张图片
来简单看下SpringCondition类:
在此类的matchs方法中并没有太多逻辑,只是一个模板方法有子类去实现.

	public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		String classOrMethodName = getClassOrMethodName(metadata);
		/*省略。。。。*/
			//此处为abstract方法,由子类去实现各自的逻辑(模板方法)
			ConditionOutcome outcome = getMatchOutcome(context, metadata);
			//日志
			logOutcome(classOrMethodName, outcome);
			//记录
			recordEvaluation(context, classOrMethodName, outcome);
			return outcome.isMatch();
	   /*省略。。。。*/

FilteringSpringBootCondition类中也并没有实现getMatchOutcome()方法,那现在去OnWebApplicationCondition中具体看一下怎么实现的:

/**
* 根据元数据的配置来过滤自动配置类
* @Param autoConfigurationClasses 候选自动配置类集合
* @Param autoConfigurationMetadata 候选自动配置类元数据
* @Return outcomes  匹配完的结果集
*/
    @Override
	protected 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) {
				//具体判断逻辑
				//autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnWebApplication"),
				//以this.properties.getProperty(className + "." + key)获取配置的参数,如果为空则取值null
				outcomes[i] = getOutcome(
						autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnWebApplication"));
			}
		}
		return outcomes;
	}

具体的判断逻辑,如果没有匹配的属性值,则直接返回空,否则再去与属性值进行比较,进行判断。

private ConditionOutcome getOutcome(String type) {
		if (type == null) {
			return null;
		}
		ConditionMessage.Builder message = ConditionMessage.forCondition(ConditionalOnWebApplication.class);
		//判断@ConditionalOnWebApplication上的type属性是否与源信息的type相等
		if (ConditionalOnWebApplication.Type.SERVLET.name().equals(type)) {
			//判断是否存在该类,如果不存在则不匹配
			if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, getBeanClassLoader())) {
				return ConditionOutcome.noMatch(message.didNotFind("servlet web application classes").atAll());
			}
		}
		/*省略。。。。*/
		return null;
	}

2、SpringBoot注册自动配置类

在第一篇中我们看到处理这些候选的自动配置类是在
org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorGroupingHandler#processGroupImports
————————》
org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorGrouping#getImports
————————》
org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry()此方法处,具体在:
该行代码处configurations = getConfigurationClassFilter().filter(configurations);
该行代码分两部分查看:

2.1 getConfigurationClassFilter获取过滤器

private ConfigurationClassFilter getConfigurationClassFilter() {
		if (this.configurationClassFilter == null) {
		//获取filters,此处进去我们可以看到其实例化了AutoConfigurationImportFilter类的三个子类
			List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();
			//执行Aware方法,注入属性
			for (AutoConfigurationImportFilter filter : filters) {
				invokeAwareMethods(filter);
			}
         //包装到ConfigurationClassFilter,并扫描到metadata赋值给autoConfigurationMetadata
			this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);
		}
		return this.configurationClassFilter;
	}

2.2 filter(configurations)执行过滤判断

configurations由之前可知此处已经是所有相关Jar包中spring.factories中经过过滤和去重的配置信息

List<String> filter(List<String> configurations) {
			long startTime = System.nanoTime();
			//候选的自动配置类
			String[] candidates = StringUtils.toStringArray(configurations);
			boolean skipped = false;
			//三个过滤器进行过滤
			for (AutoConfigurationImportFilter filter : this.filters) {
			//调用各自的match方法---》在父类SpringBootApplication中实现----》实际调用方法为getOutcomes,由各子类实现
				boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
				for (int i = 0; i < match.length; i++) {
					//判断该位置的元素是否通过匹配,不匹配设置为null,跳过设置为true,不直接返回候选结果
					if (!match[i]) {
						candidates[i] = null;
						skipped = true;
					}
				}
			}
			if (!skipped) {
				return configurations;
			}
			List<String> result = new ArrayList<>(candidates.length);
			for (String candidate : candidates) {
				if (candidate != null) {
					result.add(candidate);
				}
			}
		/*省略。。。。*/
			return result;
		}

可以看到匹配的未找到的情况:
SpringBoot学习(三)--@Conditional按条件注册_第4张图片
此处最终返回匹配完成的结果并做一些处理,最终调用到:
org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorGroupingHandler#processGroupImports方法处进行注册:

2.3 最终注册

getImports()逻辑执行完毕,获取的集合为Iterable,Group.Entry中包含了获取的符合条件的候选配置类。再执行processImports方法直接注册。

public void processGroupImports() {
			for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
				Predicate<String> exclusionFilter = grouping.getCandidateFilter();
                //grouping.getImports()为返回的需要导入的自动配置类的集合
				grouping.getImports().forEach(entry -> {
					ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
					try {
					//此处直接调用processImports注册该类
					//this.importStack.registerImport(
					//			currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
						processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
								Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
								exclusionFilter, false);
					}
					catch (BeanDefinitionStoreException ex) {
						throw ex;
					}
					catch (Throwable ex) {
						throw new BeanDefinitionStoreException(
								"Failed to process import candidates for configuration class [" +
										configurationClass.getMetadata().getClassName() + "]", ex);
					}
				});
			}
		}

到此,SpringBoot自动配置类注册完成。

你可能感兴趣的:(Spring注解开发)