pf4j 试用

pf4j 试用上还是比较灵活的,并没有太多的配置,而且比较灵活,支持类隔离

参考项目

  • 项目结构
├── README.md
├── bootstrap  // 启动入口,使用了assembly 进行打包,当然对图spring 项目也是可以的
│   ├── pom.xml
│   └── src
│       ├── main
│       │   ├──assembly
│       │   ├── java
│       │   └── resources
│       └── test
│           └── java
├── loginpluginb  // 插件1
│   ├── pom.xml
│   └── src
│       ├── main
│       │   ├── java
│       │   └── resources
│       └── test
│           └── java
├── loginpluginc // 插件2
│   ├── pom.xml
│   └── src
│       ├── main
│       │   ├── java
│       │   └── resources
│       └── test
│           └── java
├── pom.xml
├── service-contract // 插件服务契约
│   ├── pom.xml
│   └── src
│       ├── main
│       │   ├── java
│       │   └── resources
│       └── test
│           └── java
└── src
    ├── main
    │   ├── java
    │   └── resources
    └── test
        └── java
  • 代码说明
    service-contract 定义实现契约注意需要继承ExtensionPoint (所以也需要添加pf4j依赖,推荐使用provide模式)
 
package com.dalong;
 
import org.pf4j.ExtensionPoint;
 
/**
 * @author dalong
 * userlogin service contract
 */
public interface UserLogin extends  ExtensionPoint {
    /**
     * userlogin service contract
     * @param name name
     * @param password password
     * @return token
     */
    String token(String name,String password);
}

插件实现(继承Plugin)进行扩展,添加注解@Extension
实现Plugin的目的是进行生命周期的控制

 
package com.dalong;
 
import org.pf4j.Extension;
import org.pf4j.Plugin;
import org.pf4j.PluginWrapper;
 
/**
 * login plugin c
 */
public class MyLoginPluginC extends Plugin {
    public MyLoginPluginC(PluginWrapper wrapper) {
        super(wrapper);
    }
 
    @Override
    public void delete() {
        super.delete();
        System.out.println("pluginc  delete");
    }
 
    @Override
    public void stop() {
        super.stop();
        System.out.println("pluginc  stop");
 
    }
 
    @Override
    public void start() {
        super.start();
        System.out.println("pluginc  start");
 
    }
    @Extension
    public  static  class MyLoginC implements  UserLogin {
 
        @Override
        public String token(String name, String password) {
            return String.format("%s-%s-plugin c",name,password);
        }
    }
}

插件打包说明
默认插件的加载包含了classpath 以及serviceloader(spi,但是默认没开启)可以基于jar 的manifest以及plugin.properties
基于jar 模式比较好,而且比较标准

 
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>pf4j-learning</artifactId>
        <groupId>com.dalong</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
 
    <artifactId>loginpluginc</artifactId>
 
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <plugin.id>loginpluginc</plugin.id>
        <plugin.class>com.dalong.MyLoginPluginC</plugin.class>
        <plugin.version>0.0.1</plugin.version>
        <plugin.provider>dalong</plugin.provider>
        <plugin.dependencies />
    </properties>
 
    <dependencies>
        <dependency>
            <groupId>com.dalong</groupId>
            <artifactId>service-contract</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.pf4j</groupId>
            <artifactId>pf4j</artifactId>
            <version>3.6.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                // manifest 维护
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifestEntries>
                            <Plugin-Id>${plugin.id}</Plugin-Id>
                            <Plugin-Class>${plugin.class}</Plugin-Class>
                            <Plugin-Version>${plugin.version}</Plugin-Version>
                            <Plugin-Provider>${plugin.provider}</Plugin-Provider>
                            <Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

bootstap 入口,比较简单,包含了支持serviceloader 的以及自定义classpath的
servieloader模式的
pf4j 试用

 

 

 
public class MyDefaultLogin implements  UserLogin{
    @Override
    public String token(String name, String password) {
        return String.format("%s-%s-default ",name,password);
    }
}
 

自定义的

package com.dalong;
 
import org.pf4j.Extension;
 
@Extension
public class Pf4JLogin implements  UserLogin{
    @Override
    public String token(String name, String password) {
        return String.format("%s-%s-Pf4JLogin ",name,password);
    }
}
 

入口打包(方法很多,可以使用shared 以及maven-assembly-plugin)
maven pom.xml


 
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>pf4j-learning</artifactId>
        <groupId>com.dalong</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
 
    <artifactId>bootstrap</artifactId>
 
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <log4j.version>2.17.1</log4j.version>
        <main.class>com.dalong.Application</main.class>
    </properties>
 
    <dependencies>
        <dependency>
            <groupId>org.pf4j</groupId>
            <artifactId>pf4j</artifactId>
            <version>3.6.0</version>
        </dependency>
        <dependency>
            <groupId>com.dalong</groupId>
            <artifactId>service-contract</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>${log4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>${log4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>${log4j.version}</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <descriptors>
                        <descriptor>src/main/assembly/assembly.xml</descriptor>
                    </descriptors>
                    <appendAssemblyId>false</appendAssemblyId>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>attached</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.5.1</version>
                <configuration>
                    <annotationProcessors>
                        <annotationProcessor>org.pf4j.processor.ExtensionAnnotationProcessor</annotationProcessor>
                    </annotationProcessors>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId> // 配置一些manifest
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>lib/</classpathPrefix>
                            <mainClass>${main.class}</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-deploy-plugin</artifactId>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

assembly.xm

<assembly>
    <id>app</id>
    <formats>
        <format>dir</format>
        <format>zip</format>
    </formats>
    <includeBaseDirectory>false</includeBaseDirectory>
    <dependencySets>
        <dependencySet>
            <useProjectArtifact>false</useProjectArtifact>
            <outputDirectory>lib</outputDirectory>
            <includes>
                <include>*:jar:*</include>
            </includes>
        </dependencySet>
    </dependencySets>
    <fileSets>
        <fileSet>
            <directory>${project.build.directory}</directory>
            <outputDirectory></outputDirectory>
            <includes>
                <include>*.jar</include>
            </includes>
            <excludes>
                <exclude>*-javadoc.jar</exclude>
                <exclude>*-sources.jar</exclude>
            </excludes>
        </fileSet>
    </fileSets>
</assembly>

入口代码

package com.dalong;
 
import org.pf4j.*;
 
import java.util.List;
import java.util.function.Consumer;
 
public class Application {
    public static void main(String[] args) {
        PluginManager pluginManager = new DefaultPluginManager(){
            @Override
            protected ExtensionFinder createExtensionFinder() {
                DefaultExtensionFinder extensionFinder= (DefaultExtensionFinder) super.createExtensionFinder();
                extensionFinder.addServiceProviderExtensionFinder();// 开启servieloader 模式,
                return extensionFinder;
            }
        };
        pluginManager.loadPlugins();
        pluginManager.startPlugins();
        pluginManager.getPlugins().forEach(new Consumer<PluginWrapper>() {
            @Override
            public void accept(PluginWrapper pluginWrapper) {
                System.out.println("load plugin:"+pluginWrapper.getPluginId()+pluginWrapper.getPluginState());
            }
        });
        List<UserLogin> userLoginList=  pluginManager.getExtensions(UserLogin.class);
        userLoginList.forEach(new Consumer<UserLogin>() {
            @Override
            public void accept(UserLogin userLogin) {
                System.out.println(userLogin.token("name","dalong"));
            }
        });
    }
}

构建&启动

  • 构建
mvn clean package
  • 使用
    pf4j 对于插件加载有自己的流程,默认是运行目录的plugins 下,对于pf4j可以是jar 也可以是zip 文件(后边会介绍)

pf4j 试用

 

 


运行效果
截取部分
pf4j 试用

 

 

问题

  • 关于插件目录
    默认pf4j是当前运行目录的plugins下查找的,我们可以在运行的时候指定取值为System.getProperty("pf4j.pluginsDir", "plugins")
    启东时配置java -Dpf4j.pluginsDir=demoapp -jar bootstrap-1.0-SNAPSHOT.jar
  • 插件包元数据
    推荐基于jar 文件定义,可以通过jar 插件
  • 线程安全问题
    AbstractPluginManager 以及 DefaultPluginManager 都不是线程安全的,所以加载的时候需要自己包装线程安全
  • 几个扩展
    官方还提供了几个很不错的扩展spring,update。。。具体可以参考github
  • 默认ExtensionFactory
    默认是基于Class.newInstance java9 以及废弃了,而且如果有构造函数的就不方便了,可以使用Constructor.newInstance 或者自己开发
    因为实际中我们很多时候是需要传递参数的,比较推荐的是在服务契约中定义一个context,我们基于context进行服务的创建
  • 插件的开启以及禁用
    pf4j 提供了基于文本的插件配置,enabled.txt 以及disabled.txt我们可以开启以及禁用插件,文件内容就是插件id
  • 插件的依赖
    扩展可以包含依赖,可以基于注解添加,但是注意需要asm包,同时注意插件依赖,必须配置为可选的
  • 没有发现插件
    插件基于了java annotation processing,需要包含一个extensions.idx文件,可以在compile的时候指定java annotation processing
    也可以通过日志查看
 
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>2.5.1</version>
    <configuration>
        <annotationProcessors>
            <annotationProcessor>org.pf4j.processor.ExtensionAnnotationProcessor</annotationProcessor>
        </annotationProcessors>
    </configuration>
</plugin>
  • 测试
    pf4j 提供了测试包,可以用来进行方便的测试,目前有PluginJar,PluginZip 以及ClassDataProvider
  • Fat jar 的一个问题
    对于fat jar 可能会出现插件加载不成功的问题,比如我们的一个插件需要依赖其他jar,一般我们可能会通过shared 解决
    注意对于服务契约使用scope provider 模式,很重要
    参考pom 配置
 
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>pf4j-learning</artifactId>
        <groupId>com.dalong</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
 
    <artifactId>loginpluginc</artifactId>
 
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <plugin.id>loginpluginc</plugin.id>
        <plugin.class>com.dalong.MyLoginPluginC</plugin.class>
        <plugin.version>0.0.1</plugin.version>
        <plugin.provider>dalong</plugin.provider>
        <plugin.dependencies />
    </properties>
 
    <dependencies>
        <dependency>
            <groupId>com.dalong</groupId>
            <artifactId>service-contract</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>provided</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.pf4j</groupId>
            <artifactId>pf4j</artifactId>
            <version>3.6.0</version>
            <scope>provided</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.hashids</groupId>
            <artifactId>hashids</artifactId>
            <version>1.0.3</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifestEntries>
                            <Plugin-Id>${plugin.id}</Plugin-Id>
                            <Plugin-Class>${plugin.class}</Plugin-Class>
                            <Plugin-Version>${plugin.version}</Plugin-Version>
                            <Plugin-Provider>${plugin.provider}</Plugin-Provider>
                            <Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.3</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

参考资料

https://pf4j.org/doc/getting-started.html
https://github.com/rongfengliang/pf4j-learning
https://docs.oracle.com/javase/tutorial/reflect/member/ctorInstance.html
https://asm.ow2.io/
https://pf4j.org/doc/plugins.html#optional-plugin-dependencies
https://pf4j.org/doc/testing.html

上一篇:重写Redis序列化方式,redis可视化工具不乱码


下一篇:控制你的DTO数据结构