背景
同一個(gè)接口有多種實(shí)現(xiàn),項(xiàng)目啟動(dòng)時(shí)按某種規(guī)則來(lái)選擇性的啟用其中一種實(shí)現(xiàn),再具體一點(diǎn)怪嫌,比如Controller初始化的時(shí)候,根據(jù)配置文件的指定的實(shí)現(xiàn)類(lèi)前綴柳沙,來(lái)記載具體Service岩灭,不同Service使用不同的Dao和數(shù)據(jù)庫(kù)。
看到這里赂鲤,我們會(huì)想到使用SPI機(jī)制噪径,或Spring按條件加載Bean機(jī)制來(lái)實(shí)現(xiàn),下面主要討論后者数初。
定義接口
定義2個(gè)Service層接口:OrderService熄云、OrderPromotionService,分別有一個(gè)方法妙真,如下:
// OrderService.java
public interface OrderService {
/**
* 通過(guò)tid查詢(xún)訂單信息
* @param tid 訂單主鍵
*/
List<Order> findByTid(Long tid);
}
...
// OrderPromotionService.java
public interface OrderPromotionService {
/**
* 通過(guò)tid獲取促銷(xiāo)詳情.
* @param tid 訂單唯一標(biāo)識(shí)
*/
List<OrderPromotion> findByTid(Long tid);
}
默認(rèn)實(shí)現(xiàn)
分別實(shí)現(xiàn)上面2個(gè)接口的各自的方法缴允,包路徑:com.a.b。
為達(dá)到根據(jù)規(guī)則裝載不同ServiceImpl的目的珍德,需要使用@Conditional注解练般,并且實(shí)現(xiàn)規(guī)則定義DefaultCondition。當(dāng)Spring掃描到@Service注解時(shí)锈候,會(huì)判斷DefaultCondition#matches()方法薄料,決定是否裝載該ServiceImpl。
DefaultCondition實(shí)現(xiàn)如下:
// 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實(shí)現(xiàn)了Spring的Condition接口泵琳,先通過(guò)方法ParentCondition#getInterFaceName()獲取ServiceImpl實(shí)現(xiàn)的接口名稱(chēng)interfaceName摄职,然后從配置文件中獲取該接口指定的實(shí)現(xiàn)類(lèi)的前綴implPrefix,然后判斷是否為Constant.DEFAULT_PREFIX获列,如果是谷市,則裝載該ServiceImpl。
獲取接口名稱(chēng)的實(shí)現(xiàn)定義在ParentCondition類(lèi)中(是自己實(shí)現(xiàn)的)击孩,是這里需要繼承ParentCondition的原因迫悠。
- Service層實(shí)現(xiàn)
// 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);
}
}
...
/**
* 默認(rèn)實(shí)現(xiàn).
*/
@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層實(shí)現(xiàn)
// 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層使用的是自己的數(shù)據(jù)源,代碼注入的是firstJdbcTemplate巩梢,Spring-Boot多數(shù)據(jù)源配置不是今天討論的重點(diǎn)创泄,這里不再詳細(xì)說(shuō)明。
第三方實(shí)現(xiàn)
這里假設(shè)上面的2個(gè)Service接口還有一種第三方的實(shí)現(xiàn)括蝠,包路徑:com.c.d鞠抑。跟默認(rèn)實(shí)現(xiàn)類(lèi)似,這里也要定義自己的ServiceImpl裝載規(guī)則ThirdPartyCondition忌警,實(shí)現(xiàn)如下:
// 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實(shí)現(xiàn)的接口名稱(chēng)interfaceName搁拙,然后從配置文件中獲取該接口指定的實(shí)現(xiàn)類(lèi)的前綴implPrefix,然后判斷是否為Constant.THIRD_PARTY_PREFIX,如果是感混,則裝載該ServiceImpl端幼。
- Service層實(shí)現(xiàn)
// 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層實(shí)現(xiàn)
省略,Dao層實(shí)現(xiàn)使用另一個(gè)數(shù)據(jù)源弧满,注入secondJdbcTemplate婆跑。
以上分別為OrderService和OrderPromotionService提供了兩種實(shí)現(xiàn),如下:
Service接口 | Service默認(rèn)實(shí)現(xiàn) | Service第三方實(shí)現(xiàn) |
---|---|---|
OrderService | DefaultOrderServiceImp 使用Dao層OrderDao庭呜,數(shù)據(jù)源firstJdbcTemplate |
ThirdPartyOrderServiceImpl 使用Dao層ThirdPartyOrderDao滑进,數(shù)據(jù)源secondJdbcTemplate |
OrderPromotionService | DefaultOrderPromotionServiceImpl 使用Dao層DefaultOrderPromotionServiceImpl,數(shù)據(jù)源firstJdbcTemplate |
ThirdPartyOrderPromotionServiceImpl 使用Dao層ThirdPartyOrderPromotionDao募谎,數(shù)據(jù)源secondJdbcTemplate |
配置規(guī)則
ServiceImpl定義完成扶关,裝載規(guī)則也定義了,下面我們?cè)赟pring-Boot中分別指定兩個(gè)類(lèi)的加載對(duì)象
// application.properties
com.free.spring.jdbc.demo.service.OrderPromotionService=ThirdParty
com.free.spring.jdbc.demo.service.OrderService=Default
如上数冬,定義了OrderPromotionService接口使用第三方的實(shí)現(xiàn)节槐,OrderService接口使用默認(rèn)實(shí)現(xiàn)
下面簡(jiǎn)單的寫(xiě)兩個(gè)Controller看一下效果。
運(yùn)行效果
1. 數(shù)據(jù)準(zhǔn)備
為first庫(kù)的2張表分別添加1條數(shù)據(jù)拐纱,如下:
為second庫(kù)的2張表添加數(shù)據(jù)
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;
}
}
我們通過(guò)Controller分別請(qǐng)求OrderService和OrderPromotionService铜异,通過(guò)返回的數(shù)據(jù)判斷是否真的實(shí)現(xiàn)了選擇性裝載ServiceImpl。
然后我們分別請(qǐng)求上面兩個(gè)接口秸架,觀(guān)察結(jié)果:
OK揍庄!沒(méi)問(wèn)題!
如果大家有更簡(jiǎn)單的方式东抹,歡迎探討~
Github源碼:https://github.com/nmyphp/spring-cloud-demo (spring-jdbc-demo模塊)