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依赖去掉了,日志也就打不出来了。

  

posted on 2020-07-12 23:40  不想下火车的人  阅读(2932)  评论(0编辑  收藏  举报

导航