├── .gitee ├── ISSUE_TEMPLATE.zh-CN.md └── PULL_REQUEST_TEMPLATE.zh-CN.md ├── .gitignore ├── LICENSE ├── README.md ├── README.zh-cn.md ├── images ├── architecture.png └── docker-img.png ├── pom.xml ├── spring-hot-plugin-common ├── .gitignore ├── pom.xml └── src │ └── main │ └── java │ └── vip │ └── aliali │ └── spring │ └── plugin │ └── constants │ ├── PluginConstants.java │ ├── PluginState.java │ └── RuntimeMode.java ├── spring-hot-plugin-core ├── .gitignore ├── pom.xml └── src │ └── main │ ├── java │ └── vip │ │ └── aliali │ │ └── spring │ │ └── plugin │ │ ├── AbstractPluginApplication.java │ │ ├── DefaultPluginApplication.java │ │ ├── DefaultPluginManager.java │ │ ├── PluginAutoConfiguration.java │ │ ├── PluginClassRegister.java │ │ ├── PluginConfiguration.java │ │ ├── PluginException.java │ │ ├── PluginInfo.java │ │ ├── PluginManager.java │ │ ├── PluginStarter.java │ │ ├── listener │ │ ├── DefaultPluginListenerFactory.java │ │ └── PluginListener.java │ │ ├── register │ │ ├── AbstractRegister.java │ │ ├── ClearLoggerCache.java │ │ ├── Register.java │ │ ├── RegisterController.java │ │ └── RegisterScheduled.java │ │ └── util │ │ └── DeployUtils.java │ └── resources │ └── META-INF │ ├── spring-configuration-metadata.json │ └── spring.factories ├── spring-hot-plugin-example ├── plugin-demo-2 │ ├── .gitignore │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── csdn │ │ └── itsaysay │ │ └── demo │ │ └── plugin2 │ │ ├── IndexController.java │ │ ├── bean │ │ ├── Person.java │ │ └── User.java │ │ ├── mapper │ │ ├── TestMapper.java │ │ └── xml │ │ │ └── TestMapper.xml │ │ ├── service │ │ ├── TestService.java │ │ └── impl │ │ │ └── TestServiceImpl.java │ │ └── task │ │ └── TaskDemo.java ├── plugin-demo-mybatis │ ├── .gitignore │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── csdn │ │ └── itsaysay │ │ └── demo │ │ └── mybatis │ │ ├── IndexController.java │ │ ├── bean │ │ └── User.java │ │ ├── mapper │ │ ├── TestMapper.java │ │ └── TestMapper.xml │ │ └── service │ │ ├── TestService.java │ │ └── impl │ │ └── TestServiceImpl.java ├── plugin-demo │ ├── .gitignore │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── csdn │ │ │ └── itsaysay │ │ │ └── demo │ │ │ └── plugin │ │ │ ├── IndexController.java │ │ │ ├── bean │ │ │ ├── Person.java │ │ │ └── User.java │ │ │ ├── hik │ │ │ └── HCNetSDK.java │ │ │ ├── mapper │ │ │ ├── TestMapper.java │ │ │ └── TestMapper2.java │ │ │ ├── service │ │ │ ├── TestService.java │ │ │ └── impl │ │ │ │ └── TestServiceImpl.java │ │ │ └── task │ │ │ └── TaskDemo.java │ │ └── resources │ │ ├── csdn │ │ └── itsaysay │ │ │ └── demo │ │ │ └── plugin │ │ │ └── mapper │ │ │ └── TestMapper.xml │ │ ├── linux-x86-64 │ │ └── libXMTQRCode.so │ │ └── win32-x86-64 │ │ ├── AudioRender.dll │ │ ├── GdiPlus.dll │ │ ├── GdiPlus.lib │ │ ├── HCCore.dll │ │ ├── HCCore.lib │ │ ├── HCNetSDK.dll │ │ ├── HCNetSDK.lib │ │ ├── HCNetSDKCom │ │ ├── AnalyzeData.dll │ │ ├── AudioIntercom.dll │ │ ├── AudioRender.dll │ │ ├── HCAlarm.dll │ │ ├── HCAlarm.lib │ │ ├── HCCoreDevCfg.dll │ │ ├── HCDisplay.dll │ │ ├── HCGeneralCfgMgr.dll │ │ ├── HCGeneralCfgMgr.lib │ │ ├── HCIndustry.dll │ │ ├── HCPlayBack.dll │ │ ├── HCPreview.dll │ │ ├── HCPreview.lib │ │ ├── HCVoiceTalk.dll │ │ ├── OpenAL32.dll │ │ ├── StreamTransClient.dll │ │ ├── SystemTransform.dll │ │ └── libiconv2.dll │ │ ├── HXVA.dll │ │ ├── HmMerge.dll │ │ ├── LocalSensorAdd.dat │ │ ├── MP_Render.dll │ │ ├── NPQos.dll │ │ ├── OpenAL32.dll │ │ ├── PlayCtrl.dll │ │ ├── PlayCtrl.lib │ │ ├── SuperRender.dll │ │ ├── YUVProcess.dll │ │ ├── examples.jar │ │ ├── hlog.dll │ │ ├── hpr.dll │ │ ├── libcrypto-1_1-x64.dll │ │ ├── libmmd.dll │ │ ├── libssl-1_1-x64.dll │ │ └── zlib1.dll ├── pom.xml ├── spring-hot-plugin-admin │ ├── .gitignore │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── csdn │ │ │ └── itsaysay │ │ │ └── main │ │ │ └── admin │ │ │ └── AdminApplication.java │ │ └── resources │ │ └── application.yml ├── spring-hot-plugin-demo │ ├── .gitignore │ ├── Dockerfile │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── csdn │ │ │ └── itsaysay │ │ │ └── main │ │ │ ├── DemoApplication.java │ │ │ └── plugin │ │ │ ├── PluginController.java │ │ │ ├── req │ │ │ └── PluginReq.java │ │ │ ├── res │ │ │ └── PluginRes.java │ │ │ └── service │ │ │ ├── PluginListener.java │ │ │ ├── PluginService.java │ │ │ └── impl │ │ │ └── PluginServiceImpl.java │ │ └── resources │ │ ├── application.yml │ │ ├── data.sql │ │ └── schema.sql ├── spring-hot-plugin-example.iml └── spring-hot-plugin-task-demo │ ├── .gitignore │ ├── Dockerfile │ ├── pom.xml │ └── src │ └── main │ ├── java │ └── csdn │ │ └── itsaysay │ │ └── task │ │ ├── TaskDemoApplication.java │ │ └── service │ │ └── TaskService.java │ └── resources │ └── application.yml ├── spring-hot-plugin-loader ├── .gitignore ├── pom.xml └── src │ ├── main │ └── java │ │ └── vip │ │ └── aliali │ │ └── spring │ │ └── plugin │ │ └── loader │ │ ├── ClassPathIndexFile.java │ │ ├── ExecutableArchiveLauncher.java │ │ ├── JarLauncher.java │ │ ├── LaunchedURLClassLoader.java │ │ ├── Launcher.java │ │ ├── MainMethodRunner.java │ │ ├── PropertiesLauncher.java │ │ ├── WarLauncher.java │ │ ├── archive │ │ ├── Archive.java │ │ ├── ExplodedArchive.java │ │ ├── JarFileArchive.java │ │ └── package-info.java │ │ ├── data │ │ ├── RandomAccessData.java │ │ ├── RandomAccessDataFile.java │ │ └── package-info.java │ │ ├── jar │ │ ├── AbstractJarFile.java │ │ ├── AsciiBytes.java │ │ ├── Bytes.java │ │ ├── CentralDirectoryEndRecord.java │ │ ├── CentralDirectoryFileHeader.java │ │ ├── CentralDirectoryParser.java │ │ ├── CentralDirectoryVisitor.java │ │ ├── FileHeader.java │ │ ├── Handler.java │ │ ├── JarEntry.java │ │ ├── JarEntryCertification.java │ │ ├── JarEntryFilter.java │ │ ├── JarFile.java │ │ ├── JarFileEntries.java │ │ ├── JarFileWrapper.java │ │ ├── JarURLConnection.java │ │ ├── StringSequence.java │ │ ├── ZipInflaterInputStream.java │ │ └── package-info.java │ │ ├── jarmode │ │ ├── JarMode.java │ │ ├── JarModeLauncher.java │ │ ├── TestJarMode.java │ │ └── package-info.java │ │ ├── package-info.java │ │ └── util │ │ ├── SystemPropertyUtils.java │ │ └── package-info.java │ └── test │ └── java │ ├── AbstractExecutableArchiveLauncherTests.java │ └── JarLauncherTest.java ├── spring-hot-plugin-maven ├── .gitignore ├── pom.xml └── src │ └── main │ └── java │ └── vip │ └── aliali │ └── spring │ └── plugin │ └── maven │ ├── PackageConstants.java │ └── PackagePlugin.java └── spring-hot-plugin-mybatis ├── .gitignore ├── pom.xml └── src └── main ├── java └── vip │ └── aliali │ └── spring │ └── plugin │ └── mybatis │ ├── MybatisPluginConfiguration.java │ ├── MybatisPlusUtil.java │ ├── RegisterMybatis.java │ └── util │ └── MapperUtils.java └── resources └── META-INF └── spring.factories /.gitee/ISSUE_TEMPLATE.zh-CN.md: -------------------------------------------------------------------------------- 1 | ### 该问题是怎么引起的? 2 | 3 | 4 | 5 | ### 重现步骤 6 | 7 | 8 | 9 | ### 报错信息 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.gitee/PULL_REQUEST_TEMPLATE.zh-CN.md: -------------------------------------------------------------------------------- 1 | ### 相关的Issue 2 | 3 | 4 | ### 原因(目的、解决的问题等) 5 | 6 | 7 | ### 描述(做了什么,变更了什么) 8 | 9 | 10 | ### 测试用例(新增、改动、可能影响的功能) 11 | 12 | 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | .idea 26 | .settings/ 27 | .project 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | Spring Plugin Development Framework 4 | 5 | 6 | [🤔Reporting Issues][Issues-url] [📘中文][chinese-url] 7 | 8 | [![license][license-image]][license-url] 9 | [![build][build-image]][build-url] 10 | [![jdk][jdk-image]][jdk-url] 11 | [![hutool][hutool-image]][hutool-url] 12 |
13 | 14 | [license-image]: https://img.shields.io/badge/license-Apache%202.0-green 15 | [stars-image]: https://badgen.net/github/stars/jujunchen/spring-hot-plugin 16 | [build-image]: https://img.shields.io/badge/build-Spring%20Boot%202.7.18-45e91c 17 | [jdk-image]: https://img.shields.io/badge/JDK-8+-green 18 | [hutool-image]: https://img.shields.io/badge/hutool-5.8.4-green 19 | 20 | [license-url]: ./LICENSE 21 | [build-url]: https://github.com/spring-projects/spring-boot 22 | [jdk-url]: https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html 23 | [hutool-url]: https://github.com/dromara/hutool 24 | [Issues-url]: https://github.com/jujunchen/spring-hot-plugin/issues 25 | [chinese-url]: README.zh-cn.md 26 | 27 | ## Introduction 28 | A lightweight, fast, easy, and stable Spring-based plugin development framework. It does not require exposing core module code, reduces code coupling, supports hot reloading for dynamic updates, and improves development efficiency. 29 | 30 | Lightweight: Lightweight 31 | 32 | Fast: Fast startup 33 | 34 | Easy: Easy to use, native Spring programming 35 | 36 | Stable: Stable, strong compatibility 37 | 38 | ## Supported Features 39 | v1.2 (Under Development) 40 | - Support for distributed deployment 41 | - Support for Freemarker template engine 42 | 43 | v1.1.1 44 | - Support for using third-party dependencies in plugins, including jar and dll files 45 | - Support for MyBatis and MyBatisPlus 46 | 47 | v1.0 48 | - Support for subclasses referencing parent Spring Beans 49 | - Isolation of plugin code from main program code 50 | - Support for hot reloading of ordinary classes and various Spring Beans 51 | - Support for hot reloading of Controller controllers 52 | - Support for hot reloading of scheduled tasks 53 | - Support for using third-party dependencies in plugins 54 | - Support for the main program to listen to plugin startup and uninstallation events 55 | 56 | ## Principle 57 | 58 | ![Architecture Diagram](./images/architecture.png) 59 | 60 | Based on Spring's applicationContext and classLoader, hot reloading of classes in plugins is performed. During uninstallation, efforts are made to cut off GC ROOTs to avoid memory leaks. 61 | 62 | ## Installation Guide 63 | - spring-hot-plugin-common: Plugin common package 64 | - spring-hot-plugin-core: Plugin core package 65 | - spring-hot-plugin-loader: Plugin dependency loading package 66 | - spring-hot-plugin-maven: Plugin Maven packaging tool 67 | - spring-hot-plugin-mybatis: Plugin MyBatis dependency package 68 | - spring-hot-plugin-example: Plugin example project 69 | 70 | ### Maven Installation 71 | #### Using controller, scheduled tasks, third-party dependencies 72 | ```xml 73 | 74 | 75 | vip.aliali.spring 76 | spring-hot-plugin-core 77 | ${lastVersion} 78 | 79 | ``` 80 | #### Using Mybatis, Mybatis-plus 81 | ```xml 82 | 83 | 84 | vip.aliali.spring 85 | spring-hot-plugin-mybatis 86 | ${lastVersion} 87 | 88 | ``` 89 | ### Source Code Build 90 | 1. git clone this project 91 | 2. Import the project in IDEA, run `mvn clean install` in the root directory (or upload to a private repository) 92 | 3. Introduce the plugin core package in the main program, modify the version to the latest version 93 | 94 | ```xml 95 | 96 | 97 | csdn.itsaysay.plugin 98 | spring-hot-plugin-core 99 | ${lastVersion} 100 | 101 | ``` 102 | 103 | 4. Introduce other dependencies as needed 104 | 105 | ### Usage Instructions 106 | 1. Introduce plugin dependencies in the main program's pom.xml 107 | 2. In the main program, refer to the `spring-hot-plugin-demo` project to create an interface for installing plugins 108 | 3. Configure the plugin 109 | 110 | ```yml 111 | plugin: 112 | #Whether to enable plugin functionality 113 | enable: 114 | #Run mode, development environment: dev, production environment: prod 115 | runMode: 116 | #Backup directory after uninstalling the plugin 117 | backupPath: 118 | #Plugin path, if the plugin path exists, it will be automatically loaded 119 | pluginPath: 120 | #Package path to scan 121 | basePackage: 122 | ``` 123 | 4. Plugin Development 124 | >- Refer to `plugin-demo`, introduce the main program with `provided` lifecycle in Maven, so that the plugin can reference the main program's Beans. 125 | >Other development methods are the same as usual 126 | >- Packaging tool, refer to the pom file configuration of `plugin-demo` for `spring-hot-plugin-maven` 127 | 5. Install Plugin 128 | - Perform `dynamic` installation through the previously created interface, select the jar package with `-repackage` suffix (**Recommended**) 129 | - Directly place it in the plugin installation directory, requires restarting the main program 130 | 131 | ## Performance Test 132 | Test Case: 133 | Simulate frequent plugin installation, uninstallation, and data operations to observe memory consumption. 134 | 135 | Docker container maximum memory: 256MB 136 | 137 | 1. Install plugin-demo-mybatis plugin 138 | 2. Add data 139 | 3. Query data 140 | 4. Delete data 141 | 5. Uninstall plugin-demo-mybatis plugin 142 | 143 | ![img.png](images/docker-img.png) 144 | 145 | ## Contribution 146 | 147 | 1. Fork this repository 148 | 2. Create a new Feat_xxx branch 149 | 3. Submit code 150 | 4. Create a Pull Request 151 | -------------------------------------------------------------------------------- /README.zh-cn.md: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | Spring 插件化开发框架 5 | 6 | 7 | [🤔Reporting Issues][Issues-url] [📘English Documentation][english-url] 8 | 9 | [![license][license-image]][license-url] 10 | [![build][build-image]][build-url] 11 | [![jdk][jdk-image]][jdk-url] 12 | [![hutool][hutool-image]][hutool-url] 13 |
14 | 15 | [license-image]: https://img.shields.io/badge/license-Apache%202.0-green 16 | [stars-image]: https://badgen.net/github/stars/jujunchen/spring-hot-plugin 17 | [build-image]: https://img.shields.io/badge/build-Spring%20Boot%202.7.18-45e91c 18 | [jdk-image]: https://img.shields.io/badge/JDK-8+-green 19 | [hutool-image]: https://img.shields.io/badge/hutool-5.8.4-green 20 | 21 | [license-url]: ./LICENSE 22 | [build-url]: https://github.com/spring-projects/spring-boot 23 | [jdk-url]: https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html 24 | [hutool-url]: https://github.com/dromara/hutool 25 | [Issues-url]: https://github.com/jujunchen/spring-hot-plugin/issues 26 | [english-url]: README.md 27 | 28 | ## 介绍 29 | 基于Spring 的插件化开发框架,轻、快、易、稳,无需暴露核心模块代码,降低代码耦合,热加载动态更新,提高开发效率。 30 | 31 | 轻:轻量 32 | 33 | 快:启动速度快 34 | 35 | 易:使用简单,原生spring编程 36 | 37 | 稳:稳定,兼容性强 38 | 39 | ## 支持特性 40 | v1.2(开发中) 41 | - 支持分布式部署 42 | - 支持freemarker模板引擎 43 | 44 | v1.1.1 45 | - 支持插件中使用第三方依赖,jar、dll文件 46 | - 支持Mybatis、MybatisPlus 47 | 48 | v1.0 49 | - 支持子类引用父类Spring Bean 50 | - 插件代码与主程序代码隔离 51 | - 支持热加载普通类、各类Spring Bean 52 | - 支持热加载Controller控制器 53 | - 支持热加载定时任务 54 | - 支持插件中使用第三方依赖 55 | - 支持主程序监听插件启动卸载事件 56 | 57 | ## 原理 58 | 59 | ![架构图](./images/architecture.png) 60 | 61 | 基于Spring的 applicationContext 和 classLoader 对插件中的类进行热加载,卸载的时候尽可能切断GC ROOT,避免内存泄露。 62 | 63 | ## 安装教程 64 | - spring-hot-plugin-common 插件公共包 65 | - spring-hot-plugin-core 插件核心包 66 | - spring-hot-plugin-loader 插件依赖加载包 67 | - spring-hot-plugin-maven 插件maven打包工具 68 | - spring-hot-plugin-mybatis 插件mybatis依赖包 69 | - spring-hot-plugin-example 插件示例项目 70 | 71 | ### Maven 安装 72 | #### 使用controller、定时任务、第三方依赖 73 | ```xml 74 | 75 | 76 | vip.aliali.spring 77 | spring-hot-plugin-core 78 | ${lastVersion} 79 | 80 | ``` 81 | #### 使用mybatis、mybatis-plus 82 | ```xml 83 | 84 | 85 | vip.aliali.spring 86 | spring-hot-plugin-mybatis 87 | ${lastVersion} 88 | 89 | ``` 90 | 91 | 92 | ### 源码构建 93 | 1. git clone 本项目 94 | 2. IDEA导入项目,根目录运行 mvn clean install(或者上传到私服) 95 | 3. 主程序中引入插件核心包,修改版本为最新版本 96 | ```xml 97 | 98 | 99 | csdn.itsaysay.plugin 100 | spring-hot-plugin-core 101 | ${lastVersion} 102 | 103 | ``` 104 | 4. 其他按需引入依赖 105 | 106 | 107 | ### 使用说明 108 | 1. 在主程序pom.xml中引入插件依赖 109 | 1. 在主程序中,参考`spring-hot-plugin-demo`项目创建一个安装插件的接口 110 | 2. 配置插件 111 | ```yml 112 | plugin: 113 | #是否启用插件功能 114 | enable: 115 | #运行模式,开发环境: dev,生产环境: prod 116 | runMode: 117 | #在卸载插件后, 备份插件的目录 118 | backupPath: 119 | #插件的路径,如果插件路径下存在插件会自动加载 120 | pluginPath: 121 | #扫描的包路径 122 | basePackage: 123 | ``` 124 | 3. 插件开发 125 | >- 参考`plugin-demo`,将主程序以`provided`的生命周期引入maven,这样就可以在插件中引用主程序的Bean。 126 | >其他就如同平时开发方式一样 127 | >- 打包工具,同样参考plugin-demo 的 pom文件配置 `spring-hot-plugin-maven` 128 | 4. 安装插件 129 | - 通过前面创建的接口进行`动态`安装,选择`-repackage`结尾的jar包(**推荐**) 130 | - 直接放入插件安装目录,需要重启主程序 131 | 132 | ## 性能测试 133 | 测试用例: 134 | 模拟实际频繁的插件安装卸载和增删改数据操作,观察内存消耗情况。 135 | 136 | Docker 容器最大内容:256MB 137 | 138 | 1. 安装plugin-demo-mybatis插件 139 | 2. 新增数据 140 | 3. 查询数据 141 | 4. 删除数据 142 | 5. 卸载plugin-demo-mybatis插件 143 | 144 | ![img.png](images/docker-img.png) 145 | 146 | 147 | ## 参与贡献 148 | 149 | 1. Fork 本仓库 150 | 2. 新建 Feat_xxx 分支 151 | 3. 提交代码 152 | 4. 新建 Pull Request 153 | -------------------------------------------------------------------------------- /images/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/images/architecture.png -------------------------------------------------------------------------------- /images/docker-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/images/docker-img.png -------------------------------------------------------------------------------- /spring-hot-plugin-common/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/modules.xml 8 | .idea/jarRepositories.xml 9 | .idea/compiler.xml 10 | .idea/libraries/ 11 | *.iws 12 | *.iml 13 | *.ipr 14 | 15 | ### Eclipse ### 16 | .apt_generated 17 | .classpath 18 | .factorypath 19 | .project 20 | .settings 21 | .springBeans 22 | .sts4-cache 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | 37 | ### Mac OS ### 38 | .DS_Store -------------------------------------------------------------------------------- /spring-hot-plugin-common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | vip.aliali.spring 8 | spring-hot-plugin-parent 9 | 1.1.1 10 | 11 | 12 | spring-hot-plugin-common 13 | 插件公共组件 14 | 15 | 16 | 8 17 | 8 18 | UTF-8 19 | 20 | 21 | 22 | 23 | cn.hutool 24 | hutool-core 25 | ${hutool.version} 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /spring-hot-plugin-common/src/main/java/vip/aliali/spring/plugin/constants/PluginConstants.java: -------------------------------------------------------------------------------- 1 | package vip.aliali.spring.plugin.constants; 2 | 3 | 4 | /** 5 | * 插件常量 6 | * @author jujunChen 7 | * 8 | */ 9 | public class PluginConstants { 10 | 11 | public static final String TARGET = "target"; 12 | 13 | public static final String POM = "pom.xml"; 14 | 15 | public static final String JAR_SUFFIX = ".jar"; 16 | 17 | public static final String REPACKAGE = "repackage"; 18 | 19 | public static final String CLASSES = "classes"; 20 | 21 | public static final String LIB = "lib"; 22 | 23 | public static final String CLASS_SUFFIX = ".class"; 24 | 25 | public static final String MANIFEST = "MANIFEST.MF"; 26 | 27 | public static final String PLUGINID = "pluginId"; 28 | 29 | public static final String PLUGINVERSION = "pluginVersion"; 30 | 31 | public static final String PLUGINDESCRIPTION = "pluginDescription"; 32 | 33 | public static final String PLUGIN_POM = "plugins/pom.xml"; 34 | public static final String FILE_PREFIX = "file:/"; 35 | 36 | public static final String JAR_DELIMITER = "!/"; 37 | 38 | public static final String JAR_File_DELIMITER = "/"; 39 | 40 | public static final String JAR_File_ACROSS = "-"; 41 | 42 | public static final String JAR_NAME = "jar"; 43 | } 44 | -------------------------------------------------------------------------------- /spring-hot-plugin-common/src/main/java/vip/aliali/spring/plugin/constants/PluginState.java: -------------------------------------------------------------------------------- 1 | package vip.aliali.spring.plugin.constants; 2 | 3 | /** 4 | * 插件状态 5 | * @author jujunChen 6 | * 7 | */ 8 | public enum PluginState { 9 | /** 10 | * 被禁用状态 11 | */ 12 | DISABLED("DISABLED"), 13 | 14 | /** 15 | * 启动状态 16 | */ 17 | STARTED("STARTED"), 18 | 19 | 20 | /** 21 | * 停止状态 22 | */ 23 | STOPPED("STOPPED"); 24 | 25 | private final String status; 26 | 27 | PluginState(String status) { 28 | this.status = status; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /spring-hot-plugin-common/src/main/java/vip/aliali/spring/plugin/constants/RuntimeMode.java: -------------------------------------------------------------------------------- 1 | package vip.aliali.spring.plugin.constants; 2 | 3 | 4 | /** 5 | * 插件运行环境 6 | * @author jujunChen 7 | * 8 | */ 9 | public enum RuntimeMode { 10 | 11 | /** 12 | * 开发环境 13 | */ 14 | DEV("dev"), 15 | 16 | /** 17 | * 生产环境 18 | */ 19 | PROD("prod"); 20 | 21 | private final String mode; 22 | 23 | public static RuntimeMode byName(String model){ 24 | if(DEV.name().equalsIgnoreCase(model)){ 25 | return RuntimeMode.DEV; 26 | } else { 27 | return RuntimeMode.PROD; 28 | } 29 | } 30 | 31 | RuntimeMode(String mode) { 32 | this.mode = mode; 33 | } 34 | 35 | public String getMode() { 36 | return mode; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /spring-hot-plugin-core/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/modules.xml 8 | .idea/jarRepositories.xml 9 | .idea/compiler.xml 10 | .idea/libraries/ 11 | *.iws 12 | *.iml 13 | *.ipr 14 | 15 | ### Eclipse ### 16 | .apt_generated 17 | .classpath 18 | .factorypath 19 | .project 20 | .settings 21 | .springBeans 22 | .sts4-cache 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | 37 | ### Mac OS ### 38 | .DS_Store -------------------------------------------------------------------------------- /spring-hot-plugin-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | vip.aliali.spring 8 | spring-hot-plugin-parent 9 | 1.1.1 10 | 11 | 12 | spring-hot-plugin-core 13 | jar 14 | 15 | 插件核心模块 16 | 17 | 18 | 8 19 | 8 20 | UTF-8 21 | 22 | 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-web 27 | provided 28 | true 29 | 30 | 31 | 32 | org.apache.maven 33 | maven-model 34 | 3.8.4 35 | 36 | 37 | 38 | vip.aliali.spring 39 | spring-hot-plugin-common 40 | ${project.version} 41 | 42 | 43 | 44 | 45 | vip.aliali.spring 46 | spring-hot-plugin-loader 47 | ${project.version} 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /spring-hot-plugin-core/src/main/java/vip/aliali/spring/plugin/AbstractPluginApplication.java: -------------------------------------------------------------------------------- 1 | package vip.aliali.spring.plugin; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.factory.BeanCreationException; 6 | import org.springframework.context.ApplicationContext; 7 | 8 | import java.util.Objects; 9 | import java.util.concurrent.atomic.AtomicBoolean; 10 | 11 | public abstract class AbstractPluginApplication { 12 | 13 | private final Logger log = LoggerFactory.getLogger(this.getClass()); 14 | 15 | private final AtomicBoolean beInitialized = new AtomicBoolean(false); 16 | 17 | public synchronized void initialize(ApplicationContext applicationContext) { 18 | Objects.requireNonNull(applicationContext, "ApplicationContext can't be null"); 19 | if(beInitialized.get()) { 20 | throw new RuntimeException("Plugin has been initialized"); 21 | } 22 | //获取配置 23 | PluginAutoConfiguration configuration = getConfiguration(applicationContext); 24 | 25 | if (Boolean.FALSE.equals(configuration.getEnable())) { 26 | log.info("插件已禁用"); 27 | return; 28 | } 29 | 30 | try { 31 | log.info("插件加载环境: {},插件目录: {}", configuration.getRunMode(), String.join(",", configuration.getPluginPath())); 32 | 33 | DefaultPluginManager pluginManager = getPluginManager(applicationContext); 34 | pluginManager.createPluginListenerFactory(); 35 | pluginManager.loadPlugins(); 36 | beInitialized.set(true); 37 | 38 | log.info("插件启动完成"); 39 | } catch (Exception e) { 40 | log.error("初始化插件异常", e); 41 | } 42 | } 43 | 44 | protected PluginAutoConfiguration getConfiguration(ApplicationContext applicationContext) { 45 | PluginAutoConfiguration configuration = null; 46 | try { 47 | configuration = applicationContext.getBean(PluginAutoConfiguration.class); 48 | } catch (Exception e){ 49 | // no show exception 50 | } 51 | if(configuration == null){ 52 | throw new BeanCreationException("没有发现 Bean"); 53 | } 54 | return configuration; 55 | } 56 | 57 | protected DefaultPluginManager getPluginManager(ApplicationContext applicationContext) { 58 | DefaultPluginManager pluginManager = null; 59 | try { 60 | pluginManager = applicationContext.getBean(DefaultPluginManager.class); 61 | } catch (Exception e){ 62 | // no show exception 63 | } 64 | if(pluginManager == null){ 65 | throw new BeanCreationException("没有发现 Bean"); 66 | } 67 | return pluginManager; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /spring-hot-plugin-core/src/main/java/vip/aliali/spring/plugin/DefaultPluginApplication.java: -------------------------------------------------------------------------------- 1 | package vip.aliali.spring.plugin; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.boot.context.event.ApplicationStartedEvent; 5 | import org.springframework.context.ApplicationContext; 6 | import org.springframework.context.ApplicationContextAware; 7 | import org.springframework.context.ApplicationListener; 8 | import org.springframework.context.annotation.Import; 9 | 10 | @Import(PluginConfiguration.class) 11 | public class DefaultPluginApplication extends AbstractPluginApplication implements ApplicationContextAware, ApplicationListener { 12 | 13 | private ApplicationContext applicationContext; 14 | 15 | //主程序启动后加载插件 16 | @Override 17 | public void onApplicationEvent(ApplicationStartedEvent event) { 18 | super.initialize(applicationContext); 19 | } 20 | 21 | @Override 22 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 23 | this.applicationContext = applicationContext; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /spring-hot-plugin-core/src/main/java/vip/aliali/spring/plugin/PluginAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package vip.aliali.spring.plugin; 2 | 3 | import vip.aliali.spring.plugin.constants.RuntimeMode; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | 7 | /** 8 | * 插件自动化配置 9 | * @author jujunChen 10 | * 11 | */ 12 | @ConfigurationProperties(prefix = "plugin") 13 | public class PluginAutoConfiguration { 14 | 15 | /** 16 | * 是否启用插件功能 17 | */ 18 | @Value("${enable:true}") 19 | private Boolean enable; 20 | 21 | /** 22 | * 运行模式 23 | * 开发环境: dev 24 | * 生产环境: prod 25 | */ 26 | @Value("${runMode:dev}") 27 | private String runMode; 28 | 29 | /** 30 | * 插件的路径 31 | */ 32 | private String pluginPath; 33 | 34 | /** 35 | * 在卸载插件后, 备份插件的目录 36 | */ 37 | @Value("${backupPath:backupPlugin}") 38 | private String backupPath; 39 | 40 | /** 41 | * 扫描的包路径 42 | */ 43 | @Value("${basePackage:csdn.itsaysay.plugin}") 44 | private String basePackage; 45 | 46 | public RuntimeMode environment() { 47 | return RuntimeMode.byName(runMode); 48 | } 49 | 50 | public Boolean getEnable() { 51 | return enable; 52 | } 53 | 54 | public void setEnable(Boolean enable) { 55 | this.enable = enable; 56 | } 57 | 58 | public String getRunMode() { 59 | return runMode; 60 | } 61 | 62 | public void setRunMode(String runMode) { 63 | this.runMode = runMode; 64 | } 65 | 66 | public String getPluginPath() { 67 | return pluginPath; 68 | } 69 | 70 | public void setPluginPath(String pluginPath) { 71 | this.pluginPath = pluginPath; 72 | } 73 | 74 | public String getBackupPath() { 75 | return backupPath; 76 | } 77 | 78 | public void setBackupPath(String backupPath) { 79 | this.backupPath = backupPath; 80 | } 81 | 82 | public String getBasePackage() { 83 | return basePackage; 84 | } 85 | 86 | public void setBasePackage(String basePackage) { 87 | this.basePackage = basePackage; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /spring-hot-plugin-core/src/main/java/vip/aliali/spring/plugin/PluginClassRegister.java: -------------------------------------------------------------------------------- 1 | package vip.aliali.spring.plugin; 2 | 3 | import cn.hutool.core.util.ReflectUtil; 4 | import vip.aliali.spring.plugin.loader.LaunchedURLClassLoader; 5 | import vip.aliali.spring.plugin.loader.jar.JarFile; 6 | import vip.aliali.spring.plugin.register.Register; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.context.ApplicationContext; 9 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 10 | 11 | import java.io.IOException; 12 | import java.net.URL; 13 | import java.net.URLClassLoader; 14 | import java.security.ProtectionDomain; 15 | import java.util.Map; 16 | import java.util.Vector; 17 | 18 | /** 19 | * 插件注册 20 | */ 21 | @Slf4j 22 | public class PluginClassRegister { 23 | private ApplicationContext applicationContext; 24 | private PluginAutoConfiguration configuration; 25 | private Map pluginBeans; 26 | 27 | public PluginClassRegister(ApplicationContext applicationContext, PluginAutoConfiguration configuration, Map pluginBeans) { 28 | this.applicationContext = applicationContext; 29 | this.configuration = configuration; 30 | this.pluginBeans = pluginBeans; 31 | } 32 | 33 | 34 | public ApplicationContext register(PluginInfo pluginInfo) { 35 | ApplicationContext pluginApplicationContext = registerBean(pluginInfo); 36 | pluginBeans.put(pluginInfo.getId(), pluginApplicationContext); 37 | return pluginApplicationContext; 38 | } 39 | 40 | public boolean unRegister(PluginInfo pluginInfo) throws IOException { 41 | return unRegisterBean(pluginInfo); 42 | } 43 | 44 | private boolean unRegisterBean(PluginInfo pluginInfo) throws IOException { 45 | AnnotationConfigApplicationContext pluginApplicationContext = (AnnotationConfigApplicationContext) pluginBeans.get(pluginInfo.getId()); 46 | //取消注册 47 | applyUnRegister(pluginApplicationContext, pluginInfo); 48 | clearClassLoader(pluginApplicationContext); 49 | clearApplicationContext(pluginApplicationContext); 50 | pluginBeans.remove(pluginInfo.getId()); 51 | //及时回收掉 52 | System.gc(); 53 | return true; 54 | } 55 | 56 | /** 57 | * 清理applicationContext 58 | * @param pluginApplicationContext 59 | */ 60 | private void clearApplicationContext(AnnotationConfigApplicationContext pluginApplicationContext) { 61 | pluginApplicationContext.setClassLoader(null); 62 | pluginApplicationContext.close(); 63 | } 64 | 65 | /** 66 | * 清理classLoader 67 | * @param pluginApplicationContext 68 | */ 69 | private void clearClassLoader(AnnotationConfigApplicationContext pluginApplicationContext) { 70 | LaunchedURLClassLoader launchedURLClassLoader = ((LaunchedURLClassLoader)pluginApplicationContext.getClassLoader()); 71 | ProtectionDomain protectionDomain = ((ProtectionDomain) ReflectUtil.getFieldValue(launchedURLClassLoader, "defaultDomain")); 72 | ReflectUtil.setFieldValue(protectionDomain, "classloader", null); 73 | ((Map)ReflectUtil.getFieldValue(launchedURLClassLoader, "packages")).clear(); 74 | ((Map)ReflectUtil.getFieldValue(launchedURLClassLoader, "pdcache")).clear(); 75 | ((Vector)ReflectUtil.getFieldValue(launchedURLClassLoader, "classes")).clear(); 76 | launchedURLClassLoader.close(); 77 | } 78 | 79 | private ApplicationContext registerBean(PluginInfo pluginInfo) { 80 | URLClassLoader classLoader = null; 81 | try { 82 | URL[] dependenciesURLs = pluginInfo.getDependenciesPath(); 83 | //插件的类路径加入到自定义classLoader中 84 | classLoader = createClassLoader(dependenciesURLs); 85 | 86 | //一个插件创建一个applicationContext 87 | AnnotationConfigApplicationContext pluginApplicationContext = new AnnotationConfigApplicationContext(); 88 | pluginApplicationContext.setClassLoader(classLoader); 89 | pluginApplicationContext.setParent(applicationContext); 90 | pluginApplicationContext.scan(configuration.getBasePackage()); 91 | //上下文刷新前的操作 92 | applyRefreshBeforeRegister(pluginApplicationContext, pluginInfo); 93 | pluginApplicationContext.refresh(); 94 | //上下文刷新后的操作 95 | applyRefreshAfterRegister(pluginApplicationContext, pluginInfo); 96 | 97 | return pluginApplicationContext; 98 | } catch (Exception | Error e) { 99 | throw new PluginException("注册bean异常", e); 100 | } 101 | } 102 | 103 | private URLClassLoader createClassLoader(URL[] newPath) { 104 | JarFile.registerUrlProtocolHandler(); 105 | return new LaunchedURLClassLoader(newPath, getClass().getClassLoader()); 106 | } 107 | 108 | private void applyUnRegister(ApplicationContext pluginApplicationContext, PluginInfo pluginInfo) { 109 | Map registers = applicationContext.getBeansOfType(Register.class); 110 | registers.forEach((name, register) -> { 111 | register.unRegister(pluginApplicationContext, pluginInfo); 112 | }); 113 | } 114 | 115 | private void applyRefreshAfterRegister(ApplicationContext pluginApplicationContext, PluginInfo pluginInfo) { 116 | Map registers = applicationContext.getBeansOfType(Register.class); 117 | registers.forEach((name, register) -> { 118 | register.refreshAfterRegister(pluginApplicationContext, pluginInfo); 119 | }); 120 | } 121 | 122 | private void applyRefreshBeforeRegister(AnnotationConfigApplicationContext pluginApplicationContext, PluginInfo pluginInfo) { 123 | Map registers = applicationContext.getBeansOfType(Register.class); 124 | registers.forEach((name, register) -> { 125 | register.refreshBeforeRegister(pluginApplicationContext, pluginInfo); 126 | }); 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /spring-hot-plugin-core/src/main/java/vip/aliali/spring/plugin/PluginConfiguration.java: -------------------------------------------------------------------------------- 1 | package vip.aliali.spring.plugin; 2 | 3 | import vip.aliali.spring.plugin.register.ClearLoggerCache; 4 | import vip.aliali.spring.plugin.register.RegisterController; 5 | import vip.aliali.spring.plugin.register.RegisterScheduled; 6 | import org.springframework.context.ApplicationContext; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | @Configuration 11 | public class PluginConfiguration { 12 | 13 | 14 | @Bean 15 | public PluginManager createPluginManager(PluginAutoConfiguration configuration, ApplicationContext applicationContext) { 16 | return new DefaultPluginManager(configuration, applicationContext); 17 | } 18 | 19 | @Bean 20 | public RegisterController createRegisterController(ApplicationContext main) { 21 | return new RegisterController(main); 22 | } 23 | 24 | 25 | @Bean 26 | public RegisterScheduled createRegisterScheduled(ApplicationContext main) { 27 | return new RegisterScheduled(main); 28 | } 29 | 30 | @Bean 31 | public ClearLoggerCache createClearLoggerCache(ApplicationContext main) { 32 | return new ClearLoggerCache(main); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /spring-hot-plugin-core/src/main/java/vip/aliali/spring/plugin/PluginException.java: -------------------------------------------------------------------------------- 1 | package vip.aliali.spring.plugin; 2 | 3 | 4 | import java.util.function.Supplier; 5 | 6 | public class PluginException extends RuntimeException { 7 | public PluginException() { 8 | super(); 9 | } 10 | 11 | public PluginException(String message) { 12 | super(message); 13 | } 14 | 15 | public PluginException(Throwable cause) { 16 | super(cause); 17 | } 18 | 19 | public PluginException(String message, Throwable cause, String... arg) { 20 | super(String.format(message, arg), cause); 21 | } 22 | 23 | public static PluginException getPluginException(Throwable throwable, Supplier getException){ 24 | if(throwable instanceof PluginException){ 25 | return (PluginException) throwable; 26 | } 27 | return getException.get(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /spring-hot-plugin-core/src/main/java/vip/aliali/spring/plugin/PluginInfo.java: -------------------------------------------------------------------------------- 1 | package vip.aliali.spring.plugin; 2 | 3 | import vip.aliali.spring.plugin.constants.PluginState; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | 7 | import java.net.URL; 8 | import java.util.Objects; 9 | 10 | /** 11 | * 插件信息 12 | * @author jujunChen 13 | * 14 | */ 15 | @Builder 16 | @Data 17 | public class PluginInfo { 18 | 19 | /** 20 | * 插件id 21 | */ 22 | private String id; 23 | 24 | /** 25 | * 版本 26 | */ 27 | private String version; 28 | 29 | /** 30 | * 描述 31 | */ 32 | private String description; 33 | 34 | /** 35 | * 插件路径 36 | */ 37 | private String path; 38 | 39 | /** 40 | * 插件的依赖所在路径 41 | */ 42 | private URL[] dependenciesPath; 43 | 44 | /** 45 | * 插件启动状态 46 | */ 47 | private PluginState pluginState; 48 | 49 | @Override 50 | public boolean equals(Object obj) { 51 | if (this == obj) 52 | return true; 53 | if (obj == null) 54 | return false; 55 | if (getClass() != obj.getClass()) 56 | return false; 57 | PluginInfo other = (PluginInfo) obj; 58 | return Objects.equals(id, other.id); 59 | } 60 | 61 | @Override 62 | public int hashCode() { 63 | return Objects.hash(id); 64 | } 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /spring-hot-plugin-core/src/main/java/vip/aliali/spring/plugin/PluginManager.java: -------------------------------------------------------------------------------- 1 | package vip.aliali.spring.plugin; 2 | 3 | import org.springframework.context.ApplicationContext; 4 | import org.springframework.web.multipart.MultipartFile; 5 | 6 | import java.lang.annotation.Annotation; 7 | import java.util.List; 8 | 9 | /** 10 | * 插件管理接口 11 | * @author jujunChen 12 | * 13 | */ 14 | public interface PluginManager { 15 | 16 | /** 17 | * 加载配置目录中全部插件 18 | * @return 19 | */ 20 | List loadPlugins() throws Exception; 21 | 22 | /** 23 | * 安装插件,覆盖安装,并备份原版本,只适用于prod环境 24 | * @param file 插件输入流 25 | * @return 26 | */ 27 | PluginInfo install(MultipartFile file); 28 | 29 | /** 30 | * 卸载插件,停止插件,删除插件,只适用于prod环境 31 | * @param pluginId 插件id 32 | */ 33 | void uninstall(String pluginId); 34 | 35 | /** 36 | * 启动插件 37 | * @param pluginId 插件id 38 | * @return 39 | */ 40 | PluginInfo start(String pluginId); 41 | 42 | /** 43 | * 停止插件 44 | * @param pluginId 插件id 45 | * @return 46 | */ 47 | PluginInfo stop(String pluginId); 48 | 49 | /** 50 | * 获取当前插件列表 51 | * @param pluginId 插件id 52 | * @return 53 | */ 54 | List list(String pluginId); 55 | 56 | 57 | /** 58 | * 获取插件加载上下文 59 | * @param pluginId 60 | * @return 61 | */ 62 | ApplicationContext getApplicationContext(String pluginId); 63 | 64 | /** 65 | * 从插件上下文中获取指定名称的bean 66 | * @param beanName bean名称 67 | * @return 68 | */ 69 | Object getBeanFromApplicationContext(String beanName); 70 | 71 | /** 72 | * 通过注解获取具体插件中的 Bean 73 | * @param pluginId 插件id 74 | * @param annotationType 指定注解 75 | * @return 76 | */ 77 | List getBeansWithAnnotation(String pluginId, Class annotationType); 78 | } 79 | -------------------------------------------------------------------------------- /spring-hot-plugin-core/src/main/java/vip/aliali/spring/plugin/PluginStarter.java: -------------------------------------------------------------------------------- 1 | package vip.aliali.spring.plugin; 2 | 3 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.context.annotation.Import; 6 | 7 | @Configuration 8 | @EnableConfigurationProperties(PluginAutoConfiguration.class) 9 | @Import(DefaultPluginApplication.class) 10 | public class PluginStarter { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /spring-hot-plugin-core/src/main/java/vip/aliali/spring/plugin/listener/DefaultPluginListenerFactory.java: -------------------------------------------------------------------------------- 1 | package vip.aliali.spring.plugin.listener; 2 | 3 | import vip.aliali.spring.plugin.PluginInfo; 4 | import org.springframework.context.ApplicationContext; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | * 插件监听工厂 12 | * @author jujunChen 13 | * 14 | */ 15 | public class DefaultPluginListenerFactory implements PluginListener { 16 | private final List listeners; 17 | 18 | public DefaultPluginListenerFactory(ApplicationContext applicationContext){ 19 | listeners = new ArrayList<>(); 20 | addExtendPluginListener(applicationContext); 21 | } 22 | 23 | public DefaultPluginListenerFactory(){ 24 | listeners = new ArrayList<>(); 25 | } 26 | 27 | 28 | private void addExtendPluginListener(ApplicationContext applicationContext){ 29 | Map beansOfTypeMap = applicationContext.getBeansOfType(PluginListener.class); 30 | if (!beansOfTypeMap.isEmpty()) { 31 | listeners.addAll(beansOfTypeMap.values()); 32 | } 33 | } 34 | 35 | public synchronized void addPluginListener(PluginListener pluginListener) { 36 | if(pluginListener != null){ 37 | listeners.add(pluginListener); 38 | } 39 | } 40 | 41 | public List getListeners() { 42 | return listeners; 43 | } 44 | 45 | 46 | @Override 47 | public void startSuccess(PluginInfo pluginInfo) { 48 | for (PluginListener listener : listeners) { 49 | try { 50 | listener.startSuccess(pluginInfo); 51 | } catch (Exception e) { 52 | 53 | } 54 | } 55 | } 56 | 57 | @Override 58 | public void startFailure(PluginInfo pluginInfo, Throwable throwable) { 59 | for (PluginListener listener : listeners) { 60 | try { 61 | listener.startFailure(pluginInfo, throwable); 62 | } catch (Exception e) { 63 | 64 | } 65 | } 66 | } 67 | 68 | @Override 69 | public void stopSuccess(PluginInfo pluginInfo) { 70 | for (PluginListener listener : listeners) { 71 | try { 72 | listener.stopSuccess(pluginInfo); 73 | } catch (Exception e) { 74 | 75 | } 76 | } 77 | } 78 | 79 | @Override 80 | public void stopFailure(PluginInfo pluginInfo, Throwable throwable) { 81 | for (PluginListener listener : listeners) { 82 | try { 83 | listener.stopFailure(pluginInfo, throwable); 84 | } catch (Exception e) { 85 | 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /spring-hot-plugin-core/src/main/java/vip/aliali/spring/plugin/listener/PluginListener.java: -------------------------------------------------------------------------------- 1 | package vip.aliali.spring.plugin.listener; 2 | 3 | 4 | import vip.aliali.spring.plugin.PluginInfo; 5 | 6 | /** 7 | * 插件监听器 8 | * @author jujunChen 9 | * 10 | */ 11 | public interface PluginListener { 12 | 13 | 14 | /** 15 | * 注册插件成功 16 | * @param pluginInfo 插件信息 17 | */ 18 | default void startSuccess(PluginInfo pluginInfo){} 19 | 20 | 21 | /** 22 | * 启动失败 23 | * @param pluginInfo 插件信息 24 | * @param throwable 异常信息 25 | */ 26 | default void startFailure(PluginInfo pluginInfo, Throwable throwable){} 27 | 28 | /** 29 | * 卸载插件成功 30 | * @param pluginInfo 插件信息 31 | */ 32 | default void stopSuccess(PluginInfo pluginInfo){} 33 | 34 | 35 | /** 36 | * 停止失败 37 | * @param pluginInfo 插件信息 38 | * @param throwable 异常信息 39 | */ 40 | default void stopFailure(PluginInfo pluginInfo, Throwable throwable){} 41 | } 42 | -------------------------------------------------------------------------------- /spring-hot-plugin-core/src/main/java/vip/aliali/spring/plugin/register/AbstractRegister.java: -------------------------------------------------------------------------------- 1 | package vip.aliali.spring.plugin.register; 2 | 3 | import vip.aliali.spring.plugin.PluginInfo; 4 | import org.springframework.context.ApplicationContext; 5 | 6 | public abstract class AbstractRegister implements Register{ 7 | protected ApplicationContext main; 8 | 9 | public AbstractRegister(ApplicationContext main) { 10 | this.main = main; 11 | } 12 | 13 | @Override 14 | public void refreshBeforeRegister(ApplicationContext plugin, PluginInfo pluginInfo) { 15 | 16 | } 17 | 18 | @Override 19 | public void refreshAfterRegister(ApplicationContext plugin, PluginInfo pluginInfo) { 20 | 21 | } 22 | 23 | @Override 24 | public void unRegister(ApplicationContext plugin, PluginInfo pluginInfo) { 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /spring-hot-plugin-core/src/main/java/vip/aliali/spring/plugin/register/ClearLoggerCache.java: -------------------------------------------------------------------------------- 1 | package vip.aliali.spring.plugin.register; 2 | 3 | import ch.qos.logback.classic.Logger; 4 | import ch.qos.logback.classic.LoggerContext; 5 | import cn.hutool.core.collection.CollUtil; 6 | import cn.hutool.core.util.ReflectUtil; 7 | import vip.aliali.spring.plugin.PluginAutoConfiguration; 8 | import vip.aliali.spring.plugin.PluginInfo; 9 | import vip.aliali.spring.plugin.util.DeployUtils; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.slf4j.ILoggerFactory; 12 | import org.slf4j.LoggerFactory; 13 | import org.springframework.context.ApplicationContext; 14 | 15 | import java.util.*; 16 | 17 | /** 18 | * 清理logger缓存 19 | * @author jujun.chen 20 | */ 21 | @Slf4j 22 | public class ClearLoggerCache extends AbstractRegister { 23 | 24 | private Map loggerCache; 25 | 26 | public ClearLoggerCache(ApplicationContext main) { 27 | super(main); 28 | } 29 | 30 | @Override 31 | public void unRegister(ApplicationContext plugin, PluginInfo pluginInfo) { 32 | try { 33 | ILoggerFactory iLoggerFactory = LoggerFactory.getILoggerFactory(); 34 | if (iLoggerFactory instanceof LoggerContext) { 35 | LoggerContext loggerContext = (LoggerContext) iLoggerFactory; 36 | loggerCache = (Map) ReflectUtil.getFieldValue(loggerContext, "loggerCache"); 37 | clearLoggerCache(pluginInfo); 38 | } 39 | } catch (Exception ex) { 40 | log.error("clear logger cache error", ex); 41 | } 42 | } 43 | 44 | private void clearLoggerCache(PluginInfo pluginInfo) { 45 | Set keysToRemove = new HashSet<>(); 46 | List classesNames = new ArrayList<>(DeployUtils.readClassFile(pluginInfo.getPath())); 47 | if (CollUtil.isEmpty(classesNames)) { 48 | return; 49 | } 50 | 51 | for (String key : loggerCache.keySet()) { 52 | String nextPackage = getNextPackage(classesNames.get(0)); 53 | if (key.startsWith(nextPackage)) { 54 | keysToRemove.add(key); 55 | } 56 | } 57 | 58 | for (String key : keysToRemove) { 59 | Logger logger = loggerCache.get(key); 60 | //从父级开始删除本身引用,删除后,类实例化的时候会重新创建 61 | Logger parent = (Logger) ReflectUtil.getFieldValue(logger, "parent"); 62 | List childrenList = (List)ReflectUtil.getFieldValue(parent, "childrenList"); 63 | if (CollUtil.isNotEmpty(childrenList)) { 64 | childrenList.remove(logger); 65 | } 66 | loggerCache.remove(key); 67 | } 68 | } 69 | 70 | /** 71 | * 从全限定名中获取basePackage的下一个包名 72 | * @param className 全限定类名 73 | * @return 74 | */ 75 | private String getNextPackage(String className) { 76 | PluginAutoConfiguration pluginConfiguration = main.getBean(PluginAutoConfiguration.class); 77 | String basePackage = pluginConfiguration.getBasePackage(); 78 | int startIndex = className.indexOf(basePackage); 79 | if (startIndex == -1) { 80 | return ""; 81 | } 82 | 83 | // 从已知字符串的末尾开始查找下一个'.' 84 | int endIndex = className.indexOf('.', startIndex + basePackage.length() + 1); 85 | if (endIndex == -1) { 86 | return basePackage; 87 | } 88 | 89 | // 截取我们需要的字符串部分 90 | return className.substring(0, endIndex); 91 | } 92 | 93 | 94 | } 95 | -------------------------------------------------------------------------------- /spring-hot-plugin-core/src/main/java/vip/aliali/spring/plugin/register/Register.java: -------------------------------------------------------------------------------- 1 | package vip.aliali.spring.plugin.register; 2 | 3 | import vip.aliali.spring.plugin.PluginInfo; 4 | import org.springframework.context.ApplicationContext; 5 | 6 | /** 7 | * 功能性bean 注册接口,比如controller接口、定时任务 8 | */ 9 | public interface Register { 10 | 11 | void refreshBeforeRegister(ApplicationContext plugin, PluginInfo pluginInfo); 12 | 13 | void refreshAfterRegister(ApplicationContext plugin, PluginInfo pluginInfo); 14 | 15 | void unRegister(ApplicationContext plugin, PluginInfo pluginInfo); 16 | } 17 | -------------------------------------------------------------------------------- /spring-hot-plugin-core/src/main/java/vip/aliali/spring/plugin/register/RegisterController.java: -------------------------------------------------------------------------------- 1 | package vip.aliali.spring.plugin.register; 2 | 3 | import cn.hutool.core.util.ReflectUtil; 4 | import vip.aliali.spring.plugin.PluginInfo; 5 | import vip.aliali.spring.plugin.util.DeployUtils; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.beans.factory.support.DefaultListableBeanFactory; 8 | import org.springframework.cglib.core.ReflectUtils; 9 | import org.springframework.context.ApplicationContext; 10 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 11 | import org.springframework.stereotype.Controller; 12 | import org.springframework.web.method.HandlerMethod; 13 | import org.springframework.web.servlet.mvc.method.RequestMappingInfo; 14 | import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; 15 | import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; 16 | 17 | import java.lang.reflect.Method; 18 | import java.util.HashSet; 19 | import java.util.Map; 20 | import java.util.Objects; 21 | import java.util.Set; 22 | import java.util.concurrent.ConcurrentHashMap; 23 | 24 | /** 25 | * controller 接口注册程序 26 | */ 27 | @Slf4j 28 | public class RegisterController extends AbstractRegister { 29 | 30 | private Map> requestMappings = new ConcurrentHashMap<>(); 31 | 32 | public RegisterController(ApplicationContext main) { 33 | super(main); 34 | } 35 | @Override 36 | public void refreshAfterRegister(ApplicationContext plugin, PluginInfo pluginInfo) { 37 | try { 38 | Set classNames = DeployUtils.readClassFile(pluginInfo.getPath()); 39 | Set pluginRequestMappings = new HashSet<>(); 40 | for (String className : classNames) { 41 | Class aClass = Class.forName(className, false, plugin.getClassLoader()); 42 | if (DeployUtils.isController(aClass)) { 43 | Object bean = plugin.getBean(aClass); 44 | Set requestMappingInfos = registerController(bean); 45 | printRegisterSuccessController(pluginInfo, requestMappingInfos); 46 | pluginRequestMappings.addAll(requestMappingInfos); 47 | } 48 | } 49 | //注册成功的缓存起来 50 | requestMappings.put(pluginInfo.getId(), pluginRequestMappings); 51 | } catch (Exception ex) { 52 | log.error("register controller error", ex); 53 | } 54 | } 55 | 56 | @Override 57 | public void unRegister(ApplicationContext plugin, PluginInfo pluginInfo) { 58 | Set requestMappingInfoSet = requestMappings.get(pluginInfo.getId()); 59 | if (requestMappingInfoSet != null) { 60 | requestMappingInfoSet.forEach(this::unRegisterController); 61 | } 62 | //删除RequestMappingHandlerAdapter的缓存,否则不销毁class 63 | RequestMappingHandlerAdapter requestMappingHandlerAdapter = main.getBean(RequestMappingHandlerAdapter.class); 64 | 65 | //销毁单例Bean 66 | Map controllerBeans = plugin.getBeansWithAnnotation(Controller.class); 67 | controllerBeans.forEach((name, bean) -> { 68 | ((Map)ReflectUtil.getFieldValue(requestMappingHandlerAdapter, "sessionAttributesHandlerCache")).remove(bean.getClass()); 69 | ((Map)ReflectUtil.getFieldValue(requestMappingHandlerAdapter, "initBinderCache")).remove(bean.getClass()); 70 | ((Map)ReflectUtil.getFieldValue(requestMappingHandlerAdapter, "modelAttributeCache")).remove(bean.getClass()); 71 | ((DefaultListableBeanFactory)((AnnotationConfigApplicationContext)plugin).getBeanFactory()).destroySingleton(name); 72 | }); 73 | requestMappings.remove(pluginInfo.getId()); 74 | } 75 | 76 | 77 | private void printRegisterSuccessController(PluginInfo pluginInfo, Set requestMappingInfos) { 78 | requestMappingInfos.forEach(requestMappingInfo -> log.info("插件{}注册接口{}", pluginInfo.getId(), requestMappingInfo)); 79 | } 80 | 81 | 82 | private Set registerController(Object bean) { 83 | Set requestMappingInfos = new HashSet<>(); 84 | Method[] methods = bean.getClass().getDeclaredMethods(); 85 | for (Method method : methods) { 86 | if (DeployUtils.isHaveRequestMapping(method)) { 87 | try { 88 | //解释接口参数 89 | RequestMappingInfo requestMappingInfo = (RequestMappingInfo) 90 | Objects.requireNonNull(getMappingForMethod()).invoke(getRequestMappingHandlerMapping(), method, bean.getClass()); 91 | //注册接口 92 | getRequestMappingHandlerMapping().registerMapping(requestMappingInfo, bean , method); 93 | requestMappingInfos.add(requestMappingInfo); 94 | } catch (Exception e){ 95 | log.error("接口注册异常", e); 96 | } 97 | } 98 | } 99 | return requestMappingInfos; 100 | } 101 | 102 | private void unRegisterController(RequestMappingInfo requestMappingInfo) { 103 | Map handlerMethodMap = getRequestMappingHandlerMapping().getHandlerMethods(); 104 | HandlerMethod handlerMethod = handlerMethodMap.get(requestMappingInfo); 105 | //取消方法Bean的对象引用 106 | ReflectUtil.setFieldValue(handlerMethod, "bean", new Object()); 107 | ReflectUtil.setFieldValue(handlerMethod, "beanType", Object.class); 108 | //取消方法的映射 109 | getRequestMappingHandlerMapping().unregisterMapping(requestMappingInfo); 110 | } 111 | 112 | private Method getMappingForMethod() { 113 | try { 114 | Method method = ReflectUtils.findDeclaredMethod(getRequestMappingHandlerMapping().getClass(), "getMappingForMethod", new Class[] { Method.class, Class.class }); 115 | method.setAccessible(true); 116 | return method; 117 | } catch (Exception ex) { 118 | log.error("反射获取detectHandlerMethods异常", ex); 119 | } 120 | return null; 121 | } 122 | 123 | private RequestMappingHandlerMapping getRequestMappingHandlerMapping() { 124 | return (RequestMappingHandlerMapping) main.getBean("requestMappingHandlerMapping"); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /spring-hot-plugin-core/src/main/java/vip/aliali/spring/plugin/register/RegisterScheduled.java: -------------------------------------------------------------------------------- 1 | package vip.aliali.spring.plugin.register; 2 | 3 | import vip.aliali.spring.plugin.PluginInfo; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.context.annotation.AnnotationConfigApplicationContext; 6 | import org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor; 7 | import org.springframework.scheduling.config.TaskManagementConfigUtils; 8 | 9 | /** 10 | * 定时任务控制程序 11 | */ 12 | public class RegisterScheduled extends AbstractRegister { 13 | 14 | public RegisterScheduled(ApplicationContext main) { 15 | super(main); 16 | } 17 | 18 | @Override 19 | public void refreshBeforeRegister(ApplicationContext plugin, PluginInfo pluginInfo) { 20 | if (plugin instanceof AnnotationConfigApplicationContext) { 21 | ((AnnotationConfigApplicationContext)plugin).registerBean(TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME, ScheduledAnnotationBeanPostProcessor.class); 22 | } 23 | } 24 | 25 | @Override 26 | public void unRegister(ApplicationContext plugin, PluginInfo pluginInfo) { 27 | Object bean = plugin.getBean(TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME); 28 | ((ScheduledAnnotationBeanPostProcessor)bean).destroy(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /spring-hot-plugin-core/src/main/resources/META-INF/spring-configuration-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "groups": [ 3 | { 4 | "name": "plugin", 5 | "type": "csdn.itsaysay.plugin.PluginAutoConfiguration", 6 | "sourceType": "csdn.itsaysay.plugin.PluginAutoConfiguration" 7 | } 8 | ], 9 | "properties": [ 10 | { 11 | "name": "plugin.enable", 12 | "type": "java.lang.Boolean", 13 | "sourceType": "csdn.itsaysay.plugin.PluginAutoConfiguration", 14 | "description": "是否启用插件功能", 15 | "defaultValue": true 16 | }, 17 | { 18 | "name": "plugin.runMode", 19 | "type": "java.lang.String", 20 | "sourceType": "csdn.itsaysay.plugin.PluginAutoConfiguration", 21 | "description": "运行模式. 开发环境: dev; 生产/部署 环境: prod", 22 | "defaultValue": "dev" 23 | }, 24 | { 25 | "name": "plugin.pluginPath", 26 | "type": "java.lang.String", 27 | "sourceType": "csdn.itsaysay.plugin.PluginAutoConfiguration", 28 | "description": "插件的路径. 开发环境下配置为插件模块上级目录; 生产环境下配置到插件jar包存放目录。建议配置绝对路径", 29 | "defaultValue": "~/plugins/" 30 | }, 31 | { 32 | "name": "plugin.backupPath", 33 | "type": "java.lang.String", 34 | "sourceType": "csdn.itsaysay.plugin.PluginAutoConfiguration", 35 | "description": "卸载插件后, 备份插件的目录" 36 | }, 37 | { 38 | "name": "plugin.basePackage", 39 | "type": "java.lang.String", 40 | "sourceType": "csdn.itsaysay.plugin.PluginAutoConfiguration", 41 | "description": "插件扫描包路径,使用统一包路径,默认:csdn.itsaysay.plugin" 42 | } 43 | ] 44 | } -------------------------------------------------------------------------------- /spring-hot-plugin-core/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | vip.aliali.spring.plugin.PluginStarter -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo-2/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/modules.xml 8 | .idea/jarRepositories.xml 9 | .idea/compiler.xml 10 | .idea/libraries/ 11 | *.iws 12 | *.iml 13 | *.ipr 14 | 15 | ### Eclipse ### 16 | .apt_generated 17 | .classpath 18 | .factorypath 19 | .project 20 | .settings 21 | .springBeans 22 | .sts4-cache 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | 37 | ### Mac OS ### 38 | .DS_Store -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo-2/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | csdn.itsaysay.plugin 8 | spring-hot-plugin-example 9 | 1.1 10 | 11 | 12 | plugin-demo-2 13 | 测试插件Demo2 14 | 15 | 16 | 8 17 | 8 18 | UTF-8 19 | 20 | 21 | 22 | 23 | csdn.itsaysay.plugin 24 | spring-hot-plugin-demo 25 | ${project.version} 26 | provided 27 | true 28 | 29 | 30 | 31 | com.alibaba 32 | fastjson 33 | 2.0.39 34 | 35 | 36 | 37 | com.baomidou 38 | mybatis-plus-boot-starter 39 | 3.5.7 40 | 41 | 42 | 43 | 44 | com.hik 45 | examples 46 | 1.0 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | vip.aliali.spring 55 | spring-hot-plugin-maven 56 | ${project.version} 57 | 58 | 59 | repackage 60 | package 61 | 62 | repackage 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo-2/src/main/java/csdn/itsaysay/demo/plugin2/IndexController.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.demo.plugin2; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import csdn.itsaysay.demo.plugin2.bean.Person; 5 | import csdn.itsaysay.demo.plugin2.bean.User; 6 | import csdn.itsaysay.demo.plugin2.service.TestService; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.PathVariable; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | import javax.annotation.Resource; 14 | 15 | @Slf4j 16 | @RestController 17 | @RequestMapping("/demo2") 18 | public class IndexController { 19 | 20 | @Resource 21 | private TestService testService; 22 | 23 | @GetMapping({"/hello"}) 24 | public String hello() { 25 | return "Hello itsaysay!"; 26 | } 27 | 28 | @GetMapping({"/hello/name"}) 29 | public Person helloName() { 30 | Person person = new Person(); 31 | person.setName("Human"); 32 | String json = JSON.toJSONString(person); 33 | log.info(json); 34 | return person; 35 | } 36 | 37 | 38 | @GetMapping("/mybatis/getUser/{id}") 39 | public User getUser(@PathVariable("id") Integer id) { 40 | return testService.getUser(id); 41 | } 42 | 43 | @GetMapping("/mybatis/getUserXml/{id}") 44 | public User getUserXml(@PathVariable("id") Integer id) { 45 | return testService.getUserXml(id); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo-2/src/main/java/csdn/itsaysay/demo/plugin2/bean/Person.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.demo.plugin2.bean; 2 | 3 | 4 | import lombok.Data; 5 | 6 | @Data 7 | public class Person { 8 | 9 | private String name; 10 | } 11 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo-2/src/main/java/csdn/itsaysay/demo/plugin2/bean/User.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.demo.plugin2.bean; 2 | 3 | import com.baomidou.mybatisplus.annotation.TableId; 4 | import com.baomidou.mybatisplus.annotation.TableName; 5 | import lombok.Data; 6 | 7 | @Data 8 | @TableName("usertb") 9 | public class User { 10 | 11 | @TableId 12 | private Integer id; 13 | 14 | private String name; 15 | 16 | private Integer age; 17 | } 18 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo-2/src/main/java/csdn/itsaysay/demo/plugin2/mapper/TestMapper.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.demo.plugin2.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import csdn.itsaysay.demo.plugin2.bean.User; 5 | import org.apache.ibatis.annotations.Mapper; 6 | import org.apache.ibatis.annotations.Param; 7 | 8 | @Mapper 9 | public interface TestMapper extends BaseMapper { 10 | 11 | User selectByIdXml(@Param("id") Integer id); 12 | } 13 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo-2/src/main/java/csdn/itsaysay/demo/plugin2/mapper/xml/TestMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo-2/src/main/java/csdn/itsaysay/demo/plugin2/service/TestService.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.demo.plugin2.service; 2 | 3 | import csdn.itsaysay.demo.plugin2.bean.User; 4 | 5 | public interface TestService { 6 | User getUser(Integer id); 7 | 8 | User getUserXml(Integer id); 9 | } 10 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo-2/src/main/java/csdn/itsaysay/demo/plugin2/service/impl/TestServiceImpl.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.demo.plugin2.service.impl; 2 | 3 | import csdn.itsaysay.demo.plugin2.bean.User; 4 | import csdn.itsaysay.demo.plugin2.service.TestService; 5 | import csdn.itsaysay.demo.plugin2.mapper.TestMapper; 6 | import org.springframework.stereotype.Service; 7 | 8 | import javax.annotation.Resource; 9 | 10 | @Service 11 | public class TestServiceImpl implements TestService { 12 | 13 | @Resource 14 | private TestMapper testMapper; 15 | 16 | 17 | @Override 18 | public User getUser(Integer id) { 19 | return testMapper.selectById(id); 20 | } 21 | 22 | @Override 23 | public User getUserXml(Integer id) { 24 | return testMapper.selectByIdXml(id); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo-2/src/main/java/csdn/itsaysay/demo/plugin2/task/TaskDemo.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.demo.plugin2.task; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.scheduling.annotation.Scheduled; 5 | import org.springframework.stereotype.Component; 6 | 7 | /** 8 | * 定时任务 9 | */ 10 | @Slf4j 11 | @Component 12 | public class TaskDemo { 13 | 14 | @Scheduled(cron = "0/1 * * * * ?") 15 | public void task() { 16 | log.info("demo2 task starting"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo-mybatis/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/modules.xml 8 | .idea/jarRepositories.xml 9 | .idea/compiler.xml 10 | .idea/libraries/ 11 | *.iws 12 | *.iml 13 | *.ipr 14 | 15 | ### Eclipse ### 16 | .apt_generated 17 | .classpath 18 | .factorypath 19 | .project 20 | .settings 21 | .springBeans 22 | .sts4-cache 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | 37 | ### Mac OS ### 38 | .DS_Store -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo-mybatis/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | csdn.itsaysay.plugin 8 | spring-hot-plugin-example 9 | 1.1 10 | 11 | 12 | plugin-demo-mybatis 13 | 测试插件Demo-mybatis 14 | 15 | 16 | 8 17 | 8 18 | UTF-8 19 | 20 | 21 | 22 | 23 | csdn.itsaysay.plugin 24 | spring-hot-plugin-demo 25 | ${project.version} 26 | provided 27 | true 28 | 29 | 30 | 31 | com.alibaba 32 | fastjson 33 | 2.0.39 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | src/main/java 43 | 44 | **/*.xml 45 | 46 | 47 | 48 | src/main/resources 49 | 50 | **/* 51 | 52 | 53 | 54 | 55 | 56 | vip.aliali.spring 57 | spring-hot-plugin-maven 58 | ${project.version} 59 | 60 | 61 | repackage 62 | package 63 | 64 | repackage 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo-mybatis/src/main/java/csdn/itsaysay/demo/mybatis/IndexController.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.demo.mybatis; 2 | 3 | import csdn.itsaysay.demo.mybatis.bean.User; 4 | import csdn.itsaysay.demo.mybatis.service.TestService; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.web.bind.annotation.DeleteMapping; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.PathVariable; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | @Slf4j 13 | @RestController 14 | public class IndexController { 15 | 16 | @Autowired 17 | private TestService testService; 18 | 19 | @GetMapping("/mybatis/getUser/{id}") 20 | public User getUser(@PathVariable("id") Integer id) { 21 | return testService.getUser(id); 22 | } 23 | 24 | @GetMapping("/mybatis/getUserXml/{id}") 25 | public User getUserXml(@PathVariable("id") Integer id) { 26 | return testService.getUserXml(id); 27 | } 28 | 29 | @GetMapping("/mybatis/insert") 30 | public Integer insert() { 31 | return testService.insert(); 32 | } 33 | 34 | @DeleteMapping("/mybatis/delete/{id}") 35 | public void delete(@PathVariable("id") Integer id) { 36 | testService.delete(id); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo-mybatis/src/main/java/csdn/itsaysay/demo/mybatis/bean/User.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.demo.mybatis.bean; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class User { 7 | 8 | private Integer id; 9 | 10 | private String name; 11 | 12 | private Integer age; 13 | } 14 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo-mybatis/src/main/java/csdn/itsaysay/demo/mybatis/mapper/TestMapper.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.demo.mybatis.mapper; 2 | 3 | import csdn.itsaysay.demo.mybatis.bean.User; 4 | import org.apache.ibatis.annotations.*; 5 | 6 | @Mapper 7 | public interface TestMapper { 8 | 9 | @Select("select * from usertb where id = #{idd}") 10 | User selectById(@Param("idd") Integer idd); 11 | 12 | User selectByIdXml(@Param("id") Integer id); 13 | 14 | @Insert("insert into usertb(name,age) values(#{user.name},#{user.age})") 15 | @Options(useGeneratedKeys = true, keyProperty = "id") 16 | int insert(@Param("user") User user); 17 | 18 | @Delete("delete from usertb where id = #{id}") 19 | void deleteById(@Param("id") Integer id); 20 | } 21 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo-mybatis/src/main/java/csdn/itsaysay/demo/mybatis/mapper/TestMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo-mybatis/src/main/java/csdn/itsaysay/demo/mybatis/service/TestService.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.demo.mybatis.service; 2 | 3 | import csdn.itsaysay.demo.mybatis.bean.User; 4 | 5 | public interface TestService { 6 | User getUser(Integer id); 7 | 8 | User getUserXml(Integer id); 9 | 10 | Integer insert(); 11 | 12 | void delete(Integer id); 13 | } 14 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo-mybatis/src/main/java/csdn/itsaysay/demo/mybatis/service/impl/TestServiceImpl.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.demo.mybatis.service.impl; 2 | 3 | import csdn.itsaysay.demo.mybatis.bean.User; 4 | import csdn.itsaysay.demo.mybatis.mapper.TestMapper; 5 | import csdn.itsaysay.demo.mybatis.service.TestService; 6 | import org.springframework.stereotype.Service; 7 | 8 | import javax.annotation.Resource; 9 | 10 | @Service 11 | public class TestServiceImpl implements TestService { 12 | 13 | @Resource 14 | private TestMapper testMapper; 15 | 16 | @Override 17 | public User getUser(Integer id) { 18 | return testMapper.selectById(id); 19 | } 20 | 21 | @Override 22 | public User getUserXml(Integer id) { 23 | return testMapper.selectByIdXml(id); 24 | } 25 | 26 | @Override 27 | public Integer insert() { 28 | User user = new User(); 29 | user.setName("test"); 30 | user.setAge(18); 31 | testMapper.insert(user); 32 | return user.getId(); 33 | } 34 | 35 | @Override 36 | public void delete(Integer id) { 37 | testMapper.deleteById(id); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/modules.xml 8 | .idea/jarRepositories.xml 9 | .idea/compiler.xml 10 | .idea/libraries/ 11 | *.iws 12 | *.iml 13 | *.ipr 14 | 15 | ### Eclipse ### 16 | .apt_generated 17 | .classpath 18 | .factorypath 19 | .project 20 | .settings 21 | .springBeans 22 | .sts4-cache 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | 37 | ### Mac OS ### 38 | .DS_Store -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | csdn.itsaysay.plugin 8 | spring-hot-plugin-example 9 | 1.1 10 | 11 | 12 | plugin-demo 13 | 测试插件Demo 14 | 15 | 16 | 8 17 | 8 18 | UTF-8 19 | 20 | 21 | 22 | 23 | csdn.itsaysay.plugin 24 | spring-hot-plugin-demo 25 | ${project.version} 26 | provided 27 | true 28 | 29 | 30 | 31 | com.alibaba 32 | fastjson 33 | 2.0.39 34 | 35 | 36 | 37 | com.baomidou 38 | mybatis-plus-boot-starter 39 | 3.5.7 40 | provided 41 | 42 | 43 | 44 | 45 | com.hik 46 | examples 47 | 1.0 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | vip.aliali.spring 56 | spring-hot-plugin-maven 57 | ${project.version} 58 | 59 | 60 | repackage 61 | package 62 | 63 | repackage 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/java/csdn/itsaysay/demo/plugin/IndexController.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.demo.plugin; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.sun.jna.Native; 5 | import csdn.itsaysay.demo.plugin.bean.Person; 6 | import csdn.itsaysay.demo.plugin.bean.User; 7 | import csdn.itsaysay.demo.plugin.hik.HCNetSDK; 8 | import csdn.itsaysay.demo.plugin.service.TestService; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.PathVariable; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | import javax.annotation.Resource; 15 | 16 | @Slf4j 17 | @RestController 18 | public class IndexController { 19 | 20 | @Resource 21 | private TestService testService; 22 | 23 | @GetMapping({"/hello"}) 24 | public String hello() { 25 | return "Hello itsaysay!"; 26 | } 27 | 28 | @GetMapping({"/hello/name"}) 29 | public Person helloName() { 30 | Person person = new Person(); 31 | person.setName("Human"); 32 | String json = JSON.toJSONString(person); 33 | log.info(json); 34 | return person; 35 | } 36 | 37 | @GetMapping("/hik/sdk/init") 38 | public Boolean hikSDKInit() { 39 | //海康的SDK dll依赖了其他的dll,需要指定目录,放插件包没用 40 | //只有一个dll的sdk,不需要,可以放在插件包内 41 | //这是java native的限制 42 | HCNetSDK hCNetSDK = (HCNetSDK) Native.loadLibrary("D:\\javaprojects\\spring-hot-plugin\\spring-hot-plugin-example\\plugin-demo\\target\\classes\\win32-x86-64\\HCNetSDK.dll", HCNetSDK.class); 43 | if (hCNetSDK == null) { 44 | return false; 45 | } 46 | return true; 47 | } 48 | 49 | @GetMapping("/mp/getUser/{id}") 50 | public User getUser(@PathVariable("id") Integer id) { 51 | return testService.getUser(id); 52 | } 53 | 54 | @GetMapping("/mp/getUserXml/{id}") 55 | public User getUserXml(@PathVariable("id") Integer id) { 56 | return testService.getUserXml(id); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/java/csdn/itsaysay/demo/plugin/bean/Person.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.demo.plugin.bean; 2 | 3 | 4 | import lombok.Data; 5 | 6 | @Data 7 | public class Person { 8 | 9 | private String name; 10 | } 11 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/java/csdn/itsaysay/demo/plugin/bean/User.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.demo.plugin.bean; 2 | 3 | import com.baomidou.mybatisplus.annotation.TableId; 4 | import com.baomidou.mybatisplus.annotation.TableName; 5 | import lombok.Data; 6 | 7 | @Data 8 | @TableName("usertb") 9 | public class User { 10 | 11 | @TableId 12 | private Integer id; 13 | 14 | private String name; 15 | 16 | private Integer age; 17 | } 18 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/java/csdn/itsaysay/demo/plugin/mapper/TestMapper.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.demo.plugin.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import csdn.itsaysay.demo.plugin.bean.User; 5 | import org.apache.ibatis.annotations.Mapper; 6 | import org.apache.ibatis.annotations.Param; 7 | 8 | @Mapper 9 | public interface TestMapper extends BaseMapper { 10 | 11 | User selectByIdXml(@Param("id") Integer id); 12 | } 13 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/java/csdn/itsaysay/demo/plugin/mapper/TestMapper2.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.demo.plugin.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import csdn.itsaysay.demo.plugin.bean.User; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | @Mapper 8 | public interface TestMapper2 extends BaseMapper { 9 | } 10 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/java/csdn/itsaysay/demo/plugin/service/TestService.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.demo.plugin.service; 2 | 3 | import csdn.itsaysay.demo.plugin.bean.User; 4 | 5 | public interface TestService { 6 | User getUser(Integer id); 7 | 8 | User getUserXml(Integer id); 9 | } 10 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/java/csdn/itsaysay/demo/plugin/service/impl/TestServiceImpl.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.demo.plugin.service.impl; 2 | 3 | import csdn.itsaysay.demo.plugin.bean.User; 4 | import csdn.itsaysay.demo.plugin.mapper.TestMapper; 5 | import csdn.itsaysay.demo.plugin.mapper.TestMapper2; 6 | import csdn.itsaysay.demo.plugin.service.TestService; 7 | import org.springframework.stereotype.Service; 8 | 9 | import javax.annotation.Resource; 10 | 11 | @Service 12 | public class TestServiceImpl implements TestService { 13 | 14 | @Resource 15 | private TestMapper testMapper; 16 | @Resource 17 | private TestMapper2 testMapper2; 18 | 19 | 20 | @Override 21 | public User getUser(Integer id) { 22 | return testMapper.selectById(id); 23 | } 24 | 25 | @Override 26 | public User getUserXml(Integer id) { 27 | return testMapper.selectByIdXml(id); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/java/csdn/itsaysay/demo/plugin/task/TaskDemo.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.demo.plugin.task; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.scheduling.annotation.Scheduled; 5 | import org.springframework.stereotype.Component; 6 | 7 | /** 8 | * 定时任务 9 | */ 10 | @Slf4j 11 | @Component 12 | public class TaskDemo { 13 | 14 | @Scheduled(cron = "0/1 * * * * ?") 15 | public void task() { 16 | log.info("task starting"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/csdn/itsaysay/demo/plugin/mapper/TestMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/linux-x86-64/libXMTQRCode.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/linux-x86-64/libXMTQRCode.so -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/AudioRender.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/AudioRender.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/GdiPlus.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/GdiPlus.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/GdiPlus.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/GdiPlus.lib -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCCore.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCCore.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCCore.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCCore.lib -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDK.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDK.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDK.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDK.lib -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/AnalyzeData.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/AnalyzeData.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/AudioIntercom.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/AudioIntercom.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/AudioRender.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/AudioRender.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/HCAlarm.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/HCAlarm.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/HCAlarm.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/HCAlarm.lib -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/HCCoreDevCfg.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/HCCoreDevCfg.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/HCDisplay.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/HCDisplay.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/HCGeneralCfgMgr.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/HCGeneralCfgMgr.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/HCGeneralCfgMgr.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/HCGeneralCfgMgr.lib -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/HCIndustry.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/HCIndustry.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/HCPlayBack.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/HCPlayBack.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/HCPreview.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/HCPreview.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/HCPreview.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/HCPreview.lib -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/HCVoiceTalk.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/HCVoiceTalk.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/OpenAL32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/OpenAL32.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/StreamTransClient.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/StreamTransClient.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/SystemTransform.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/SystemTransform.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/libiconv2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HCNetSDKCom/libiconv2.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HXVA.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HXVA.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HmMerge.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/HmMerge.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/LocalSensorAdd.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/LocalSensorAdd.dat -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/MP_Render.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/MP_Render.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/NPQos.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/NPQos.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/OpenAL32.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/OpenAL32.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/PlayCtrl.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/PlayCtrl.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/PlayCtrl.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/PlayCtrl.lib -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/SuperRender.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/SuperRender.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/YUVProcess.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/YUVProcess.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/examples.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/examples.jar -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/hlog.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/hlog.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/hpr.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/hpr.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/libcrypto-1_1-x64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/libcrypto-1_1-x64.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/libmmd.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/libmmd.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/libssl-1_1-x64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/libssl-1_1-x64.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/zlib1.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jujunchen/spring-hot-plugin/5c066a4030b3fe269583fc2f24320821c2883e75/spring-hot-plugin-example/plugin-demo/src/main/resources/win32-x86-64/zlib1.dll -------------------------------------------------------------------------------- /spring-hot-plugin-example/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | csdn.itsaysay.plugin 6 | spring-hot-plugin-example 7 | 1.1 8 | pom 9 | 10 | 11 | spring-hot-plugin-demo 12 | plugin-demo 13 | plugin-demo-2 14 | plugin-demo-mybatis 15 | spring-hot-plugin-admin 16 | spring-hot-plugin-task-demo 17 | 18 | 19 | 20 | UTF-8 21 | 1.8 22 | UTF-8 23 | ${java.version} 24 | ${java.version} 25 | 26 | 1.18.20 27 | 2.7.18 28 | 29 | 30 | 31 | 32 | 33 | 34 | org.projectlombok 35 | lombok 36 | provided 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-dependencies 46 | ${springboot.version} 47 | pom 48 | import 49 | 50 | 51 | 52 | 53 | 54 | 55 | aliyun-repository 56 | aliyun nexus 57 | https://maven.aliyun.com/nexus/content/groups/public/ 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/spring-hot-plugin-admin/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/modules.xml 8 | .idea/jarRepositories.xml 9 | .idea/compiler.xml 10 | .idea/libraries/ 11 | *.iws 12 | *.iml 13 | *.ipr 14 | 15 | ### Eclipse ### 16 | .apt_generated 17 | .classpath 18 | .factorypath 19 | .project 20 | .settings 21 | .springBeans 22 | .sts4-cache 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | 37 | ### Mac OS ### 38 | .DS_Store -------------------------------------------------------------------------------- /spring-hot-plugin-example/spring-hot-plugin-admin/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | csdn.itsaysay.plugin 8 | spring-hot-plugin-example 9 | 1.1 10 | 11 | 12 | spring-hot-plugin-admin 13 | 监控-测试插件主程序 14 | 15 | 16 | 8 17 | 8 18 | UTF-8 19 | 2.4.5 20 | 21 | 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter-web 26 | 27 | 28 | 29 | de.codecentric 30 | spring-boot-admin-starter-server 31 | 2.6.11 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/spring-hot-plugin-admin/src/main/java/csdn/itsaysay/main/admin/AdminApplication.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.main.admin; 2 | 3 | import de.codecentric.boot.admin.server.config.EnableAdminServer; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @SpringBootApplication 8 | @EnableAdminServer 9 | public class AdminApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(AdminApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/spring-hot-plugin-admin/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8081 3 | servlet: 4 | context-path: / 5 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/spring-hot-plugin-demo/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/modules.xml 8 | .idea/jarRepositories.xml 9 | .idea/compiler.xml 10 | .idea/libraries/ 11 | *.iws 12 | *.iml 13 | *.ipr 14 | 15 | ### Eclipse ### 16 | .apt_generated 17 | .classpath 18 | .factorypath 19 | .project 20 | .settings 21 | .springBeans 22 | .sts4-cache 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | 37 | ### Mac OS ### 38 | .DS_Store -------------------------------------------------------------------------------- /spring-hot-plugin-example/spring-hot-plugin-demo/Dockerfile: -------------------------------------------------------------------------------- 1 | # 指定 java 环境镜像 2 | FROM openjdk:8 3 | # 维护者信息 4 | LABEL service-name=spring-hot-plugin-demo 5 | # 复制文件到容器 6 | COPY target/*.jar /application.jar 7 | 8 | RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' > /etc/timezone 9 | 10 | ENTRYPOINT ["java", "-jar", "-Xms256m", "-Xmx256m", "/application.jar"] 11 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/spring-hot-plugin-demo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | csdn.itsaysay.plugin 8 | spring-hot-plugin-example 9 | 1.1 10 | 11 | 12 | spring-hot-plugin-demo 13 | 14 | 15 | 8 16 | 8 17 | UTF-8 18 | 2.4.5 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | 28 | com.baomidou 29 | mybatis-plus-boot-starter 30 | 3.5.7 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | mysql 48 | mysql-connector-java 49 | 8.0.31 50 | 51 | 52 | 53 | 54 | de.codecentric 55 | spring-boot-admin-starter-client 56 | 2.6.11 57 | 58 | 59 | 60 | 61 | 62 | vip.aliali.spring 63 | spring-hot-plugin-core 64 | 1.1.1 65 | 66 | 67 | 68 | vip.aliali.spring 69 | spring-hot-plugin-mybatis 70 | 1.1.1 71 | 72 | 73 | 74 | com.alibaba 75 | fastjson 76 | 2.0.39 77 | 78 | 79 | 80 | net.java.dev.jna 81 | jna 82 | 5.14.0 83 | 84 | 85 | 86 | 87 | 88 | 89 | ${project.artifactId} 90 | 91 | 92 | org.springframework.boot 93 | spring-boot-maven-plugin 94 | ${spring-boot-maven-plugin.version} 95 | 96 | 97 | 98 | repackage 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/spring-hot-plugin-demo/src/main/java/csdn/itsaysay/main/DemoApplication.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.main; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication() 7 | public class DemoApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(DemoApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/spring-hot-plugin-demo/src/main/java/csdn/itsaysay/main/plugin/PluginController.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.main.plugin; 2 | 3 | import csdn.itsaysay.main.plugin.service.PluginService; 4 | import csdn.itsaysay.main.plugin.req.PluginReq; 5 | import csdn.itsaysay.main.plugin.res.PluginRes; 6 | import org.springframework.web.bind.annotation.*; 7 | import org.springframework.web.multipart.MultipartFile; 8 | 9 | import javax.annotation.Resource; 10 | import java.util.List; 11 | 12 | @RestController 13 | @RequestMapping("/plugin") 14 | public class PluginController { 15 | 16 | @Resource 17 | PluginService pluginService; 18 | 19 | /** 20 | * 安装插件 21 | * @param file 22 | */ 23 | @PostMapping("/install") 24 | public void install(@RequestParam("file") MultipartFile file) { 25 | pluginService.install(file); 26 | } 27 | 28 | /** 29 | * 卸载插件 30 | * @param pluginId 31 | */ 32 | @PostMapping("/uninstall") 33 | public void uninstall(@RequestParam("pluginId") String pluginId) { 34 | pluginService.uninstall(pluginId); 35 | } 36 | 37 | /** 38 | * 启动插件 39 | * @param pluginId 40 | * @return 41 | */ 42 | @PostMapping("/start") 43 | public Boolean start(@RequestParam("pluginId")String pluginId) { 44 | return pluginService.start(pluginId); 45 | } 46 | 47 | /** 48 | * 暂停插件 49 | * @param pluginId 50 | * @return 51 | */ 52 | @PostMapping("/stop") 53 | public Boolean stop(@RequestParam("pluginId")String pluginId) { 54 | return pluginService.stop(pluginId); 55 | } 56 | 57 | /** 58 | * 获取插件列表 59 | * @param req 60 | * @return 61 | */ 62 | @PostMapping("/list") 63 | public List list(@RequestBody PluginReq req) { 64 | return pluginService.list(req); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/spring-hot-plugin-demo/src/main/java/csdn/itsaysay/main/plugin/req/PluginReq.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.main.plugin.req; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class PluginReq { 7 | 8 | private String pluginId; 9 | } 10 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/spring-hot-plugin-demo/src/main/java/csdn/itsaysay/main/plugin/res/PluginRes.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.main.plugin.res; 2 | 3 | import vip.aliali.spring.plugin.constants.PluginState; 4 | import lombok.Data; 5 | 6 | @Data 7 | public class PluginRes { 8 | 9 | /** 10 | * 插件id 11 | */ 12 | private String id; 13 | 14 | /** 15 | * 版本 16 | */ 17 | private String version; 18 | 19 | /** 20 | * 描述 21 | */ 22 | private String description; 23 | 24 | /** 25 | * 插件启动状态 26 | */ 27 | private PluginState pluginState; 28 | } 29 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/spring-hot-plugin-demo/src/main/java/csdn/itsaysay/main/plugin/service/PluginListener.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.main.plugin.service; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.stereotype.Component; 5 | import vip.aliali.spring.plugin.PluginInfo; 6 | 7 | /** 8 | * 插件事件监听 9 | * @author jujun.chen 10 | */ 11 | @Slf4j 12 | @Component 13 | public class PluginListener implements vip.aliali.spring.plugin.listener.PluginListener { 14 | 15 | @Override 16 | public void startSuccess(PluginInfo pluginInfo) { 17 | log.info("{}--->启动成功", pluginInfo.getId()); 18 | } 19 | 20 | @Override 21 | public void startFailure(PluginInfo pluginInfo, Throwable throwable) { 22 | log.info("{}--->启动失败", pluginInfo.getId()); 23 | } 24 | 25 | @Override 26 | public void stopSuccess(PluginInfo pluginInfo) { 27 | log.info("{}--->停止成功", pluginInfo.getId()); 28 | } 29 | 30 | @Override 31 | public void stopFailure(PluginInfo pluginInfo, Throwable throwable) { 32 | log.info("{}--->停止失败", pluginInfo.getId()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/spring-hot-plugin-demo/src/main/java/csdn/itsaysay/main/plugin/service/PluginService.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.main.plugin.service; 2 | 3 | import csdn.itsaysay.main.plugin.req.PluginReq; 4 | import csdn.itsaysay.main.plugin.res.PluginRes; 5 | import org.springframework.web.multipart.MultipartFile; 6 | 7 | import java.util.List; 8 | 9 | public interface PluginService { 10 | 11 | void install(MultipartFile file); 12 | 13 | Boolean start(String pluginId); 14 | 15 | Boolean stop(String pluginId); 16 | 17 | void uninstall(String pluginId); 18 | 19 | List list(PluginReq req); 20 | } 21 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/spring-hot-plugin-demo/src/main/java/csdn/itsaysay/main/plugin/service/impl/PluginServiceImpl.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.main.plugin.service.impl; 2 | 3 | import cn.hutool.core.convert.Convert; 4 | import csdn.itsaysay.main.plugin.service.PluginService; 5 | import csdn.itsaysay.main.plugin.req.PluginReq; 6 | import csdn.itsaysay.main.plugin.res.PluginRes; 7 | import vip.aliali.spring.plugin.PluginAutoConfiguration; 8 | import vip.aliali.spring.plugin.PluginInfo; 9 | import vip.aliali.spring.plugin.PluginManager; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.springframework.stereotype.Service; 12 | import org.springframework.web.multipart.MultipartFile; 13 | 14 | import javax.annotation.Resource; 15 | import java.util.List; 16 | 17 | @Slf4j 18 | @Service 19 | public class PluginServiceImpl implements PluginService { 20 | 21 | @Resource 22 | PluginManager pluginManager; 23 | @Resource 24 | PluginAutoConfiguration pluginAutoConfiguration; 25 | 26 | @Override 27 | public void install(MultipartFile file) { 28 | try { 29 | String firstPath = pluginAutoConfiguration.getPluginPath() + "/tmp"; 30 | //安装 31 | pluginManager.install(file); 32 | } catch (Exception ex) { 33 | throw new RuntimeException(ex.getMessage(), ex); 34 | } 35 | } 36 | 37 | @Override 38 | public void uninstall(String pluginId) { 39 | pluginManager.uninstall(pluginId); 40 | } 41 | 42 | @Override 43 | public List list(PluginReq req) { 44 | List pluginInfoList = pluginManager.list(req.getPluginId()); 45 | return Convert.toList(PluginRes.class, pluginInfoList); 46 | } 47 | 48 | @Override 49 | public Boolean start(String pluginId) { 50 | return null; 51 | } 52 | 53 | @Override 54 | public Boolean stop(String pluginId) { 55 | return null; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/spring-hot-plugin-demo/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | servlet: 4 | context-path: / 5 | 6 | spring: 7 | servlet: 8 | multipart: 9 | max-file-size: 100MB 10 | max-request-size: 100MB 11 | application: 12 | name: spring-hot-plugin-demo 13 | main: 14 | allow-bean-definition-overriding: true 15 | mvc: 16 | throw-exception-if-no-handler-found: true 17 | static-path-pattern: /static/** 18 | datasource: 19 | # url: jdbc:h2:mem:testdb 20 | # driver-class-name: org.h2.Driver 21 | # username: sa 22 | # password: 23 | url: jdbc:mysql://172.17.0.2:3306/testdb?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC 24 | driver-class-name: com.mysql.cj.jdbc.Driver 25 | username: root 26 | password: root123 27 | 28 | # admin 29 | # boot: 30 | # admin: 31 | # client: 32 | # url: http://localhost:8081 33 | management: 34 | endpoints: 35 | web: 36 | exposure: 37 | include: "*" 38 | info: 39 | env: 40 | enabled: true 41 | 42 | 43 | # 开启调式日志 44 | logging: 45 | file: 46 | name: logs/spring-hot-plugin-demo.log 47 | level: 48 | csdn.itsaysay.plugin: DEBUG 49 | 50 | #插件配置 51 | plugin: 52 | runMode: prod 53 | backup-path: /home/cjj/spring-hot-plugin-demo/plugin-path/back 54 | pluginPath: /home/cjj/spring-hot-plugin-demo/plugin-path 55 | basePackage: csdn.itsaysay.demo 56 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/spring-hot-plugin-demo/src/main/resources/data.sql: -------------------------------------------------------------------------------- 1 | insert into usertb (id, name, age) values(1, 'spring-hot-plugin', 18); -------------------------------------------------------------------------------- /spring-hot-plugin-example/spring-hot-plugin-demo/src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | -- 创建一张用户表,包含id,name,age,id是主键 2 | CREATE TABLE IF NOT EXISTS usertb ( 3 | id INTEGER AUTO_INCREMENT PRIMARY KEY, 4 | name VARCHAR(255), 5 | age INTEGER 6 | ); -------------------------------------------------------------------------------- /spring-hot-plugin-example/spring-hot-plugin-example.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/spring-hot-plugin-task-demo/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/modules.xml 8 | .idea/jarRepositories.xml 9 | .idea/compiler.xml 10 | .idea/libraries/ 11 | *.iws 12 | *.iml 13 | *.ipr 14 | 15 | ### Eclipse ### 16 | .apt_generated 17 | .classpath 18 | .factorypath 19 | .project 20 | .settings 21 | .springBeans 22 | .sts4-cache 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | 37 | ### Mac OS ### 38 | .DS_Store -------------------------------------------------------------------------------- /spring-hot-plugin-example/spring-hot-plugin-task-demo/Dockerfile: -------------------------------------------------------------------------------- 1 | # 指定 java 环境镜像 2 | FROM openjdk:8 3 | # 维护者信息 4 | LABEL service-name=spring-hot-plugin-task-demo 5 | # 复制文件到容器 6 | COPY target/*.jar /application.jar 7 | 8 | RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' > /etc/timezone 9 | 10 | ENTRYPOINT ["java", "-jar", "-Xms256m", "-Xmx256m", "/application.jar"] 11 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/spring-hot-plugin-task-demo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | csdn.itsaysay.plugin 8 | spring-hot-plugin-example 9 | 1.1 10 | 11 | 12 | spring-hot-plugin-task-demo 13 | 性能测试,定时执行安装卸载,查询任务 14 | 15 | 16 | 8 17 | 8 18 | UTF-8 19 | 2.4.5 20 | 21 | 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter-web 26 | 27 | 28 | 29 | com.baomidou 30 | mybatis-plus-boot-starter 31 | 3.5.7 32 | 33 | 34 | 35 | 36 | mysql 37 | mysql-connector-java 38 | 8.0.31 39 | 40 | 41 | 42 | com.alibaba 43 | fastjson 44 | 2.0.39 45 | 46 | 47 | 48 | cn.hutool 49 | hutool-all 50 | 5.8.32 51 | 52 | 53 | 54 | 55 | 56 | 57 | ${project.artifactId} 58 | 59 | 60 | org.springframework.boot 61 | spring-boot-maven-plugin 62 | ${spring-boot-maven-plugin.version} 63 | 64 | 65 | 66 | repackage 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/spring-hot-plugin-task-demo/src/main/java/csdn/itsaysay/task/TaskDemoApplication.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.task; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication() 7 | public class TaskDemoApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(TaskDemoApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/spring-hot-plugin-task-demo/src/main/java/csdn/itsaysay/task/service/TaskService.java: -------------------------------------------------------------------------------- 1 | package csdn.itsaysay.task.service; 2 | 3 | import cn.hutool.core.thread.ThreadUtil; 4 | import cn.hutool.http.HttpRequest; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.scheduling.annotation.EnableScheduling; 7 | import org.springframework.scheduling.annotation.Scheduled; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.io.File; 11 | 12 | /** 13 | * @author jujun.chen 14 | */ 15 | @Slf4j 16 | @Component 17 | @EnableScheduling 18 | public class TaskService { 19 | 20 | private static final String TASK_URL = "http://172.17.0.5:8080"; 21 | 22 | public static final String INSALL_PLUGIN = "/plugin/install"; 23 | public static final String UNINSTALL_PLUGIN = "/plugin/uninstall"; 24 | public static final String MYBATIS_INSERT = "/mybatis/insert"; 25 | public static final String MYBATIS_QUERY = "/mybatis/getUserXml/%s"; 26 | public static final String MYBATIS_DELETE = "/mybatis/delete/%s"; 27 | 28 | //每10分钟执行一次,新增,查询,删除操作 29 | @Scheduled(cron = "0 0/10 * * * ?") 30 | public void task() { 31 | //插件路径 32 | File file = new File("/home/cjj/spring-hot-plugin-demo/plugin-path/back/plugin-demo-mybatis-1.1-repackage.jar"); 33 | 34 | //安装插件 35 | log.info("安装插件"); 36 | String response = HttpRequest.post(TASK_URL + INSALL_PLUGIN) 37 | .form("file", file) 38 | .execute().body(); 39 | log.info("执行1000次,增查删"); 40 | //循环查询1000次 41 | for (int i = 0; i < 1000; i++) { 42 | //调用插入数据接口 43 | String insertId = HttpRequest.get(TASK_URL + MYBATIS_INSERT) 44 | .execute().body(); 45 | log.info("插入数据:{}", insertId); 46 | // ThreadUtil.sleep(1000); 47 | //查询数据 48 | String query = HttpRequest.get(TASK_URL + String.format(MYBATIS_QUERY, insertId)) 49 | .execute().body(); 50 | log.info("查询数据:{}", query); 51 | // ThreadUtil.sleep(1000); 52 | //删除数据 53 | HttpRequest.delete(TASK_URL + String.format(MYBATIS_DELETE, insertId)) 54 | .execute(); 55 | } 56 | 57 | //卸载插件,参数是pluginId 58 | log.info("卸载插件"); 59 | HttpRequest.post(TASK_URL + UNINSTALL_PLUGIN) 60 | .form("pluginId", "plugin-demo-mybatis") 61 | .execute(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /spring-hot-plugin-example/spring-hot-plugin-task-demo/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | servlet: 4 | context-path: / 5 | 6 | spring: 7 | servlet: 8 | multipart: 9 | max-file-size: 100MB 10 | max-request-size: 100MB 11 | application: 12 | name: spring-hot-plugin-task-demo 13 | main: 14 | allow-bean-definition-overriding: true 15 | mvc: 16 | throw-exception-if-no-handler-found: true 17 | static-path-pattern: /static/** 18 | datasource: 19 | # url: jdbc:h2:mem:testdb 20 | # driver-class-name: org.h2.Driver 21 | # username: sa 22 | # password: 23 | url: jdbc:mysql://172.17.0.2:3306/testdb?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC 24 | driver-class-name: com.mysql.cj.jdbc.Driver 25 | username: root 26 | password: root123 27 | 28 | 29 | 30 | # 开启调式日志 31 | logging: 32 | file: 33 | name: logs/spring-hot-plugin-task-demo.log 34 | level: 35 | csdn.itsaysay.plugin: DEBUG 36 | 37 | -------------------------------------------------------------------------------- /spring-hot-plugin-loader/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/modules.xml 8 | .idea/jarRepositories.xml 9 | .idea/compiler.xml 10 | .idea/libraries/ 11 | *.iws 12 | *.iml 13 | *.ipr 14 | 15 | ### Eclipse ### 16 | .apt_generated 17 | .classpath 18 | .factorypath 19 | .project 20 | .settings 21 | .springBeans 22 | .sts4-cache 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | 37 | ### Mac OS ### 38 | .DS_Store -------------------------------------------------------------------------------- /spring-hot-plugin-loader/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | vip.aliali.spring 8 | spring-hot-plugin-parent 9 | 1.1.1 10 | 11 | 12 | spring-hot-plugin-loader 13 | 插件依赖包加载组件 14 | 15 | 16 | 8 17 | 8 18 | UTF-8 19 | 20 | 21 | 22 | 23 | org.springframework 24 | spring-core 25 | provided 26 | true 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /spring-hot-plugin-loader/src/main/java/vip/aliali/spring/plugin/loader/ClassPathIndexFile.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package vip.aliali.spring.plugin.loader; 18 | 19 | import java.io.*; 20 | import java.net.MalformedURLException; 21 | import java.net.URISyntaxException; 22 | import java.net.URL; 23 | import java.nio.charset.StandardCharsets; 24 | import java.util.ArrayList; 25 | import java.util.Collections; 26 | import java.util.List; 27 | import java.util.stream.Collectors; 28 | 29 | /** 30 | * A class path index file that provides ordering information for JARs. 31 | * 32 | * @author Madhura Bhave 33 | * @author Phillip Webb 34 | */ 35 | final class ClassPathIndexFile { 36 | 37 | private final File root; 38 | 39 | private final List lines; 40 | 41 | private ClassPathIndexFile(File root, List lines) { 42 | this.root = root; 43 | this.lines = lines.stream().map(this::extractName).collect(Collectors.toList()); 44 | } 45 | 46 | private String extractName(String line) { 47 | if (line.startsWith("- \"") && line.endsWith("\"")) { 48 | return line.substring(3, line.length() - 1); 49 | } 50 | throw new IllegalStateException("Malformed classpath index line [" + line + "]"); 51 | } 52 | 53 | int size() { 54 | return this.lines.size(); 55 | } 56 | 57 | boolean containsEntry(String name) { 58 | if (name == null || name.isEmpty()) { 59 | return false; 60 | } 61 | return this.lines.contains(name); 62 | } 63 | 64 | List getUrls() { 65 | return Collections.unmodifiableList(this.lines.stream().map(this::asUrl).collect(Collectors.toList())); 66 | } 67 | 68 | private URL asUrl(String line) { 69 | try { 70 | return new File(this.root, line).toURI().toURL(); 71 | } 72 | catch (MalformedURLException ex) { 73 | throw new IllegalStateException(ex); 74 | } 75 | } 76 | 77 | static ClassPathIndexFile loadIfPossible(URL root, String location) throws IOException { 78 | return loadIfPossible(asFile(root), location); 79 | } 80 | 81 | private static ClassPathIndexFile loadIfPossible(File root, String location) throws IOException { 82 | return loadIfPossible(root, new File(root, location)); 83 | } 84 | 85 | private static ClassPathIndexFile loadIfPossible(File root, File indexFile) throws IOException { 86 | if (indexFile.exists() && indexFile.isFile()) { 87 | try (InputStream inputStream = new FileInputStream(indexFile)) { 88 | return new ClassPathIndexFile(root, loadLines(inputStream)); 89 | } 90 | } 91 | return null; 92 | } 93 | 94 | private static List loadLines(InputStream inputStream) throws IOException { 95 | List lines = new ArrayList<>(); 96 | BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); 97 | String line = reader.readLine(); 98 | while (line != null) { 99 | if (!line.trim().isEmpty()) { 100 | lines.add(line); 101 | } 102 | line = reader.readLine(); 103 | } 104 | return Collections.unmodifiableList(lines); 105 | } 106 | 107 | private static File asFile(URL url) { 108 | if (!"file".equals(url.getProtocol())) { 109 | throw new IllegalArgumentException("URL does not reference a file"); 110 | } 111 | try { 112 | return new File(url.toURI()); 113 | } 114 | catch (URISyntaxException ex) { 115 | return new File(url.getPath()); 116 | } 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /spring-hot-plugin-loader/src/main/java/vip/aliali/spring/plugin/loader/JarLauncher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2021 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package vip.aliali.spring.plugin.loader; 18 | 19 | import vip.aliali.spring.plugin.loader.archive.Archive; 20 | 21 | import java.util.Iterator; 22 | 23 | /** 24 | * {@link Launcher} for JAR based archives. This launcher assumes that dependency jars are 25 | * included inside a {@code /BOOT-INF/lib} directory and that application classes are 26 | * included inside a {@code /BOOT-INF/classes} directory. 27 | * 28 | * @author Phillip Webb 29 | * @author Andy Wilkinson 30 | * @author Madhura Bhave 31 | * @author Scott Frederick 32 | * @since 1.0.0 33 | */ 34 | public class JarLauncher extends ExecutableArchiveLauncher { 35 | 36 | static final Archive.EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> { 37 | if (entry.isDirectory()) { 38 | return entry.getName().equals("classes/"); 39 | } 40 | return entry.getName().startsWith("lib/"); 41 | }; 42 | 43 | public JarLauncher() { 44 | } 45 | 46 | public JarLauncher(Archive archive) { 47 | super(archive); 48 | } 49 | 50 | @Override 51 | protected boolean isPostProcessingClassPathArchives() { 52 | return false; 53 | } 54 | 55 | @Override 56 | protected boolean isNestedArchive(Archive.Entry entry) { 57 | return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry); 58 | } 59 | 60 | @Override 61 | protected String getArchiveEntryPathPrefix() { 62 | return null; 63 | } 64 | 65 | public Iterator getClassPathArchivesIterator() throws Exception { 66 | return super.getClassPathArchivesIterator(); 67 | } 68 | 69 | public static void main(String[] args) throws Exception { 70 | new JarLauncher().launch(args); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /spring-hot-plugin-loader/src/main/java/vip/aliali/spring/plugin/loader/MainMethodRunner.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package vip.aliali.spring.plugin.loader; 18 | 19 | import java.lang.reflect.Method; 20 | 21 | /** 22 | * Utility class that is used by {@link Launcher}s to call a main method. The class 23 | * containing the main method is loaded using the thread context class loader. 24 | * 25 | * @author Phillip Webb 26 | * @author Andy Wilkinson 27 | * @since 1.0.0 28 | */ 29 | public class MainMethodRunner { 30 | 31 | private final String mainClassName; 32 | 33 | private final String[] args; 34 | 35 | /** 36 | * Create a new {@link MainMethodRunner} instance. 37 | * @param mainClass the main class 38 | * @param args incoming arguments 39 | */ 40 | public MainMethodRunner(String mainClass, String[] args) { 41 | this.mainClassName = mainClass; 42 | this.args = (args != null) ? args.clone() : null; 43 | } 44 | 45 | public void run() throws Exception { 46 | Class mainClass = Class.forName(this.mainClassName, false, Thread.currentThread().getContextClassLoader()); 47 | Method mainMethod = mainClass.getDeclaredMethod("main", String[].class); 48 | mainMethod.setAccessible(true); 49 | mainMethod.invoke(null, new Object[] { this.args }); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /spring-hot-plugin-loader/src/main/java/vip/aliali/spring/plugin/loader/WarLauncher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2021 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package vip.aliali.spring.plugin.loader; 18 | 19 | import vip.aliali.spring.plugin.loader.archive.Archive; 20 | 21 | /** 22 | * {@link Launcher} for WAR based archives. This launcher for standard WAR archives. 23 | * Supports dependencies in {@code WEB-INF/lib} as well as {@code WEB-INF/lib-provided}, 24 | * classes are loaded from {@code WEB-INF/classes}. 25 | * 26 | * @author Phillip Webb 27 | * @author Andy Wilkinson 28 | * @author Scott Frederick 29 | * @since 1.0.0 30 | */ 31 | public class WarLauncher extends ExecutableArchiveLauncher { 32 | 33 | public WarLauncher() { 34 | } 35 | 36 | protected WarLauncher(Archive archive) { 37 | super(archive); 38 | } 39 | 40 | @Override 41 | protected boolean isPostProcessingClassPathArchives() { 42 | return false; 43 | } 44 | 45 | @Override 46 | public boolean isNestedArchive(Archive.Entry entry) { 47 | if (entry.isDirectory()) { 48 | return entry.getName().equals("WEB-INF/classes/"); 49 | } 50 | return entry.getName().startsWith("WEB-INF/lib/") || entry.getName().startsWith("WEB-INF/lib-provided/"); 51 | } 52 | 53 | @Override 54 | protected String getArchiveEntryPathPrefix() { 55 | return "WEB-INF/"; 56 | } 57 | 58 | public static void main(String[] args) throws Exception { 59 | new WarLauncher().launch(args); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /spring-hot-plugin-loader/src/main/java/vip/aliali/spring/plugin/loader/archive/Archive.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2021 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package vip.aliali.spring.plugin.loader.archive; 18 | 19 | 20 | import vip.aliali.spring.plugin.loader.Launcher; 21 | import vip.aliali.spring.plugin.loader.jar.JarFile; 22 | 23 | import java.io.IOException; 24 | import java.net.MalformedURLException; 25 | import java.net.URL; 26 | import java.util.*; 27 | import java.util.function.Consumer; 28 | import java.util.jar.Manifest; 29 | 30 | /** 31 | * An archive that can be launched by the {@link Launcher}. 32 | * 33 | * @author Phillip Webb 34 | * @since 1.0.0 35 | * @see JarFileArchive 36 | */ 37 | public interface Archive extends Iterable, AutoCloseable { 38 | 39 | /** 40 | * Returns a URL that can be used to load the archive. 41 | * @return the archive URL 42 | * @throws MalformedURLException if the URL is malformed 43 | */ 44 | URL getUrl() throws MalformedURLException; 45 | 46 | /** 47 | * Returns the manifest of the archive. 48 | * @return the manifest 49 | * @throws IOException if the manifest cannot be read 50 | */ 51 | Manifest getManifest() throws IOException; 52 | 53 | /** 54 | * Returns nested {@link Archive}s for entries that match the specified filters. 55 | * @param searchFilter filter used to limit when additional sub-entry searching is 56 | * required or {@code null} if all entries should be considered. 57 | * @param includeFilter filter used to determine which entries should be included in 58 | * the result or {@code null} if all entries should be included 59 | * @return the nested archives 60 | * @throws IOException on IO error 61 | * @since 2.3.0 62 | */ 63 | default Iterator getNestedArchives(EntryFilter searchFilter, EntryFilter includeFilter) 64 | throws IOException { 65 | EntryFilter combinedFilter = (entry) -> (searchFilter == null || searchFilter.matches(entry)) 66 | && (includeFilter == null || includeFilter.matches(entry)); 67 | List nestedArchives = getNestedArchives(combinedFilter); 68 | return nestedArchives.iterator(); 69 | } 70 | 71 | /** 72 | * Returns nested {@link Archive}s for entries that match the specified filter. 73 | * @param filter the filter used to limit entries 74 | * @return nested archives 75 | * @throws IOException if nested archives cannot be read 76 | * @deprecated since 2.3.0 for removal in 2.5.0 in favor of 77 | * {@link #getNestedArchives(EntryFilter, EntryFilter)} 78 | */ 79 | @Deprecated 80 | default List getNestedArchives(EntryFilter filter) throws IOException { 81 | throw new IllegalStateException("Unexpected call to getNestedArchives(filter)"); 82 | } 83 | 84 | /** 85 | * Return a new iterator for the archive entries. 86 | * @deprecated since 2.3.0 for removal in 2.5.0 in favor of using 87 | * {@link JarFile} to access entries and 88 | * {@link #getNestedArchives(EntryFilter, EntryFilter)} for accessing nested archives. 89 | * @see Iterable#iterator() 90 | */ 91 | @Deprecated 92 | @Override 93 | Iterator iterator(); 94 | 95 | /** 96 | * Performs the given action for each element of the {@code Iterable} until all 97 | * elements have been processed or the action throws an exception. 98 | * @deprecated since 2.3.0 for removal in 2.5.0 in favor of using 99 | * {@link JarFile} to access entries and 100 | * {@link #getNestedArchives(EntryFilter, EntryFilter)} for accessing nested archives. 101 | * @see Iterable#forEach 102 | */ 103 | @Deprecated 104 | @Override 105 | default void forEach(Consumer action) { 106 | Objects.requireNonNull(action); 107 | for (Entry entry : this) { 108 | action.accept(entry); 109 | } 110 | } 111 | 112 | /** 113 | * Creates a {@link Spliterator} over the elements described by this {@code Iterable}. 114 | * @deprecated since 2.3.0 for removal in 2.5.0 in favor of using 115 | * {@link JarFile} to access entries and 116 | * {@link #getNestedArchives(EntryFilter, EntryFilter)} for accessing nested archives. 117 | * @see Iterable#spliterator 118 | */ 119 | @Deprecated 120 | @Override 121 | default Spliterator spliterator() { 122 | return Spliterators.spliteratorUnknownSize(iterator(), 0); 123 | } 124 | 125 | /** 126 | * Return if the archive is exploded (already unpacked). 127 | * @return if the archive is exploded 128 | * @since 2.3.0 129 | */ 130 | default boolean isExploded() { 131 | return false; 132 | } 133 | 134 | /** 135 | * Closes the {@code Archive}, releasing any open resources. 136 | * @throws Exception if an error occurs during close processing 137 | * @since 2.2.0 138 | */ 139 | @Override 140 | default void close() throws Exception { 141 | 142 | } 143 | 144 | /** 145 | * Represents a single entry in the archive. 146 | */ 147 | interface Entry { 148 | 149 | /** 150 | * Returns {@code true} if the entry represents a directory. 151 | * @return if the entry is a directory 152 | */ 153 | boolean isDirectory(); 154 | 155 | /** 156 | * Returns the name of the entry. 157 | * @return the name of the entry 158 | */ 159 | String getName(); 160 | 161 | } 162 | 163 | /** 164 | * Strategy interface to filter {@link Entry Entries}. 165 | */ 166 | @FunctionalInterface 167 | interface EntryFilter { 168 | 169 | /** 170 | * Apply the jar entry filter. 171 | * @param entry the entry to filter 172 | * @return {@code true} if the filter matches 173 | */ 174 | boolean matches(Entry entry); 175 | 176 | } 177 | 178 | } 179 | -------------------------------------------------------------------------------- /spring-hot-plugin-loader/src/main/java/vip/aliali/spring/plugin/loader/archive/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Abstraction over logical Archives be they backed by a JAR file or unpacked into a 19 | * directory. 20 | * 21 | * @see vip.aliali.spring.plugin.loader.archive.Archive 22 | */ 23 | package vip.aliali.spring.plugin.loader.archive; 24 | -------------------------------------------------------------------------------- /spring-hot-plugin-loader/src/main/java/vip/aliali/spring/plugin/loader/data/RandomAccessData.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package vip.aliali.spring.plugin.loader.data; 18 | 19 | import java.io.EOFException; 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | 23 | /** 24 | * Interface that provides read-only random access to some underlying data. 25 | * Implementations must allow concurrent reads in a thread-safe manner. 26 | * 27 | * @author Phillip Webb 28 | * @since 1.0.0 29 | */ 30 | public interface RandomAccessData { 31 | 32 | /** 33 | * Returns an {@link InputStream} that can be used to read the underlying data. The 34 | * caller is responsible close the underlying stream. 35 | * @return a new input stream that can be used to read the underlying data. 36 | * @throws IOException if the stream cannot be opened 37 | */ 38 | InputStream getInputStream() throws IOException; 39 | 40 | /** 41 | * Returns a new {@link RandomAccessData} for a specific subsection of this data. 42 | * @param offset the offset of the subsection 43 | * @param length the length of the subsection 44 | * @return the subsection data 45 | */ 46 | RandomAccessData getSubsection(long offset, long length); 47 | 48 | /** 49 | * Reads all the data and returns it as a byte array. 50 | * @return the data 51 | * @throws IOException if the data cannot be read 52 | */ 53 | byte[] read() throws IOException; 54 | 55 | /** 56 | * Reads the {@code length} bytes of data starting at the given {@code offset}. 57 | * @param offset the offset from which data should be read 58 | * @param length the number of bytes to be read 59 | * @return the data 60 | * @throws IOException if the data cannot be read 61 | * @throws IndexOutOfBoundsException if offset is beyond the end of the file or 62 | * subsection 63 | * @throws EOFException if offset plus length is greater than the length of the file 64 | * or subsection 65 | */ 66 | byte[] read(long offset, long length) throws IOException; 67 | 68 | /** 69 | * Returns the size of the data. 70 | * @return the size 71 | */ 72 | long getSize(); 73 | 74 | } 75 | -------------------------------------------------------------------------------- /spring-hot-plugin-loader/src/main/java/vip/aliali/spring/plugin/loader/data/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2021 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Classes and interfaces to allow random access to a block of data. 19 | * 20 | * @see vip.aliali.spring.plugin.loader.data.RandomAccessData 21 | */ 22 | package vip.aliali.spring.plugin.loader.data; 23 | -------------------------------------------------------------------------------- /spring-hot-plugin-loader/src/main/java/vip/aliali/spring/plugin/loader/jar/AbstractJarFile.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package vip.aliali.spring.plugin.loader.jar; 18 | 19 | import java.io.File; 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | import java.net.MalformedURLException; 23 | import java.net.URL; 24 | import java.security.Permission; 25 | 26 | /** 27 | * Base class for extended variants of {@link java.util.jar.JarFile}. 28 | * 29 | * @author Phillip Webb 30 | */ 31 | abstract class AbstractJarFile extends java.util.jar.JarFile { 32 | 33 | /** 34 | * Create a new {@link AbstractJarFile}. 35 | * @param file the root jar file. 36 | * @throws IOException on IO error 37 | */ 38 | AbstractJarFile(File file) throws IOException { 39 | super(file); 40 | } 41 | 42 | /** 43 | * Return a URL that can be used to access this JAR file. NOTE: the specified URL 44 | * cannot be serialized and or cloned. 45 | * @return the URL 46 | * @throws MalformedURLException if the URL is malformed 47 | */ 48 | abstract URL getUrl() throws MalformedURLException; 49 | 50 | /** 51 | * Return the {@link JarFileType} of this instance. 52 | * @return the jar file type 53 | */ 54 | abstract JarFileType getType(); 55 | 56 | /** 57 | * Return the security permission for this JAR. 58 | * @return the security permission. 59 | */ 60 | abstract Permission getPermission(); 61 | 62 | /** 63 | * Return an {@link InputStream} for the entire jar contents. 64 | * @return the contents input stream 65 | * @throws IOException on IO error 66 | */ 67 | abstract InputStream getInputStream() throws IOException; 68 | 69 | /** 70 | * The type of a {@link JarFile}. 71 | */ 72 | enum JarFileType { 73 | 74 | DIRECT, NESTED_DIRECTORY, NESTED_JAR 75 | 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /spring-hot-plugin-loader/src/main/java/vip/aliali/spring/plugin/loader/jar/Bytes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package vip.aliali.spring.plugin.loader.jar; 18 | 19 | /** 20 | * Utilities for dealing with bytes from ZIP files. 21 | * 22 | * @author Phillip Webb 23 | */ 24 | final class Bytes { 25 | 26 | private Bytes() { 27 | } 28 | 29 | static long littleEndianValue(byte[] bytes, int offset, int length) { 30 | long value = 0; 31 | for (int i = length - 1; i >= 0; i--) { 32 | value = ((value << 8) | (bytes[offset + i] & 0xFF)); 33 | } 34 | return value; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /spring-hot-plugin-loader/src/main/java/vip/aliali/spring/plugin/loader/jar/CentralDirectoryParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2021 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package vip.aliali.spring.plugin.loader.jar; 18 | 19 | import vip.aliali.spring.plugin.loader.data.RandomAccessData; 20 | 21 | import java.io.IOException; 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | 25 | /** 26 | * Parses the central directory from a JAR file. 27 | * 28 | * @author Phillip Webb 29 | * @author Andy Wilkinson 30 | * @see CentralDirectoryVisitor 31 | */ 32 | class CentralDirectoryParser { 33 | 34 | private static final int CENTRAL_DIRECTORY_HEADER_BASE_SIZE = 46; 35 | 36 | private final List visitors = new ArrayList<>(); 37 | 38 | T addVisitor(T visitor) { 39 | this.visitors.add(visitor); 40 | return visitor; 41 | } 42 | 43 | /** 44 | * Parse the source data, triggering {@link CentralDirectoryVisitor visitors}. 45 | * @param data the source data 46 | * @param skipPrefixBytes if prefix bytes should be skipped 47 | * @return the actual archive data without any prefix bytes 48 | * @throws IOException on error 49 | */ 50 | RandomAccessData parse(RandomAccessData data, boolean skipPrefixBytes) throws IOException { 51 | CentralDirectoryEndRecord endRecord = new CentralDirectoryEndRecord(data); 52 | if (skipPrefixBytes) { 53 | data = getArchiveData(endRecord, data); 54 | } 55 | RandomAccessData centralDirectoryData = endRecord.getCentralDirectory(data); 56 | visitStart(endRecord, centralDirectoryData); 57 | parseEntries(endRecord, centralDirectoryData); 58 | visitEnd(); 59 | return data; 60 | } 61 | 62 | private void parseEntries(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData) 63 | throws IOException { 64 | byte[] bytes = centralDirectoryData.read(0, centralDirectoryData.getSize()); 65 | CentralDirectoryFileHeader fileHeader = new CentralDirectoryFileHeader(); 66 | int dataOffset = 0; 67 | for (int i = 0; i < endRecord.getNumberOfRecords(); i++) { 68 | fileHeader.load(bytes, dataOffset, null, 0, null); 69 | visitFileHeader(dataOffset, fileHeader); 70 | dataOffset += CENTRAL_DIRECTORY_HEADER_BASE_SIZE + fileHeader.getName().length() 71 | + fileHeader.getComment().length() + fileHeader.getExtra().length; 72 | } 73 | } 74 | 75 | private RandomAccessData getArchiveData(CentralDirectoryEndRecord endRecord, RandomAccessData data) { 76 | long offset = endRecord.getStartOfArchive(data); 77 | if (offset == 0) { 78 | return data; 79 | } 80 | return data.getSubsection(offset, data.getSize() - offset); 81 | } 82 | 83 | private void visitStart(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData) { 84 | for (CentralDirectoryVisitor visitor : this.visitors) { 85 | visitor.visitStart(endRecord, centralDirectoryData); 86 | } 87 | } 88 | 89 | private void visitFileHeader(long dataOffset, CentralDirectoryFileHeader fileHeader) { 90 | for (CentralDirectoryVisitor visitor : this.visitors) { 91 | visitor.visitFileHeader(fileHeader, dataOffset); 92 | } 93 | } 94 | 95 | private void visitEnd() { 96 | for (CentralDirectoryVisitor visitor : this.visitors) { 97 | visitor.visitEnd(); 98 | } 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /spring-hot-plugin-loader/src/main/java/vip/aliali/spring/plugin/loader/jar/CentralDirectoryVisitor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2021 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package vip.aliali.spring.plugin.loader.jar; 18 | 19 | import vip.aliali.spring.plugin.loader.data.RandomAccessData; 20 | 21 | /** 22 | * Callback visitor triggered by {@link CentralDirectoryParser}. 23 | * 24 | * @author Phillip Webb 25 | */ 26 | interface CentralDirectoryVisitor { 27 | 28 | void visitStart(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData); 29 | 30 | void visitFileHeader(CentralDirectoryFileHeader fileHeader, long dataOffset); 31 | 32 | void visitEnd(); 33 | 34 | } 35 | -------------------------------------------------------------------------------- /spring-hot-plugin-loader/src/main/java/vip/aliali/spring/plugin/loader/jar/FileHeader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package vip.aliali.spring.plugin.loader.jar; 18 | 19 | import java.util.zip.ZipEntry; 20 | 21 | /** 22 | * A file header record that has been loaded from a Jar file. 23 | * 24 | * @author Phillip Webb 25 | * @see JarEntry 26 | * @see CentralDirectoryFileHeader 27 | */ 28 | interface FileHeader { 29 | 30 | /** 31 | * Returns {@code true} if the header has the given name. 32 | * @param name the name to test 33 | * @param suffix an additional suffix (or {@code 0}) 34 | * @return {@code true} if the header has the given name 35 | */ 36 | boolean hasName(CharSequence name, char suffix); 37 | 38 | /** 39 | * Return the offset of the load file header within the archive data. 40 | * @return the local header offset 41 | */ 42 | long getLocalHeaderOffset(); 43 | 44 | /** 45 | * Return the compressed size of the entry. 46 | * @return the compressed size. 47 | */ 48 | long getCompressedSize(); 49 | 50 | /** 51 | * Return the uncompressed size of the entry. 52 | * @return the uncompressed size. 53 | */ 54 | long getSize(); 55 | 56 | /** 57 | * Return the method used to compress the data. 58 | * @return the zip compression method 59 | * @see ZipEntry#STORED 60 | * @see ZipEntry#DEFLATED 61 | */ 62 | int getMethod(); 63 | 64 | } 65 | -------------------------------------------------------------------------------- /spring-hot-plugin-loader/src/main/java/vip/aliali/spring/plugin/loader/jar/JarEntry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package vip.aliali.spring.plugin.loader.jar; 18 | 19 | import java.io.IOException; 20 | import java.net.MalformedURLException; 21 | import java.net.URL; 22 | import java.security.CodeSigner; 23 | import java.security.cert.Certificate; 24 | import java.util.jar.Attributes; 25 | import java.util.jar.Manifest; 26 | 27 | /** 28 | * Extended variant of {@link java.util.jar.JarEntry} returned by {@link JarFile}s. 29 | * 30 | * @author Phillip Webb 31 | * @author Andy Wilkinson 32 | */ 33 | class JarEntry extends java.util.jar.JarEntry implements FileHeader { 34 | 35 | private final int index; 36 | 37 | private final AsciiBytes name; 38 | 39 | private final AsciiBytes headerName; 40 | 41 | private final JarFile jarFile; 42 | 43 | private long localHeaderOffset; 44 | 45 | private volatile JarEntryCertification certification; 46 | 47 | JarEntry(JarFile jarFile, int index, CentralDirectoryFileHeader header, AsciiBytes nameAlias) { 48 | super((nameAlias != null) ? nameAlias.toString() : header.getName().toString()); 49 | this.index = index; 50 | this.name = (nameAlias != null) ? nameAlias : header.getName(); 51 | this.headerName = header.getName(); 52 | this.jarFile = jarFile; 53 | this.localHeaderOffset = header.getLocalHeaderOffset(); 54 | setCompressedSize(header.getCompressedSize()); 55 | setMethod(header.getMethod()); 56 | setCrc(header.getCrc()); 57 | setComment(header.getComment().toString()); 58 | setSize(header.getSize()); 59 | setTime(header.getTime()); 60 | if (header.hasExtra()) { 61 | setExtra(header.getExtra()); 62 | } 63 | } 64 | 65 | int getIndex() { 66 | return this.index; 67 | } 68 | 69 | AsciiBytes getAsciiBytesName() { 70 | return this.name; 71 | } 72 | 73 | @Override 74 | public boolean hasName(CharSequence name, char suffix) { 75 | return this.headerName.matches(name, suffix); 76 | } 77 | 78 | /** 79 | * Return a {@link URL} for this {@link JarEntry}. 80 | * @return the URL for the entry 81 | * @throws MalformedURLException if the URL is not valid 82 | */ 83 | URL getUrl() throws MalformedURLException { 84 | return new URL(this.jarFile.getUrl(), getName()); 85 | } 86 | 87 | @Override 88 | public Attributes getAttributes() throws IOException { 89 | Manifest manifest = this.jarFile.getManifest(); 90 | return (manifest != null) ? manifest.getAttributes(getName()) : null; 91 | } 92 | 93 | @Override 94 | public Certificate[] getCertificates() { 95 | return getCertification().getCertificates(); 96 | } 97 | 98 | @Override 99 | public CodeSigner[] getCodeSigners() { 100 | return getCertification().getCodeSigners(); 101 | } 102 | 103 | private JarEntryCertification getCertification() { 104 | if (!this.jarFile.isSigned()) { 105 | return JarEntryCertification.NONE; 106 | } 107 | JarEntryCertification certification = this.certification; 108 | if (certification == null) { 109 | certification = this.jarFile.getCertification(this); 110 | this.certification = certification; 111 | } 112 | return certification; 113 | } 114 | 115 | @Override 116 | public long getLocalHeaderOffset() { 117 | return this.localHeaderOffset; 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /spring-hot-plugin-loader/src/main/java/vip/aliali/spring/plugin/loader/jar/JarEntryCertification.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package vip.aliali.spring.plugin.loader.jar; 18 | 19 | import java.security.CodeSigner; 20 | import java.security.cert.Certificate; 21 | 22 | /** 23 | * {@link Certificate} and {@link CodeSigner} details for a {@link JarEntry} from a signed 24 | * {@link JarFile}. 25 | * 26 | * @author Phillip Webb 27 | */ 28 | class JarEntryCertification { 29 | 30 | static final JarEntryCertification NONE = new JarEntryCertification(null, null); 31 | 32 | private final Certificate[] certificates; 33 | 34 | private final CodeSigner[] codeSigners; 35 | 36 | JarEntryCertification(Certificate[] certificates, CodeSigner[] codeSigners) { 37 | this.certificates = certificates; 38 | this.codeSigners = codeSigners; 39 | } 40 | 41 | Certificate[] getCertificates() { 42 | return (this.certificates != null) ? this.certificates.clone() : null; 43 | } 44 | 45 | CodeSigner[] getCodeSigners() { 46 | return (this.codeSigners != null) ? this.codeSigners.clone() : null; 47 | } 48 | 49 | static JarEntryCertification from(java.util.jar.JarEntry certifiedEntry) { 50 | Certificate[] certificates = (certifiedEntry != null) ? certifiedEntry.getCertificates() : null; 51 | CodeSigner[] codeSigners = (certifiedEntry != null) ? certifiedEntry.getCodeSigners() : null; 52 | if (certificates == null && codeSigners == null) { 53 | return NONE; 54 | } 55 | return new JarEntryCertification(certificates, codeSigners); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /spring-hot-plugin-loader/src/main/java/vip/aliali/spring/plugin/loader/jar/JarEntryFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package vip.aliali.spring.plugin.loader.jar; 18 | 19 | /** 20 | * Interface that can be used to filter and optionally rename jar entries. 21 | * 22 | * @author Phillip Webb 23 | */ 24 | interface JarEntryFilter { 25 | 26 | /** 27 | * Apply the jar entry filter. 28 | * @param name the current entry name. This may be different that the original entry 29 | * name if a previous filter has been applied 30 | * @return the new name of the entry or {@code null} if the entry should not be 31 | * included. 32 | */ 33 | AsciiBytes apply(AsciiBytes name); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /spring-hot-plugin-loader/src/main/java/vip/aliali/spring/plugin/loader/jar/JarFileWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package vip.aliali.spring.plugin.loader.jar; 18 | 19 | import java.io.IOException; 20 | import java.io.InputStream; 21 | import java.net.MalformedURLException; 22 | import java.net.URL; 23 | import java.security.Permission; 24 | import java.util.Enumeration; 25 | import java.util.jar.JarEntry; 26 | import java.util.jar.Manifest; 27 | import java.util.stream.Stream; 28 | import java.util.zip.ZipEntry; 29 | 30 | /** 31 | * A wrapper used to create a copy of a {@link JarFile} so that it can be safely closed 32 | * without closing the original. 33 | * 34 | * @author Phillip Webb 35 | */ 36 | public class JarFileWrapper extends AbstractJarFile { 37 | 38 | private final JarFile parent; 39 | 40 | JarFileWrapper(JarFile parent) throws IOException { 41 | super(parent.getRootJarFile().getFile()); 42 | this.parent = parent; 43 | if (System.getSecurityManager() == null) { 44 | super.close(); 45 | } 46 | } 47 | 48 | @Override 49 | URL getUrl() throws MalformedURLException { 50 | return this.parent.getUrl(); 51 | } 52 | 53 | @Override 54 | JarFileType getType() { 55 | return this.parent.getType(); 56 | } 57 | 58 | @Override 59 | Permission getPermission() { 60 | return this.parent.getPermission(); 61 | } 62 | 63 | @Override 64 | public Manifest getManifest() throws IOException { 65 | return this.parent.getManifest(); 66 | } 67 | 68 | @Override 69 | public Enumeration entries() { 70 | return this.parent.entries(); 71 | } 72 | 73 | @Override 74 | public Stream stream() { 75 | return this.parent.stream(); 76 | } 77 | 78 | @Override 79 | public JarEntry getJarEntry(String name) { 80 | return this.parent.getJarEntry(name); 81 | } 82 | 83 | @Override 84 | public ZipEntry getEntry(String name) { 85 | return this.parent.getEntry(name); 86 | } 87 | 88 | @Override 89 | InputStream getInputStream() throws IOException { 90 | return this.parent.getInputStream(); 91 | } 92 | 93 | @Override 94 | public synchronized InputStream getInputStream(ZipEntry ze) throws IOException { 95 | return this.parent.getInputStream(ze); 96 | } 97 | 98 | @Override 99 | public String getComment() { 100 | return this.parent.getComment(); 101 | } 102 | 103 | @Override 104 | public int size() { 105 | return this.parent.size(); 106 | } 107 | 108 | @Override 109 | public String toString() { 110 | return this.parent.toString(); 111 | } 112 | 113 | @Override 114 | public String getName() { 115 | return this.parent.getName(); 116 | } 117 | 118 | static JarFile unwrap(java.util.jar.JarFile jarFile) { 119 | if (jarFile instanceof JarFile) { 120 | return (JarFile) jarFile; 121 | } 122 | if (jarFile instanceof JarFileWrapper) { 123 | return unwrap(((JarFileWrapper) jarFile).parent); 124 | } 125 | throw new IllegalStateException("Not a JarFile or Wrapper"); 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /spring-hot-plugin-loader/src/main/java/vip/aliali/spring/plugin/loader/jar/StringSequence.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package vip.aliali.spring.plugin.loader.jar; 18 | 19 | import java.util.Objects; 20 | 21 | /** 22 | * A {@link CharSequence} backed by a single shared {@link String}. Unlike a regular 23 | * {@link String}, {@link #subSequence(int, int)} operations will not copy the underlying 24 | * character array. 25 | * 26 | * @author Phillip Webb 27 | */ 28 | final class StringSequence implements CharSequence { 29 | 30 | private final String source; 31 | 32 | private final int start; 33 | 34 | private final int end; 35 | 36 | private int hash; 37 | 38 | StringSequence(String source) { 39 | this(source, 0, (source != null) ? source.length() : -1); 40 | } 41 | 42 | StringSequence(String source, int start, int end) { 43 | Objects.requireNonNull(source, "Source must not be null"); 44 | if (start < 0) { 45 | throw new StringIndexOutOfBoundsException(start); 46 | } 47 | if (end > source.length()) { 48 | throw new StringIndexOutOfBoundsException(end); 49 | } 50 | this.source = source; 51 | this.start = start; 52 | this.end = end; 53 | } 54 | 55 | StringSequence subSequence(int start) { 56 | return subSequence(start, length()); 57 | } 58 | 59 | @Override 60 | public StringSequence subSequence(int start, int end) { 61 | int subSequenceStart = this.start + start; 62 | int subSequenceEnd = this.start + end; 63 | if (subSequenceStart > this.end) { 64 | throw new StringIndexOutOfBoundsException(start); 65 | } 66 | if (subSequenceEnd > this.end) { 67 | throw new StringIndexOutOfBoundsException(end); 68 | } 69 | if (start == 0 && subSequenceEnd == this.end) { 70 | return this; 71 | } 72 | return new StringSequence(this.source, subSequenceStart, subSequenceEnd); 73 | } 74 | 75 | /** 76 | * Returns {@code true} if the sequence is empty. Public to be compatible with JDK 15. 77 | * @return {@code true} if {@link #length()} is {@code 0}, otherwise {@code false} 78 | */ 79 | public boolean isEmpty() { 80 | return length() == 0; 81 | } 82 | 83 | @Override 84 | public int length() { 85 | return this.end - this.start; 86 | } 87 | 88 | @Override 89 | public char charAt(int index) { 90 | return this.source.charAt(this.start + index); 91 | } 92 | 93 | int indexOf(char ch) { 94 | return this.source.indexOf(ch, this.start) - this.start; 95 | } 96 | 97 | int indexOf(String str) { 98 | return this.source.indexOf(str, this.start) - this.start; 99 | } 100 | 101 | int indexOf(String str, int fromIndex) { 102 | return this.source.indexOf(str, this.start + fromIndex) - this.start; 103 | } 104 | 105 | boolean startsWith(String prefix) { 106 | return startsWith(prefix, 0); 107 | } 108 | 109 | boolean startsWith(String prefix, int offset) { 110 | int prefixLength = prefix.length(); 111 | int length = length(); 112 | if (length - prefixLength - offset < 0) { 113 | return false; 114 | } 115 | return this.source.startsWith(prefix, this.start + offset); 116 | } 117 | 118 | @Override 119 | public boolean equals(Object obj) { 120 | if (this == obj) { 121 | return true; 122 | } 123 | if (!(obj instanceof CharSequence)) { 124 | return false; 125 | } 126 | CharSequence other = (CharSequence) obj; 127 | int n = length(); 128 | if (n != other.length()) { 129 | return false; 130 | } 131 | int i = 0; 132 | while (n-- != 0) { 133 | if (charAt(i) != other.charAt(i)) { 134 | return false; 135 | } 136 | i++; 137 | } 138 | return true; 139 | } 140 | 141 | @Override 142 | public int hashCode() { 143 | int hash = this.hash; 144 | if (hash == 0 && length() > 0) { 145 | for (int i = this.start; i < this.end; i++) { 146 | hash = 31 * hash + this.source.charAt(i); 147 | } 148 | this.hash = hash; 149 | } 150 | return hash; 151 | } 152 | 153 | @Override 154 | public String toString() { 155 | return this.source.substring(this.start, this.end); 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /spring-hot-plugin-loader/src/main/java/vip/aliali/spring/plugin/loader/jar/ZipInflaterInputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package vip.aliali.spring.plugin.loader.jar; 18 | 19 | import java.io.EOFException; 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | import java.util.zip.Inflater; 23 | import java.util.zip.InflaterInputStream; 24 | 25 | /** 26 | * {@link InflaterInputStream} that supports the writing of an extra "dummy" byte (which 27 | * is required with JDK 6) and returns accurate available() results. 28 | * 29 | * @author Phillip Webb 30 | */ 31 | class ZipInflaterInputStream extends InflaterInputStream { 32 | 33 | private int available; 34 | 35 | private boolean extraBytesWritten; 36 | 37 | ZipInflaterInputStream(InputStream inputStream, int size) { 38 | super(inputStream, new Inflater(true), getInflaterBufferSize(size)); 39 | this.available = size; 40 | } 41 | 42 | @Override 43 | public int available() throws IOException { 44 | if (this.available < 0) { 45 | return super.available(); 46 | } 47 | return this.available; 48 | } 49 | 50 | @Override 51 | public int read(byte[] b, int off, int len) throws IOException { 52 | int result = super.read(b, off, len); 53 | if (result != -1) { 54 | this.available -= result; 55 | } 56 | return result; 57 | } 58 | 59 | @Override 60 | public void close() throws IOException { 61 | super.close(); 62 | this.inf.end(); 63 | } 64 | 65 | @Override 66 | protected void fill() throws IOException { 67 | try { 68 | super.fill(); 69 | } 70 | catch (EOFException ex) { 71 | if (this.extraBytesWritten) { 72 | throw ex; 73 | } 74 | this.len = 1; 75 | this.buf[0] = 0x0; 76 | this.extraBytesWritten = true; 77 | this.inf.setInput(this.buf, 0, this.len); 78 | } 79 | } 80 | 81 | private static int getInflaterBufferSize(long size) { 82 | size += 2; // inflater likes some space 83 | size = (size > 65536) ? 8192 : size; 84 | size = (size <= 0) ? 4096 : size; 85 | return (int) size; 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /spring-hot-plugin-loader/src/main/java/vip/aliali/spring/plugin/loader/jar/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Support for loading and manipulating JAR/WAR files. 19 | */ 20 | package vip.aliali.spring.plugin.loader.jar; 21 | -------------------------------------------------------------------------------- /spring-hot-plugin-loader/src/main/java/vip/aliali/spring/plugin/loader/jarmode/JarMode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package vip.aliali.spring.plugin.loader.jarmode; 18 | 19 | /** 20 | * Interface registered in {@code spring.factories} to provides extended 'jarmode' 21 | * support. 22 | * 23 | * @author Phillip Webb 24 | * @since 2.3.0 25 | */ 26 | public interface JarMode { 27 | 28 | /** 29 | * Returns if this accepts and can run the given mode. 30 | * @param mode the mode to check 31 | * @return if this instance accepts the mode 32 | */ 33 | boolean accepts(String mode); 34 | 35 | /** 36 | * Run the jar in the given mode. 37 | * @param mode the mode to use 38 | * @param args any program arguments 39 | */ 40 | void run(String mode, String[] args); 41 | 42 | } 43 | -------------------------------------------------------------------------------- /spring-hot-plugin-loader/src/main/java/vip/aliali/spring/plugin/loader/jarmode/JarModeLauncher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package vip.aliali.spring.plugin.loader.jarmode; 18 | 19 | import org.springframework.core.io.support.SpringFactoriesLoader; 20 | import org.springframework.util.ClassUtils; 21 | 22 | import java.util.List; 23 | 24 | /** 25 | * Delegate class used to launch the fat jar in a specific mode. 26 | * 27 | * @author Phillip Webb 28 | * @since 2.3.0 29 | */ 30 | public final class JarModeLauncher { 31 | 32 | static final String DISABLE_SYSTEM_EXIT = JarModeLauncher.class.getName() + ".DISABLE_SYSTEM_EXIT"; 33 | 34 | private JarModeLauncher() { 35 | } 36 | 37 | public static void main(String[] args) { 38 | String mode = System.getProperty("jarmode"); 39 | List candidates = SpringFactoriesLoader.loadFactories(JarMode.class, 40 | ClassUtils.getDefaultClassLoader()); 41 | for (JarMode candidate : candidates) { 42 | if (candidate.accepts(mode)) { 43 | candidate.run(mode, args); 44 | return; 45 | } 46 | } 47 | System.err.println("Unsupported jarmode '" + mode + "'"); 48 | if (!Boolean.getBoolean(DISABLE_SYSTEM_EXIT)) { 49 | System.exit(1); 50 | } 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /spring-hot-plugin-loader/src/main/java/vip/aliali/spring/plugin/loader/jarmode/TestJarMode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package vip.aliali.spring.plugin.loader.jarmode; 18 | 19 | import java.util.Arrays; 20 | 21 | /** 22 | * {@link JarMode} for testing. 23 | * 24 | * @author Phillip Webb 25 | */ 26 | class TestJarMode implements JarMode { 27 | 28 | @Override 29 | public boolean accepts(String mode) { 30 | return "test".equals(mode); 31 | } 32 | 33 | @Override 34 | public void run(String mode, String[] args) { 35 | System.out.println("running in " + mode + " jar mode " + Arrays.asList(args)); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /spring-hot-plugin-loader/src/main/java/vip/aliali/spring/plugin/loader/jarmode/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Support for launching the JAR using jarmode. 19 | * 20 | * @see vip.aliali.spring.plugin.loader.jarmode.JarModeLauncher 21 | */ 22 | package vip.aliali.spring.plugin.loader.jarmode; 23 | -------------------------------------------------------------------------------- /spring-hot-plugin-loader/src/main/java/vip/aliali/spring/plugin/loader/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * System that allows self-contained JAR/WAR archives to be launched using 19 | * {@code java -jar}. Archives can include nested packaged dependency JARs (there is no 20 | * need to create shade style jars) and are executed without unpacking. The only 21 | * constraint is that nested JARs must be stored in the archive uncompressed. 22 | * 23 | * @see vip.aliali.spring.plugin.loader.JarLauncher 24 | * @see vip.aliali.spring.plugin.loader.WarLauncher 25 | */ 26 | package vip.aliali.spring.plugin.loader; 27 | -------------------------------------------------------------------------------- /spring-hot-plugin-loader/src/main/java/vip/aliali/spring/plugin/loader/util/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2019 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Utilities used by Spring Boot's JAR loading. 19 | */ 20 | package vip.aliali.spring.plugin.loader.util; 21 | -------------------------------------------------------------------------------- /spring-hot-plugin-loader/src/test/java/AbstractExecutableArchiveLauncherTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2021 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import vip.aliali.spring.plugin.loader.archive.Archive; 18 | import org.junit.jupiter.api.io.TempDir; 19 | import org.springframework.util.FileCopyUtils; 20 | 21 | import java.io.*; 22 | import java.net.MalformedURLException; 23 | import java.net.URL; 24 | import java.nio.charset.StandardCharsets; 25 | import java.util.*; 26 | import java.util.jar.JarEntry; 27 | import java.util.jar.JarFile; 28 | import java.util.jar.JarOutputStream; 29 | import java.util.jar.Manifest; 30 | import java.util.zip.CRC32; 31 | import java.util.zip.ZipEntry; 32 | 33 | /** 34 | * 用来创建临时测试的jar包 35 | */ 36 | public abstract class AbstractExecutableArchiveLauncherTests { 37 | 38 | @TempDir 39 | File tempDir; 40 | 41 | protected File createJarArchive(String name, String entryPrefix) throws IOException { 42 | return createJarArchive(name, entryPrefix, false, Collections.emptyList()); 43 | } 44 | 45 | @SuppressWarnings("resource") 46 | protected File createJarArchive(String name, String entryPrefix, boolean indexed, List extraLibs) 47 | throws IOException { 48 | return createJarArchive(name, null, entryPrefix, indexed, extraLibs); 49 | } 50 | 51 | @SuppressWarnings("resource") 52 | protected File createJarArchive(String name, Manifest manifest, String entryPrefix, boolean indexed, 53 | List extraLibs) throws IOException { 54 | File archive = new File(this.tempDir, name); 55 | JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(archive)); 56 | if (manifest != null) { 57 | jarOutputStream.putNextEntry(new JarEntry("META-INF/")); 58 | jarOutputStream.putNextEntry(new JarEntry("META-INF/MANIFEST.MF")); 59 | manifest.write(jarOutputStream); 60 | jarOutputStream.closeEntry(); 61 | } 62 | jarOutputStream.putNextEntry(new JarEntry(entryPrefix + "/")); 63 | jarOutputStream.putNextEntry(new JarEntry(entryPrefix + "/classes/")); 64 | jarOutputStream.putNextEntry(new JarEntry(entryPrefix + "/lib/")); 65 | if (indexed) { 66 | jarOutputStream.putNextEntry(new JarEntry(entryPrefix + "/classpath.idx")); 67 | Writer writer = new OutputStreamWriter(jarOutputStream, StandardCharsets.UTF_8); 68 | writer.write("- \"" + entryPrefix + "/lib/foo.jar\"\n"); 69 | writer.write("- \"" + entryPrefix + "/lib/bar.jar\"\n"); 70 | writer.write("- \"" + entryPrefix + "/lib/baz.jar\"\n"); 71 | writer.flush(); 72 | jarOutputStream.closeEntry(); 73 | } 74 | addNestedJars(entryPrefix, "/lib/foo.jar", jarOutputStream); 75 | addNestedJars(entryPrefix, "/lib/bar.jar", jarOutputStream); 76 | addNestedJars(entryPrefix, "/lib/baz.jar", jarOutputStream); 77 | for (String lib : extraLibs) { 78 | addNestedJars(entryPrefix, "/lib/" + lib, jarOutputStream); 79 | } 80 | jarOutputStream.close(); 81 | return archive; 82 | } 83 | 84 | protected File createJarArchive(String name) throws IOException { 85 | File archive = new File(this.tempDir, name); 86 | JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(archive)); 87 | 88 | jarOutputStream.putNextEntry(new JarEntry("classes/")); 89 | jarOutputStream.putNextEntry(new JarEntry("lib/")); 90 | 91 | addNestedJars("", "lib/foo.jar", jarOutputStream); 92 | addNestedJars("", "lib/bar.jar", jarOutputStream); 93 | addNestedJars("", "lib/baz.jar", jarOutputStream); 94 | jarOutputStream.close(); 95 | return archive; 96 | } 97 | 98 | private void addNestedJars(String entryPrefix, String lib, JarOutputStream jarOutputStream) throws IOException { 99 | JarEntry libFoo = new JarEntry(entryPrefix + lib); 100 | libFoo.setMethod(ZipEntry.STORED); 101 | ByteArrayOutputStream fooJarStream = new ByteArrayOutputStream(); 102 | new JarOutputStream(fooJarStream).close(); 103 | libFoo.setSize(fooJarStream.size()); 104 | CRC32 crc32 = new CRC32(); 105 | crc32.update(fooJarStream.toByteArray()); 106 | libFoo.setCrc(crc32.getValue()); 107 | jarOutputStream.putNextEntry(libFoo); 108 | jarOutputStream.write(fooJarStream.toByteArray()); 109 | } 110 | 111 | protected File explode(File archive) throws IOException { 112 | File exploded = new File(this.tempDir, "exploded"); 113 | exploded.mkdirs(); 114 | JarFile jarFile = new JarFile(archive); 115 | Enumeration entries = jarFile.entries(); 116 | while (entries.hasMoreElements()) { 117 | JarEntry entry = entries.nextElement(); 118 | File entryFile = new File(exploded, entry.getName()); 119 | if (entry.isDirectory()) { 120 | entryFile.mkdirs(); 121 | } 122 | else { 123 | FileCopyUtils.copy(jarFile.getInputStream(entry), new FileOutputStream(entryFile)); 124 | } 125 | } 126 | jarFile.close(); 127 | return exploded; 128 | } 129 | 130 | protected Set getUrls(List archives) throws MalformedURLException { 131 | Set urls = new LinkedHashSet<>(archives.size()); 132 | for (Archive archive : archives) { 133 | urls.add(archive.getUrl()); 134 | } 135 | return urls; 136 | } 137 | 138 | protected final URL toUrl(File file) { 139 | try { 140 | return file.toURI().toURL(); 141 | } 142 | catch (MalformedURLException ex) { 143 | throw new IllegalStateException(ex); 144 | } 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /spring-hot-plugin-loader/src/test/java/JarLauncherTest.java: -------------------------------------------------------------------------------- 1 | import vip.aliali.spring.plugin.loader.JarLauncher; 2 | import vip.aliali.spring.plugin.loader.archive.Archive; 3 | import vip.aliali.spring.plugin.loader.archive.JarFileArchive; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.io.File; 7 | import java.net.URL; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | 13 | /** 14 | * Jar 包读取测试类 15 | * @author 阿提说说 16 | */ 17 | public class JarLauncherTest extends AbstractExecutableArchiveLauncherTests { 18 | 19 | @Test 20 | public void test() throws Exception { 21 | File jarRoot = createJarArchive("archive.jar"); 22 | try (JarFileArchive archive = new JarFileArchive(jarRoot)) { 23 | JarLauncher launcher = new JarLauncher(archive); 24 | List classPathArchives = new ArrayList<>(); 25 | launcher.getClassPathArchivesIterator().forEachRemaining(classPathArchives::add); 26 | assertThat(classPathArchives).hasSize(4); 27 | assertThat(getUrls(classPathArchives)).containsOnly( 28 | new URL("jar:" + jarRoot.toURI().toURL() + "!/classes!/"), 29 | new URL("jar:" + jarRoot.toURI().toURL() + "!/lib/foo.jar!/"), 30 | new URL("jar:" + jarRoot.toURI().toURL() + "!/lib/bar.jar!/"), 31 | new URL("jar:" + jarRoot.toURI().toURL() + "!/lib/baz.jar!/")); 32 | for (Archive classPathArchive : classPathArchives) { 33 | classPathArchive.close(); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /spring-hot-plugin-maven/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/modules.xml 8 | .idea/jarRepositories.xml 9 | .idea/compiler.xml 10 | .idea/libraries/ 11 | *.iws 12 | *.iml 13 | *.ipr 14 | 15 | ### Eclipse ### 16 | .apt_generated 17 | .classpath 18 | .factorypath 19 | .project 20 | .settings 21 | .springBeans 22 | .sts4-cache 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | 37 | ### Mac OS ### 38 | .DS_Store -------------------------------------------------------------------------------- /spring-hot-plugin-maven/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | vip.aliali.spring 8 | spring-hot-plugin-parent 9 | 1.1.1 10 | 11 | 12 | spring-hot-plugin-maven 13 | maven-plugin 14 | 插件Maven打包组件 15 | 16 | 17 | 8 18 | 8 19 | UTF-8 20 | 3.9.8 21 | 3.4.0 22 | 3.6.2 23 | 3.9.8 24 | 3.2 25 | 26 | 27 | 28 | 29 | 30 | org.apache.maven 31 | maven-plugin-api 32 | ${maven-plugin-api.version} 33 | 34 | 35 | 36 | org.apache.maven.shared 37 | maven-common-artifact-filters 38 | ${maven-common-artifact-filters.version} 39 | 40 | 41 | 42 | 43 | org.apache.maven 44 | maven-archiver 45 | ${maven-archiver.version} 46 | 47 | 48 | 49 | org.apache.maven 50 | maven-core 51 | ${maven-core.version} 52 | 53 | 54 | 55 | org.apache.maven.plugin-tools 56 | maven-plugin-annotations 57 | ${maven-plugin-annotations.version} 58 | provided 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | org.apache.maven.plugins 67 | maven-plugin-plugin 68 | 3.13.1 69 | 70 | repackage 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /spring-hot-plugin-maven/src/main/java/vip/aliali/spring/plugin/maven/PackageConstants.java: -------------------------------------------------------------------------------- 1 | package vip.aliali.spring.plugin.maven; 2 | 3 | public class PackageConstants { 4 | 5 | public static final String CLASS_DIR = "classes"; 6 | 7 | public static final String LIB_DIR = "lib"; 8 | 9 | public static final String PLUGIN_ID = "pluginId"; 10 | 11 | public static final String PLUGIN_VERSION = "pluginVersion"; 12 | 13 | public static final String PLUGIN_DES = "pluginDescription"; 14 | 15 | public static final String DEVELOPER_NAME = "developers"; 16 | } 17 | -------------------------------------------------------------------------------- /spring-hot-plugin-maven/src/main/java/vip/aliali/spring/plugin/maven/PackagePlugin.java: -------------------------------------------------------------------------------- 1 | package vip.aliali.spring.plugin.maven; 2 | 3 | import org.apache.commons.io.FileUtils; 4 | import org.apache.commons.io.filefilter.TrueFileFilter; 5 | import org.apache.commons.lang3.StringUtils; 6 | import org.apache.maven.archiver.MavenArchiveConfiguration; 7 | import org.apache.maven.archiver.MavenArchiver; 8 | import org.apache.maven.artifact.Artifact; 9 | import org.apache.maven.artifact.DependencyResolutionRequiredException; 10 | import org.apache.maven.execution.MavenSession; 11 | import org.apache.maven.plugin.AbstractMojo; 12 | import org.apache.maven.plugin.MojoExecutionException; 13 | import org.apache.maven.plugin.MojoFailureException; 14 | import org.apache.maven.plugins.annotations.Component; 15 | import org.apache.maven.plugins.annotations.Mojo; 16 | import org.apache.maven.plugins.annotations.Parameter; 17 | import org.apache.maven.project.MavenProject; 18 | import org.apache.maven.shared.artifact.filter.ScopeArtifactFilter; 19 | import org.codehaus.plexus.archiver.jar.JarArchiver; 20 | import org.codehaus.plexus.archiver.jar.ManifestException; 21 | 22 | import java.io.File; 23 | import java.io.IOException; 24 | import java.util.Collection; 25 | import java.util.Set; 26 | 27 | 28 | /** 29 | * 插件打包工具 30 | */ 31 | @Mojo(name = "repackage") 32 | public class PackagePlugin extends AbstractMojo { 33 | 34 | @Parameter(defaultValue = "${project.build.directory}/classes", readonly = true) 35 | private File classesDir; 36 | 37 | @Parameter(defaultValue = "${project.build.directory}/${project.build.finalName}-repackage.jar", readonly = true) 38 | private File jarFile; 39 | 40 | @Parameter(defaultValue = "${project}", readonly = true) 41 | private MavenProject project; 42 | 43 | @Component 44 | private MavenSession mavenSession; 45 | 46 | 47 | 48 | @Override 49 | public void execute() throws MojoExecutionException, MojoFailureException { 50 | //打包 51 | try { 52 | packageJar(); 53 | } catch (Exception ex) { 54 | getLog().error(ex); 55 | } 56 | } 57 | 58 | private void packageJar() throws DependencyResolutionRequiredException, IOException, ManifestException { 59 | MavenArchiver mavenArchiver = createJarPackage(); 60 | addClasses(mavenArchiver); 61 | addArtifacts(mavenArchiver); 62 | writeJarPackage(mavenArchiver); 63 | } 64 | 65 | private void writeJarPackage(MavenArchiver mavenArchiver) throws DependencyResolutionRequiredException, IOException, ManifestException { 66 | mavenArchiver.createArchive(mavenSession, project, getJarConfiguration()); 67 | } 68 | 69 | private void addClasses(MavenArchiver mavenArchiver) { 70 | getLog().info("Start packing the class files......"); 71 | 72 | Collection classesList = FileUtils.listFiles(classesDir, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE); 73 | for (File file : classesList) { 74 | mavenArchiver.getArchiver().addFile(file, PackageConstants.CLASS_DIR + getRelativeFilePath(file)); 75 | } 76 | } 77 | 78 | private String getRelativeFilePath(File file) { 79 | return StringUtils.substringAfter(file.getAbsolutePath(), PackageConstants.CLASS_DIR); 80 | } 81 | 82 | private void addArtifacts(MavenArchiver mavenArchiver) { 83 | getLog().info("Start packing the artifacts files......"); 84 | 85 | //依赖 86 | Set artifacts = filterArtifacts(); 87 | for (Artifact artifact : artifacts) { 88 | mavenArchiver.getArchiver().addFile(artifact.getFile(), PackageConstants.LIB_DIR + File.separator + artifact.getFile().getName()); 89 | } 90 | } 91 | 92 | private MavenArchiveConfiguration getJarConfiguration() { 93 | getLog().info("Start packing the Manifest file......"); 94 | 95 | MavenArchiveConfiguration jarConfiguration = new MavenArchiveConfiguration(); 96 | jarConfiguration.addManifestEntry(PackageConstants.PLUGIN_ID, project.getModel().getArtifactId()); 97 | jarConfiguration.addManifestEntry(PackageConstants.PLUGIN_VERSION, project.getModel().getVersion()); 98 | jarConfiguration.addManifestEntry(PackageConstants.PLUGIN_DES, project.getModel().getDescription()); 99 | jarConfiguration.addManifestEntry(PackageConstants.DEVELOPER_NAME, String.join(";", project.getModel().getDevelopers().toArray(new String[0]))); 100 | 101 | jarConfiguration.setCompress(false); 102 | return jarConfiguration; 103 | } 104 | 105 | private MavenArchiver createJarPackage() { 106 | MavenArchiver mavenArchiver = new MavenArchiver(); 107 | mavenArchiver.setOutputFile(jarFile); 108 | mavenArchiver.setArchiver(new JarArchiver()); 109 | return mavenArchiver; 110 | } 111 | 112 | 113 | private Set filterArtifacts() { 114 | ScopeArtifactFilter scopeArtifactFilter = new ScopeArtifactFilter(); 115 | scopeArtifactFilter.setIncludeRuntimeScopeWithImplications(true); 116 | project.setArtifactFilter(scopeArtifactFilter); 117 | return project.getArtifacts(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /spring-hot-plugin-mybatis/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/modules.xml 8 | .idea/jarRepositories.xml 9 | .idea/compiler.xml 10 | .idea/libraries/ 11 | *.iws 12 | *.iml 13 | *.ipr 14 | 15 | ### Eclipse ### 16 | .apt_generated 17 | .classpath 18 | .factorypath 19 | .project 20 | .settings 21 | .springBeans 22 | .sts4-cache 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | 37 | ### Mac OS ### 38 | .DS_Store -------------------------------------------------------------------------------- /spring-hot-plugin-mybatis/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | vip.aliali.spring 8 | spring-hot-plugin-parent 9 | 1.1.1 10 | 11 | 12 | spring-hot-plugin-mybatis 13 | 插件Mybatis、MybatisPlus组件 14 | 15 | 16 | 8 17 | 8 18 | UTF-8 19 | 3.5.7 20 | 21 | 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter-web 26 | provided 27 | true 28 | 29 | 30 | 31 | org.mybatis.spring.boot 32 | mybatis-spring-boot-starter 33 | ${mybatis.version} 34 | provided 35 | true 36 | 37 | 38 | 39 | com.baomidou 40 | mybatis-plus-core 41 | ${mp.version} 42 | true 43 | 44 | 45 | 46 | com.baomidou 47 | mybatis-plus-extension 48 | ${mp.version} 49 | true 50 | 51 | 52 | 53 | vip.aliali.spring 54 | spring-hot-plugin-core 55 | ${project.version} 56 | provided 57 | 58 | 59 | 60 | vip.aliali.spring 61 | spring-hot-plugin-common 62 | ${project.version} 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /spring-hot-plugin-mybatis/src/main/java/vip/aliali/spring/plugin/mybatis/MybatisPluginConfiguration.java: -------------------------------------------------------------------------------- 1 | package vip.aliali.spring.plugin.mybatis; 2 | 3 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | @Configuration 9 | public class MybatisPluginConfiguration { 10 | 11 | @Bean("registerMybatis") 12 | @ConditionalOnClass(name = "org.apache.ibatis.session.SqlSession") 13 | public RegisterMybatis createRegisterMybatis(ApplicationContext main) { 14 | return new RegisterMybatis(main); 15 | } 16 | 17 | /** 18 | * 用来处理MybatisPlus 19 | * @return MybatisPlus的处理工具 20 | */ 21 | @Bean("mybatisPlusUtil") 22 | @ConditionalOnClass(name = "com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration") 23 | public MybatisPlusUtil createMybatisPluginUtil() { 24 | return new MybatisPlusUtil(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spring-hot-plugin-mybatis/src/main/java/vip/aliali/spring/plugin/mybatis/MybatisPlusUtil.java: -------------------------------------------------------------------------------- 1 | package vip.aliali.spring.plugin.mybatis; 2 | 3 | import cn.hutool.core.util.ReflectUtil; 4 | import com.baomidou.mybatisplus.core.MybatisConfiguration; 5 | import com.baomidou.mybatisplus.core.MybatisMapperRegistry; 6 | import com.baomidou.mybatisplus.core.mapper.Mapper; 7 | import com.baomidou.mybatisplus.core.metadata.TableInfo; 8 | import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; 9 | import com.baomidou.mybatisplus.core.toolkit.GlobalConfigUtils; 10 | import com.baomidou.mybatisplus.core.toolkit.ReflectionKit; 11 | import com.baomidou.mybatisplus.core.toolkit.StringPool; 12 | import com.baomidou.mybatisplus.extension.toolkit.SqlHelper; 13 | import org.apache.ibatis.mapping.MappedStatement; 14 | import org.apache.ibatis.mapping.ResultMap; 15 | import org.apache.ibatis.session.Configuration; 16 | import org.mybatis.spring.mapper.MapperFactoryBean; 17 | import org.springframework.context.ApplicationContext; 18 | 19 | import java.util.Map; 20 | import java.util.Set; 21 | import java.util.stream.Collectors; 22 | 23 | /** 24 | * 用来处理MybatisPlus的工具类 25 | * @author 阿提说说 26 | */ 27 | public class MybatisPlusUtil { 28 | 29 | 30 | public void setMPConfiguration(Configuration configuration) { 31 | if (configuration instanceof MybatisConfiguration) { 32 | MybatisConfiguration mpConfiguration = (MybatisConfiguration) configuration; 33 | //关闭mappedStatement的短key缓存生成 34 | mpConfiguration.setUseGeneratedShortKey(false); 35 | } 36 | } 37 | 38 | public void clearMPQuote(Configuration configuration, ApplicationContext plugin) { 39 | //处理使用MP时候的引用 40 | if (configuration instanceof MybatisConfiguration) { 41 | MybatisConfiguration mpConfiguration = (MybatisConfiguration) configuration; 42 | //当前插件加载的mapper 43 | Map mapperFactoryBeanMap = plugin.getBeansOfType(MapperFactoryBean.class); 44 | Set mapperRegistryCache = GlobalConfigUtils.getGlobalConfig(mpConfiguration).getMapperRegistryCache(); 45 | for (MapperFactoryBean mapperFactoryBean : mapperFactoryBeanMap.values()) { 46 | Class mapper = mapperFactoryBean.getMapperInterface(); 47 | 48 | //清除MP没有清除掉的引用缓存 49 | Class modelClass = ReflectionKit.getSuperClassGenericType(mapper, Mapper.class, 0); 50 | TableInfo tableInfo = TableInfoHelper.getTableInfo(modelClass); 51 | //null 表示已经清理过 52 | if (tableInfo != null) { 53 | //清除Model 的Class引用缓存 54 | ((Map) ReflectUtil.getFieldValue(mpConfiguration.getReflectorFactory(), "reflectorMap")).remove(modelClass); 55 | ((Map)ReflectUtil.getFieldValue(ReflectionKit.class, "CLASS_FIELD_CACHE")).remove(modelClass); 56 | ((Map)ReflectUtil.getFieldValue(TableInfoHelper.class, "TABLE_NAME_INFO_CACHE")).remove(tableInfo.getTableName()); 57 | //删除类型转换处理器中的引用 58 | ((Map)ReflectUtil.getFieldValue(configuration.getTypeHandlerRegistry(), "typeHandlerMap")).remove(modelClass); 59 | // 清空实体表信息映射信息 60 | TableInfoHelper.remove(modelClass); 61 | } 62 | 63 | // 清空 Mapper 缓存信息 64 | final String mapperType = mapper.toString(); 65 | MybatisMapperRegistry mapperRegistry = ((MybatisMapperRegistry) ReflectUtil.getFieldValue(mpConfiguration, "mybatisMapperRegistry")); 66 | ReflectUtil.invoke(mapperRegistry, "removeMapper", mapper); 67 | //删除MappedStatement缓存 68 | Map mappedStatements = ((Map) ReflectUtil.getFieldValue(mpConfiguration, "mappedStatements")); 69 | final String typeKey = mapper.getName() + StringPool.DOT; 70 | Set mapperSet = mappedStatements.keySet().stream().filter(ms -> ms.startsWith(typeKey)).collect(Collectors.toSet()); 71 | if (!mapperSet.isEmpty()) { 72 | mapperSet.forEach(mappedStatements::remove); 73 | } 74 | //删除ResultMap缓存 75 | Map resultMaps = ((Map)ReflectUtil.getFieldValue(mpConfiguration, "resultMaps")); 76 | Set resultMapSet = resultMaps.keySet().stream().filter(ms -> ms.startsWith(typeKey)).collect(Collectors.toSet()); 77 | if (!resultMapSet.isEmpty()) { 78 | resultMapSet.forEach(resultMaps::remove); 79 | } 80 | //删除MP的CRUD的Mapper缓存 81 | mapperRegistryCache.remove(mapperType); 82 | 83 | //重置资源加载标识 84 | Set loadedResources = ((Set)ReflectUtil.getFieldValue(configuration, "loadedResources")); 85 | String xmlResource = getXmlResource(mapper.getName()); 86 | loadedResources.remove(xmlResource); 87 | loadedResources.remove(mapper.toString()); 88 | } 89 | 90 | SqlHelper.FACTORY = null; 91 | } 92 | } 93 | 94 | private String getXmlResource(String name) { 95 | return name.replace('.', '/') + ".xml"; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /spring-hot-plugin-mybatis/src/main/java/vip/aliali/spring/plugin/mybatis/util/MapperUtils.java: -------------------------------------------------------------------------------- 1 | package vip.aliali.spring.plugin.mybatis.util; 2 | 3 | import cn.hutool.core.io.FileUtil; 4 | import cn.hutool.core.text.CharSequenceUtil; 5 | import vip.aliali.spring.plugin.constants.PluginConstants; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.apache.ibatis.annotations.Mapper; 8 | import org.springframework.core.annotation.AnnotatedElementUtils; 9 | 10 | import java.io.File; 11 | import java.io.InputStream; 12 | import java.util.Enumeration; 13 | import java.util.HashSet; 14 | import java.util.List; 15 | import java.util.Set; 16 | import java.util.jar.JarEntry; 17 | import java.util.jar.JarFile; 18 | 19 | //暂时无用 20 | @Slf4j 21 | public class MapperUtils { 22 | 23 | /** 24 | * 读取jar包中所有类文件 25 | */ 26 | public static Set readJarFile(String jarAddress) { 27 | Set classNameSet = new HashSet<>(); 28 | 29 | try(JarFile jarFile = new JarFile(jarAddress)) { 30 | Enumeration entries = jarFile.entries();//遍历整个jar文件 31 | while (entries.hasMoreElements()) { 32 | JarEntry jarEntry = entries.nextElement(); 33 | String name = jarEntry.getName(); 34 | //读取非依赖包的类文件 35 | if (!name.startsWith(PluginConstants.LIB) && name.endsWith(PluginConstants.CLASS_SUFFIX)) { 36 | String className = name.replaceFirst(PluginConstants.CLASSES + "/", "").replace(PluginConstants.CLASS_SUFFIX, "").replaceAll("/", "."); 37 | // String className = CharSequenceUtil.sub(name, name.lastIndexOf(PluginConstants.JAR_File_DELIMITER), name.indexOf(PluginConstants.CLASS_SUFFIX)).replace(PluginConstants.JAR_File_DELIMITER, ""); 38 | classNameSet.add(className); 39 | } 40 | } 41 | } catch (Exception e) { 42 | log.warn("加载jar包失败", e); 43 | } 44 | return classNameSet; 45 | } 46 | 47 | public static InputStream readManifestJarFile(File jarAddress) { 48 | try { 49 | JarFile jarFile = new JarFile(jarAddress); 50 | //遍历整个jar文件 51 | Enumeration entries = jarFile.entries(); 52 | while (entries.hasMoreElements()) { 53 | JarEntry jarEntry = entries.nextElement(); 54 | String name = jarEntry.getName(); 55 | if (name.contains(PluginConstants.MANIFEST)) { 56 | return jarFile.getInputStream(jarEntry); 57 | } 58 | } 59 | } catch (Exception e) { 60 | log.warn("加载jar包失败", e); 61 | } 62 | return null; 63 | } 64 | 65 | public static boolean isMapper(Class beanType) { 66 | return AnnotatedElementUtils.hasAnnotation(beanType, Mapper.class); 67 | } 68 | 69 | /** 70 | * 类名首字母小写 作为spring容器beanMap的key 71 | */ 72 | public static String transformName(String className) { 73 | String tmpstr = className.substring(className.lastIndexOf(".") + 1); 74 | return tmpstr.substring(0, 1).toLowerCase() + tmpstr.substring(1); 75 | } 76 | 77 | /** 78 | * 读取class文件 79 | * @param path 80 | * @return 81 | */ 82 | public static Set readClassFile(String path) { 83 | if (path.endsWith(PluginConstants.JAR_SUFFIX)) { 84 | return readJarFile(path); 85 | } else { 86 | List pomFiles = FileUtil.loopFiles(path, file -> file.getName().endsWith(PluginConstants.CLASS_SUFFIX)); 87 | Set classNameSet = new HashSet<>(); 88 | for (File file : pomFiles) { 89 | String name = file.getPath(); 90 | //读取非依赖包的类文件 91 | if (!name.contains(PluginConstants.CLASSES + File.separator + PluginConstants.LIB)) { 92 | String className = CharSequenceUtil.subBetween(name, PluginConstants.CLASSES + File.separator, PluginConstants.CLASS_SUFFIX).replace(File.separator, "."); 93 | classNameSet.add(className); 94 | } 95 | } 96 | return classNameSet; 97 | } 98 | } 99 | 100 | 101 | } 102 | 103 | -------------------------------------------------------------------------------- /spring-hot-plugin-mybatis/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | vip.aliali.spring.plugin.mybatis.MybatisPluginConfiguration --------------------------------------------------------------------------------