使用@Conditional注解根据配置文件注入不同的对象

 

背景:

项目中使用了datahub,kafka两种消息队列,为了避免硬编码,应该根据配置文件来决定使用哪种消息队列,以及初始化哪个对象。

为了简洁,我只写了简单代码来表明实现逻辑。

定义一个IBase的接口,声明一个send方法,然后datahub,kafka去实现:

public interface IBase {
    void send();
}
@Component
public class Config {
    public void init(String param){
        System.out.println("初始化:" + param);
    }
}

 

@Component
public class Datahub implements IBase{
    @Autowired
    Config config;

    @Override
    public void send() {
        System.out.println("datahub send");
        config.init("Datahub");
    }
}
@Component
public class Kafka implements IBase{
    @Autowired
    Config config;

    @Override
    public void send() {
        System.out.println("kafka send");
        config.init("kafka");
    }
}

application.properties:

mq.type=kafka
 

① 起初我的想法是,创建一个工具类,然后根据配置文件来返回具体的IBase实现类,如下:

@Component
public class Util {
    @Value("${mq.type:kafka}")   //mq.type在application.properties里定义,":kafka" 设置默认值为kafka
    private String type;

    public IBase get(){
        if (type.equals("kafka"))
            return new Kafka();
        else
            return new Datahub();
    }

}

验证①:

@SpringBootTest
@RunWith(SpringRunner.class)
public class test01 {
    @Autowired
    Util util;

    @Test
    public void valiate(){
        IBase iBase = util.get();
        iBase.send();
    }

}

实际运行结果:

使用@Conditional注解根据配置文件注入不同的对象_第1张图片

很明显,报错, config为空。

原因:我们在Util.get()方法中,自己new Kafka()返回的,注意Kafka中有一个属性是config通过@Autowired注解的,因为我们手动创建对象,并没有被Spring框架管理,故Spring不会自动注入Config对象,因此config对象为空。

Ok,尝试第二种方法。

②内心活动:既然不能手动new,那么就让Spring注入,因此更改Util类如下:

@Component
public class Util {
    @Value("${mq.type:kafka}")   //mq.type在application.properties里定义,":kafka" 设置默认值为kafka
    private String type;
    @Autowired
    Datahub datahub;
    @Autowired
    Kafka kafka;

    public IBase get(){
        if (type.equals("kafka"))
            return kafka;
        else
            return datahub;
    }
}

ok,测试代码:

@SpringBootTest
@RunWith(SpringRunner.class)
public class test01 {
    @Autowired
    Util util;

    @Test
    public void valiate(){
        IBase iBase = util.get();
        iBase.send();
    }

}

然后我们运行一波:

使用@Conditional注解根据配置文件注入不同的对象_第2张图片

emmm,并没有什么问题。

但是注意:这中办法的思路是,现在Util中使用提前使用Spring来注入Datahub和Kafka(因为由Spring注入,所以Spring也会管理类里面的相关注解注册的对象,故Datahub,Kafka里面的config也由Spring来装配,不会为null),然后根据type来返回具体的对象。但是:不管你的application.properties里配置的是datahub还是kafka,Util总会为你注入两个对象,以及他们类里面各自依赖的其他对象如Config,这就会带来两个问题。

  1. 因为不管配置哪一种,都会创建datahub和kafka,这就浪费了不必要的内存,内存浪费过多可能会带来outOfMemory问题
  2. 假如你的配置文件中是kafka,但是Util会注入Datahub对象,如果datahub里面有很多初始化的方法,但是你并没有相关的属性配置它,因此注入datahub时可能会报错

故第二种方法虽然可以实现功能,但也有纰漏之处。

emmm那么试试第三种方法吧,也就是这篇文章的主角:

③使用@Conditional注解来确定到底装配哪个对象

为Datahub加上@Conditional注解:

@Component
@Conditional(DatahubCondition.class)
public class Datahub implements IBase{
    @Autowired
    Config config;

    @Override
    public void send() {
        System.out.println("datahub send");
        config.init("Datahub");
    }
}
public class DatahubCondition implements Condition {

    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        Environment environment = conditionContext.getEnvironment();
       String type = environment.getProperty("mq.type");
        if (type.contains("datahub")){
            return true;
        }
        return false;
    }
}

Datahub使用@Conditional注解,需要传入一个Condition的实现类,并实现其matchs方法,当方法返回true则注入Datahub,否则不注入。代码中通过context取得运行环境,然后获得mq.type定义的属性.

为Kafka加上@Conditional注解:

@Component
@Conditional(KafkaCondition.class)
public class Kafka implements IBase{
    @Autowired
    Config config;

    @Override
    public void send() {
        System.out.println("kafka send");
        config.init("kafka");
    }
}
public class KafkaCondition implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        Environment environment = conditionContext.getEnvironment();
       String type = environment.getProperty("mq.type");
        if(type.contains("kafka")){
            return true;
        }
        return false;
    }
}

 

现在我们改一下test代码,已经不需要Util了,直接使用Spring的自动装配。

@SpringBootTest
@RunWith(SpringRunner.class)
public class test01 {

   @Autowired
   IBase iBase;

    @Test
    public void valiate(){
        iBase.send();
    }
}

因为application.properties配置的mq.type=kafka,所有DatahubCondition的matchs方法返回false,因此Datahub不自动装配到Spring容器;此时KafkaCondition的matchs方法返回true,启动时Kafka会被Spring容器加载。对IBase进行@Autowired注解,Spring会在已加载的容器里寻找合适的bean(IBase的实现类)去注入,因为此时只有Kafka被加载到容器,所以能够注入Kafka对象。

验证一下:

使用@Conditional注解根据配置文件注入不同的对象_第3张图片

可以看到,和预想一致。此方案也不会出现造成空间浪费和初始化未配置的对象。因此是这三种方法里最合适的。

over。 

你可能感兴趣的:(框架,java碎碎片,@Conditional,自动注入)