springboot插件式开发框架

springboot插件式开发框架

介绍

该框架主要是集成于springboot项目,用于开发插件式应用的集成框架。

核心功能

  1. 插件配置式插拔于springboot项目。
  2. 在springboot上可以进行插件式开发, 扩展性极强, 可以针对不同项目开发不同插件, 进行不同插件jar包的部署。
  3. 可通过配置文件指定要启用或者禁用插件。
  4. 支持上传插件和插件配置文件到服务器, 并且无需重启主程序, 动态部署插件、更新插件。
  5. 支持查看插件运行状态, 查看插件安装位置。
  6. 无需重启主程序, 动态的安装插件、卸载插件、启用插件、停止插件、备份插件、删除插件。
  7. 在插件应用模块上可以使用Spring注解定义组件, 进行依赖注入。
  8. 支持在插件中开发Rest接口。
  9. 支持在插件中单独定义持久层访问等需求。
  10. 可以遵循主程序提供的插件接口开发任意扩展功能。
  11. 插件可以自定义配置文件。目前只支持yml文件。
  12. 支持自定义扩展开发接口, 使用者可以在预留接口上扩展额外功能。
  13. 支持插件之间的通信。
  14. 支持插件中使用事务注解。
  15. 支持Swagger。(仅支持首次启动初始化的插件)

扩展包功能

  1. SpringBoot-Mybatis扩展包
  • 支持在插件中自定义Mapper接口、Mapper xml 以及对应的实体bean。

  • 支持实体bean的别名。

  • 支持集成Mybatis-Plus。

详见 插件SpringBoot Mybatis扩展

  1. 静态资源访问扩展包

支持通过http访问插件中静态资源。

详见 插件静态资源访问扩展

源码地址

https://gitee.com/starblues/springboot-plugin-framework-parent

运行环境

  1. jdk1.8+
  2. apache maven 3.6

mavne 仓库地址

https://mvnrepository.com/artifact/com.gitee.starblues/springboot-plugin-framework

最新文档

https://gitee.com/starblues/springboot-plugin-framework-parent/wikis/pages

快速入门

新建项目。

Maven目录结构下所示

-example
    - example-runner
        - pom.xml
    - example-main
        - pom.xml
    - example-plugin-parent
        - pom.xml
    - plugins
        - example-plugin1
            - pom.xml
            - plugin.properties
        - example-plugin2
            - pom.xml
            - plugin.properties
        - pom.xml
    - pom.xml

结构说明:

  1. pom.xml 代表maven的pom.xml
  2. plugin.properties 为开发环境下, 插件的元信息配置文件, 配置内容详见下文。
  3. example 为项目的总Maven目录。
  4. example-runner 在运行环境下启动的模块。主要依赖example-main模块和插件中使用到的依赖包。
  5. example-main 该模块为项目的主程序模块。
  6. example-plugin-parent 该模块为插件的父级maven pom 模块, 主要定义插件中公共用到的依赖, 以及插件的打包配置。
  7. plugins 该文件夹下主要存储插件模块。上述模块中主要包括example-plugin1、example-plugin2 两个插件。
  8. example-plugin1、example-plugin2 分别为两个插件Maven包。

主程序集成步骤

主程序为上述目录结构中的 example-main 模块。

  1. 在主程序中新增maven依赖包
<dependency>
    <groupId>com.gitee.starbluesgroupId>
    <artifactId>springboot-plugin-frameworkartifactId>
    <version>${springboot-plugin-framework.version}version>
dependency>

最新版本:
<dependency>
    <groupId>com.gitee.starbluesgroupId>
    <artifactId>springboot-plugin-frameworkartifactId>
    <version>2.1.2-RELEASEversion>
dependency>

  1. 实现并定义配置

实现 com.plugin.development.integration.IntegrationConfiguration 接口。

import com.gitee.starblues.integration.DefaultIntegrationConfiguration;
import org.pf4j.RuntimeMode;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "plugin")
public class PluginConfiguration extends DefaultIntegrationConfiguration {

    /**
     * 运行模式
     *  开发环境: development、dev
     *  生产/部署 环境: deployment、prod
     */
    @Value("${runMode:dev}")
    private String runMode;

    /**
     * 插件的路径
     */
    @Value("${pluginPath:plugins}")
    private String pluginPath;

    /**
     * 插件文件的路径
     */
    @Value("${pluginConfigFilePath:pluginConfigs}")
    private String pluginConfigFilePath;


    @Override
    public RuntimeMode environment() {
        return RuntimeMode.byName(runMode);
    }

    @Override
    public String pluginPath() {
        return pluginPath;
    }

    @Override
    public String pluginConfigFilePath() {
        return pluginConfigFilePath;
    }

    /**
     * 重写上传插件包的临时存储路径。只适用于生产环境
     * @return String
     */
    @Override
    public String uploadTempPath() {
        return "temp";
    }

    /**
     * 重写插件备份路径。只适用于生产环境
     * @return String
     */
    @Override
    public String backupPath() {
        return "backupPlugin";
    }

    /**
     * 重写插件RestController请求的路径前缀
     * @return String
     */
    @Override
    public String pluginRestControllerPathPrefix() {
        return "/api/plugins";
    }

    /**
     * 重写是否启用插件id作为RestController请求的路径前缀。
     * 启动则插件id会作为二级路径前缀。即: /api/plugins/pluginId/**
     * @return String
     */
    @Override
    public boolean enablePluginIdRestControllerPathPrefix() {
        return true;
    }

    public String getRunMode() {
        return runMode;
    }

    public void setRunMode(String runMode) {
        this.runMode = runMode;
    }


    public String getPluginPath() {
        return pluginPath;
    }

    public void setPluginPath(String pluginPath) {
        this.pluginPath = pluginPath;
    }

    public String getPluginConfigFilePath() {
        return pluginConfigFilePath;
    }

    public void setPluginConfigFilePath(String pluginConfigFilePath) {
        this.pluginConfigFilePath = pluginConfigFilePath;
    }

    @Override
    public String toString() {
        return "PluginArgConfiguration{" +
                "runMode='" + runMode + '\'' +
                ", pluginPath='" + pluginPath + '\'' +
                ", pluginConfigFilePath='" + pluginConfigFilePath + '\'' +
                '}';
    }
}

配置说明:

runMode:运行项目时的模式。分为开发环境(dev)、生产环境(prod)

pluginPath: 插件的路径。开发环境建议直接配置为插件模块的父级目录。例如: plugins。如果启动主程序时, 插件为加载, 请检查该配置是否正确。

pluginConfigFilePath: 在生产环境下, 插件的配置文件路径。在生产环境下, 请将所有插件使用到的配置文件统一放到该路径下管理。如果启动主程序时, 报插件的配置文件加载错误, 有可能是该该配置不合适导致的。

uploadTempPath: 上传插件包时使用。上传插件包存储的临时路径。默认 temp(相对于主程序jar路径)

backupPath: 备份插件包时使用。备份插件包的路径。默认: backupPlugin(相对于主程序jar路径)

pluginRestControllerPathPrefix: 插件RestController请求的路径前缀

enablePluginIdRestControllerPathPrefix: 是否启用插件id作为RestController请求的路径前缀。启动则插件id会作为二级路径前缀。即: /api/plugins/pluginId/**

  1. 配置bean
import com.gitee.starblues.integration.*;
import com.gitee.starblues.integration.initialize.AutoPluginInitializer;
import com.gitee.starblues.integration.initialize.PluginInitializer;
import org.pf4j.PluginException;
import org.pf4j.PluginManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class PluginBeanConfig {

  /**
     * 通过默认的集成工厂返回 PluginManager
     * @param integrationConfiguration 集成的配置文件
     * @return
     * @throws PluginException
     */
    @Bean
    public PluginManager pluginManager(IntegrationConfiguration integrationConfiguration) throws PluginException {
        IntegrationFactory integrationFactory = new DefaultIntegrationFactory();
        return integrationFactory.getPluginManager(integrationConfiguration);
    }

    /**
     * 定义默认的插件应用。使用可以注入它操作插件。
     * @return
     */
    @Bean
    public PluginApplication pluginApplication(){
        return new DefaultPluginApplication();
    }

    /**
     * 初始化插件。此处定义可以在系统启动时自动加载插件。
     *  如果想手动加载插件, 则可以使用 com.plugin.development.integration.initialize.ManualPluginInitializer 来初始化插件。
     * @param pluginApplication
     * @return
     */
    @Bean
    public PluginInitializer pluginInitializer(PluginApplication pluginApplication){
        AutoPluginInitializer autoPluginInitializer = new AutoPluginInitializer(pluginApplication);
        return autoPluginInitializer;
    }

}

插件包集成步骤

  1. 插件包pom.xml配置说明

provided 方式引入springboot-plugin-framework包

<dependency>
    <groupId>com.gitee.starbluesgroupId>
    <artifactId>springboot-plugin-frameworkartifactId>
    <version>${springboot-plugin-framework.version}version>
    <scope>providedscope>
dependency>

定义打包配置.主要用途是将 Plugin-Id、Plugin-Version、Plugin-Provider、Plugin-Class、Plugin-Dependencies的配置值定义到META-INF\MANIFEST.MF文件中

<properties>
    <plugin.id>springboot-plugin-example-plugin1plugin.id>
    <plugin.class>com.plugin.example.plugin1.DefinePluginplugin.class>
    <plugin.version>${project.version}plugin.version>
    <plugin.provider>StarBluesplugin.provider>
    <plugin.dependencies>plugin.dependencies>

    <java.version>1.8java.version>
    <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>

    <maven-compiler-plugin.version>3.7.0maven-compiler-plugin.version>
    <maven-assembly-plugin.version>3.1.1maven-assembly-plugin.version>
properties>
<build>
    <plugins>
        
        <plugin>
            <groupId>org.apache.maven.pluginsgroupId>
            <artifactId>maven-compiler-pluginartifactId>
            <version>${maven-compiler-plugin.version}version>
            <configuration>
                <source>${java.version}source>
                <target>${java.version}target>
                <encoding>${project.build.sourceEncoding}encoding>
            configuration>
        plugin>

        <plugin>
            <groupId>org.apache.maven.pluginsgroupId>
            <artifactId>maven-assembly-pluginartifactId>
            <version>${maven-assembly-plugin.version}version>
            <configuration>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependenciesdescriptorRef>
                descriptorRefs>
                <archive>
                    <manifest>
                        <addDefaultImplementationEntries>trueaddDefaultImplementationEntries>
                        <addDefaultSpecificationEntries>trueaddDefaultSpecificationEntries>
                    manifest>
                    <manifestEntries>
                        <Plugin-Id>${plugin.id}Plugin-Id>
                        <Plugin-Version>${plugin.version}Plugin-Version>
                        <Plugin-Provider>${plugin.provider}Plugin-Provider>
                        <Plugin-Class>${plugin.class}Plugin-Class>
                    manifestEntries>
                archive>
            configuration>
            <executions>
                <execution>
                    <id>make-assemblyid>
                    <phase>packagephase>
                    <goals>
                        <goal>singlegoal>
                    goals>
                execution>
            executions>
        plugin>
    plugins>
build>
  1. 在插件包的一级目录下新建plugin.properties文件(用于开发环境)
    新增如下内容(属性值同步骤1中pom.xml定义的manifestEntries属性一致):
plugin.id=springboot-plugin-example-plugin1
plugin.class=com.plugin.example.plugin1.DefinePlugin
plugin.version=2.0-SNAPSHOT
plugin.provider=StarBlues

配置说明:

plugin.id: 插件id
plugin.class: 插件实现类。见步骤3说明
plugin.version: 插件版本
plugin.provider: 插件作者
  1. 继承 com.gitee.starblues.realize.BasePlugin
import com.gitee.starblues.realize.BasePlugin;
import org.pf4j.PluginException;
import org.pf4j.PluginWrapper;

public class DefinePlugin extends BasePlugin {
   public DefinePlugin(PluginWrapper wrapper) {
        super(wrapper);
    }

    @Override
    protected void startEvent() throws PluginException {

    }

    @Override
    protected void deleteEvent() throws PluginException {

    }

    @Override
    protected void stopEvent() {

    }
}

并且将该类的包路径(com.plugin.example.plugin1.DefinePlugin)配置在步骤1和2的plugin.class属性中。

  1. 新增HelloPlugin1 controller

此步骤主要验证环境是否加载插件成功。


@RestController
@RequestMapping(path = "plugin1")
public class HelloPlugin1 {

    @GetMapping()
    public String getConfig(){
        return "hello plugin1"
    }

}

运行配置

  1. 配置模块 example-runner 的pom.xml
  • 将主程序的依赖新增到pom.xml 下
  • 将插件中的依赖以 provided 方式引入到 pom.xml 下

如下所示:


<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">

    <modelVersion>4.0.0modelVersion>

    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.0.3.RELEASEversion>
        <relativePath/>
    parent>

    <groupId>com.gitee.starbluesgroupId>
    <artifactId>plugin-example-runnerartifactId>
    <version>2.0-RELEASEversion>
    <packaging>pompackaging>

    <properties>
        <gson.version>2.8.2gson.version>
    properties>

    <dependencies>
        <dependency>
            <groupId>com.gitee.starbluesgroupId>
            <artifactId>plugin-example-startartifactId>
            <version>${project.version}version>
        dependency>

        
        <dependency>
            <groupId>com.google.code.gsongroupId>
            <artifactId>gsonartifactId>
            <version>${gson.version}version>
            <scope>providedscope>
        dependency>

    dependencies>

project>
  1. 设置idea的启动

Working directory : D:\xx\xx\springboot-plugin-framework-parent\plugin-example

Use classpath of module: plugin-exampe-runner

勾选: Include dependencies with “Provided” scope

  1. 启动2步骤的配置。

观察日志出现如下说明加载插件成功。

 Plugin '[email protected]' resolved
 Start plugin '[email protected]'
 Init Plugins <springboot-plugin-example-plugin1> Success
  1. 访问插件中的Controller 验证。

浏览器输入:http://ip:port/api/plugins/springboot-plugin-example-plugin1/plugin1

响应并显示: hello plugin1

说明集成成功!

使用说明

插件中定义配置文件

  1. 在插件包的 resources 目录下定义配置文件 plugin1.yml
name: plugin1
plugin: examplePlugin1
setString:
  - set1
  - set2
listInteger:
  - 1
  - 2
  - 3
subConfig:
  subName: subConfigName
  1. 在代码中定义对应的bean
import com.gitee.starblues.annotation.ConfigDefinition;
import java.util.List;
import java.util.Set;

@ConfigDefinition("plugin1.yml")
public class PluginConfig1 {

    private String name;
    private String plugin;
    private Set<String> setString;
    private List<Integer> listInteger;
    private String defaultValue = "defaultValue";
    private SubConfig subConfig;

    // 自行提供get set 方法

}


public class SubConfig {

    private String subName;
    public String getSubName() {
        return subName;
    }
    
    // 自行提供get set 方法
}

该bean必须加上 @ConfigDefinition(“plugin1.yml”) 注解。其中值为插件文件的名称。

  1. 其他地方使用时, 可以通过注入方式使用。

例如:

@Component("plugin2HelloService")
public class HelloService {

    private final PluginConfig1 pluginConfig1;
    private final Service2 service2;

    @Autowired
    public HelloService(PluginConfig1 pluginConfig1, Service2 service2) {
        this.pluginConfig1 = pluginConfig1;
        this.service2 = service2;
    }

    public PluginConfig1 getPluginConfig1(){
        return pluginConfig1;
    }


    public String sayService2(){
        return service2.getName();
    }

}
  1. 注意事项

在开发环境:配置文件必须放在resources目录下。并且@ConfigDefinition(“plugin1.yml”)中定义的文件名和resources下配置的文件名一致。

在生产环境: 该文件存放在pluginConfigFilePath配置的目录下。

插件之间数据交互功能

插件之间的数据交互功能, 是在同一JVM运行环境下, 基于代理、反射机制完成方法调用。使用说明如下:

  1. 被调用类需要使用注解 @Supplier(""), 注解值为被调用者的唯一key, (全局key不能重复) 供调用者使用。例如:
@Supplier("SupplierService")
public class SupplierService {

    public Integer add(Integer a1, Integer a2){
        return a1 + a2;
    }
    
}
  1. 另一个插件中要调用1步骤中定义的调用类时, 需要定义一个接口,新增注解@Caller(""), 值为1步骤中被调用者定义的全局key。其中方法名、参数个数和类型、返回类型需要和被调用者中定义的方法名、参数个数和类型一致。例如:
@Caller("SupplierService")
public interface CallerService {

    Integer add(Integer a1, Integer a2);
    
}

3.被调用者和调用者也可以使用注解定义被调用的方法。例如:

被调用者:

@Supplier("SupplierService")
public class SupplierService {

    @Supplier.Method("call")
    public String call(CallerInfo callerInfo, String key){
        System.out.println(callerInfo);
        return key;
    }
    
}

调用者:

@Caller("SupplierService")
public interface CallerService {

    @Caller.Method("call")
    String test(CallerInfo callerInfo, String key);
    
}

该场景主要用于参数类型不在同一个地方定义时使用。比如 被调用者的参数类: CallerInfo 定义在被调用者的插件中, 调用者的参数类: CallerInfo 定义在调用者的插件中。就必须配合 @Supplier.Method("")、@Caller.Method("") 注解使用, 否则会导致NotFoundClass 异常。

如果调用者没有使用注解 @Caller.Method("") 则默认使用方法和参数类型来调用。

4.对于3步骤中问题的建议

可以将被调用者和调用者的公用参数和返回值定义在主程序中、或者单独提出一个api maven包, 然后两者都依赖该包。

5.案例位置

pluging-example:

com.plugin.example.plugin1.service.SupplierService
com.plugin.example.plugin2.service.CallerService
com.plugin.example.plugin2.rest.ProxyController

集成扩展

  1. SpringBoot Mybatis 扩展

文档见: springboot-plugin-framework-extension-mybatis

案例部署

普通例子运行见:package/example

windows环境下运行: package.bat

linux、mac 环境下运行: package.sh

mybatis 案例部署

普通例子运行见:package/example-persistence

windows环境下运行: package.bat

linux、mac 环境下运行: package.sh

sql在 plugin-example-persistence/sql 文件夹下。

生产环境目录

-main.jar

-main.yml

-plugins
  -plugin1.jar
  -plugin2.jar
  
-pluginFile
  -plugin1.yml
  -plugin2.yml

案例说明

plugin-example:插件基础功能案例。

plugin-example-persistence: 针对Mybatis集成的案例。

生产环境配置禁用启用功能

启用功能

1.在插件目录下新建 enabled.txt 文件
2.enabled.txt的内容为:

########################################
# - 启用的插件
########################################
example-plugin1

将需要启用的插件id配置到文件中。

所有注释行(以#字符开头的行)都将被忽略。

启用、禁用功能

1.在插件目录下新建 disabled.txt 文件
2.disabled.txt的内容为:

########################################
# - 禁用的插件
########################################
example-plugin1

将需要启用的插件id配置到文件中。

所有注释行(以#字符开头的行)都将被忽略。

注意事项

  1. 插件中代码编写完后, 请保证在class文件下的类都是最新编译的, 再运行主程序, 否则会导致运行的插件代码不是最新的。
  2. 如果启动时插件没有加载。请检查配置文件中的 pluginPath
如果pluginPath 配置为相当路径,请检查是否是相对于当前工作环境的目录。

如果pluginPath配置为绝对路径,请检查路径是否正确。
  1. 如果出现Spring包冲突。可以排除Spring包。
    例如:
<dependency>
<groupId>com.gitee.starbluesgroupId>
<artifactId>springboot-plugin-frameworkartifactId>
<version>${springboot-plugin-framework.version}version>
<exclusions>
    <exclusion>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-contextartifactId>
    exclusion>
     <exclusion>
        <groupId>org.springframeworkgroupId>
        <artifactId>spring-webmvcartifactId>
     exclusion>
exclusions>
dependency>
  1. 以下功能只适用于生产环境下。
  • 插件的上传。
  • 插件的动态更新(上传并安装插件)。
  • 插件的备份。
  • 插件的配置文件上传。
  • 删除插件

小技巧

  1. idea 启动主程序时, 自动编译插件包的配置
    选择
    File->Project Structure->Project Settings->Artifacts->点击+号->JAR->From modules whith dependencies->选择对应的插件包->确认OK

启动配置:
在Before launch 下-> 点击小+号 -> Build ->Artifacts -> 选择上一步新增的>Artifacts

QQ交流群

859570617

版本更新

2.1.2 版本

  1. 修复使用多AOP情况, 无法加载插件类(被AOP代理的类)的bug。
  2. 新增可以通过插件id获取插件中的bean的实现。详见:PluginUser->getPluginBeans(String pluginId, Class aClass)
  3. 新增插件注册监听器可通过Class方式添加。案例详见: basic-eaxmple->com.basic.example.main.config.ExamplePluginListener

2.1.1 版本

  1. 插件中支持事务注解。
  2. 修复重复启动插件时报错的bug。

2.1.0 版本

  1. 修复mybatis案例无法加载mapper.xml的bug。
  2. 优化代码逻辑。
  3. 新增插件间的通信。详见文档-使用说明->插件之间数据交互功能

2.0.3 版本

  1. 修复插件动态重新安装后, 无法访问到插件中的接口的bug。

2.0.2 版本

  1. 新增 com.gitee.starblues.integration.user.PluginUser

使用场景: 在主程序中定义了接口, 插件中存在实现了该接口的实现类, 通过PluginUser 的 getPluginBeans(接口Class) 可以获取所有插件中实现该接口的实现类。具体详见源码。

  1. 新增插件bean刷新抽象类。继承它可动态获取接口实现类集合。

2.0.1 版本

  1. 修复插件的Controller无法定义一级请求路径的bug。

2.0 版本(重大版本更新)

  1. 重构代码。
  2. 新增扩展机制。
  3. 简化依赖注入注解, 保持与SpringBoot依赖注入方式一致。
  4. 新增插件工厂监听器、新增插件初始化监听器(适用于第一次启动)。
  5. 新增插件包Mybatis的集成, 可在插件包中独立定义Mapper接口、Mapper xml、实体bean。

1.1 版本

  1. 新增插件注册、卸载监听器。
  2. 新增可通过 PluginUser 获取插件中实现主程序中定义的接口的实现类。
  3. 新增插件注册、卸载时监听器。

你可能感兴趣的:(springboot,Spring,java)