Java利用SPI實(shí)現(xiàn)解耦的示例詳解
概述
SPI的全稱是服務(wù)提供接口,可以用其來啟動(dòng)框架的擴(kuò)展和替換組件。
其本質(zhì)是利用 接口實(shí)現(xiàn)+策略模式+配置文件來實(shí)現(xiàn)對(duì)實(shí)現(xiàn)類的動(dòng)態(tài)加載。
在具體的使用中,存在一些約定:
(1)規(guī)定在 classPath 的 META-INF/services/ 下,創(chuàng)建該接口的全名稱文件
(2)在該文件中,寫入該接口實(shí)現(xiàn)類全稱(路徑+文件名),多個(gè)實(shí)現(xiàn)類的話,分行寫。
(3)用的2時(shí)候,使用 java.util.ServiceLoader 的 load(Interface.class),獲取到實(shí)現(xiàn)類,就可以使用了。
值得注意的是,接口實(shí)現(xiàn)類必須有一個(gè)不帶參數(shù)的構(gòu)造方法。
實(shí)現(xiàn)案例
在本應(yīng)用中,存在兩個(gè)模塊,分別為A模塊和B模塊,這兩個(gè)模塊中,A模塊是主模塊,B是從模塊,B模塊是依賴A模塊的。但是在目前有一個(gè)類,該類中實(shí)現(xiàn)在B模塊中,A模塊需要調(diào)用這個(gè)類的函數(shù),而模塊不能再依賴B模塊,此時(shí)需要進(jìn)行解耦。在本實(shí)現(xiàn)中,利用SPI的方式進(jìn)行解耦實(shí)現(xiàn)。具體實(shí)現(xiàn)方案為:
(1)在A模塊新建一個(gè)接口:MyLogAppender,具體實(shí)現(xiàn)為:
/** * @author Huang gen(kenfeng) * @description 自定義的appender接口 * @Since 2021/02/21 **/ public interface MyLogAppender { /** * 獲取實(shí)現(xiàn)的appender * @return 返回新建的appender對(duì)象 * */ Appender getAppender(); }
這個(gè)接口很簡(jiǎn)單,只是返回一個(gè)appender的對(duì)象。對(duì)于對(duì)象的實(shí)際操作,在接口的實(shí)現(xiàn)中進(jìn)行操作。
(2)在B模塊添加對(duì)這個(gè)接口的實(shí)現(xiàn),具體的操作為:
/** * @author Huang gen(kenfeng) * @description 自定義的appender * @Since 2021/02/21 **/ @Component public class MeshLogAppender extends UnsynchronizedAppenderBase<ILoggingEvent> implements MyLogAppender,ApplicationContextAware { private ApplicationContext applicationContext; public MeshLogAppender(){ } @Override public Appender getAppender() { MeshLogAppender meshLogAppender = new MeshLogAppender(); return meshLogAppender; } @Override protected void append(ILoggingEvent iLoggingEvent) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String std = simpleDateFormat.format(new Date(Long.parseLong(String.valueOf(iLoggingEvent.getTimeStamp())))); String log = std + "\t" + iLoggingEvent.getLevel() +"\t"+"--- ["+ iLoggingEvent.getThreadName()+"]\t"+iLoggingEvent.getCallerData()[0]+":\t "+iLoggingEvent.getMessage(); FlowMessage input = new FlowMessage(); MeshFlowService meshFlowService = SandboxSystemServiceFactory.getService(MeshFlowService.class); Map<String, Object> body = new HashMap<>(2); body.put("log",log); input.setTenantCode(DefaultTenant.get()); input.setAppCode("epoch"); input.setFlowCode("log_broadcast"); input.setBody(body); FlowMessage output = meshFlowService.process(input); if(!StringUtils.isEmpty(output.getErrorMessage())){ throw new RuntimeException("發(fā)布日志時(shí),廣播失敗"); } } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
在該接口的申明和接口的實(shí)現(xiàn)中,存在一些小的技巧的實(shí)現(xiàn)。在接口中,只聲明一個(gè)類的獲取,并沒有實(shí)現(xiàn)具體的方法。在實(shí)現(xiàn)類中,對(duì)這個(gè)類進(jìn)行實(shí)例化,new一個(gè)新的類并返回,此時(shí)用戶根據(jù)這個(gè)get方法就可以拿到這個(gè)實(shí)現(xiàn)類,然后進(jìn)行實(shí)現(xiàn)類的一些操作。這樣寫可以帶來兩個(gè)好處: i. 代碼更簡(jiǎn)潔,接口的代碼簡(jiǎn)單易懂 ii. 可以在實(shí)現(xiàn)類的構(gòu)造方法中注入一些參數(shù),當(dāng)用戶使用時(shí),直接在get方法里面注入即可。
(3) 在實(shí)現(xiàn)類所在文件夾下,也就是sandbox-app-epoch-starter中添加一個(gè)配置文件,其配置文件的路徑默認(rèn)為: resources/META-INF/services/,在這個(gè)文件夾下新建一個(gè)問題,文件名為接口的路徑,內(nèi)容是實(shí)現(xiàn)類的路徑。由此可以實(shí)現(xiàn)接口-->實(shí)現(xiàn)類的映射。
如上圖中,文件名為:com.alibaba.halo.sandbox.app.util.MyLogAppender
其文件中的內(nèi)容為:com.alibaba.lattice2.epoch.util.MeshLogAppender
其原理是,當(dāng)用戶使用接口時(shí),會(huì)掃描項(xiàng)目下的所有文件,查找文件名為com.alibaba.halo.sandbox.app.util.MyLogAppender,然后根據(jù)其內(nèi)容來查找到相關(guān)的實(shí)現(xiàn)類
(4)在A,可以直接使用接口來進(jìn)行調(diào)用,具體實(shí)現(xiàn)如下:
ServiceLoader<MyLogAppender> myLoaderInterfaceServiceLoader = ServiceLoader.load(MyLogAppender.class); Iterator<MyLogAppender> myLoaderInterfaceIterator = myLoaderInterfaceServiceLoader.iterator(); while (myLoaderInterfaceIterator.hasNext()){ MyLogAppender myLoaderInterface = myLoaderInterfaceIterator.next(); Appender newAppender = myLoaderInterface.getAppender(); newAppender.setName("application"); newAppender.setContext(loggerContext); newAppender.start(); rootLogger.addAppender(newAppender); }
從上面可以看到,其可以直接調(diào)用MyLogAppender接口,利用這個(gè)接口獲取的Appender,之后直接賦值即可。
優(yōu)勢(shì)和不足
優(yōu)點(diǎn):可以實(shí)現(xiàn)代碼的解耦
缺點(diǎn):存在多個(gè)實(shí)現(xiàn)類的話,無法根據(jù)某個(gè)參數(shù)或者標(biāo)志位獲取實(shí)例,只能通過遍歷獲取,沒有實(shí)現(xiàn)所謂的懶加載
到此這篇關(guān)于Java利用SPI實(shí)現(xiàn)解耦的示例詳解的文章就介紹到這了,更多相關(guān)Java SPI解耦內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot用多線程批量導(dǎo)入數(shù)據(jù)庫實(shí)現(xiàn)方法
這篇文章主要介紹了SpringBoot用多線程批量導(dǎo)入數(shù)據(jù)庫實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-02-02SpringCloud修改Feign日志記錄級(jí)別過程淺析
OpenFeign源于Netflix的Feign,是http通信的客戶端。屏蔽了網(wǎng)絡(luò)通信的細(xì)節(jié),直接面向接口的方式開發(fā),讓開發(fā)者感知不到網(wǎng)絡(luò)通信細(xì)節(jié)。所有遠(yuǎn)程調(diào)用,都像調(diào)用本地方法一樣完成2023-02-02Java并發(fā)編程Lock?Condition和ReentrantLock基本原理
這篇文章主要介紹了Java并發(fā)編程Lock?Condition和ReentrantLock基本原理,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09Java處理時(shí)間格式CST和GMT轉(zhuǎn)換方法示例
這篇文章主要給大家介紹了關(guān)于Java處理時(shí)間格式CST和GMT轉(zhuǎn)換方法的相關(guān)資料,相信很多小伙伴在時(shí)間格式轉(zhuǎn)換的時(shí)候非常頭疼,文中通過代碼示例介紹的非常詳細(xì),需要的朋友可以參考下2023-09-09解決ThingsBoard編譯報(bào)錯(cuò)問題:Failure?to?find?org.gradle:gradle-too
這篇文章主要介紹了ThingsBoard編譯報(bào)錯(cuò):Failure?to?find?org.gradle:gradle-tooling-api:jar:6.3,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03Java中Random簡(jiǎn)介_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
本文詳細(xì)給大家介紹了Java中Random簡(jiǎn)介相關(guān)知識(shí),非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧2017-06-06mybatis-plus分頁查詢?nèi)N方法小結(jié)
本文主要介紹了mybatis-plus分頁查詢?nèi)N方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05