首頁  >  文章  >  Java  >  Java怎麼用SPI實作解耦

Java怎麼用SPI實作解耦

王林
王林轉載
2023-05-19 21:58:151405瀏覽

概述

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中文網其他相關文章!

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除