自定义Conditional

young 682 2022-08-24

@Conditional是SpringFramework的功能,SpringBoot在其基础上进行了一定的扩展。

然而实际开发过程中,Spring或SpringBoot提供的注解并不能满足我们的需求,此时则需要我们自己去定义相关注解。

查看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();

}

可以看到Conditional中需要传入Condition类型的Class

@FunctionalInterface
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);

}

所以我们可以创建一个实现了Condition接口的类,重写matches方法即可。

ConditionContext是Condition的上下文,可以获取BeanDefinitionRegistryConfigurableListableBeanFactoryEnvironment等数据。

AnnotatedTypeMetadata可以用于获取枚举的信息。

例如我现在有一个需要基于操作系统进行装配选择的场景。

可以通过@ConditionalOnExpression注解,通过SpEL表达式进行判断,如

@ConditionalOnExpression("T(org.apache.commons.lang3.SystemUtils).IS_OS_LINUX")

@ConditionalOnExpression("T(org.apache.commons.lang3.SystemUtils).IS_OS_WINDOWS")

也可以使用自定义注解去实现。

创建Condition接口的实现类

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

@Slf4j
public class OnOsCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String conditionOsName = (String) metadata.getAnnotationAttributes(ConditionalOnOS.class.getName()).get("value");
        if (log.isDebugEnabled()){
            log.debug("OS_NAME IS {}",SystemUtils.OS_NAME);
        }
        return StringUtils.startsWithIgnoreCase(SystemUtils.OS_NAME,conditionOsName);
    }
}

创建自定义注解ConditionOnOs

import org.springframework.context.annotation.Conditional;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Target(value = {ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(OnOsCondition.class)
public @interface ConditionalOnOS {
    String value();
}

此时,我们如果需要对类进行选择装配,则可以在相关类上添加注解,如

@ConditionalOnOS("Linux")

@ConditionalOnOS("Mac")

@ConditionalOnOS("Windows")

也可以对此注解进行二次封装,此时需要对ConditionalOnOS添加继承注解@Inherited,同时使注解的作用域可作用域注解之上

import org.springframework.context.annotation.Conditional;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Target(value = {ElementType.TYPE,ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(OnOsCondition.class)
@Inherited
public @interface ConditionalOnOS {
    String value();
}

创建基于操作系统的的注解

@Documented
@Target(value = {ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@ConditionalOnOS("Windows")
public @interface ConditionalOnWindows {
}
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(value = {ElementType.TYPE,ElementType.METHOD})
@ConditionalOnOS("Linux")
public @interface ConditionOnLinux {
}
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(value = {ElementType.TYPE,ElementType.METHOD})
@ConditionalOnOS("Mac")
public @interface ConditionOnMac {
}

这样就可以直接使用二次封装过的接口进行Condition配置了