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模式的
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是当前运行目录的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