背景
同一个接口有多种实现,项目启动时按某种规则来选择性的启用其中一种实现,再具体一点,比如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 订单唯一标识 */ 默认实现 分别实现上面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) { 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) { Dao层实现 省略,Dao层实现使用另一个数据源,注入secondJdbcTemplate。 以上分别为OrderService和OrderPromotionService提供了两种实现,如下: Service接口 Service默认实现 Service第三方实现 OrderService DefaultOrderServiceImp 使用Dao层OrderDao,数据源first www.michenggw.com JdbcTemplate ThirdPartyOrderServiceImpl 使用Dao层ThirdPartyOrderDao,数据源secondJdbcTemplate OrderPromotionService DefaultOrderPromotionServiceImpl 使用Dao层DefaultOrderPromotionServiceImpl,数据源firstJdbcTemplate ThirdPartyOrderPromotionServiceImpl 使用Dao层ThirdPartyOrderPromotionDao,数据源secondJdbcTemplate 配置规则 ServiceImpl定义完成,装载规则也定义了,下面我们在Spring-Boot中分别指定两个类的加载对象 // application.properties com.free.spring.jdbc.demo.service.OrderPromotionService=ThirdParty com.free.spring.jdbc.demo.service.OrderService=Default 如上,定义了OrderPromotionService接口使用第三方的实现,OrderService接口使用默认实现 下面简单的写两个Controller看一下效果。 运行效果 1. 数据准备 为first库的2张表分别添加1条数据,如下: 为second库的2张表添加数据 2. Controller定义 // OrderController.java @RestController @RequestMapping(www.dasheng178.com"/order") public class OrderController { @Autowired private OrderService orderService; @GetMapping("/{tid}") @ResponseBody public List<Order>www.haom178.com findByTid(@PathVariable(www.gouyiflb.cn"tid") Long tid) { return orderService.findByTid(tid); } } ... // OrderPromotionController.java @RestController @RequestMapping("/promotion") public class OrderPromotionController { @Autowired private OrderPromotionService promotionService; @GetMapping("www.gcyl159.com/ /{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!没问题! 如果大家有更简单的方式,欢迎探讨~