04-Conditional注解

@Conditional条件注入

不使用条件注入前

package com.xiong.config;

import com.xiong.bean.Person;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;

@Configuration
public class PersonConfig {

    @Bean(value = "linus")
    Person linus() {
        return new Person("linus", 50);
    }

    @Bean(value = "bill")
    Person bill() {
        return new Person("bill gate", 58);
    }
}
package com.xiong;


import com.xiong.bean.Person;
import com.xiong.config.PersonConfig;
import com.xiong.service.PersonService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainTest {
    @Test
    public void noConditionalBeanTest() {
        ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig.class);
        System.out.println("====================bean对象调用前后=======================");
        String[] persons = context.getBeanNamesForType(Person.class);
        for (String person : persons) {
            System.out.println(person);
        }
    }
}

image-20221118095453376

使用条件注入后

根据操作系统类型进行条件注入: 在Linux系统中注入linus, 在Windows系统中注入bill

  1. 自定义条件判断类XXXCondition实现Condition接口
  2. 满足条件返回true, 否则返回false
  3. 在@Bean上面使用@Conditional(XXXCondition.class)来使用自定义条件判断类

自定义条件判断类

package com.xiong.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

import java.util.Locale;

/**
 * 用来判断当前系统是否是Linux系统
 */
public class LinuxCondition implements Condition {
    /**
     * @param context  表示上下文环境, 可以获取需要的信息
     * @param metadata 标注了@Conditional注解的类的元数据
     * @return
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //获取环境变量
        Environment environment = context.getEnvironment();
        //获取操作系统名称
        String osType = environment.getProperty("os.name");
        return osType != null && osType.toLowerCase(Locale.ROOT).contains("linux");
    }
}
package com.xiong.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

import java.util.Locale;

public class WindowsCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //获取环境变量
        Environment environment = context.getEnvironment();
        //获取操作系统名称
        String osType = environment.getProperty("os.name");
        //获取保存所有bean对象的registry
        BeanDefinitionRegistry registry = context.getRegistry();
        boolean hasPerson = registry.containsBeanDefinition("person");

        return osType != null && osType.toLowerCase(Locale.ROOT).contains("windows");
    }
}

使用条件判断类

package com.xiong.config;

import com.xiong.bean.Person;
import com.xiong.condition.LinuxCondition;
import com.xiong.condition.WindowsCondition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Configuration
public class PersonConfig {

    @Conditional(LinuxCondition.class)
    @Bean(value = "linus")
    Person linus() {
        return new Person("linus", 50);
    }

    @Conditional(WindowsCondition.class)
    @Bean(value = "bill")
    Person bill() {
        return new Person("bill gate", 58);
    }
}

测试类

package com.xiong;


import com.xiong.bean.Person;
import com.xiong.config.PersonConfig;
import com.xiong.service.PersonService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainTest {

    @Test
    public void conditionalBeanTest() {
        ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig.class);
        System.out.println("====================bean对象调用前后=======================");
        String[] persons = context.getBeanNamesForType(Person.class);
        for (String person : persons) {
            System.out.println(person);
        }
    }
}

实验结果

image-20221118101253393

拓展学习

在使用自定义条件判断类实现Condition接口后, match()方法中有两个类型参数, 学习了解ConditionContext和AnnotatedTypeMetadata可以更好地帮助实现各种需求的条件判断类

ConditionContext 和 AnnotatedTypeMetadata类

ConditionContext 使用的实现类是 ConditionEvaluator$ConditionContextImpl(即 ConditionEvaluator 中的内部类 ConditionContextImpl)。但是由于ConditionEvaluator 是一个包级别的私有类(缺省修饰),因此通过 Class.forName() 反射调用的方式来对其进行测试。其内部包含 5 个字段:

  • BeanDefinitionRegistry

    实际上仅需要BeanDefinitionRegistry,其本身就是一个BeanFactory 或者 ApplicationContext,下面的配置在默认情况下都由BeanDefinitionRegistry导出生成。如果这里实际是一个 ApplicationContext,那么其实现了 Resource、Environment 等接口,可以直接获取其中的 Resource、Environment 等对象。但如果这里是一个 BeanFactory,同时在外部没有传值的情况下,那么就使用这些接口的一个标准的或默认的实现。

  • ConfigurableListableBeanFactory

  • Environment

  • ResourceLoader:加载类路径下的资源文件

  • ClassLoader:

AnnotatedTypeMetadata 使用的实现类是 SimpleMethodMetadata,表示方法的元数据信息。在该案例中,用于获取 @Conditional 所标注的方法的元数据。而MethodMetadata 是 AnnotatedTypeMetadata 的子接口,其中包含的信息有:

  • 方法名(methodName)
  • 返回值类型名(returnTypeName)
  • 所在的类名(declaringClassName)
  • 方法上包含的注解(annotations)
  • 是否使用final修饰、是否静态、是否重载、是否是抽象方法等标识位
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

import java.lang.reflect.Constructor;

@SpringBootTest
public class ConditionalAnnotationTest {
    // GenericWebApplicationContext
    @Autowired
    private ApplicationContext context;

    @Test
    public void conditionalAnnotationTest() throws Exception {
        // 加载内部类使用 "$"
        Class<?> clazz = Class.forName("org.springframework.context.annotation.ConditionEvaluator$ConditionContextImpl");
        Class<?> enclosingClass = clazz.getEnclosingClass();
        System.out.println(enclosingClass.getName());

        Constructor<?> constructor = clazz.getDeclaredConstructor(BeanDefinitionRegistry.class, Environment.class, ResourceLoader.class);
        constructor.setAccessible(true);
        ConditionContext conditionContext = (ConditionContext) constructor.newInstance(context, null, null);

        ResourceLoader resourceLoader = conditionContext.getResourceLoader();
        Resource resource = resourceLoader.getResource("classpath:application.yml");
        String absolutePath = resource.getFile().getAbsolutePath();
        System.out.println("absolutePath = " + absolutePath);

    }
}

   转载规则


《04-Conditional注解》 熊水斌 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
03-Scope注解 03-Scope注解
@Scope设置组件作用域总结 singleton: ioc容器创建的时候调用bean方法创建bean对象 prototype: 每次bean对象被调用的时候创建 使用 @Lazy 可以使得bean对象在被调用的时候才加载 验证proto
2023-05-13
下一篇 
09-Profile注解 09-Profile注解
@Profile 的作用在不同的环境下使用不同的组件和配置,方便环境的动态切换 使用步骤 指定组件在哪个环境下生效 在 yml 文件中指定激活的环境 生效环境 = 激活环境/默认环境 + 包含环境(include) 环境分组使用group
2023-05-13
  目录