使用pf4j实现基于插件化的扩展开发

2,098 阅读3分钟

前言

近期在搭建共享服务,需要对共享服务进行扩展。按照现有的方式,常见有以下几种扩展方式:

  1. 直接覆盖Spring Bean: 通常使用Spring @Primary 注解来实现。
  2. 使用AOP切面进行原有能力的扩展:自定义Annotation,通过AOP切面可以进行能力的前置处理、替换处理、后置处理。
  3. 使用类似JAVAssist字节码技术实现编译阶段的方法覆盖。

以上这几种扩展方式,有个共同点:原共享服务(宿主服务)需要显式的感知扩展代码的存在,在编译阶段引入扩展代码,处理后重新打包才能生效,扩展方对宿主方存在很强的依赖关系。

那么共享服务的扩展,能否有一种可插拔的插件机制,扩展方编写自己的插件,对原有能力进行扩展,发布时基于一个约定的目录,进行扩展能力的加载?

刚好网上找到了pf4j这一插件框架,可以轻松的将一个java应用变成转成插件化的应用,可以针对不同的业务场景,实现不同的插件,按需加载,来满足业务的扩展需求。另外分析了一下也有对应的pf4j-spring来支持Spring的集成,可以快速集成进入现有的Spring框架,所以就快速尝试下。

pf4j-spring 的使用

由于共享服务基于SpringBoot框架搭建的,所以直接跳过pf4j的基础概念,直接尝试引入使用pf4j-spring,在使用过程中再结合实际了解pf4j自身的机制内容(可以直接下载pf4j-spring源码 进行查看)。

关键核心类

pf4j-spring非常小巧的,有以下几个关键的核心类:

  • SpringPluginManager:入口类,实现ApplicationContextAware接口,通过该类触发插件的加载、扩展类的注册
  • ExtensionsInjector:扫描插件的所有扩展类,并将扩展类注册为SpringBean
  • SpringExtensionFactory:扩展类创建的工厂类,该类实现了pf4j的ExtensionFactory。
  • SpringPlugin:每个插件的插件标记,并进行ApplicationContext的处理(在pf4j中,每个插件的ApplicationContext默认都是相互独立的)。

引入使用

  1. 在新建的插件工程里,引入maven依赖
<dependency>
    <groupId>org.pf4j</groupId>
    <artifactId>pf4j</artifactId>
    <version>${pf4j.version}</version>
</dependency>
<dependency>
    <groupId>org.pf4j</groupId>
    <artifactId>pf4j-spring</artifactId>
    <version>${pf4j-spring.version}</version>
</dependency>   
  1. 定义SpringPlugin的声明类
package com.test.plugin.extrasvr.plugin;

import org.pf4j.spring.SpringPlugin;
import org.pf4j.PluginWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * TestPlugin插件自描述类
 *
 * @author xiami
 * @since 1.0.0
 */
public class TestPlugin extends SpringPlugin {
    private static Logger logger = LoggerFactory.getLogger(TestPlugin.class);
    public TestPlugin(PluginWrapper wrapper) {
        super(wrapper);
    }

    @Override
    public void start(){
        logger.info("test plugin start");
    }

    protected ApplicationContext createApplicationContext(){
        // 这里创建具体的ApplicationContext
    }
}

  1. 在插件工程声明插件,在根目录下新建plugin.properties,声明插件的相关基础信息
plugin.id=test_plugin
plugin.class=com.test.plugin.extrasvr.plugin.TestPlugin
plugin.version=0.0.1
plugin.provider=xiami
plugin.dependencies=
  1. 编写扩展类,继承ExtensionPoint,并添加注解 @Extension
package com.test.plugin.extrasvr.plugin.rest;

import io.swagger.annotations.ApiOperation;
import org.pf4j.Extension;
import org.pf4j.ExtensionPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController("testExtraRest4")
@RequestMapping("/v1/testExtra4")
@Extension
public class TestExtra4Rest implements ExtensionPoint {
    private static Logger logger = LoggerFactory.getLogger(TestExtra4Rest.class);

    /**
     * 根据ID主键查询
     *
     * @param id 主键
     * @return RestResponse<ExampleDto>
     */
    @RequestMapping(value = {"/{id}"}, produces = "application/json", method = RequestMethod.GET)
    @ApiOperation(value = "根据ID查询详情", notes = "根据ID查询详情")
    Long queryById(@PathVariable("id") Long id){
        return id;
    }
}
  1. 将该应用工程打包为插件,放在一个自定义的文件夹目录,并在文件夹目录创建一个文件enabled.txt,填写第三步的plugin.id
test_plugin
  1. 在宿主工程中(前言提到的共享服务),引入第一步的Maven依赖,同时增加启动参数:扫描第5步的插件文件夹目录: -Dpf4j.pluginsDir=<具体目录地址>

  2. 最后启动宿主工程,即可在无需编译引入插件的情况下,访问插件实现的接口。