Spring-Boot基于配置按条件装载Bean

背景

同一个接口有多种实现,项目启动时按某种规则来选择性的启用其中一种实现,再具体一点,比如Controller初始化的时候,根据配置文件的指定的实现类前缀,来加载具体Service,不同Service使用不同的Dao和数据库。
在这里插入图片描述
看到这里,我们会想到使用SPI机制,或Spring按条件加载Bean机制来实现,下面主要讨论后者。

定义接口

定义2个Service层接口:OrderService、OrderPromotionService,分别有一个方法,如下:

// OrderService.java

public interface OrderService {

    /**
     * 通过tid查询订单信息
     * @param tid 订单主键
     */
    List<Order> findByTid(Long tid);
}

...

// OrderPromotionService.java

public interface OrderPromotionService {

    /**
     * 通过tid获取促销详情.
     * @param tid 订单唯一标识
     */
    List<OrderPromotion> findByTid(Long tid);
}

默认实现

分别实现上面2个接口的各自的方法,包路径:com.a.b

为达到根据规则装载不同ServiceImpl的目的,需要使用@Conditional注解,并且实现规则定义DefaultCondition。当Spring扫描到@Service注解时,会判断DefaultCondition#matches()方法,决定是否装载该ServiceImpl。

DefaultCondition实现如下:

// DefaultCondition.java

public class DefaultCondition extends ParentCondition implements Condition  {

    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        Environment environment = conditionContext.getEnvironment();

        String interfaceName = getInterFaceName(annotatedTypeMetadata);
        String implPrefix = environment.getProperty(interfaceName);
        if (StringUtils.isEmpty(implPrefix)) {
            return true;
        }
        return Constant.DEFAULT_PREFIX.equals(implPrefix);
    }
}

...

// Constant.java

public class Constant {
    public static final String THIRD_PARTY_PREFIX = "ThirdParty";
    public static final String DEFAULT_PREFIX = "Default";

}

DefaultCondition实现了Spring的Condition接口,先通过方法ParentCondition#getInterFaceName()获取ServiceImpl实现的接口名称interfaceName,然后从配置文件中获取该接口指定的实现类的前缀implPrefix,然后判断是否为Constant.DEFAULT_PREFIX,如果是,则装载该ServiceImpl。

获取接口名称的实现定义在ParentCondition类中(是自己实现的),是这里需要继承ParentCondition的原因。

  • Service层实现
// DefaultOrderServiceImpl.java

@Conditional(DefaultCondition.class)
@Service
public class DefaultOrderServiceImpl implements OrderService {

    @Autowired
    private OrderDao orderDao;

    @Override
    public List<Order> findByTid(Long tid) {
        return orderDao.findByTid(tid);
    }
}

...

/**
 * 默认实现.
 */
@Conditional(DefaultCondition.class)
@Service
public class DefaultOrderPromotionServiceImpl implements OrderPromotionService {

    @Autowired
    private OrderPromotionDao promotionDao;

    @Override
    public List<OrderPromotion> findByTid(Long tid) {
        return promotionDao.findByTid(tid);
    }
}
  • Dao层实现
// OrderDao.java

@Repository
public class OrderDao {

    @Autowired
    @Qualifier("firstJdbcTemplate")
    private JdbcTemplate jdbcTemplate;

    public List<Order> findByTid(Long tid) {
       // 省略...
    }
}

...

@Repository
public class OrderPromotionDao {

    @Autowired
    @Qualifier("firstJdbcTemplate")
    private JdbcTemplate jdbcTemplate;

    public List<OrderPromotion> findByTid(final Long tid) {
       // 省略...
    }
}

Dao层使用的是自己的数据源,代码注入的是firstJdbcTemplate,Spring-Boot多数据源配置不是今天讨论的重点,这里不再详细说明。

第三方实现

这里假设上面的2个Service接口还有一种第三方的实现,包路径:com.c.d。跟默认实现类似,这里也要定义自己的ServiceImpl装载规则ThirdPartyCondition,实现如下:

// ThirdPartyOrderServiceImpl.java

public class ThirdPartyCondition extends ParentCondition implements Condition {

    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        Environment environment = conditionContext.getEnvironment();

        String interfaceName = getInterFaceName(annotatedTypeMetadata);
        String implPrefix = environment.getProperty(interfaceName);
        return Constant.THIRD_PARTY_PREFIX.equals(implPrefix);
    }
}

先获取ServiceImpl实现的接口名称interfaceName,然后从配置文件中获取该接口指定的实现类的前缀implPrefix,然后判断是否为Constant.THIRD_PARTY_PREFIX,如果是,则装载该ServiceImpl。

  • Service层实现
// ThirdPartyOrderServiceImpl.java

@Conditional({ThirdPartyCondition.class})
@Service
public class ThirdPartyOrderServiceImpl implements OrderService {

    @Autowired
    private ThirdPartyOrderDao thirdPartyOrderDao;

    @Override
    public List<Order> findByTid(Long tid) {
        return thirdPartyOrderDao.findByTid(tid);
    }
}

...

//ThirdPartyOrderPromotionServiceImpl.java

@Conditional(ThirdPartyCondition.class)
@Service
public class ThirdPartyOrderPromotionServiceImpl implements OrderPromotionService {

    @Autowired
    private ThirdPartyOrderPromotionDao thirdPartyOrderPromotionDao;

    @Override
    public List<OrderPromotion> findByTid(Long tid) {
        return thirdPartyOrderPromotionDao.findByTid(tid);
    }
}
  • Dao层实现

省略,Dao层实现使用另一个数据源,注入secondJdbcTemplate。

以上分别为OrderService和OrderPromotionService提供了两种实现,如下:

Service接口 Service默认实现 Service第三方实现
OrderService DefaultOrderServiceImp
使用Dao层OrderDao,数据源firstJdbcTemplate
ThirdPartyOrderServiceImpl
使用Dao层ThirdPartyOrderDao,数据源secondJdbcTemplate
OrderPromotionService DefaultOrderPromotionServiceImpl
使用Dao层DefaultOrderPromotionServiceImpl,数据源firstJdbcTemplate
ThirdPartyOrderPromotionServiceImpl
使用Dao层ThirdPartyOrderPromotionDao,数据源secondJdbcTemplate

配置规则

ServiceImpl定义完成,装载规则也定义了,下面我们在Spring-Boot中分别指定两个类的加载对象

// application.properties

com.a.b.OrderPromotionService=ThirdParty
com.a.b.OrderService=Default

如上,定义了OrderPromotionService接口使用第三方的实现,OrderService接口使用默认实现
下面简单的写两个Controller看一下效果。

运行效果

1. 数据准备

为first库的2张表分别添加1条数据,如下:

在这里插入图片描述

为second库的2张表添加数据

在这里插入图片描述

2. Controller定义

// OrderController.java

@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private OrderService orderService;

    @GetMapping("/{tid}")
    @ResponseBody
    public List<Order> findByTid(@PathVariable("tid") Long tid) {
        return orderService.findByTid(tid);
    }
}

...

// OrderPromotionController.java

@RestController
@RequestMapping("/promotion")
public class OrderPromotionController {

    @Autowired
    private OrderPromotionService promotionService;

    @GetMapping("/{tid}")
    @ResponseBody
    public List<OrderPromotion> query(@PathVariable("tid") Long tid) {
        try {
            return promotionService.findByTid(tid);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

我们通过Controller分别请求OrderService和OrderPromotionService,通过返回的数据判断是否真的实现了选择性装载ServiceImpl。

然后我们分别请求上面两个接口,观察结果:

在这里插入图片描述

OK!没问题!
如果大家有更简单的方式,欢迎探讨~
Github源码:https://github.com/nmyphp/spring-cloud-demo (spring-jdbc-demo模块)

发布了36 篇原创文章 · 获赞 23 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/Cmainlove/article/details/85797553