JAVA中的SPI實踐學習

介紹

SPI ,全稱爲 Service Provider Interface,是一種服務發現機制。它通過在ClassPath路徑下的META-INF/services文件夾查找文件,自動加載文件裏所定義的類。
這一機制爲很多框架擴展提供了可能,比如在Dubbo、JDBC中都使用到了SPI機制。其實介紹可以自行百度,下面通過簡單的示例代碼介紹相關功能。

Animal接口

定義一個單獨的工程用於定義Animal接口並定義方法sayHello,代碼如下,非常簡單

package com.zte.sunquan.spi;

/**
 * Animal class
 *
 * @author 10184538
 * @date 2019/7/6
 */
public interface Animal {
    String sayHello();
}

代碼結構爲:
工程目錄結構

Animal接口實現

在另一個工程中完成Animal接口的實現,簡單地打印相關信息即可。代碼如下:

package com.zte.sunquan.demo.spi.impl;

import com.zte.sunquan.spi.Animal;

/**
 * Bird class
 *
 * @author 10184538
 * @date 2019/7/6
 */
public class Bird implements Animal {
    @Override
    public String sayHello() {
        System.out.println("bird say hello.");
        return "bird say hello.";
    }
}

在實現的模塊中需要注意的是,爲能SPI能夠發現這個Animal的實現,需要在META-INF.services中增加配置文件,文件名以接口類的全路徑名命名,內容爲實現類的全路徑名。具體爲:
在這裏插入圖片描述
內容爲:
com.zte.sunquan.demo.spi.impl.Bird

調用測試模塊

另外新建一個測試調用的模塊,用於執行Animal中的打印功能,首先需要在pom中加入兩者的依賴

        <dependency>
            <groupId>com.zte.sunquan.demo</groupId>
            <artifactId>spi-demo-interface</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.zte.sunquan.demo</groupId>
            <artifactId>spi-demo-impl</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>

此時如果沒有SPI機制,當然也是可以調用到Bird裏的animal實現的,最簡單的可以通過

Animal animal=new Bird();

如果是基於Spring或者OSGI的服務引用,可以直接引用到

@Service
private Animal bird;

下面看SPI是怎麼實現的,代碼爲:

package com.zte.sunquan.demo.main;

import java.util.ServiceLoader;

import com.zte.sunquan.demo.spi.impl.Bird;
import com.zte.sunquan.spi.Animal;

/**
 * App class
 *
 * @author 10184538
 * @date 2019/7/6
 */
public class App {
    public static void main(String[] args) {
        ServiceLoader<Animal> shouts = ServiceLoader.load(Animal.class);
        for (Animal s : shouts) {
            System.out.println(s.getClass().getName());
            s.sayHello();
        }
    }
}

通過ServiceLoader.load可以直接取得所有的animal實現。特別的在基於OSGI的bundle熱加載的機制中,可以利用這點實現更爲靈活的控制。

更進一步

利用SPI的特點實現一個按優點級對數據處理的處理器

具體實現邏輯依賴爲三步

  • 定義接口
package com.zte.sunquan.spi;

import com.zte.sunquan.spi.demo.Cow;

/**
 * Animal class
 *
 * @author 10184538
 * @date 2019/7/6
 */
public interface Handler {
    void process(String input);

    void process(Cow input);

    default Integer getPriority() {
        return 10;
    }
}

這邊出於測試,定義了兩個process接口,一個處理基本類型,一個處理對象類型,gePriority爲默認Handler的優先級,因爲需求中要按優先級對handler列表進行排序

  • 定義實現

這裏代碼兩個實現的Handler,代碼爲:

package com.zte.sunquan.demo.spi.impl;


import com.zte.sunquan.spi.Handler;
import com.zte.sunquan.spi.demo.Cow;

/**
 * AProcessHandler class
 *
 * @author 10184538
 * @date 2019/7/6
 */
public class AProcessHandler implements Handler {
    @Override
    public void process(String input) {
        System.out.println("AProcessHandler:" + input);
        input += "AProcessHandler:";
    }

    @Override
    public void process(Cow input) {
        System.out.println("AProcessHandler:" + input);
        String name = input.getName();
        input.setName(name + "AProcessHandler");
    }

    @Override
    public Integer getPriority() {
        return 15;
    }
}

package com.zte.sunquan.demo.spi.impl;


import com.zte.sunquan.spi.Handler;
import com.zte.sunquan.spi.demo.Cow;

/**
 * AProcessHandler class
 *
 * @author 10184538
 * @date 2019/7/6
 */
public class BProcessHandler implements Handler {
    @Override
    public void process(String input) {
        System.out.println("BProcessHandler:" + input);
        input += "BProcessHandler:";
    }

    @Override
    public void process(Cow input) {
        System.out.println("BProcessHandler:" + input);
        String name = input.getName();
        input.setName(name + "BProcessHandler");
    }

    @Override
    public Integer getPriority() {
        return 5;
    }
}

可以發現BProcessHandler的優先級要高於AProcessHandler。
同樣在META-INF.services文件中要加入SPI機制可識別的配置文件,

com.zte.sunquan.spi.Handler
com.zte.sunquan.demo.spi.imp.ProcessHandler
com.zte.sunquan.demo.spi.imp.BProcessHandler

  • 執行
    在執行前,通過==SericeLoader.load(Handler.class)==獲得所有的Handler,還需要排序
    所以增加了一個基於泛型的HandlerEx用於對普通Handler包裝並排序,代碼如下:
package com.zte.sunquan.demo.domain;

import java.util.Objects;

import com.zte.sunquan.spi.Handler;

/**
 * HandlerExt class
 *
 * @author 10184538
 * @date 2019/7/6
 */
public class HandlerExt<T extends Handler> implements Comparable<HandlerExt<T>> {
    private T handler;
    private Integer pri;

    public HandlerExt(T handler, Integer pri) {
        this.handler = handler;
        this.pri = pri;
    }

    public T getHandler() {
        return handler;
    }

    public Integer getPri() {
        return pri;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        HandlerExt<?> that = (HandlerExt<?>) o;
        return Objects.equals(handler, that.handler) &&
                Objects.equals(pri, that.pri);
    }

    @Override
    public int hashCode() {
        return Objects.hash(pri);
    }

    @Override
    public int compareTo(HandlerExt<T> o) {
        return this.getPri() - o.getPri();
    }
}

最後調用代碼如下:

package com.zte.sunquan.demo.main;

import java.util.Collections;
import java.util.List;
import java.util.ServiceLoader;
import java.util.stream.Collectors;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.zte.sunquan.demo.domain.HandlerExt;
import com.zte.sunquan.spi.Handler;
import com.zte.sunquan.spi.demo.Cow;

/**
 * App class
 *
 * @author 10184538
 * @date 2019/7/6
 */
public class App2 {
    //這樣方式可以取代目前的handler機制嗎,省去註冊的步驟(如下是可以的)
    //優先級控制
    public static void main(String[] args) {
        ServiceLoader<Handler> shouts = ServiceLoader.load(Handler.class);
        List<Handler> handlers = Lists.newArrayList(shouts.iterator());
        List<HandlerExt<Handler>> handlerList = Lists.newLinkedList();

        handlers.forEach(handler -> {
            handlerList.add(new HandlerExt<>(handler,handler.getPriority()));
        });
        Collections.sort(handlerList);


        ImmutableList<Handler> handlers1 = ImmutableList.copyOf(handlerList.stream()
                .map(HandlerExt::getHandler).collect(Collectors.toList()));

        String input = "sunquan";
        shouts.forEach(handler -> handler.process(input));
        //如果入參是對象,這種方式數據變更可以傳遞
        Cow cow = new Cow();
        cow.setName("sunquan");
        handlers1.forEach(handler -> handler.process(cow));
    }
}

返回值
在這裏插入圖片描述

  • 結論
    基於類型的修改無法在handler間傳遞,但對象類型的變化可以在handler間傳遞
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章