1. 程式人生 > 其它 >Pf4j的SPI簡單例項

Pf4j的SPI簡單例項

  同Dubbo的擴充套件SPI一樣,Pf4j這個外掛框架也師出同門,都是由JDK自帶的SPI(參見Java的SPI簡單例項)衍化而來。但Pf4j畢竟是一個外掛框架,對外掛的支援相對專業一些。官網上的介紹說:PF4J是一個開源(Apache許可證)輕量級(約100kb)的java外掛框架,具有最小的依賴性(只有slf4japi和javasemver),並且具有很強的可擴充套件性。接下來我們還是用一個簡單的例子說明:

  1、介面類,繼承ExtensionPoint:

package com.wlf.service;

import org.pf4j.ExtensionPoint;

public interface IPf4jGreeting extends ExtensionPoint {

    void sayHello();
}

  2、介面實現類,需要@Extension註解證明它是擴充套件點的實現。一般都放在jar包或zip包中,但這裡我們為了方便,直接放在同一個專案中:

package com.wlf.service.impl;

import com.wlf.service.IPf4jGreeting;
import org.pf4j.Extension;

@Extension
public class IPf4jGreetingImpl1 implements IPf4jGreeting {
    @Override
    public void sayHello() {
        System.out.println("hello, world.");
    }
}
package com.wlf.service.impl;

import com.wlf.service.IPf4jGreeting;
import org.pf4j.Extension;

@Extension
public class IPf4jGreetingImpl2 implements IPf4jGreeting {
    @Override
    public void sayHello() {
        System.out.println("hi, mia.");
    }
}

  3、應用類,執行外掛載入和例項化:

package com.wlf.service;

import org.pf4j.DefaultPluginManager;
import org.pf4j.PluginManager;

import java.util.List;

public class TestPf4jServiceLoader {
    public static void main(String[] args) {
        PluginManager pluginManager = new DefaultPluginManager();

        List<IPf4jGreeting> greetings = pluginManager.getExtensions(IPf4jGreeting.class);
        greetings.forEach(greeting -> {
            greeting.sayHello();
        });

        pluginManager.stopPlugins();
    }
}

  完事了。跑一下試試:

23:03:44.619 [main] INFO org.pf4j.DefaultPluginStatusProvider - Enabled plugins: []
23:03:44.627 [main] INFO org.pf4j.DefaultPluginStatusProvider - Disabled plugins: []
23:03:44.637 [main] INFO org.pf4j.DefaultPluginManager - PF4J version 3.0.1 in 'deployment' mode
23:03:44.637 [main] DEBUG org.pf4j.AbstractPluginManager - Lookup plugins in 'plugins'
23:03:44.641 [main] WARN org.pf4j.AbstractPluginManager - No 'plugins' root
23:03:44.650 [main] DEBUG org.pf4j.AbstractExtensionFinder - Finding extensions of extension point 'com.wlf.service.IPf4jGreeting'
23:03:44.650 [main] DEBUG org.pf4j.LegacyExtensionFinder - Reading extensions storages from classpath
23:03:44.650 [main] DEBUG org.pf4j.LegacyExtensionFinder - Read '/E:/workspace/subtitle-synthesis/target/classes/META-INF/extensions.idx'
23:03:44.664 [main] DEBUG org.pf4j.AbstractExtensionFinder - Found possible 2 extensions:
23:03:44.665 [main] DEBUG org.pf4j.AbstractExtensionFinder -    com.wlf.service.impl.IPf4jGreetingImpl2
23:03:44.665 [main] DEBUG org.pf4j.AbstractExtensionFinder -    com.wlf.service.impl.IPf4jGreetingImpl1
23:03:44.665 [main] DEBUG org.pf4j.LegacyExtensionFinder - Reading extensions storages from plugins
23:03:44.665 [main] DEBUG org.pf4j.AbstractExtensionFinder - Finding extensions of extension point 'com.wlf.service.IPf4jGreeting' for plugin 'null'
23:03:44.665 [main] DEBUG org.pf4j.AbstractExtensionFinder - Loading class 'com.wlf.service.impl.IPf4jGreetingImpl2' using class loader 'sun.misc.Launcher$AppClassLoader@18b4aac2'
23:03:44.673 [main] DEBUG org.pf4j.AbstractExtensionFinder - Checking extension type 'com.wlf.service.impl.IPf4jGreetingImpl2'
23:03:44.697 [main] DEBUG org.pf4j.AbstractExtensionFinder - Added extension 'com.wlf.service.impl.IPf4jGreetingImpl2' with ordinal 0
23:03:44.697 [main] DEBUG org.pf4j.AbstractExtensionFinder - Loading class 'com.wlf.service.impl.IPf4jGreetingImpl1' using class loader 'sun.misc.Launcher$AppClassLoader@18b4aac2'
23:03:44.708 [main] DEBUG org.pf4j.AbstractExtensionFinder - Checking extension type 'com.wlf.service.impl.IPf4jGreetingImpl1'
23:03:44.709 [main] DEBUG org.pf4j.AbstractExtensionFinder - Added extension 'com.wlf.service.impl.IPf4jGreetingImpl1' with ordinal 0
23:03:44.710 [main] DEBUG org.pf4j.AbstractExtensionFinder - Found 2 extensions for extension point 'com.wlf.service.IPf4jGreeting'
23:03:44.710 [main] DEBUG org.pf4j.AbstractExtensionFinder - Found 2 extensions for extension point 'com.wlf.service.IPf4jGreeting'
23:03:44.710 [main] DEBUG org.pf4j.DefaultExtensionFactory - Create instance for extension 'com.wlf.service.impl.IPf4jGreetingImpl2'
23:03:44.711 [main] DEBUG org.pf4j.DefaultExtensionFactory - Create instance for extension 'com.wlf.service.impl.IPf4jGreetingImpl1'
hi, mia.
hello, world.

  按通常用法,需要把實現類1和實現類2分別打成兩個jar包或zip包,然後放到一個指定目錄(預設是一個叫plugins的目錄)中。這裡為啥放在同一個專案中,Pf4j還能找到我們的擴充套件點實現類呢?其實日誌已經出賣了一切,它最開始就是去plugins目錄中尋找的,沒找著,接著去classpath目錄下需要,在target目錄中找到了編譯後extension.idx檔案:

  檔案的內容剛好就是@Extension註解標識出來的擴充套件點實現類。Pf4j通過getExtensions方法找到我們的實現類後,做了一層包裝,把實現類包裝進ExtensionWrapper物件,再由該物件的getExtension方法,呼叫擴充套件工廠ExtensionFactory生成例項:

/**
 * A wrapper over extension instance.
 *
 * @author Decebal Suiu
 */
public class ExtensionWrapper<T> implements Comparable<ExtensionWrapper<T>> {

    private final ExtensionDescriptor descriptor;
    private final ExtensionFactory extensionFactory;
    private T extension; // cache

    public ExtensionWrapper(ExtensionDescriptor descriptor, ExtensionFactory extensionFactory) {
        this.descriptor = descriptor;
        this.extensionFactory = extensionFactory;
    }

    @SuppressWarnings("unchecked")
    public T getExtension() {
        if (extension == null) {
            extension = (T) extensionFactory.create(descriptor.extensionClass);
        }

        return extension;
    }

    public ExtensionDescriptor getDescriptor() {
        return descriptor;
    }

    public int getOrdinal() {
        return descriptor.ordinal;
    }

    @Override
    public int compareTo(ExtensionWrapper<T> o) {
        return (getOrdinal() - o.getOrdinal());
    }

}
**
 * The default implementation for {@link ExtensionFactory}.
 * It uses {@link Class#newInstance} method.
 *
 * @author Decebal Suiu
 */
public class DefaultExtensionFactory implements ExtensionFactory {

    private static final Logger log = LoggerFactory.getLogger(DefaultExtensionFactory.class);

    /**
     * Creates an extension instance.
     */
    @Override
    public <T> T create(Class<T> extensionClass) {
        log.debug("Create instance for extension '{}'", extensionClass.getName());
        try {
            return extensionClass.newInstance();
        } catch (Exception e) {
            throw new PluginRuntimeException(e);
        }
    }

}

  包裝類ExtensionWrapper做了排序,所以我們可以指定外掛載入的順序,ordinal預設是0,所以我們只需指定外掛2的排序為1,即可讓外掛1先執行:

  既然Pf4j是基於JDK的SPI機制而構建的,自然我們也可以直接使用ServiceLoader指定的META-INF/services目錄來讓Pf4j發現我們的擴充套件點實現類,此時就沒有必要使用註解@Extension來標識了。當然,沒有了註解,我們也沒法指定擴充套件點實現類的載入順序,原生SPI沒有那麼強的功能。

  我們去掉兩個實現類的註解:

  在META-INF/services目錄新增檔案com.wlf.service.IPf4jGreeting:

  最後在例項化PluginManager時指定使用JDK的SPI方式載入,重寫了createExtensionFinder方法,執行結果如下:

  它會讀取所有META-INF/services目錄下的檔案,包括依賴的jar包。所以為了排除一些類載入失敗,我把其他jar依賴去掉了,日誌也就打不出來了。