diff --git a/.gitignore b/.gitignore index 00e7e9cbcc16998f6c300dc49d7f01d739878be0..dae69c503907873b2d326f420c2fa747f038f219 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,6 @@ dist # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* + +# custom +springboot-plugin-framework-parent.zip diff --git a/README.md b/README.md index 7c01cb34c67448aace978a6e6ea9d98655efa186..7a0ac8123277d98aaa5b7969ae57e9c8e0492fb0 100644 --- a/README.md +++ b/README.md @@ -29,840 +29,10 @@ [https://mvnrepository.com/artifact/com.gitee.starblues/springboot-plugin-framework](https://mvnrepository.com/artifact/com.gitee.starblues/springboot-plugin-framework) -### 快速入门 +### 文档地址 -#### 新建项目。 -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 -``` +[https://gitee.com/starblues/springboot-plugin-framework-parent/wikis/pages](https://gitee.com/starblues/springboot-plugin-framework-parent/wikis/pages) -结构说明: - -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依赖包 - -```xml - - com.gitee.starblues - springboot-plugin-framework - ${springboot-plugin-framework.version} - - -最新版本: - - com.gitee.starblues - springboot-plugin-framework - 2.1.3-RELEASE - - -``` - -2. 实现并定义配置 - -实现 **com.plugin.development.integration.IntegrationConfiguration** 接口。 - -```java -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/** - - -3. 配置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包 - -```xml - - com.gitee.starblues - springboot-plugin-framework - ${springboot-plugin-framework.version} - provided - -``` - -定义打包配置.主要用途是将 `Plugin-Id、Plugin-Version、Plugin-Provider、Plugin-Class、Plugin-Dependencies`的配置值定义到`META-INF\MANIFEST.MF`文件中 -```xml - - example-plugin1 - com.plugin.example.plugin1.DefinePlugin - ${project.version} - StarBlues - - - 1.8 - UTF-8 - UTF-8 - - 3.7.0 - 3.1.1 - - - - - - org.apache.maven.plugins - maven-compiler-plugin - ${maven-compiler-plugin.version} - - ${java.version} - ${java.version} - ${project.build.sourceEncoding} - - - - - org.apache.maven.plugins - maven-assembly-plugin - ${maven-assembly-plugin.version} - - - jar-with-dependencies - - - - true - true - - - ${plugin.id} - ${plugin.version} - ${plugin.provider} - ${plugin.class} - - - - - - make-assembly - package - - single - - - - - - -``` - -2. 在插件包的一级目录下新建plugin.properties文件(用于开发环境) -新增如下内容(属性值同步骤1中pom.xml定义的`manifestEntries`属性一致): -``` -plugin.id=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: 插件作者 -``` - -3. 继承 `com.gitee.starblues.realize.BasePlugin` 包 -``` java -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属性中。 - -4. 新增HelloPlugin1 controller - -此步骤主要验证环境是否加载插件成功。 - -```java - -@RestController -@RequestMapping(path = "plugin1") -public class HelloPlugin1 { - - @GetMapping() - public String getConfig(){ - return "hello plugin1 example"; - } - -} - -``` - -#### 运行配置 -1. 配置模块 example-runner 的pom.xml - -- 将主程序的依赖新增到pom.xml 下 -- 将插件中的依赖以 `provided` 方式引入到 pom.xml 下 - -如下所示: - -``` xml - - - - 4.0.0 - - - org.springframework.boot - spring-boot-starter-parent - 2.0.3.RELEASE - - - - com.gitee.starblues - plugin-example-runner - 2.0-RELEASE - pom - - - 2.8.2 - - - - - com.gitee.starblues - plugin-example-start - ${project.version} - - - - - - - - -``` - -2. 设置idea的启动 - -Working directory : D:\xx\xx\plugin-example - -Use classpath of module: plugin-exampe-runner - -勾选: Include dependencies with "Provided" scope - -3. 启动2步骤的配置。 - -观察日志出现如下说明加载插件成功。 - -``` java - Plugin 'example-plugin1@2.0-RELEASE' resolved - Start plugin 'example-plugin1@2.0-RELEASE' - Init Plugins Success -``` - -4. 访问插件中的Controller 验证。 - -浏览器输入:http://ip:port/api/plugins/example-plugin1/plugin1 - -响应并显示: hello plugin1 example - -说明集成成功! - -### 使用说明 - -#### 插件中定义配置文件 - -1. 在插件包的 resources 目录下定义配置文件 plugin1.yml - -```yml -name: plugin1 -plugin: examplePlugin1 -setString: - - set1 - - set2 -listInteger: - - 1 - - 2 - - 3 -subConfig: - subName: subConfigName -``` - -2. 在代码中定义对应的bean - -```java -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 setString; - private List 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") 注解。其中值为插件文件的名称。 - -3. 其他地方使用时, 可以通过注入方式使用。 - -例如: -``` java -@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(); - } - -} -``` - -4. 注意事项 - -*在开发环境:配置文件必须放在resources目录下。并且@ConfigDefinition("plugin1.yml")中定义的文件名和resources下配置的文件名一致。* - -*在生产环境: 该文件存放在`pluginConfigFilePath`配置的目录下。生产环境下插件的配置文件必须外置, 不能使用jar包里面的配置文件 * - -#### 插件之间数据交互功能 - -插件之间的数据交互功能, 是在同一JVM运行环境下, 基于代理、反射机制完成方法调用。使用说明如下: - -1. 被调用类需要使用注解 @Supplier(""), 注解值为被调用者的唯一key, (全局key不能重复) 供调用者使用。例如: -```java -@Supplier("SupplierService") -public class SupplierService { - - public Integer add(Integer a1, Integer a2){ - return a1 + a2; - } - -} -``` -2. 另一个插件中要调用1步骤中定义的调用类时, 需要定义一个接口,新增注解@Caller(""), 值为1步骤中被调用者定义的全局key。其中方法名、参数个数和类型、返回类型需要和被调用者中定义的方法名、参数个数和类型一致。例如: -```java -@Caller("SupplierService") -public interface CallerService { - - Integer add(Integer a1, Integer a2); - -} -``` - -3.被调用者和调用者也可以使用注解定义被调用的方法。例如: - -被调用者: - -```java -@Supplier("SupplierService") -public class SupplierService { - - @Supplier.Method("call") - public String call(CallerInfo callerInfo, String key){ - System.out.println(callerInfo); - return key; - } - -} -``` - -调用者: - -```java -@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.案例位置 - -basic-example: - -com.basic.example.plugin1.service.SupplierService -com.basic.example.plugin2.service.CallerService -com.basic.example.plugin2.rest.ProxyController - - - -### 集成扩展 - -1. SpringBoot Mybatis 扩展 - -文档见: [springboot-plugin-framework-extension-mybatis](https://gitee.com/starblues/springboot-plugin-framework-parent/tree/master/springboot-plugin-framework-extension/springboot-plugin-framework-extension-mybatis) - - -### 案例说明 - -basic-example:插件基础功能案例。 - -integration-mybatis: 针对Mybatis集成的案例。 - -integration-mybatisplus: 针对Mybatis-Plus集成的案例 - - -#### 基础功能案例演示 - -- 普通例子运行见:basic-example - -- windows环境下运行: package.bat - -- linux、mac 环境下运行: package.sh - -- 接口地址查看: http://127.0.0.1:8080/doc.html - -#### mybatis 案例演示 - -- 例子见:integration-mybatis - -- windows环境下运行: package.bat - -- linux、mac 环境下运行: package.sh - -- sql在 integration-mybatis/sql 文件夹下。 - -- 接口地址查看: http://127.0.0.1:8081/doc.html - -#### mybatis-plus 案例演示 - -- 例子见:integration-mybatisplus - -- windows环境下运行: package.bat - -- linux、mac 环境下运行: package.sh - -- sql在 integration-mybatisplus/sql 文件夹下。 - -- 接口地址查看: http://127.0.0.1:8082/doc.html - -#### 案例常见报错 - -1. 插件未编译 -java.lang.ClassNotFoundException: com.basic.example.plugin1.DefinePlugin -java.lang.ClassNotFoundException: com.basic.example.plugin2.DefinePlugin -该类型报错是由于插件源码没有编译成 class 文件; 需要手动编译, 保证在插件目录出现 target 文件 - -### 生产环境目录 - -```text --main.jar - --main.yml - --plugins - -plugin1.jar - -plugin2.jar - --pluginFile - -plugin1.yml - -plugin2.yml - -``` - - -### 生产环境配置禁用启用功能 - -#### 启用功能 - -1.在插件目录下新建 `enabled.txt` 文件 -2.enabled.txt的内容为: - -```text -######################################## -# - 启用的插件 -######################################## -example-plugin1 -``` -将需要启用的插件id配置到文件中。 - -所有注释行(以#字符开头的行)都将被忽略。 - -#### 启用、禁用功能 - -1.在插件目录下新建 `disabled.txt` 文件 -2.disabled.txt的内容为: - -```text -######################################## -# - 禁用的插件 -######################################## -example-plugin1 -``` -将需要启用的插件id配置到文件中。 - -所有注释行(以#字符开头的行)都将被忽略。 - - -### 注意事项 - -1. 插件中代码编写完后, 请保证在class文件下的类都是最新编译的, 再运行主程序, 否则会导致运行的插件代码不是最新的。 -2. 如果启动时插件没有加载。请检查配置文件中的 pluginPath - -```text -如果 pluginPath 配置为相当路径,请检查是否是相对于当前工作环境的目录。 - -如果 pluginPath 配置为绝对路径,请检查路径是否正确。 -``` - -3. 如果出现Spring包冲突。可以排除Spring包。 -例如: -```xml - -com.gitee.starblues -springboot-plugin-framework -${springboot-plugin-framework.version} - - - org.springframework - spring-context - - - org.springframework - spring-webmvc - - - -``` -4. 以下功能只适用于生产环境下。 -- 插件的上传。 -- 插件的动态更新(上传并安装插件)。 -- 插件的备份。 -- 插件的配置文件上传。 -- 删除插件 - - -### 小技巧 -1. idea 启动主程序时, 自动编译插件包的配置 -选择 -File->Project Structure->Project Settings->Artifacts->点击+号->JAR->From modules whith dependencies->选择对应的插件包->确认OK - -启动配置: -在Before launch 下-> 点击小+号 -> Build ->Artifacts -> 选择上一步新增的>Artifacts ### QQ交流群 -859570617 - -### 版本更新 - -#### 2.1.3 版本 -在PluginUser接口新增getMainBeans方法, 用于获取Spring管理的主程序接口的实现类。 - -#### 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) 可以获取所有插件中实现该接口的实现类。具体详见源码。 - -2. 新增插件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. 新增插件注册、卸载时监听器。 \ No newline at end of file +859570617 \ No newline at end of file diff --git a/example/basic-example/basic-example-main/src/main/java/com/basic/example/main/config/PluginConfiguration.java b/example/basic-example/basic-example-main/src/main/java/com/basic/example/main/config/PluginConfiguration.java index 466c9b0422fa3393c93d8bbeee1e322eb7d77690..1052f283df2bc349d16bcff7bb1aae75eb5875fc 100644 --- a/example/basic-example/basic-example-main/src/main/java/com/basic/example/main/config/PluginConfiguration.java +++ b/example/basic-example/basic-example-main/src/main/java/com/basic/example/main/config/PluginConfiguration.java @@ -90,6 +90,10 @@ public class PluginConfiguration extends DefaultIntegrationConfiguration { return true; } + + + + public String getRunMode() { return runMode; } diff --git a/example/basic-example/basic-example-main/src/main/java/com/basic/example/main/rest/PluginResource.java b/example/basic-example/basic-example-main/src/main/java/com/basic/example/main/rest/PluginResource.java index 5491bd0ceb3b6ddd28e34d3d336951a5e85bfe97..e26cf8714afac90743ce904c5d0b5378ca8f004b 100644 --- a/example/basic-example/basic-example-main/src/main/java/com/basic/example/main/rest/PluginResource.java +++ b/example/basic-example/basic-example-main/src/main/java/com/basic/example/main/rest/PluginResource.java @@ -1,6 +1,5 @@ package com.basic.example.main.rest; -import com.gitee.starblues.exception.PluginPlugException; import com.gitee.starblues.integration.PluginApplication; import com.gitee.starblues.integration.operator.PluginOperator; import com.gitee.starblues.integration.operator.module.PluginInfo; @@ -46,7 +45,7 @@ public class PluginResource { public Set getPluginFilePaths(){ try { return pluginOperator.getPluginFilePaths(); - } catch (PluginPlugException e) { + } catch (Exception e) { e.printStackTrace(); return null; } @@ -63,7 +62,7 @@ public class PluginResource { try { pluginOperator.stop(id); return "plugin<" + id +"> stop success"; - } catch (PluginPlugException e) { + } catch (Exception e) { e.printStackTrace(); return "plugin<" + id +"> stop failure : " + e.getMessage(); } @@ -79,7 +78,7 @@ public class PluginResource { try { pluginOperator.start(id); return "plugin<" + id +"> start success"; - } catch (PluginPlugException e) { + } catch (Exception e) { e.printStackTrace(); return "plugin<" + id +"> start failure : " + e.getMessage(); } @@ -96,7 +95,7 @@ public class PluginResource { try { pluginOperator.uninstall(id); return "plugin<" + id +"> uninstall success"; - } catch (PluginPlugException e) { + } catch (Exception e) { e.printStackTrace(); return "plugin<" + id +"> uninstall failure : " + e.getMessage(); } @@ -113,7 +112,7 @@ public class PluginResource { try { pluginOperator.install(Paths.get(path)); return "installByPath success"; - } catch (PluginPlugException e) { + } catch (Exception e) { e.printStackTrace(); return "installByPath failure : " + e.getMessage(); } @@ -130,7 +129,7 @@ public class PluginResource { try { pluginOperator.uploadPluginAndStart(multipartFile); return "install success"; - } catch (PluginPlugException e) { + } catch (Exception e) { e.printStackTrace(); return "install failure : " + e.getMessage(); } @@ -147,7 +146,7 @@ public class PluginResource { try { pluginOperator.uploadConfigFile(multipartFile); return "uploadConfig success"; - } catch (PluginPlugException e) { + } catch (Exception e) { e.printStackTrace(); return "uploadConfig failure : " + e.getMessage(); } @@ -164,7 +163,7 @@ public class PluginResource { try { pluginOperator.delete(pluginId); return "deleteById success"; - } catch (PluginPlugException e) { + } catch (Exception e) { e.printStackTrace(); return "deleteById failure : " + e.getMessage(); } @@ -180,7 +179,7 @@ public class PluginResource { try { pluginOperator.delete(Paths.get(pluginJarPath)); return "deleteByPath success"; - } catch (PluginPlugException e) { + } catch (Exception e) { e.printStackTrace(); return "deleteByPath failure : " + e.getMessage(); } @@ -196,7 +195,7 @@ public class PluginResource { try { pluginOperator.backupPlugin(pluginId, "testBack"); return "backupPlugin success"; - } catch (PluginPlugException e) { + } catch (Exception e) { e.printStackTrace(); return "backupPlugin failure : " + e.getMessage(); } diff --git a/example/integration-mybatis/integration-mybatis-main/src/main/java/com/mybatis/main/rest/PluginResource.java b/example/integration-mybatis/integration-mybatis-main/src/main/java/com/mybatis/main/rest/PluginResource.java index 0a966c8777616446dcb8327d7a1813709a6aa16b..cdaa874431e7044f7513fe6c14f99bbdeb1b8cc3 100644 --- a/example/integration-mybatis/integration-mybatis-main/src/main/java/com/mybatis/main/rest/PluginResource.java +++ b/example/integration-mybatis/integration-mybatis-main/src/main/java/com/mybatis/main/rest/PluginResource.java @@ -1,10 +1,10 @@ package com.mybatis.main.rest; -import com.gitee.starblues.exception.PluginPlugException; import com.gitee.starblues.integration.PluginApplication; import com.gitee.starblues.integration.operator.PluginOperator; import com.gitee.starblues.integration.operator.module.PluginInfo; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -46,7 +46,7 @@ public class PluginResource { public Set getPluginFilePaths(){ try { return pluginOperator.getPluginFilePaths(); - } catch (PluginPlugException e) { + } catch (Exception e) { e.printStackTrace(); return null; } @@ -63,7 +63,7 @@ public class PluginResource { try { pluginOperator.stop(id); return "plugin<" + id +"> stop success"; - } catch (PluginPlugException e) { + } catch (Exception e) { e.printStackTrace(); return "plugin<" + id +"> stop failure : " + e.getMessage(); } @@ -79,7 +79,7 @@ public class PluginResource { try { pluginOperator.start(id); return "plugin<" + id +"> start success"; - } catch (PluginPlugException e) { + } catch (Exception e) { e.printStackTrace(); return "plugin<" + id +"> start failure : " + e.getMessage(); } @@ -96,7 +96,7 @@ public class PluginResource { try { pluginOperator.uninstall(id); return "plugin<" + id +"> uninstall success"; - } catch (PluginPlugException e) { + } catch (Exception e) { e.printStackTrace(); return "plugin<" + id +"> uninstall failure : " + e.getMessage(); } @@ -113,7 +113,7 @@ public class PluginResource { try { pluginOperator.install(Paths.get(path)); return "installByPath success"; - } catch (PluginPlugException e) { + } catch (Exception e) { e.printStackTrace(); return "installByPath failure : " + e.getMessage(); } @@ -130,7 +130,7 @@ public class PluginResource { try { pluginOperator.uploadPluginAndStart(multipartFile); return "install success"; - } catch (PluginPlugException e) { + } catch (Exception e) { e.printStackTrace(); return "install failure : " + e.getMessage(); } @@ -147,7 +147,7 @@ public class PluginResource { try { pluginOperator.uploadConfigFile(multipartFile); return "uploadConfig success"; - } catch (PluginPlugException e) { + } catch (Exception e) { e.printStackTrace(); return "uploadConfig failure : " + e.getMessage(); } @@ -164,7 +164,7 @@ public class PluginResource { try { pluginOperator.delete(pluginId); return "deleteById success"; - } catch (PluginPlugException e) { + } catch (Exception e) { e.printStackTrace(); return "deleteById failure : " + e.getMessage(); } @@ -180,7 +180,7 @@ public class PluginResource { try { pluginOperator.delete(Paths.get(pluginJarPath)); return "deleteByPath success"; - } catch (PluginPlugException e) { + } catch (Exception e) { e.printStackTrace(); return "deleteByPath failure : " + e.getMessage(); } @@ -196,7 +196,7 @@ public class PluginResource { try { pluginOperator.backupPlugin(pluginId, "testBack"); return "backupPlugin success"; - } catch (PluginPlugException e) { + } catch (Exception e) { e.printStackTrace(); return "backupPlugin failure : " + e.getMessage(); } diff --git a/example/integration-mybatis/plugins/integration-mybatis-plugin1/src/main/java/com/mybatis/plugin1/service/TestTransactional1.java b/example/integration-mybatis/plugins/integration-mybatis-plugin1/src/main/java/com/mybatis/plugin1/service/TestTransactional1.java index f0ce55a19899a3f840d3c2ec291edaa9c46276d9..f3efe5919224d580e25d947d2cdb5f8e3f321517 100644 --- a/example/integration-mybatis/plugins/integration-mybatis-plugin1/src/main/java/com/mybatis/plugin1/service/TestTransactional1.java +++ b/example/integration-mybatis/plugins/integration-mybatis-plugin1/src/main/java/com/mybatis/plugin1/service/TestTransactional1.java @@ -2,6 +2,8 @@ package com.mybatis.plugin1.service; import com.mybatis.plugin1.mapper.Plugin1Mapper; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; diff --git a/springboot-plugin-framework/pom.xml b/springboot-plugin-framework/pom.xml index 55f83872dd50e13ee33decf2e9528de81c607cf4..1ed1d4d71185b060966b8a8c844930db83cdb922 100644 --- a/springboot-plugin-framework/pom.xml +++ b/springboot-plugin-framework/pom.xml @@ -105,6 +105,12 @@ ${jackson.version} + + commons-io + commons-io + 2.4 + + javax.servlet javax.servlet-api diff --git a/springboot-plugin-framework/src/main/java/com/gitee/starblues/exception/PluginPlugException.java b/springboot-plugin-framework/src/main/java/com/gitee/starblues/exception/PluginPlugException.java deleted file mode 100644 index 6654af4341f73755c6934c9b960ae48cd77694df..0000000000000000000000000000000000000000 --- a/springboot-plugin-framework/src/main/java/com/gitee/starblues/exception/PluginPlugException.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.gitee.starblues.exception; - -/** - * 插件安装异常 - * @author zhangzhuo - * @version 1.0 - */ -public class PluginPlugException extends Exception{ - - - public PluginPlugException() { - super(); - } - - public PluginPlugException(String message) { - super(message); - } - - public PluginPlugException(String message, Throwable cause) { - super(message, cause); - } - - public PluginPlugException(Throwable cause) { - super(cause); - } - - protected PluginPlugException(String message, Throwable cause, boolean enableSuppression, - boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } -} diff --git a/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/DefaultPluginFactory.java b/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/DefaultPluginFactory.java index 4e8c24c9a9b638b8f2a3c788c942dd392a130524..1aa92b295293a5f65cba72097fe23046733023a5 100644 --- a/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/DefaultPluginFactory.java +++ b/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/DefaultPluginFactory.java @@ -84,7 +84,7 @@ public class DefaultPluginFactory implements PluginFactory { return this; } catch (Exception e) { pluginListenerFactory.failure(pluginWrapper.getPluginId(), e); - throw new Exception(e); + throw e; } finally { buildType = 1; AopUtils.recoverAop(); @@ -107,7 +107,7 @@ public class DefaultPluginFactory implements PluginFactory { return this; } catch (Exception e) { pluginListenerFactory.failure(pluginId, e); - throw new Exception(e); + throw e; } finally { buildType = 2; } diff --git a/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/PluginRegistryInfo.java b/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/PluginRegistryInfo.java index 120d0cec854ee2df7b65e99a1d06373484447708..9d74d0b6dcb03cbb11d27db2ed4b3bf0eef81d34 100644 --- a/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/PluginRegistryInfo.java +++ b/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/PluginRegistryInfo.java @@ -4,6 +4,7 @@ import com.gitee.starblues.realize.BasePlugin; import org.pf4j.PluginWrapper; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; /** * 注册的插件信息 @@ -13,11 +14,15 @@ import java.util.*; */ public class PluginRegistryInfo { + /** + * 全局扩展信息 + */ + private static Map globalExtensionMap = new ConcurrentHashMap<>(); /** * 扩展存储项 */ - private Map extensionMap = new HashMap<>(); + private Map extensionMap = new ConcurrentHashMap<>(); private PluginWrapper pluginWrapper; private BasePlugin basePlugin; @@ -104,8 +109,6 @@ public class PluginRegistryInfo { - - /** * 添加扩展数据 * @param key 扩展的key @@ -130,4 +133,30 @@ public class PluginRegistryInfo { } } + + + /** + * 添加全局扩展数据 + * @param key 扩展的key + * @param value 扩展值 + */ + public static synchronized void addGlobalExtension(String key, Object value){ + globalExtensionMap.put(key, value); + } + + /** + * 获取全局扩展值 + * @param key 全局扩展的key + * @param 返回值泛型 + * @return 扩展值 + */ + public static synchronized T getGlobalExtension(String key){ + Object o = globalExtensionMap.get(key); + if(o == null){ + return null; + } else { + return (T) o; + } + } + } diff --git a/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/SpringBeanRegister.java b/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/SpringBeanRegister.java index 82cced357a9cbc8a024bb52de54e459d0aecf593..e1307dc23066d5056f36343afbac493d5c17a154 100644 --- a/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/SpringBeanRegister.java +++ b/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/SpringBeanRegister.java @@ -1,6 +1,8 @@ package com.gitee.starblues.factory; import com.gitee.starblues.factory.process.pipe.bean.name.PluginAnnotationBeanNameGenerator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.context.ApplicationContext; @@ -17,6 +19,8 @@ import java.util.function.Consumer; */ public class SpringBeanRegister { + private static final Logger logger = LoggerFactory.getLogger(SpringBeanRegister.class); + private final GenericApplicationContext applicationContext; public SpringBeanRegister(ApplicationContext applicationContext){ @@ -36,12 +40,12 @@ public class SpringBeanRegister { /** * 默认注册 * @param pluginId 插件id - * @param namePrefix bean名称前缀 + * @param suffixName bean 后缀名称 * @param aClass 类名 * @return 注册的bean名称 */ - public String register(String pluginId, String namePrefix, Class aClass) { - return register(pluginId, namePrefix, aClass, null); + public String register(String pluginId, String suffixName, Class aClass) { + return register(pluginId, suffixName, aClass, null); } @@ -59,25 +63,24 @@ public class SpringBeanRegister { /** * 默认注册 * @param pluginId 插件id - * @param namePrefix bean名称前缀 + * @param suffixName bean 后缀名称 * @param aClass 注册的类 * @param consumer 自定义处理AnnotatedGenericBeanDefinition * @return 注册的bean名称 */ - public String register(String pluginId, String namePrefix, Class aClass, Consumer consumer) { + public String register(String pluginId, String suffixName, Class aClass, + Consumer consumer) { AnnotatedGenericBeanDefinition beanDefinition = new AnnotatedGenericBeanDefinition(aClass); - if(namePrefix == null){ - namePrefix = ""; - } BeanNameGenerator beanNameGenerator = - new PluginAnnotationBeanNameGenerator(namePrefix); + new PluginAnnotationBeanNameGenerator(suffixName); String beanName = beanNameGenerator.generateBeanName(beanDefinition, applicationContext); if(PluginInfoContainer.existRegisterBeanName((beanName))){ String error = MessageFormat.format("Bean name {0} already exist of {1}", beanName, aClass.getName()); - throw new RuntimeException(error); + logger.error(error); + return null; } if(consumer != null){ consumer.accept(beanDefinition); diff --git a/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/process/pipe/bean/BasicBeanProcessor.java b/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/process/pipe/bean/BasicBeanProcessor.java index 0726d989575e163ee5dfe6d33102f5f73048e170..1ed1e8d71b309d5e7a7b0ea6546f30c40393d269 100644 --- a/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/process/pipe/bean/BasicBeanProcessor.java +++ b/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/process/pipe/bean/BasicBeanProcessor.java @@ -6,12 +6,18 @@ import com.gitee.starblues.factory.process.pipe.PluginPipeProcessor; import com.gitee.starblues.factory.process.pipe.classs.group.ComponentGroup; import com.gitee.starblues.factory.process.pipe.classs.group.ConfigurationGroup; import com.gitee.starblues.factory.process.pipe.classs.group.RepositoryGroup; +import org.springframework.aop.Advisor; +import org.springframework.aop.framework.autoproxy.BeanFactoryAdvisorRetrievalHelper; +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.ApplicationContext; +import org.springframework.core.annotation.AnnotationUtils; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; /** * 基础bean注册 @@ -21,13 +27,19 @@ import java.util.Set; */ public class BasicBeanProcessor implements PluginPipeProcessor { + private static final String KEY = "BasicBeanProcessor"; + private final static String AOP_BEAN_NAME_INC_NUM = "AOP_BEAN_NAME_INC_NUM"; + private final SpringBeanRegister springBeanRegister; + private final BeanFactoryAdvisorRetrievalHelper helper; public BasicBeanProcessor(ApplicationContext applicationContext){ Objects.requireNonNull(applicationContext); this.springBeanRegister = new SpringBeanRegister(applicationContext); + this.helper = new BeanFactoryAdvisorRetrievalHelper( + (ConfigurableListableBeanFactory)applicationContext.getAutowireCapableBeanFactory()); } @Override @@ -73,11 +85,43 @@ public class BasicBeanProcessor implements PluginPipeProcessor { if(aClass == null){ continue; } - String beanName = springBeanRegister.register(pluginId, aClass); + String namePrefix = resolveAopClass(aClass); + String beanName = springBeanRegister.register(pluginId, namePrefix, aClass); beanNames.add(beanName); } } + /** + * 该方法解决重复安装、卸载插件时, AOP 类无法注入的bug. + * 无法注入的原因如下: + * + * 第一次初始化插件时, 首先spring boot 会将要注入的类依次匹配,如果是代理类的话,则在 + * AbstractAutoProxyCreator 该类的proxyTypes属性会将生成的代理类缓存下来,并返回,然后将该代理类注入到使用的类中。 + * 第二次访问时, spring boot 会直接返回了缓存下来的代理类。导致注入类和代理类类型不匹配,无法注入。 + * 解决办法: proxyTypes 缓存的key 是通过class、和beanName 生成的。所以每次注册插件时,将代理类的beanName 用AOP_BEAN_NAME_INC_NUM + * 的递增数字作为前缀,这样每次生成都得都是新代理类。 + * @param aClass 当前要处理的类 + * @return 返回代理类的bean名称前缀 + */ + private String resolveAopClass(Class aClass){ + List advisorBeans = helper.findAdvisorBeans(); + List advisorsThatCanApply = AopUtils.findAdvisorsThatCanApply(advisorBeans, aClass); + if(advisorsThatCanApply.isEmpty()){ + // 如果不是代理类, 则返回 null + return null; + } else { + Object o = PluginRegistryInfo.getGlobalExtension(AOP_BEAN_NAME_INC_NUM); + AtomicInteger atomicInteger = null; + if(o instanceof AtomicInteger){ + atomicInteger = (AtomicInteger) o; + } else { + atomicInteger = new AtomicInteger(0); + PluginRegistryInfo.addGlobalExtension(AOP_BEAN_NAME_INC_NUM, atomicInteger); + } + // 是代理类 + return String.valueOf(atomicInteger.getAndIncrement()); + } + } } diff --git a/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/process/pipe/bean/name/PluginAnnotationBeanNameGenerator.java b/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/process/pipe/bean/name/PluginAnnotationBeanNameGenerator.java index 9b19849178591fd842b72bf8c6054b451143a3ec..700b06b127ceafdfbb79ec780aa2bba17c02f08a 100644 --- a/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/process/pipe/bean/name/PluginAnnotationBeanNameGenerator.java +++ b/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/process/pipe/bean/name/PluginAnnotationBeanNameGenerator.java @@ -15,15 +15,15 @@ import org.springframework.util.StringUtils; public class PluginAnnotationBeanNameGenerator extends AnnotationBeanNameGenerator { /** - * 插件id + * 后缀名称 */ - private final String pluginId; + private final String suffixName; - public PluginAnnotationBeanNameGenerator(String pluginId) { - if(pluginId == null){ - this.pluginId = ""; + public PluginAnnotationBeanNameGenerator(String suffixName) { + if(StringUtils.isEmpty(suffixName)){ + this.suffixName = ""; } else { - this.pluginId = pluginId + "-"; + this.suffixName = "@" + suffixName; } } @@ -35,7 +35,7 @@ public class PluginAnnotationBeanNameGenerator extends AnnotationBeanNameGenerat return beanName; } } - return pluginId + buildDefaultBeanName(definition, registry); + return buildDefaultBeanName(definition, registry) + suffixName; } diff --git a/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/process/pipe/classs/PluginClassProcess.java b/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/process/pipe/classs/PluginClassProcess.java index 33c1de6d548144fa04ae40ebf133bcd3785dcf58..45c2215346063423e6bab5b166af47378519f90a 100644 --- a/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/process/pipe/classs/PluginClassProcess.java +++ b/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/process/pipe/classs/PluginClassProcess.java @@ -9,6 +9,7 @@ import com.gitee.starblues.loader.load.PluginClassLoader; import com.gitee.starblues.realize.BasePlugin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.core.io.Resource; import org.springframework.util.StringUtils; diff --git a/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/process/post/bean/PluginControllerPostProcessor.java b/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/process/post/bean/PluginControllerPostProcessor.java index 503ed118fde1e6df2b3bd495d74505dda4c748cd..b5543f96010c22ee748f03bc43db7360e2e3a6be 100644 --- a/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/process/post/bean/PluginControllerPostProcessor.java +++ b/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/process/post/bean/PluginControllerPostProcessor.java @@ -8,9 +8,9 @@ import com.gitee.starblues.factory.SpringBeanRegister; import com.gitee.starblues.factory.process.pipe.classs.group.ControllerGroup; import com.gitee.starblues.factory.process.post.PluginPostProcessor; import com.gitee.starblues.utils.AopUtils; -import org.pf4j.PluginWrapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.context.ApplicationContext; import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.annotation.AnnotationUtils; @@ -53,20 +53,25 @@ public class PluginControllerPostProcessor implements PluginPostProcessor { public void registry(List pluginRegistryInfos) throws Exception { for (PluginRegistryInfo pluginRegistryInfo : pluginRegistryInfos) { AopUtils.resolveAop(pluginRegistryInfo.getPluginWrapper()); - List> groupClasses = pluginRegistryInfo.getGroupClasses(ControllerGroup.SPRING_CONTROLLER); - if(groupClasses == null || groupClasses.isEmpty()){ - continue; - } - List controllerBeanWrappers = new ArrayList<>(); - for (Class groupClass : groupClasses) { - if(groupClass == null){ + try { + List> groupClasses = pluginRegistryInfo.getGroupClasses(ControllerGroup.SPRING_CONTROLLER); + if(groupClasses == null || groupClasses.isEmpty()){ continue; } - ControllerBeanWrapper controllerBeanWrapper = registry(pluginRegistryInfo, groupClass); - controllerBeanWrappers.add(controllerBeanWrapper); - process(1, pluginRegistryInfo.getPluginWrapper().getPluginId(), groupClass); + List controllerBeanWrappers = new ArrayList<>(); + for (Class groupClass : groupClasses) { + if(groupClass == null){ + continue; + } + ControllerBeanWrapper controllerBeanWrapper = registry(pluginRegistryInfo, groupClass); + controllerBeanWrappers.add(controllerBeanWrapper); + process(1, pluginRegistryInfo.getPluginWrapper().getPluginId(), groupClass); + } + pluginRegistryInfo.addProcessorInfo(getKey(pluginRegistryInfo), controllerBeanWrappers); + } finally { + AopUtils.recoverAop(); } - pluginRegistryInfo.addProcessorInfo(getKey(pluginRegistryInfo), controllerBeanWrappers); + } } @@ -103,23 +108,23 @@ public class PluginControllerPostProcessor implements PluginPostProcessor { */ private ControllerBeanWrapper registry(PluginRegistryInfo pluginRegistryInfo, Class aClass) throws Exception { - String pluginId= pluginRegistryInfo.getPluginWrapper().getPluginId(); + String pluginId = pluginRegistryInfo.getPluginWrapper().getPluginId(); String beanName = springBeanRegister.register(pluginId, aClass); if(beanName == null || "".equals(beanName)){ throw new PluginBeanFactoryException("registry "+ aClass.getName() + "failure!"); } - Object object = applicationContext.getBean(beanName); - if(object == null){ - throw new PluginBeanFactoryException("registry "+ aClass.getName() + "failure! " + - "Not found The instance of" + aClass.getName()); - } - ControllerBeanWrapper controllerBeanWrapper = new ControllerBeanWrapper(); - controllerBeanWrapper.setBeanName(beanName); - setPathPrefix(pluginId, aClass); - Method getMappingForMethod = ReflectionUtils.findMethod(RequestMappingHandlerMapping.class, - "getMappingForMethod", Method.class, Class.class); - getMappingForMethod.setAccessible(true); try { + Object object = applicationContext.getBean(beanName); + if(object == null){ + throw new PluginBeanFactoryException("registry "+ aClass.getName() + "failure! " + + "Not found The instance of" + aClass.getName()); + } + ControllerBeanWrapper controllerBeanWrapper = new ControllerBeanWrapper(); + controllerBeanWrapper.setBeanName(beanName); + setPathPrefix(pluginId, aClass); + Method getMappingForMethod = ReflectionUtils.findMethod(RequestMappingHandlerMapping.class, + "getMappingForMethod", Method.class, Class.class); + getMappingForMethod.setAccessible(true); Method[] methods = aClass.getMethods(); Set requestMappingInfos = new HashSet<>(); for (Method method : methods) { @@ -133,12 +138,10 @@ public class PluginControllerPostProcessor implements PluginPostProcessor { controllerBeanWrapper.setRequestMappingInfos(requestMappingInfos); controllerBeanWrapper.setBeanClass(aClass); return controllerBeanWrapper; - } catch (SecurityException e) { - throw new Exception(e); - } catch (InvocationTargetException e) { - throw new Exception(e); } catch (Exception e){ - throw new Exception(e); + // 出现异常, 卸载该 controller bean + springBeanRegister.unregister(pluginId, beanName); + throw e; } } diff --git a/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/process/post/bean/PluginInvokePostProcessor.java b/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/process/post/bean/PluginInvokePostProcessor.java index 3aa40d8fc624347efbf77d13f8fa31d3cb043bdb..481ec1348f3aea315dbc6c0012e042d448e77323 100644 --- a/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/process/post/bean/PluginInvokePostProcessor.java +++ b/springboot-plugin-framework/src/main/java/com/gitee/starblues/factory/process/post/bean/PluginInvokePostProcessor.java @@ -53,19 +53,27 @@ public class PluginInvokePostProcessor implements PluginPostProcessor { public void registry(List pluginRegistryInfos) throws Exception { for (PluginRegistryInfo pluginRegistryInfo : pluginRegistryInfos) { AopUtils.resolveAop(pluginRegistryInfo.getPluginWrapper()); - List> suppers = pluginRegistryInfo.getGroupClasses(SupplierGroup.SUPPLIER); - if(suppers == null){ - continue; + try { + List> suppers = pluginRegistryInfo.getGroupClasses(SupplierGroup.SUPPLIER); + if(suppers == null){ + continue; + } + processSupper(pluginRegistryInfo, suppers); + } finally { + AopUtils.recoverAop(); } - processSupper(pluginRegistryInfo, suppers); } for (PluginRegistryInfo pluginRegistryInfo : pluginRegistryInfos) { AopUtils.resolveAop(pluginRegistryInfo.getPluginWrapper()); - List> callers = pluginRegistryInfo.getGroupClasses(CallerGroup.CALLER); - if(callers == null){ - continue; + try { + List> callers = pluginRegistryInfo.getGroupClasses(CallerGroup.CALLER); + if(callers == null){ + continue; + } + processCaller(pluginRegistryInfo, callers); + } finally { + AopUtils.recoverAop(); } - processCaller(pluginRegistryInfo, callers); } } diff --git a/springboot-plugin-framework/src/main/java/com/gitee/starblues/integration/IntegrationConfiguration.java b/springboot-plugin-framework/src/main/java/com/gitee/starblues/integration/IntegrationConfiguration.java index f10e9e7933339d93e7d0b62198169973495af155..76475ea8d609379145814049e412e26d9769e480 100644 --- a/springboot-plugin-framework/src/main/java/com/gitee/starblues/integration/IntegrationConfiguration.java +++ b/springboot-plugin-framework/src/main/java/com/gitee/starblues/integration/IntegrationConfiguration.java @@ -11,37 +11,39 @@ import org.pf4j.RuntimeMode; public interface IntegrationConfiguration { /** - * 运行环境 - * @return 运行环境 + * 运行环境。运行项目时的模式。分为开发环境(DEVELOPMENT)、生产环境(DEPLOYMENT) + * @return RuntimeMode.DEVELOPMENT、RuntimeMode.DEPLOYMENT */ RuntimeMode environment(); /** - * 插件的路径 + * 插件的路径。开发环境建议直接配置为插件模块的父级目录。例如: plugins。如果启动主程序时, 插件为加载, 请检查该配置是否正确。 * @return 插件的路径 */ String pluginPath(); /** - * 插件文件的配置路径 + * 插件文件的配置路径。在生产环境下, 插件的配置文件路径。 + * 在生产环境下, 请将所有插件使用到的配置文件统一放到该路径下管理。 + * 在开发环境下,配置为空串。程序会自动从 resources 获取配置文件, 所以请确保编译后的target 下存在该配置文件 * @return 插件文件的配置路径 */ String pluginConfigFilePath(); /** - * 上传插件的临时保存路径。 + * 上传插件包存储的临时路径。默认 temp(相对于主程序jar路径)。 * @return 上传插件的临时保存路径。 */ String uploadTempPath(); /** - * 插件备份路径。 + * 插件备份路径。默认 backupPlugin (相对于主程序jar路径)。 * @return 插件备份路径。 */ String backupPath(); /** - * 统一插件RestController的路径前缀 + * 插件 RestController 统一请求的路径前缀。只有 pluginRestControllerPathPrefix * @return path */ String pluginRestControllerPathPrefix(); diff --git a/springboot-plugin-framework/src/main/java/com/gitee/starblues/integration/initialize/AbstractPluginInitializer.java b/springboot-plugin-framework/src/main/java/com/gitee/starblues/integration/initialize/AbstractPluginInitializer.java index c9db9db911c6694b4d9b701c3bc28fb0d5e7737d..a9e87260e65010170166363866c44776cd8080ee 100644 --- a/springboot-plugin-framework/src/main/java/com/gitee/starblues/integration/initialize/AbstractPluginInitializer.java +++ b/springboot-plugin-framework/src/main/java/com/gitee/starblues/integration/initialize/AbstractPluginInitializer.java @@ -1,6 +1,5 @@ package com.gitee.starblues.integration.initialize; -import com.gitee.starblues.exception.PluginPlugException; import com.gitee.starblues.integration.listener.PluginInitializerListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,16 +25,16 @@ public abstract class AbstractPluginInitializer implements PluginInitializer{ } @Override - public void initialize() throws PluginPlugException { + public void initialize() throws Exception { log.info("Start execute plugin initializer."); this.executeInitialize(); } /** * 执行初始化 - * @throws PluginPlugException 插件插拔异常 + * @throws Exception 插件执行初始化异常 */ - public abstract void executeInitialize() throws PluginPlugException; + public abstract void executeInitialize() throws Exception; /** diff --git a/springboot-plugin-framework/src/main/java/com/gitee/starblues/integration/initialize/AutoPluginInitializer.java b/springboot-plugin-framework/src/main/java/com/gitee/starblues/integration/initialize/AutoPluginInitializer.java index 8a2afc1c3bc858aef9347c21f4fbc20ae9d6a5a7..28ef0c8eef5e67ce4d780821d9817a6348d310a0 100644 --- a/springboot-plugin-framework/src/main/java/com/gitee/starblues/integration/initialize/AutoPluginInitializer.java +++ b/springboot-plugin-framework/src/main/java/com/gitee/starblues/integration/initialize/AutoPluginInitializer.java @@ -1,6 +1,5 @@ package com.gitee.starblues.integration.initialize; -import com.gitee.starblues.exception.PluginPlugException; import com.gitee.starblues.integration.PluginApplication; import com.gitee.starblues.integration.listener.PluginInitializerListener; import com.gitee.starblues.integration.operator.PluginOperator; @@ -32,12 +31,8 @@ public class AutoPluginInitializer extends AbstractPluginInitializer { @PostConstruct @Override - public void executeInitialize() throws PluginPlugException { - try { - pluginOperator.initPlugins(pluginInitializerListener); - } catch (Exception e) { - e.printStackTrace(); - } + public void executeInitialize() throws Exception { + pluginOperator.initPlugins(pluginInitializerListener); } } diff --git a/springboot-plugin-framework/src/main/java/com/gitee/starblues/integration/initialize/ManualPluginInitializer.java b/springboot-plugin-framework/src/main/java/com/gitee/starblues/integration/initialize/ManualPluginInitializer.java index dc7f722bb1962ae258464a20c2a140cdb294b641..ae1d9dc95d87efcac9515606b376aa5a064b4045 100644 --- a/springboot-plugin-framework/src/main/java/com/gitee/starblues/integration/initialize/ManualPluginInitializer.java +++ b/springboot-plugin-framework/src/main/java/com/gitee/starblues/integration/initialize/ManualPluginInitializer.java @@ -1,6 +1,5 @@ package com.gitee.starblues.integration.initialize; -import com.gitee.starblues.exception.PluginPlugException; import com.gitee.starblues.integration.PluginApplication; import com.gitee.starblues.integration.listener.PluginInitializerListener; import com.gitee.starblues.integration.operator.PluginOperator; @@ -26,7 +25,7 @@ public class ManualPluginInitializer extends AbstractPluginInitializer { @Override - public void executeInitialize() throws PluginPlugException { + public void executeInitialize() throws Exception { pluginOperator.initPlugins(pluginInitializerListener); } diff --git a/springboot-plugin-framework/src/main/java/com/gitee/starblues/integration/initialize/PluginInitializer.java b/springboot-plugin-framework/src/main/java/com/gitee/starblues/integration/initialize/PluginInitializer.java index 6008f35eb88961cdcd669b6a4613bf83a47e47c6..4c4b09453a7d1719604b92c3993edd0bbcdb98ff 100644 --- a/springboot-plugin-framework/src/main/java/com/gitee/starblues/integration/initialize/PluginInitializer.java +++ b/springboot-plugin-framework/src/main/java/com/gitee/starblues/integration/initialize/PluginInitializer.java @@ -1,6 +1,5 @@ package com.gitee.starblues.integration.initialize; -import com.gitee.starblues.exception.PluginPlugException; /** * 插件初始化者 @@ -12,9 +11,9 @@ public interface PluginInitializer { /** * 初始化 - * @throws PluginPlugException 插件安装异常 + * @throws Exception 插件安装异常 */ - void initialize() throws PluginPlugException; + void initialize() throws Exception; } diff --git a/springboot-plugin-framework/src/main/java/com/gitee/starblues/integration/operator/DefaultPluginOperator.java b/springboot-plugin-framework/src/main/java/com/gitee/starblues/integration/operator/DefaultPluginOperator.java index f442856e390c4d806e29c872ca60ecb4b5e7fd9b..abcee549e99a5f685c604cc85259f55aa6279d1d 100644 --- a/springboot-plugin-framework/src/main/java/com/gitee/starblues/integration/operator/DefaultPluginOperator.java +++ b/springboot-plugin-framework/src/main/java/com/gitee/starblues/integration/operator/DefaultPluginOperator.java @@ -1,6 +1,5 @@ package com.gitee.starblues.integration.operator; -import com.gitee.starblues.exception.PluginPlugException; import com.gitee.starblues.integration.IntegrationConfiguration; import com.gitee.starblues.integration.listener.PluginInitializerListener; import com.gitee.starblues.integration.listener.PluginInitializerListenerFactory; @@ -10,8 +9,9 @@ import com.gitee.starblues.integration.operator.verify.PluginLegalVerify; import com.gitee.starblues.integration.operator.verify.PluginUploadVerify; import com.gitee.starblues.factory.DefaultPluginFactory; import com.gitee.starblues.factory.PluginFactory; +import com.gitee.starblues.utils.PluginFileUtils; +import org.apache.commons.io.FileUtils; import org.pf4j.*; -import org.pf4j.util.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; @@ -19,10 +19,12 @@ import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; @@ -67,14 +69,16 @@ public class DefaultPluginOperator implements PluginOperator { @Override - public synchronized boolean initPlugins(PluginInitializerListener pluginInitializerListener) throws PluginPlugException { + public synchronized boolean initPlugins(PluginInitializerListener pluginInitializerListener) throws Exception { if(isInit){ - throw new PluginPlugException("Plugins Already initialized. Cannot be initialized again"); + throw new RuntimeException("Plugins Already initialized. Cannot be initialized again"); } try { pluginInitializerListenerFactory.addPluginInitializerListeners(pluginInitializerListener); log.info("Start initialize plugins"); pluginInitializerListenerFactory.before(); + // 启动前, 清除空文件 + PluginFileUtils.cleanEmptyFile(pluginManager.getPluginsRoot()); pluginManager.loadPlugins(); pluginManager.startPlugins(); List pluginWrappers = pluginManager.getStartedPlugins(); @@ -92,21 +96,17 @@ public class DefaultPluginOperator implements PluginOperator { return true; } catch (Exception e){ pluginInitializerListenerFactory.failure(e); - throw new PluginPlugException(e); + throw e; } } @Override - public String loadPlugin(Path path) throws PluginPlugException { - try { - return pluginManager.loadPlugin(path); - } catch (Exception e){ - throw new PluginPlugException(e); - } + public String loadPlugin(Path path) throws Exception { + return pluginManager.loadPlugin(path); } @Override - public boolean install(Path path) throws PluginPlugException { + public boolean install(Path path) throws Exception { if(path == null){ throw new IllegalArgumentException("Method:install param [pluginId] can not be empty"); } @@ -115,37 +115,40 @@ public class DefaultPluginOperator implements PluginOperator { pluginId = pluginManager.loadPlugin(path); return start(pluginId); } catch (Exception e){ - if(pluginId != null){ - // 说明load成功, 但是没有启动成功, 则卸载该插件 - pluginManager.unloadPlugin(pluginId); - try { - backup(path, "installErrorPlugin", 2); - } catch (Exception e1) { - throw new PluginPlugException(e1); - } - } - throw new PluginPlugException(e); + // 说明load成功, 但是没有启动成功, 则卸载该插件 + uninstall(pluginId); + throw e; } } @Override - public boolean uninstall(String pluginId) throws PluginPlugException { + public boolean uninstall(String pluginId) throws Exception { + PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginId); + if(pluginWrapper == null){ + log.error("Uninstall Plugin failure, Not found plugin {}", pluginId); + return false; + } try { - stop(pluginId); - if(pluginManager.unloadPlugin(pluginId)){ - log.info("Uninstall Plugin [{}] Success", pluginId); - return true; + pluginFactory.unRegistry(pluginId); + pluginFactory.build(); + return true; + } catch (Exception e){ + throw new Exception("Stop plugin [" + pluginId + "] failure. " + e.getMessage() ,e); + } finally { + if (pluginManager.unloadPlugin(pluginId)) { + // 卸载完后,将插件文件移到备份文件中 + backup(pluginWrapper.getPluginPath(), "uninstallPlugin", 1); + log.info("Unload Plugin [{}] success", pluginId); + log.info("Uninstall Plugin [{}] success", pluginId); } else { + log.info("Unload Plugin [{}] failure", pluginId); log.info("Uninstall Plugin [{}] failure", pluginId); - return false; } - } catch (Exception e){ - throw new PluginPlugException(e); } } @Override - public boolean delete(String pluginId) throws PluginPlugException { + public boolean delete(String pluginId) throws Exception { PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginId); if(pluginWrapper == null){ log.error("Delete -> Not Found plugin [{}]", pluginId); @@ -154,16 +157,16 @@ public class DefaultPluginOperator implements PluginOperator { if(pluginWrapper.getPluginState() == PluginState.STARTED){ uninstall(pluginId); } - backup(pluginWrapper.getPluginPath(), "deleteByPluginId", 2); + backup(pluginWrapper.getPluginPath(), "deleteByPluginId", 1); log.info("Delete plugin [{}] Success", pluginId); return true; } @Override - public boolean delete(Path path) throws PluginPlugException { + public boolean delete(Path path) throws Exception { try { if(!Files.exists(path)){ - throw new PluginPlugException(path.toString() + " does not exist!"); + throw new FileNotFoundException(path.toString() + " does not exist!"); } PluginDescriptor pluginDescriptor = pluginDescriptorFinder.find(path); PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginDescriptor.getPluginId()); @@ -173,8 +176,6 @@ public class DefaultPluginOperator implements PluginOperator { log.error("Not found Plugin [{}] of path {}", pluginDescriptor.getPluginId(), path.toString()); return false; } - } catch (PluginException e) { - throw new PluginPlugException(e); } finally { backup(path, "deleteByPath", 2); } @@ -182,44 +183,40 @@ public class DefaultPluginOperator implements PluginOperator { @Override - public boolean start(String pluginId) throws PluginPlugException { + public boolean start(String pluginId) throws Exception { if(StringUtils.isEmpty(pluginId)){ throw new IllegalArgumentException("Method:start param [pluginId] can not be empty"); } PluginWrapper pluginWrapper = getPluginWrapper(pluginId, "Start"); if(pluginWrapper.getPluginState() == PluginState.STARTED){ - throw new PluginPlugException("This plugin [" + pluginId + "] have already started"); + throw new Exception("This plugin [" + pluginId + "] have already started"); } - try { - PluginState pluginState = pluginManager.startPlugin(pluginId); - if(pluginState == PluginState.STARTED){ - pluginFactory.registry(pluginWrapper); - pluginFactory.build(); - log.info("Start Plugin [{}] Success", pluginId); - return true; - } - log.error("Start Plugin [{}] Failure, plugin state is not start. State[{}]", pluginId, pluginState.toString()); - return false; - } catch (Exception e){ - throw new PluginPlugException("Start plugin " + pluginId + " failure. " + e.getMessage() ,e); + PluginState pluginState = pluginManager.startPlugin(pluginId); + if(pluginState == PluginState.STARTED){ + pluginFactory.registry(pluginWrapper); + pluginFactory.build(); + log.info("Start Plugin [{}] success", pluginId); + return true; } + log.error("Start Plugin [{}] failure, plugin state is not start. State[{}]", pluginId, pluginState.toString()); + return false; } @Override - public boolean stop(String pluginId) throws PluginPlugException { + public boolean stop(String pluginId) throws Exception { if(StringUtils.isEmpty(pluginId)){ throw new IllegalArgumentException("Method:stop param [pluginId] can not be empty"); } PluginWrapper pluginWrapper = getPluginWrapper(pluginId, "Stop"); if(pluginWrapper.getPluginState() != PluginState.STARTED){ - throw new PluginPlugException("This plugin [" + pluginId + "] is not started"); + throw new Exception("This plugin [" + pluginId + "] is not started"); } try { pluginFactory.unRegistry(pluginId); pluginFactory.build(); return true; } catch (Exception e){ - throw new PluginPlugException("Stop plugin [" + pluginId + "] failure. " + e.getMessage() ,e); + throw new Exception("Stop plugin [" + pluginId + "] failure. " + e.getMessage() ,e); } finally { pluginManager.stopPlugin(pluginId); } @@ -229,92 +226,84 @@ public class DefaultPluginOperator implements PluginOperator { @Override - public Path uploadPlugin(MultipartFile pluginFile) throws PluginPlugException { + public Path uploadPlugin(MultipartFile pluginFile) throws Exception { if(pluginFile == null){ throw new IllegalArgumentException("Method:uploadPlugin param [pluginFile] can not be null"); } + // 获取文件的后缀名 + String fileName = pluginFile.getOriginalFilename(); + String suffixName = fileName.substring(fileName.lastIndexOf(".") + 1); + //检查文件格式是否合法 + if(StringUtils.isEmpty(suffixName)){ + throw new IllegalArgumentException("Invalid file type, please select .jar or .zip file"); + } + if(!"jar".equalsIgnoreCase(suffixName) && !"zip".equalsIgnoreCase(suffixName)){ + throw new IllegalArgumentException("Invalid file type, please select .jar or .zip file"); + } + String tempPath = integrationConfiguration.uploadTempPath() + File.separator + fileName; + Path srcPath = PluginFileUtils.getExistPath(Paths.get(tempPath)); + Path tempPluginFile = Files.write(srcPath, pluginFile.getBytes()); + try { - // 获取文件的后缀名 - String fileName = pluginFile.getOriginalFilename(); - String suffixName = fileName.substring(fileName.lastIndexOf(".") + 1); - //检查文件格式是否合法 - if(StringUtils.isEmpty(suffixName)){ - throw new IllegalArgumentException("Invalid file type, please select .jar or .zip file"); - } - if(!"jar".equalsIgnoreCase(suffixName) && !"zip".equalsIgnoreCase(suffixName)){ - throw new IllegalArgumentException("Invalid file type, please select .jar or .zip file"); - } - String tempPath = integrationConfiguration.uploadTempPath() + File.separator + fileName; - Path tempPluginFile = Files.write(getExistFile(Paths.get(tempPath)), pluginFile.getBytes()); - try { - Path verifyPath = uploadPluginVerify.verify(tempPluginFile); - if(verifyPath != null){ - String pluginFilePathString = pluginManager.getPluginsRoot().toString() + - File.separator + fileName; - Path pluginFilePath = Paths.get(pluginFilePathString); - if(Files.exists(pluginFilePath)){ - // 如果存在同名插件的化, 先备份它 - backup(pluginFilePath, "uploadPluginFile", 1); - } - return Files.move(verifyPath, pluginFilePath); - } else { - PluginPlugException pluginPlugException = - new PluginPlugException(fileName + " verify failure, verifyPath is null"); - verifyFailureDelete(tempPluginFile, pluginPlugException); - throw pluginPlugException; + Path verifyPath = uploadPluginVerify.verify(tempPluginFile); + if(verifyPath != null){ + String pluginFilePathString = pluginManager.getPluginsRoot().toString() + + File.separator + fileName; + Path pluginFilePath = Paths.get(pluginFilePathString); + File target = pluginFilePath.toFile(); + if(target.exists()){ + // 存在则拷贝一份 + backup(pluginFilePath, "uploadPlugin", 2); } - } catch (Exception e){ - // 出现异常, 删除刚才上传的临时文件 - verifyFailureDelete(tempPluginFile, e); - throw new PluginPlugException("Verify failure : " + e.getMessage(), e); + FileUtils.writeByteArrayToFile(target, FileUtils.readFileToByteArray(verifyPath.toFile())); + return pluginFilePath; + } else { + Exception exception = + new Exception(fileName + " verify failure, verifyPath is null"); + verifyFailureDelete(tempPluginFile, exception); + throw exception; } } catch (Exception e){ - throw new PluginPlugException(e); + // 出现异常, 删除刚才上传的临时文件 + verifyFailureDelete(tempPluginFile, e); + throw new Exception("Verify failure : " + e.getMessage(), e); } } @Override - public boolean uploadPluginAndStart(MultipartFile pluginFile) throws PluginPlugException { + public boolean uploadPluginAndStart(MultipartFile pluginFile) throws Exception { if(pluginFile == null){ - throw new PluginPlugException("Method:uploadPluginAndStart param [pluginFile] can not be null"); - } - try { - Path path = uploadPlugin(pluginFile); - this.install(path); - log.info("Upload And Start Plugin [{}] Success. ", path.toString()); - return true; - } catch (Exception e){ - throw new PluginPlugException(e); + throw new Exception("Method:uploadPluginAndStart param [pluginFile] can not be null"); } + Path path = uploadPlugin(pluginFile); + this.install(path); + log.info("Upload And Start Plugin Success. [{}]", path.toString()); + return true; } @Override - public boolean uploadConfigFile(MultipartFile configFile) throws PluginPlugException { + public boolean uploadConfigFile(MultipartFile configFile) throws Exception { if(configFile == null){ throw new IllegalArgumentException("Method:uploadConfigFile param [configFile] can not be null"); } - try { - String fileName = configFile.getOriginalFilename(); - String configPath = integrationConfiguration.pluginConfigFilePath() + - File.separator + fileName; - Files.write(getExistFile(Paths.get(configPath)), configFile.getBytes()); - return true; - } catch (Exception e){ - throw new PluginPlugException(e); - } + String fileName = configFile.getOriginalFilename(); + String configPath = integrationConfiguration.pluginConfigFilePath() + + File.separator + fileName; + Path srcPath = PluginFileUtils.getExistPath(Paths.get(configPath)); + Files.write(srcPath, configFile.getBytes()); + return true; } @Override - public boolean backupPlugin(Path path, String appendName) throws PluginPlugException { + public boolean backupPlugin(Path path, String appendName) throws Exception { Objects.requireNonNull(path); return backup(path, appendName, 2); } - @Override - public boolean backupPlugin(String pluginId, String appendName) throws PluginPlugException { + public boolean backupPlugin(String pluginId, String appendName) throws Exception { PluginWrapper pluginManager = getPluginWrapper(pluginId, "BackupPlugin by pluginId"); return backupPlugin(pluginManager.getPluginPath(), appendName); } @@ -337,7 +326,7 @@ public class DefaultPluginOperator implements PluginOperator { @Override - public Set getPluginFilePaths() throws PluginPlugException { + public Set getPluginFilePaths() throws Exception { try { RuntimeMode environment = integrationConfiguration.environment(); Set paths = new HashSet<>(); @@ -345,13 +334,13 @@ public class DefaultPluginOperator implements PluginOperator { paths.add(integrationConfiguration.pluginPath()); return paths; } - List files = FileUtils.getJars(Paths.get(integrationConfiguration.pluginPath())); + List files = org.pf4j.util.FileUtils.getJars(Paths.get(integrationConfiguration.pluginPath())); return files.stream() .filter(file -> file != null) .map(file -> file.getAbsolutePath()) .collect(Collectors.toSet()); } catch (Exception e){ - throw new PluginPlugException(e); + throw new Exception(e); } } @@ -365,62 +354,46 @@ public class DefaultPluginOperator implements PluginOperator { * 得到插件包装类 * @param pluginId 插件id * @return PluginWrapper - * @throws PluginPlugException 插件装配异常 + * @throws Exception 插件装配异常 */ - private PluginWrapper getPluginWrapper(String pluginId, String errorMsg) throws PluginPlugException { + private PluginWrapper getPluginWrapper(String pluginId, String errorMsg) throws Exception { PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginId); if (pluginWrapper == null) { - throw new PluginPlugException(errorMsg + " -> Not found plugin " + pluginId); + throw new Exception(errorMsg + " -> Not found plugin " + pluginId); } return pluginWrapper; } - /** - * 得到存在的文件 - * @param path 插件路径 - * @return 插件路径 - * @throws IOException 没有发现文件异常 - */ - private Path getExistFile(Path path) throws IOException { - Path parent = path.getParent(); - if(!Files.exists(parent)){ - Files.createDirectories(parent); - } - if(!Files.exists(path)){ - Files.createFile(path); - } - return path; - } /** * 校验文件失败后, 删除临时文件 * @param tempPluginFile 临时文件路径 * @param e 异常信息 - * @throws PluginPlugException PluginPlugException + * @throws Exception Exception */ - private void verifyFailureDelete(Path tempPluginFile, Exception e) throws PluginPlugException { + private void verifyFailureDelete(Path tempPluginFile, Exception e) throws Exception { try { Files.deleteIfExists(tempPluginFile); }catch (IOException e1){ - throw new PluginPlugException("Verify failure and delete temp file failure : " + e.getMessage(), e); + throw new Exception("Verify failure and delete temp file failure : " + e.getMessage(), e); } } /** * 备份 - * @param path 文件的路径 + * @param sourcePath 源文件的路径 * @param appendName 追加的字符串 * @param type 类型 1移动 2拷贝 * @return 结果 - * @throws PluginPlugException PluginPlugException + * @throws Exception Exception */ - private boolean backup(Path path, String appendName, int type) throws PluginPlugException { - if(!Files.exists(path)){ - throw new PluginPlugException(path.toString() + " does not exist!"); + private boolean backup(Path sourcePath, String appendName, int type) throws Exception { + if(!Files.exists(sourcePath)){ + throw new FileNotFoundException(sourcePath.toString() + " does not exist!"); } try { - String fileName = path.getFileName().toString(); + String fileName = sourcePath.getFileName().toString(); String targetName = integrationConfiguration.backupPath() + File.separator + getNowTimeByFormat(); if(!StringUtils.isEmpty(appendName)){ targetName = targetName + "_" + appendName; @@ -429,14 +402,15 @@ public class DefaultPluginOperator implements PluginOperator { if(!Files.exists(target.getParent())){ Files.createDirectories(target.getParent()); } + File targetFile = target.toFile(); + File sourceFile = sourcePath.toFile(); + FileUtils.writeByteArrayToFile(targetFile, FileUtils.readFileToByteArray(sourceFile)); if(type == 1){ - Files.move(path, target); - } else { - Files.copy(path, target); + FileUtils.writeByteArrayToFile(sourceFile, "".getBytes()); } return true; } catch (IOException e) { - throw new PluginPlugException("BackupPlugin " + path.toString() + " failure : " + e.getMessage(), e); + throw new Exception("BackupPlugin " + sourcePath.toString() + " failure : " + e.getMessage(), e); } } diff --git a/springboot-plugin-framework/src/main/java/com/gitee/starblues/integration/operator/PluginOperator.java b/springboot-plugin-framework/src/main/java/com/gitee/starblues/integration/operator/PluginOperator.java index 072ebb4d62ac44a5972c793b88f442ff3da73174..e4a80fadb0fdce029a481706427dc96fee4372fb 100644 --- a/springboot-plugin-framework/src/main/java/com/gitee/starblues/integration/operator/PluginOperator.java +++ b/springboot-plugin-framework/src/main/java/com/gitee/starblues/integration/operator/PluginOperator.java @@ -1,6 +1,5 @@ package com.gitee.starblues.integration.operator; -import com.gitee.starblues.exception.PluginPlugException; import com.gitee.starblues.integration.listener.PluginInitializerListener; import com.gitee.starblues.integration.operator.module.PluginInfo; import org.pf4j.PluginWrapper; @@ -19,115 +18,115 @@ import java.util.Set; public interface PluginOperator { /** - * 初始化插件 + * 初始化插件。该方法只能执行一次。 * @param pluginInitializerListener 插件初始化监听者 * @return 成功返回true.不成功抛出异常或者返回false - * @throws PluginPlugException 插件插头异常 + * @throws Exception 插件插头异常 */ - boolean initPlugins(PluginInitializerListener pluginInitializerListener) throws PluginPlugException; + boolean initPlugins(PluginInitializerListener pluginInitializerListener) throws Exception; /** * 通过路径加载插件(不会启用) * @param path 插件路径 * @return 成功返回插件id - * @throws PluginPlugException 插件插头异常 + * @throws Exception 插件插头异常 */ - String loadPlugin(Path path) throws PluginPlugException; + String loadPlugin(Path path) throws Exception; /** * 通过路径安装插件(会启用) * @param path 插件路径 * @return 成功返回true.不成功抛出异常或者返回false - * @throws PluginPlugException 插件插头异常 + * @throws Exception 插件插头异常 */ - boolean install(Path path) throws PluginPlugException; + boolean install(Path path) throws Exception; /** * 卸载插件 * @param pluginId 插件id * @return 成功返回true.不成功抛出异常或者返回false - * @throws PluginPlugException 插件插头异常 + * @throws Exception 插件插头异常 */ - boolean uninstall(String pluginId) throws PluginPlugException; + boolean uninstall(String pluginId) throws Exception; /** * 通过插件id删除插件。只适用于生产环境 * @param pluginId 插件id * @return 成功返回true.不成功抛出异常或者返回false - * @throws PluginPlugException 插件插头异常 + * @throws Exception 插件插头异常 */ - boolean delete(String pluginId) throws PluginPlugException; + boolean delete(String pluginId) throws Exception; /** * 通过路径删除插件。只适用于生产环境 * @param path 插件路径 * @return 成功返回true.不成功抛出异常或者返回false - * @throws PluginPlugException 插件插头异常 + * @throws Exception 插件插头异常 */ - boolean delete(Path path) throws PluginPlugException; + boolean delete(Path path) throws Exception; /** * 启用插件 * @param pluginId 插件id * @return 成功返回true.不成功抛出异常或者返回false - * @throws PluginPlugException 插件插头异常 + * @throws Exception 插件插头异常 */ - boolean start(String pluginId) throws PluginPlugException; + boolean start(String pluginId) throws Exception; /** * 停止插件 * @param pluginId 插件id * @return 成功返回true.不成功抛出异常或者返回false - * @throws PluginPlugException 插件插头异常 + * @throws Exception 插件插头异常 */ - boolean stop(String pluginId) throws PluginPlugException; + boolean stop(String pluginId) throws Exception; /** * 上传插件。只适用于生产环境 * @param pluginFile 插件文件 * @return 成功返回插件路径.不成功返回null, 或者抛出异常 - * @throws PluginPlugException 插件插头异常 + * @throws Exception 插件插头异常 */ - Path uploadPlugin(MultipartFile pluginFile) throws PluginPlugException; + Path uploadPlugin(MultipartFile pluginFile) throws Exception; /** * 上传插件并启用插件。只适用于生产环境 * @param pluginFile 配置文件 * @return 成功返回true.不成功返回false, 或者抛出异常 - * @throws PluginPlugException 插件插头异常 + * @throws Exception 插件插头异常 */ - boolean uploadPluginAndStart(MultipartFile pluginFile) throws PluginPlugException; + boolean uploadPluginAndStart(MultipartFile pluginFile) throws Exception; /** * 上传配置文件(如果存在, 则覆盖)。只适用于生产环境 * @param configFile 配置文件 * @return 成功返回true.不成功返回false, 或者抛出异常 - * @throws PluginPlugException 插件插头异常 + * @throws Exception 插件插头异常 */ - boolean uploadConfigFile(MultipartFile configFile) throws PluginPlugException; + boolean uploadConfigFile(MultipartFile configFile) throws Exception; /** * 通过路径备份插件文件。只适用于生产环境 * @param path 路径 * @param appendName 追加的名称 * @return 成功返回true.不成功返回false, 或者抛出异常 - * @throws PluginPlugException 插件插头异常 + * @throws Exception 插件插头异常 */ - boolean backupPlugin(Path path, String appendName) throws PluginPlugException; + boolean backupPlugin(Path path, String appendName) throws Exception; /** * 通过插件id备份插件。只适用于生产环境 * @param pluginId 插件id * @param appendName 追加的名称 * @return 成功返回true.不成功返回false, 或者抛出异常 - * @throws PluginPlugException 插件插头异常 + * @throws Exception 插件插头异常 */ - boolean backupPlugin(String pluginId, String appendName) throws PluginPlugException; + boolean backupPlugin(String pluginId, String appendName) throws Exception; /** * 获取插件信息 @@ -139,9 +138,9 @@ public interface PluginOperator { /** * 得到插件文件的路径。只适用于生产环境 * @return 返回插件路径列表 - * @throws PluginPlugException 插件插头异常 + * @throws Exception 插件插头异常 */ - Set getPluginFilePaths() throws PluginPlugException; + Set getPluginFilePaths() throws Exception; /** * 得到插件的包装类 diff --git a/springboot-plugin-framework/src/main/java/com/gitee/starblues/utils/AopUtils.java b/springboot-plugin-framework/src/main/java/com/gitee/starblues/utils/AopUtils.java index 127b55c4c45467e00f92829ccd28120d7565dcfe..def4cbfd5df7e5dbe4df81830e59d084db697b00 100644 --- a/springboot-plugin-framework/src/main/java/com/gitee/starblues/utils/AopUtils.java +++ b/springboot-plugin-framework/src/main/java/com/gitee/starblues/utils/AopUtils.java @@ -12,6 +12,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; /** * AOP 无法找到插件类的解决工具类 @@ -23,6 +24,8 @@ public class AopUtils { private static final Logger LOG = LoggerFactory.getLogger(AopUtils.class); + private static AtomicBoolean isRecover = new AtomicBoolean(true); + private static final List PROXY_WRAPPERS = new ArrayList<>(); private AopUtils(){} @@ -57,6 +60,10 @@ public class AopUtils { LOG.warn("ProxyProcessorSupports is empty, And Plugin AOP can't used"); return; } + if(!isRecover.get()){ + throw new RuntimeException("Not invoking resolveAop(). And can not AopUtils.resolveAop"); + } + isRecover.set(false); ClassLoader pluginClassLoader = pluginWrapper.getPluginClassLoader(); for (ProxyWrapper proxyWrapper : PROXY_WRAPPERS) { ProxyProcessorSupport proxyProcessorSupport = proxyWrapper.getProxyProcessorSupport(); @@ -77,6 +84,7 @@ public class AopUtils { ProxyProcessorSupport proxyProcessorSupport = proxyWrapper.getProxyProcessorSupport(); proxyProcessorSupport.setProxyClassLoader(proxyWrapper.getOriginalClassLoader()); } + isRecover.set(true); } /** @@ -146,6 +154,14 @@ public class AopUtils { void setOriginalClassLoader(ClassLoader originalClassLoader) { this.originalClassLoader = originalClassLoader; } + + @Override + public String toString() { + return "ProxyWrapper{" + + "proxyProcessorSupport=" + proxyProcessorSupport + + ", originalClassLoader=" + originalClassLoader + + '}'; + } } } diff --git a/springboot-plugin-framework/src/main/java/com/gitee/starblues/utils/PluginFileUtils.java b/springboot-plugin-framework/src/main/java/com/gitee/starblues/utils/PluginFileUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..865a73540e578c9718e5bfb03a9123a6cff6b70a --- /dev/null +++ b/springboot-plugin-framework/src/main/java/com/gitee/starblues/utils/PluginFileUtils.java @@ -0,0 +1,97 @@ +package com.gitee.starblues.utils; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.filefilter.IOFileFilter; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.math.BigInteger; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; + +/** + * 插件文件工具类 + * + * @author zhangzhuo + * @version 1.0 + */ +public final class PluginFileUtils { + + private PluginFileUtils(){} + + + public static String getMd5ByFile(File file) throws FileNotFoundException { + String value = null; + FileInputStream in = new FileInputStream(file); + try { + MappedByteBuffer byteBuffer = in.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length()); + MessageDigest md5 = MessageDigest.getInstance("MD5"); + md5.update(byteBuffer); + BigInteger bi = new BigInteger(1, md5.digest()); + value = bi.toString(16); + } catch (Exception e) { + e.printStackTrace(); + } finally { + if(null != in) { + try { + in.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return value; + } + + + public static void cleanEmptyFile(Path path){ + if(path == null){ + return; + } + if(!Files.exists(path)){ + return; + } + try { + Files.list(path) + .forEach(subPath -> { + File file = subPath.toFile(); + if(!file.isFile()){ + return; + } + long length = file.length(); + if(length == 0){ + FileUtils.deleteQuietly(file); + } + }); + } catch (IOException e) { + e.printStackTrace(); + } + + } + + + + /** + * 得到存在的文件 + * @param path 插件路径 + * @return 插件路径 + * @throws IOException 没有发现文件异常 + */ + public static Path getExistPath(Path path) throws IOException { + Path parent = path.getParent(); + if(!Files.exists(parent)){ + Files.createDirectories(parent); + } + if(!Files.exists(path)){ + Files.createFile(path); + } + return path; + } + + +}