SPI的全名是服務提供接口,可以用其來啟動框架的擴展和替換元件。
其本質是利用 介面實作 策略模式 設定檔來實現對實作類別的動態載入。
在特定的使用中,存在一些約定:
(1)規定在classPath 的META-INF/services/ 下,建立該介面的全名稱檔案
# (2)在該文件中,寫入該介面實現類別全稱(路徑檔名),多個實作類別的話,分行寫入。
(3)用的2時候,使用 java.util.ServiceLoader 的 load(Interface.class),取得到實作類,就可以使用了。
值得注意的是,介面實作類別必須有一個不帶參數的建構方法。
在本應用程式中,有兩個模組,分別為A模組和B模組,這兩個模組中,A模組是主模組,B是從模組,B模組是依賴A模組的。但是目前有一個類,該類別中實作在B模組中,A模組需要呼叫這個類別的函數,而模組不能再依賴B模組,此時需要進行解耦。在本實作中,利用SPI的方式進行解耦實作。具體實作方案為:
(1)在A模組新建一個介面:MyLogAppender,具體實作為:
/** * @author Huang gen(kenfeng) * @description 自定义的appender接口 * @Since 2021/02/21 **/ public interface MyLogAppender { /** * 获取实现的appender * @return 返回新建的appender对象 * */ Appender getAppender(); }
這個介面很簡單,只是回傳一個appender的物件。對於物件的實際操作,在介面的實作中進行操作。
(2)在B模組加入對這個介面的實現,具體的操作為:
/** * @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("发布日志时,广播失败"); } } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
在該介面的申明和介面的實作中,存在一些小的技巧的實作。在介面中,只聲明一個類別的獲取,並沒有實作具體的方法。在實作類別中,對這個類別進行實例化,new一個新的類別並返回,此時使用者根據這個get方法就可以拿到這個實作類,然後進行實作類別的一些操作。這樣寫可以帶來兩個好處: i. 程式碼更簡潔,介面的程式碼簡單易懂 ii. 可以在實作類別的建構方法中註入一些參數,當使用者使用時,直接在get方法裡面注入即可。
(3) 在實作類別所在資料夾下,也就是sandbox-app-epoch-starter中加入一個設定文件,其設定檔的路徑預設為: resources/META-INF/services/,在這個資料夾下新建一個問題,檔案名為介面的路徑,內容是實作類別的路徑。由此可以實現介面-->實現類別的映射。
如上圖中,檔案名稱為:com.alibaba.halo.sandbox.app.util.MyLogAppender
其檔案中的內容為:com.alibaba.lattice2.epoch.util. MeshLogAppender
其原理是,當使用者使用介面時,會掃描專案下的所有文件,尋找文件名稱為com.alibaba.halo.sandbox.app.util.MyLogAppender,然後根據其內容來查找到相關的實作類別
(4)在A,可以直接使用介面來進行調用,具體實作如下:
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); }
從上面可以看到,其可以直接調用MyLogAppender接口,利用這個介面取得的Appender,之後直接賦值即可。
優點:可以實現程式碼的解耦
缺點在於,如果存在多個實作類,則無法透過某個參數或標誌來取得實例,只能透過遍歷獲取,且無法實作懶載入
以上是Java怎麼用SPI實作解耦的詳細內容。更多資訊請關注PHP中文網其他相關文章!