项目中使用了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();
}
}
实际运行结果:
很明显,报错, 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();
}
}
然后我们运行一波:
emmm,并没有什么问题。
但是注意:这中办法的思路是,现在Util中使用提前使用Spring来注入Datahub和Kafka(因为由Spring注入,所以Spring也会管理类里面的相关注解注册的对象,故Datahub,Kafka里面的config也由Spring来装配,不会为null),然后根据type来返回具体的对象。但是:不管你的application.properties里配置的是datahub还是kafka,Util总会为你注入两个对象,以及他们类里面各自依赖的其他对象如Config,这就会带来两个问题。
故第二种方法虽然可以实现功能,但也有纰漏之处。
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对象。
验证一下:
可以看到,和预想一致。此方案也不会出现造成空间浪费和初始化未配置的对象。因此是这三种方法里最合适的。
over。