Spring之@Conditional注解

@Conditional条件注解

一、概述

Spring中有一个条件注解@Conditional,想要探究一下这里的原理,看看是怎么来进行实现的。

二、准备工作

1、Conditional注解

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

	/**
	 * All {@link Condition} classes that must {@linkplain Condition#matches match}
	 * in order for the component to be registered.
	 */
	Class<? extends Condition>[] value();

}

看下注解信息意思是说:实现了Condition 接口的类,重写了其中的方法之后,如果匹配,会成为一个候选者。

2、Condition接口

public interface Condition {

	/**
	 * Determine if the condition matches.
	 * @param context the condition context
	 * @param metadata the metadata of the {@link org.Springframework.core.type.AnnotationMetadata class}
	 * or {@link org.Springframework.core.type.MethodMetadata method} being checked
	 * @return {@code true} if the condition matches and the component can be registered,
	 * or {@code false} to veto the annotated component's registration
	 */
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}

注释说明:

判断条件是否匹配。
参数:
context – 条件上下文
metadata – 被检查的类或方法的元数据
回报:
如果条件匹配并且可以注册组件,则为 true,否则为否决注解组件的注册为 false

3、元数据概念和使用

这个元数据信息真的很蛋疼,所以提前来说一下,Spring中的有好多元数据信息。描述类的、方法的、方法注解的

如:MetadataReader、ClassMetadata、AnnotationMetadata

在Spring中需要去解析类的信息,比如类名、类中的方法、类上的注解,这些都可以称之为类的元数据,所以Spring中对类的元数据做了抽象,并提供了一些工具类。

MetadataReader表示类的元数据读取器,默认实现类为SimpleMetadataReader。比如:

public class Test {

    public static void main(String[] args) throws IOException {
        SimpleMetadataReaderFactory simpleMetadataReaderFactory = new SimpleMetadataReaderFactory();

        // 构造一个MetadataReader
        MetadataReader metadataReader = simpleMetadataReaderFactory.getMetadataReader("com.guang.service.UserService");

        // 得到一个ClassMetadata,并获取了类名
        ClassMetadata classMetadata = metadataReader.getClassMetadata();

        System.out.println(classMetadata.getClassName());

        // 获取一个AnnotationMetadata,并获取类上的注解信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        for (String annotationType : annotationMetadata.getAnnotationTypes()) {
            System.out.println(annotationType);
        }

    }
}

需要注意的是,SimpleMetadataReader去解析类时,使用的ASM技术

为什么要使用ASM技术,Spring启动的时候需要去扫描,如果指定的包路径比较宽泛,那么扫描的类是非常多的,那如果在Spring启动时就把这些类全部加载进JVM了,这样不太好,所以使用了ASM技术。

三、开始测试

需求说明:如果当前操作系统是Linux操作系统,那么就使用Linux服务;如果当前操作系统是Windows操作系统,那么就使用Windows服务

1、接口抽象

因为想获取得到当前的bean的名称,所以实现BeanNameAware接口

public interface Service extends BeanNameAware {

}

2、实现

因为两个类都需要来进行获取得到bean名称,所以抽象到父类中来

public abstract class AbstractService implements Service{

	private String beanName;

	@Override
	public void setBeanName(String name) {
		this.beanName = name;
	}

	public String getBeanName(){
		return beanName;
	}
}

LinuxService实现

public class LinuxService extends AbstractService {

}

WindowsService实现

public class WindowsService extends AbstractService {

}

3、自定义注解

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ConditionOnService {

	String value() default "";
}

4、实现条件判断

因为需要对Linux和Windows来进行判断,所以各自实现Condition接口

public class ConditionOnServiceBeanLinux implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(ConditionOnService.class.getName());
        String value = (String) annotationAttributes.get("value");
        return "l".equals(value);
    }

}
public class ConditionOnServiceBeanWindows implements Condition {

   /**
    * spring容器
    * @param context the condition context spring容器
    * @param metadata the metadata of the {@link org.springframework.core.type.AnnotationMetadata class} 对应的元信息注解
    * or {@link org.springframework.core.type.MethodMetadata method} being checked
    * @return
    */
   @Override
   public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
      Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(ConditionOnService.class.getName());
      String value = (String) annotationAttributes.get("value");
      return "w".equals(value);
   }

}

5、配置类

@Configuration
@ComponentScan("com.gunag.ioc.demo5condition")
public class AppConfig5 {

}

6、启动类

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig5.class);
        String[] beanNamesForType = context.getBeanNamesForType(Service.class);
        for (String s : beanNamesForType) {
            System.out.println(s);
        }
    }
}

四、添加@Component注解到类上测试

所以修改一下WindowsService和LinuxService实现类:

@Component
@Conditional(value = ConditionOnServiceBeanWindows.class)
@ConditionOnService(value = "w")
public class WindowsService extends AbstractService {

}
@Component
@Conditional(value = ConditionOnServiceBeanLinux.class)
@ConditionOnService(value = "l")
public class LinuxService extends AbstractService {

}

控制台输出:

linuxService
windowsService

实现原理

因为之前看到过spring对于@Component的解析,所以能够快速定位到对应的位置上。

		// 符合includeFilters的会进行条件匹配,通过了才是Bean,也就是先看有没有@Component,再看是否符合@Conditional
		for (TypeFilter tf : this.includeFilters) {
			if (tf.match(metadataReader, getMetadataReaderFactory())) {
				return isConditionMatch(metadataReader);
			}
		}

在这里会根据元数据读取器利用ASM技术判断@Conditional是否匹配

	private boolean isConditionMatch(MetadataReader metadataReader) {
		if (this.conditionEvaluator == null) {
			this.conditionEvaluator =
					new ConditionEvaluator(getRegistry(), this.environment, this.resourcePatternResolver);
		}
		return !this.conditionEvaluator.shouldSkip(metadataReader.getAnnotationMetadata());
	}

利用condition计算器俩进行计算。

这里的方法命名很有意思,shouldSkip:是否应该跳过(因为方法返回值是boolean,一般称之为是否)

那么什么样的应该跳过,不让其成为候选者呢?因为是不匹配的应该不会成为候选者,匹配的应该成为候选者。

那么继续看:

public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
    
    if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
        return false;
    }

    if (phase == null) {
        // 是这个元信息类型的,
        if (metadata instanceof AnnotationMetadata &&
            ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
            return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
        }
        return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
    }

    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();
        }
        // 循环调用集合中的对象来调用match方法
        // 如果匹配,返回true,取反为true,表示不应该跳过,应该成为bean
        // 如果matchs方法返回的是false,返回true,表示应该跳过
        if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
            return true;
        }
    }

    return false;
}

在第一行判断中:判断类的元数据是否为空或者类上没有Conditional注解。

显然,元数据读取器不为空;判断后者的时候,如果有Conditional注解,那么if判断返回false;如果不存在Conditional注解,if判断返回false。

返回FALSE表示没有Conditional注解,表示的是应该跳过,因为取反,所以返回为true;

返回为true,表示的是有Conditional注解注解,那么在这里应该来进行判断,是否匹配的逻辑

然后来判断是否是一个配置类,根据判断,是一个配置类,然后继续下一轮判断,类似递归

第二次判断的时候就会获取得到@Conditional中value属性的值,放入到集合中来:

private List<String[]> getConditionClasses(AnnotatedTypeMetadata metadata) {
    MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(Conditional.class.getName(), true);
    Object values = (attributes != null ? attributes.get("value") : null);
    return (List<String[]>) (values != null ? values : Collections.emptyList());
}

然后在for循环中来加载对应的类并进行实例化:

private Condition getCondition(String conditionClassName, @Nullable ClassLoader classloader) {
    Class<?> conditionClass = ClassUtils.resolveClassName(conditionClassName, classloader);
    return (Condition) BeanUtils.instantiateClass(conditionClass);
}

将实例化后的对象直接放入到结合中来

AnnotationAwareOrderComparator.sort(conditions);

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;

在排序之后,这里居然开始来使用实例化对象调用matches方法。

如果匹配方法返回的是true,取反之后,直接返回FALSE,表示不应该跳过;

如果匹配方法返回的是FALSE,取反之后,直接返回true,表示应该跳过;

这里看起来很简单。

五、添加@Bean测试

将原来加在WindowsService和LinuxService上的注解去掉。

public class LinuxService extends AbstractService {}

public class WindowsService extends AbstractService {}

改造一下:

ConditionOnServiceBean

public class ConditionOnServiceBean implements Condition {
	@Override
	public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
		Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(ConditionOnService.class.getName());
		String value = (String) annotationAttributes.get("value");
		return "w".equals(value);
	}
}

注解:

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(value = ConditionOnServiceBean.class)
public @interface ConditionOnService {

	String value();
}

然后在配置上添加:

@Configuration(proxyBeanMethods = false)
@ComponentScan("com.gunag.ioc.demo5condition")
public class AppConfig5 {

	@Bean
	@ConditionOnService(value = "l")
	public Service linux() {
		LinuxService linuxService = new LinuxService();
		return linuxService;
	}

	@Bean
	@ConditionOnService(value = "w")
	public Service windows() {
		WindowsService linuxService = new WindowsService();
		return linuxService;
	}

}

运行之后的结果是:

windows

实现原理

那么这里就涉及到了@Bean的解析流程,和之前@Component解析流程不同了。

这里很简单,直接来到解析@Bean的地方

// @Bean生成BeanDefinition并注册
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
    loadBeanDefinitionsForBeanMethod(beanMethod);
}

点进去之后,映入眼帘的就是一个判断:

		if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {
			configClass.skippedBeanMethods.add(methodName);
			return;
		}
posted @ 2022-08-29 23:11  雩娄的木子  阅读(574)  评论(0编辑  收藏  举报