使用Spring特性實(shí)現(xiàn)接口多實(shí)現(xiàn)類的動態(tài)調(diào)用方式
正好用到。mark一下背景
org.springframework.beans及org.springframework.context這兩個包是Spring IoC容器的基礎(chǔ),其中重要的類有BeanFactory,BeanFactory是IoC容器的核心接口,其職責(zé)包括:實(shí)例化、定位、配置應(yīng)用程序中的對象及建立這些對象間的依賴關(guān)系。
ApplicationContext作為BeanFactory的子類,在Bean管理的功能上得到了很大的增強(qiáng),也更易于與Spring AOP集成使用。
今天我們要討論的并不是BeanFactory或者ApplicationContext的實(shí)現(xiàn)原理,而是對ApplicationContext的一種實(shí)際應(yīng)用方式。
問題的提出
在實(shí)際工作中,我們經(jīng)常會遇到一個接口及多個實(shí)現(xiàn)類的情況,并且在不同的條件下會使用不同的實(shí)現(xiàn)類。
從使用方式上看,有些類似SPI的用法,但是由于SPI的使用并不是太方便,那么怎么辦呢?我們可以借助ApplicationContext的getBeansOfType來實(shí)現(xiàn)我們需要的結(jié)果。
首先我們看一下這個方法的簽名
<T> Map<String, T> getBeansOfType(Class<T> type) throws BeansException;
從上面的代碼上我們可以看出來這個方法能返回一個接口的全部實(shí)現(xiàn)類(前提是所有實(shí)現(xiàn)類都必須由Spring IoC容器管理)。
接下來看看我們遇到的問題是什么?
"假設(shè)從A點(diǎn)到B點(diǎn)有多種交通方式,每種交通方式的費(fèi)用不同,可以根據(jù)乘客的需要進(jìn)行選擇"(好吧,我承認(rèn)這是個非常蹩腳的需求,但是可以聯(lián)想一下類似的需求,比如支付方式、快遞公司,在頁面提供幾個選項(xiàng),業(yè)務(wù)代碼根據(jù)選項(xiàng)的不同選擇不同的實(shí)現(xiàn)類實(shí)例進(jìn)行調(diào)用)。
實(shí)現(xiàn)
回到我們的例子,按照這個交通方式的需求,我們的設(shè)計(jì)如下:有一個交通方式的接口,接口有兩個方式,一個查詢費(fèi)用、一個查詢該交通方式的類型,同時(shí),我們可以用一個枚舉類型類標(biāo)識交通類型。
我們還需要一個工廠類來根據(jù)交通類型標(biāo)識查找該交通類型的Bean實(shí)例,從而使用該實(shí)例,獲得交通類型的詳細(xì)信息及該交通類型的操作。

代碼如下
接口:
/**
* 交通方式
*/
public interface TrafficMode {
/**
* 查詢交通方式編碼
* @return 編碼
*/
TrafficCode getCode();
/**
* 查詢交通方式的費(fèi)用,單位:分
* @return 費(fèi)用
*/
Integer getFee();
}枚舉:
/**
* 交通類型枚舉
*/
public enum TrafficCode {
TRAIN,
BUS
}接口有兩個實(shí)現(xiàn)類:
/**
* 汽車方式
*/
@Component
public class BusMode implements TrafficMode {
@Override
public TrafficCode getCode() {
return TrafficCode.BUS;
}
@Override
public Integer getFee() {
return 10000;
}
}/**
* 火車方式
*/
@Component
public class TrainMode implements TrafficMode {
@Override
public TrafficCode getCode() {
return TrafficCode.TRAIN;
}
@Override
public Integer getFee() {
return 9000;
}
}工廠類:
/**
* 交通方式工廠類
*/
@Component
public class TrafficModeFactory implements ApplicationContextAware {
private static Map<TrafficCode, TrafficMode> trafficBeanMap;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, TrafficMode> map = applicationContext.getBeansOfType(TrafficMode.class);
trafficBeanMap = new HashMap<>();
map.forEach((key, value) -> trafficBeanMap.put(value.getCode(), value));
}
public static <T extends TrafficMode> T getTrafficMode(TrafficCode code) {
return (T)trafficBeanMap.get(code);
}
}驗(yàn)證
有了上面的代碼之后,我們一起通過單元測試來看一下效果,單元測試代碼片段如下:
@Test
public void testGetTrafficMode() {
TrafficMode mode = TrafficModeFactory.getTrafficMode(TrafficCode.BUS);
Assert.assertEquals(mode.getFee().intValue(), 10000);
mode = TrafficModeFactory.getTrafficMode(TrafficCode.TRAIN);
Assert.assertEquals(mode.getFee().intValue(), 9000);
}運(yùn)行之后的結(jié)果呢?必然是通過。
關(guān)于SPI
文章到這里,有同學(xué)可能會問:這和SPI有什么區(qū)別呢?SPI同樣也能實(shí)現(xiàn)同樣的功能啊。首先說明一下,SPI是JDK自帶的功能,雖然歷史是比較久遠(yuǎn)的了,但是不代表它不好,而且有些場景非SPI不可(比如JDBC)。
我們明確一下SPI是什么以及它的設(shè)計(jì)是用來做什么的。SPI的全名為Service Provider Interface(服務(wù)提供接口),因?yàn)檫@個是針對廠商或者插件的。比較經(jīng)典的用法就是JDBC,java提供了標(biāo)準(zhǔn)的JDBC接口,每個數(shù)據(jù)庫廠商提供自己的數(shù)據(jù)庫驅(qū)動實(shí)現(xiàn)。
下面是JDBC驅(qū)動的接口
public interface Driver {
Connection connect(String var1, Properties var2) throws SQLException;
boolean acceptsURL(String var1) throws SQLException;
DriverPropertyInfo[] getPropertyInfo(String var1, Properties var2) throws SQLException;
int getMajorVersion();
int getMinorVersion();
boolean jdbcCompliant();
Logger getParentLogger() throws SQLFeatureNotSupportedException;
}下面是MySQL的JDBC驅(qū)動實(shí)現(xiàn)的SPI配置

關(guān)于SPI我們點(diǎn)到為止,這里只是要說明SPI和我們前面例子中使用的AP具有不同的適用場景。
在前面的例子里,我們用的是什么呢?Spring的API,API的全稱為Application Programming Interface(應(yīng)用程序編程接口),在一個應(yīng)用內(nèi)部,使用API是非常便捷的方式,也是最直接的方式。
總結(jié)一下,就是編程中,我們使用API是最多的。當(dāng)然我們也會使用SPI(雖然我們不是嚴(yán)格意義上的廠商或者插件),使用SPI也是在特定場景下為了解決問題的一種途徑。
關(guān)于SPI更多的內(nèi)容,以后在慢慢介紹吧。
【附】關(guān)于SPI的約定:當(dāng)服務(wù)的提供者,提供了服務(wù)接口的一種實(shí)現(xiàn)之后,在jar包的META-INF/services/目錄里同時(shí)創(chuàng)建一個以服務(wù)接口命名的文件。
該文件里就是實(shí)現(xiàn)該服務(wù)接口的具體實(shí)現(xiàn)類。而當(dāng)外部程序裝配這個模塊的時(shí)候,就能通過該jar包META-INF/services/里的配置文件找到具體的實(shí)現(xiàn)類名,并裝載實(shí)例化,完成模塊的注入。
基于這樣一個約定就能很好的找到服務(wù)接口的實(shí)現(xiàn)類,而不需要再代碼里制定。
jdk提供服務(wù)實(shí)現(xiàn)查找的一個工具類:java.util.ServiceLoader,通過其load方法,傳入接口便能獲得其實(shí)現(xiàn)類。
以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
MyBatis-Plus實(shí)現(xiàn)字段自動填充功能的示例
本文主要介紹了MyBatis-Plus實(shí)現(xiàn)字段自動填充功能的示例,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11
Java中增強(qiáng)for循環(huán)在一維數(shù)組和二維數(shù)組中的使用方法
下面小編就為大家?guī)硪黄狫ava中增強(qiáng)for循環(huán)在一維數(shù)組和二維數(shù)組中的使用方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-10-10
Java詳解HashMap實(shí)現(xiàn)原理和源碼分析
這篇文章主要介紹了Java關(guān)于HashMap的實(shí)現(xiàn)原理并進(jìn)行源碼分析,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-09-09
MyBatis使用resultMap如何解決列名和屬性名不一致
這篇文章主要介紹了MyBatis使用resultMap如何解決列名和屬性名不一致的問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01
SpringBoot中使用@scheduled定時(shí)執(zhí)行任務(wù)的坑
本文主要介紹了SpringBoot中使用@scheduled定時(shí)執(zhí)行任務(wù)的坑,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05

