# 动态编译工具 **Repository Path**: it168man/loader-util ## Basic Information - **Project Name**: 动态编译工具 - **Description**: 在应用运行期动态加载类、bean、rest、切面的工具 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 24 - **Created**: 2024-09-04 - **Last Updated**: 2024-09-04 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # loader-util 动态编译、加载、执行工具 [![](https://jitpack.io/v/com.gitee.wb04307201/loader-util.svg)](https://jitpack.io/#com.gitee.wb04307201/loader-util) [![star](https://gitee.com/wb04307201/loader-util/badge/star.svg?theme=dark)](https://gitee.com/wb04307201/loader-util) [![fork](https://gitee.com/wb04307201/loader-util/badge/fork.svg?theme=dark)](https://gitee.com/wb04307201/loader-util) [![star](https://img.shields.io/github/stars/wb04307201/loader-util)](https://github.com/wb04307201/loader-util) [![fork](https://img.shields.io/github/forks/wb04307201/loader-util)](https://github.com/wb04307201/loader-util) ![MIT](https://img.shields.io/badge/License-Apache2.0-blue.svg) ![JDK](https://img.shields.io/badge/JDK-17+-green.svg) ![SpringBoot](https://img.shields.io/badge/Srping%20Boot-3+-green.svg) > 在应用运行期动态编译加载类、bean、rest、切面的工具 > > 重构了代码逻辑,将class、bean、rest合成到LoaderUtils中, > 并增加DynamicClassLoader的单例模式、增加DynamicClassLoader的动态类缓存区, > 缓存到内存的动态类可以在任何地方取出并执行 ## 代码示例 1. 使用[动态编译工具](https://gitee.com/wb04307201/loader-util)实现的[动态编译工具工具示例代码](https://gitee.com/wb04307201/loader-util-test) 2. 使用[动态调度](https://gitee.com/wb04307201/dynamic-schedule-spring-boot-starter)、[消息中间件](https://gitee.com/wb04307201/message-spring-boot-starter)、[动态编译工具](https://gitee.com/wb04307201/loader-util)、[实体SQL工具](https://gitee.com/wb04307201/sql-util)实现的[在线编码、动态调度、发送钉钉群消息、快速构造web页面Demo](https://gitee.com/wb04307201/dynamic-schedule-demo) ## 快速开始 ### 引入依赖 增加 JitPack 仓库 ```xml jitpack.io https://jitpack.io ``` 1.1.0版本后升级到jdk17 SpringBoot3+ 1.2.0重构核心代码 继续使用jdk 8请查看jdk8分支 ```xml com.gitee.wb04307201 loader-util 1.2.0 ``` ### 使用 #### 编译Class并执行 ```java void testClass() { String javaSourceCode = """ package cn.wubo.loader.util; public class TestClass { public String testMethod(String name){ return String.format("Hello,%s!",name); } } """; LoaderUtils.compiler(javaSourceCode, "cn.wubo.loader.util.TestClass"); Class clazz = LoaderUtils.load("cn.wubo.loader.util.TestClass"); String str = (String) MethodUtils.invokeClass(clazz, "testMethod", "world"); } //注意:如果重复编译同样的类,会发生异常,如果确实需要这种场景请使用LoaderUtils.compilerOnce //也可以使用LoaderUtils.clear方法关闭旧的DynamicClassLoader单例后重新编译 // 通过LoaderUtils.compiler编译的类会缓存到内存中,可以在其他方法中获得 void testClassDelay() { Class clazz = LoaderUtils.load("cn.wubo.loader.util.TestClass"); String str = (String) MethodUtils.invokeClass(clazz, "testMethod", "world"); } //如果不想将编译的类会缓存到内存,请使用LoaderUtils.compilerOnce方法 void testClassOnce() { String javaSourceCode = """ package cn.wubo.loader.util; public class TestClass7 { public String testMethod(String name){ return String.format("Hello,%s!",name); } } """; Class clazz = LoaderUtils.compilerOnce(javaSourceCode, "cn.wubo.loader.util.TestClass7"); String str = (String) MethodUtils.invokeClass(clazz, "testMethod", "world"); } ``` #### 加载外部jar并执行 ```java void testJarClass() { LoaderUtils.addJarPath("./hutool-all-5.8.29.jar"); Class clazz = LoaderUtils.load("cn.hutool.core.util.IdUtil"); String str = (String) MethodUtils.invokeClass(clazz, "randomUUID"); } ``` #### 编译Class并加载到Bean > 使用DynamicBean需要配置@ComponentScan,包括cn.wubo.loader.util.SpringContextUtils文件 ```java void testBean() { String javaSourceCode = """ package cn.wubo.loader.util; public class TestClass2 { public String testMethod(String name){ return String.format("Hello,%s!",name); } } """; LoaderUtils.compiler(javaSourceCode, "cn.wubo.loader.util.TestClass2"); Class clazz = LoaderUtils.load("cn.wubo.loader.util.TestClass2"); String beanName = LoaderUtils.registerSingleton(clazz); String str = MethodUtils.invokeBean(beanName, "testMethod", "world"); } ``` #### 5. DynamicController 动态编译加载Controller并执行 ```java public void loadController() { String fullClassName = "cn.wubo.loaderutiltest.DemoController"; String javaSourceCode = """ package cn.wubo.loaderutiltest; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping(value = "test") public class DemoController { @GetMapping(value = "hello") public String hello(@RequestParam(value = "name") String name) { return String.format("Hello,%s!",name); } } """; LoaderUtils.compiler(javaSourceCode, "cn.wubo.loaderutiltest.DemoController"); Class clazz = LoaderUtils.load("cn.wubo.loaderutiltest.DemoController"); String beanName = LoaderUtils.registerController(clazz); } ``` ```http request GET http://localhost:8080/test/hello?name=world Accept: application/json Hello,world! ``` #### 动态增加切面代理 ```java void testAspect() { String javaSourceCode = """ package cn.wubo.loader.util; public class TestClass6 { public String testMethod(String name){ return String.format("Hello,%s!",name); } } """; LoaderUtils.compiler(javaSourceCode, "cn.wubo.loader.util.TestClass6"); Class clazz = LoaderUtils.load("cn.wubo.loader.util.TestClass6"); try { Object obj = MethodUtils.proxy(clazz.newInstance()); String str = MethodUtils.invokeClass(obj, "testMethod", "world"); } catch (InstantiationException | IllegalAccessException e) { throw new RuntimeException(e); } } ``` 输出示例 ```text 2023-04-08 21:22:14.174 INFO 32660 --- [nio-8080-exec-1] cn.wubo.loader.util.aspect.SimpleAspect : SimpleAspect before cn.wubo.loader.util.TestClass testMethod 2023-04-08 21:22:14.175 INFO 32660 --- [nio-8080-exec-1] cn.wubo.loader.util.aspect.SimpleAspect : SimpleAspect after cn.wubo.loader.util.TestClass testMethod 2023-04-08 21:22:14.175 INFO 32660 --- [nio-8080-exec-1] cn.wubo.loader.util.aspect.SimpleAspect : StopWatch 'cn.wubo.loader.util.TestClass testMethod': running time = 65800 ns ``` 可以通过继承IAspect接口实现自定义切面,并通过MethodUtils.proxy(Class clazz, Class aspectClass)方法调用切面 ## 如何在服务器上运行 因为本地和服务器的差异导致classpath路径不同, 进而使服务上动态编译class时会发生找不到import类的异常, 因此需要对maven编译配置和启动命令做出一定的修改 ### 1. maven编译配置增加如下部分 ```xml org.apache.maven.plugins maven-jar-plugin true lib/ cn.wubo.loaderutiltest.LoaderUtilTestApplication org.apache.maven.plugins maven-dependency-plugin copy-dependencies package copy-dependencies ${project.build.directory}/lib false false runtime ``` ### 2. 执行编译命令,会在jar包的同级目录下生成lib文件夹存放依赖包 ![img.png](img.png) ### 3. 将jar包和lib文件夹上到服务器,并在启动命令中增加`-Dloader.path=lib/` ```shell java -jar -Dloader.path=lib/ loader-util-test-0.0.1-SNAPSHOT.jar ``` ## 注意说明 ```text 如果编译报错: Can't initialize javac processor due to (most likely) a class loader problem: java.lang.NoClassDefFoundError: com/sun/tools/javac/processing/JavacProcessingEnvironment ``` #### 这是因为JAVA编译器是通过JavaFileManager来加载相关依赖类的,而JavaFileManager来自tools.jar。 解决办法: - **idea启动的话**,打开Project Strcutre,添加tools.jar ![img.png](img.png) - 服务器启动,跑jar包的时候需要加入`-Xbootclasspath/a:$toolspath/tools.jar`参数,nohup java -Xbootclasspath/a:$toolspath/tools.jar -jar loader-util-test-0.0.1-SNAPSHOT.jar > /dev/null 2>&1 &