使用Spring特性實(shí)現(xiàn)接口多實(shí)現(xiàn)類的動(dòng)態(tài)調(diào)用方式
正好用到。mark一下背景
org.springframework.beans及org.springframework.context這兩個(gè)包是Spring IoC容器的基礎(chǔ),其中重要的類有BeanFactory,BeanFactory是IoC容器的核心接口,其職責(zé)包括:實(shí)例化、定位、配置應(yīng)用程序中的對(duì)象及建立這些對(duì)象間的依賴關(guān)系。
ApplicationContext作為BeanFactory的子類,在Bean管理的功能上得到了很大的增強(qiáng),也更易于與Spring AOP集成使用。
今天我們要討論的并不是BeanFactory或者ApplicationContext的實(shí)現(xiàn)原理,而是對(duì)ApplicationContext的一種實(shí)際應(yīng)用方式。
問(wèn)題的提出
在實(shí)際工作中,我們經(jīng)常會(huì)遇到一個(gè)接口及多個(gè)實(shí)現(xiàn)類的情況,并且在不同的條件下會(huì)使用不同的實(shí)現(xiàn)類。
從使用方式上看,有些類似SPI的用法,但是由于SPI的使用并不是太方便,那么怎么辦呢?我們可以借助ApplicationContext的getBeansOfType來(lái)實(shí)現(xiàn)我們需要的結(jié)果。
首先我們看一下這個(gè)方法的簽名
<T> Map<String, T> getBeansOfType(Class<T> type) throws BeansException;
從上面的代碼上我們可以看出來(lái)這個(gè)方法能返回一個(gè)接口的全部實(shí)現(xiàn)類(前提是所有實(shí)現(xiàn)類都必須由Spring IoC容器管理)。
接下來(lái)看看我們遇到的問(wèn)題是什么?
"假設(shè)從A點(diǎn)到B點(diǎn)有多種交通方式,每種交通方式的費(fèi)用不同,可以根據(jù)乘客的需要進(jìn)行選擇"(好吧,我承認(rèn)這是個(gè)非常蹩腳的需求,但是可以聯(lián)想一下類似的需求,比如支付方式、快遞公司,在頁(yè)面提供幾個(gè)選項(xiàng),業(yè)務(wù)代碼根據(jù)選項(xiàng)的不同選擇不同的實(shí)現(xiàn)類實(shí)例進(jìn)行調(diào)用)。
實(shí)現(xiàn)
回到我們的例子,按照這個(gè)交通方式的需求,我們的設(shè)計(jì)如下:有一個(gè)交通方式的接口,接口有兩個(gè)方式,一個(gè)查詢費(fèi)用、一個(gè)查詢?cè)摻煌ǚ绞降念愋?,同時(shí),我們可以用一個(gè)枚舉類型類標(biāo)識(shí)交通類型。
我們還需要一個(gè)工廠類來(lái)根據(jù)交通類型標(biāo)識(shí)查找該交通類型的Bean實(shí)例,從而使用該實(shí)例,獲得交通類型的詳細(xì)信息及該交通類型的操作。
代碼如下
接口:
/** * 交通方式 */ public interface TrafficMode { /** * 查詢交通方式編碼 * @return 編碼 */ TrafficCode getCode(); /** * 查詢交通方式的費(fèi)用,單位:分 * @return 費(fèi)用 */ Integer getFee(); }
枚舉:
/** * 交通類型枚舉 */ public enum TrafficCode { TRAIN, BUS }
接口有兩個(gè)實(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)證
有了上面的代碼之后,我們一起通過(guò)單元測(cè)試來(lái)看一下效果,單元測(cè)試代碼片段如下:
@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ò)。
關(guān)于SPI
文章到這里,有同學(xué)可能會(huì)問(wèn):這和SPI有什么區(qū)別呢?SPI同樣也能實(shí)現(xiàn)同樣的功能啊。首先說(shuō)明一下,SPI是JDK自帶的功能,雖然歷史是比較久遠(yuǎn)的了,但是不代表它不好,而且有些場(chǎng)景非SPI不可(比如JDBC)。
我們明確一下SPI是什么以及它的設(shè)計(jì)是用來(lái)做什么的。SPI的全名為Service Provider Interface(服務(wù)提供接口),因?yàn)檫@個(gè)是針對(duì)廠商或者插件的。比較經(jīng)典的用法就是JDBC,java提供了標(biāo)準(zhǔn)的JDBC接口,每個(gè)數(shù)據(jù)庫(kù)廠商提供自己的數(shù)據(jù)庫(kù)驅(qū)動(dòng)實(shí)現(xiàn)。
下面是JDBC驅(qū)動(dòng)的接口
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ū)動(dòng)實(shí)現(xiàn)的SPI配置
關(guān)于SPI我們點(diǎn)到為止,這里只是要說(shuō)明SPI和我們前面例子中使用的AP具有不同的適用場(chǎng)景。
在前面的例子里,我們用的是什么呢?Spring的API,API的全稱為Application Programming Interface(應(yīng)用程序編程接口),在一個(gè)應(yīng)用內(nèi)部,使用API是非常便捷的方式,也是最直接的方式。
總結(jié)一下,就是編程中,我們使用API是最多的。當(dāng)然我們也會(huì)使用SPI(雖然我們不是嚴(yán)格意義上的廠商或者插件),使用SPI也是在特定場(chǎng)景下為了解決問(wèn)題的一種途徑。
關(guān)于SPI更多的內(nèi)容,以后在慢慢介紹吧。
【附】關(guān)于SPI的約定:當(dāng)服務(wù)的提供者,提供了服務(wù)接口的一種實(shí)現(xiàn)之后,在jar包的META-INF/services/目錄里同時(shí)創(chuàng)建一個(gè)以服務(wù)接口命名的文件。
該文件里就是實(shí)現(xiàn)該服務(wù)接口的具體實(shí)現(xiàn)類。而當(dāng)外部程序裝配這個(gè)模塊的時(shí)候,就能通過(guò)該jar包META-INF/services/里的配置文件找到具體的實(shí)現(xiàn)類名,并裝載實(shí)例化,完成模塊的注入。
基于這樣一個(gè)約定就能很好的找到服務(wù)接口的實(shí)現(xiàn)類,而不需要再代碼里制定。
jdk提供服務(wù)實(shí)現(xiàn)查找的一個(gè)工具類:java.util.ServiceLoader,通過(guò)其load方法,傳入接口便能獲得其實(shí)現(xiàn)類。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
詳解JavaWeb過(guò)濾器 Filter問(wèn)題解決
過(guò)濾器就是對(duì)事物進(jìn)行過(guò)濾的,在Web中的過(guò)濾器,當(dāng)然就是對(duì)請(qǐng)求進(jìn)行過(guò)濾,我們使用過(guò)濾器,就可以對(duì)請(qǐng)求進(jìn)行攔截,然后做相應(yīng)的處理,實(shí)現(xiàn)許多特殊功能,今天主要給大家講解JavaWeb過(guò)濾器 Filter問(wèn)題解決,感興趣的朋友一起看看吧2022-10-10JAVA利用接口實(shí)現(xiàn)多繼承問(wèn)題的代碼實(shí)操演示
Java語(yǔ)言并不支持多繼承,這是由于多繼承會(huì)帶來(lái)許多復(fù)雜的問(wèn)題,例如"菱形問(wèn)題"等,下面這篇文章主要給大家介紹了關(guān)于JAVA利用接口實(shí)現(xiàn)多繼承問(wèn)題的相關(guān)資料,需要的朋友可以參考下2024-03-03Java Web基于Session的登錄實(shí)現(xiàn)方法
這篇文章主要介紹了Java Web基于Session的登錄實(shí)現(xiàn)方法,涉及Java針對(duì)session的操作及表單提交與驗(yàn)證技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10Java設(shè)計(jì)模式之抽象工廠模式簡(jiǎn)析
這篇文章主要介紹了Java設(shè)計(jì)模式之抽象工廠模式簡(jiǎn)析, 抽象工廠模式是工廠方法模式的升級(jí)版本,他用來(lái)創(chuàng)建一組相關(guān)或者相互依賴的對(duì)象,他與工廠方法模式的區(qū)別就在于,工廠方法模式針對(duì)的是一個(gè)產(chǎn)品等級(jí)結(jié)構(gòu),需要的朋友可以參考下2023-12-12Java 并發(fā)編程學(xué)習(xí)筆記之核心理論基礎(chǔ)
編寫優(yōu)質(zhì)的并發(fā)代碼是一件難度極高的事情。Java語(yǔ)言從第一版本開始內(nèi)置了對(duì)多線程的支持,這一點(diǎn)在當(dāng)年是非常了不起的,但是當(dāng)我們對(duì)并發(fā)編程有了更深刻的認(rèn)識(shí)和更多的實(shí)踐后,實(shí)現(xiàn)并發(fā)編程就有了更多的方案和更好的選擇。本文是對(duì)并發(fā)編程的核心理論做了下小結(jié)2016-05-05Java經(jīng)典算法匯總之選擇排序(SelectionSort)
選擇排序也是比較簡(jiǎn)單的一種排序方法,原理也比較容易理解,選擇排序在每次遍歷過(guò)程中只記錄下來(lái)最小的一個(gè)元素的下標(biāo),待全部比較結(jié)束之后,將最小的元素與未排序的那部分序列的最前面一個(gè)元素交換,這樣就降低了交換的次數(shù),提高了排序效率。2016-04-04