前言
近期在搭建共享服务,需要对共享服务进行扩展。按照现有的方式,常见有以下几种扩展方式:
- 直接覆盖Spring Bean: 通常使用Spring @Primary 注解来实现。
- 使用AOP切面进行原有能力的扩展:自定义Annotation,通过AOP切面可以进行能力的前置处理、替换处理、后置处理。
- 使用类似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默认都是相互独立的)。
引入使用
- 在新建的插件工程里,引入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>
- 定义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
}
}
- 在插件工程声明插件,在根目录下新建plugin.properties,声明插件的相关基础信息
plugin.id=test_plugin
plugin.class=com.test.plugin.extrasvr.plugin.TestPlugin
plugin.version=0.0.1
plugin.provider=xiami
plugin.dependencies=
- 编写扩展类,继承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;
}
}
- 将该应用工程打包为插件,放在一个自定义的文件夹目录,并在文件夹目录创建一个文件enabled.txt,填写第三步的plugin.id
test_plugin
-
在宿主工程中(前言提到的共享服务),引入第一步的Maven依赖,同时增加启动参数:扫描第5步的插件文件夹目录:
-Dpf4j.pluginsDir=<具体目录地址> -
最后启动宿主工程,即可在无需编译引入插件的情况下,访问插件实现的接口。