diff --git a/README.md b/README.md index f10a45c42b60899565814ebad79b97a9614fee94..81ff26c44ed1afb6319d53a6da9aaf3bcd2c27a9 100644 --- a/README.md +++ b/README.md @@ -1,72 +1,42 @@ -# Loader Util - 动态类加载工具库 +# Dynamic Loader Utility 动态加载器工具包 -> 一个用于Java动态类加载、编译和Spring集成的工具库,支持运行时动态编译Java代码、加载类文件、注册Spring Bean等功能。 +> 一个用于动态加载和管理Java类的工具库,支持动态编译、AOP代理和Spring Bean管理功能。 -[![](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) +[![](https://jitpack.io/v/com.gitee.wb04307201/dynamic-loader-utility.svg)](https://jitpack.io/#com.gitee.wb04307201/dynamic-loader-utility) +[![star](https://gitee.com/wb04307201/dynamic-loader-utility/badge/star.svg?theme=dark)](https://gitee.com/wb04307201/dynamic-loader-utility) +[![fork](https://gitee.com/wb04307201/dynamic-loader-utility/badge/fork.svg?theme=dark)](https://gitee.com/wb04307201/dynamic-loader-utility) +[![star](https://img.shields.io/github/stars/wb04307201/dynamic-loader-utility)](https://github.com/wb04307201/dynamic-loader-utility) +[![fork](https://img.shields.io/github/forks/wb04307201/dynamic-loader-utility)](https://github.com/wb04307201/dynamic-loader-utility) ![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) -## 代码示例 -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) - ## 功能特性 -### 1. 动态编译Java代码 +### 1. 动态编译器 (`compiler`包) - 支持在运行时动态编译Java源代码 -- 编译结果存储在内存中,无需写入磁盘文件 -- 支持编译包含内部类的复杂Java代码 - -### 2. 动态类加载 -- 提供内存级类加载器 [DynamicClassLoader](src\main\java\cn\wubo\loader\util\class_loader\DynamicClassLoader.java#L16-L96) -- 支持从JAR文件加载类 -- 支持单例模式和一次性加载模式 - -### 3. Spring框架集成 -- 动态注册单例Bean到Spring上下文 -- 动态注册控制器(Controller)并自动映射请求路径 -- 支持Bean的动态注销和重新注册 - -### 4. 方法调用工具 -- 提供便捷的方法调用API -- 支持类级别和Bean级别的方法调用 -- 集成CGLIB代理和切面编程支持 -- -### 5. 切面编程支持 -- 内置简单切面实现,可记录方法执行时间 -- 支持自定义切面逻辑 -- 提供方法执行前、执行后和异常处理的拦截点 - -## 核心组件 - -### LoaderUtils -主要的工具类,提供以下功能: -- [compiler()](src\main\java\cn\wubo\loader\util\LoaderUtils.java#L23-L32): 编译Java源代码并加载到内存 -- [compilerOnce()](src\main\java\cn\wubo\loader\util\LoaderUtils.java#L45-L60): 一次性编译并加载Java代码 -- [load()](src\main\java\cn\wubo\loader\util\LoaderUtils.java#L68-L77): 加载已编译的类 -- [addJarPath()](src\main\java\cn\wubo\loader\util\LoaderUtils.java#L84-L92): 添加JAR文件到类路径 -- [registerSingleton()](src\main\java\cn\wubo\loader\util\LoaderUtils.java#L103-L114): 注册单例Bean -- [registerController()](src\main\java\cn\wubo\loader\util\LoaderUtils.java#L125-L136): 注册控制器 -- [clear()](src\main\java\cn\wubo\loader\util\LoaderUtils.java#L144-L146): 清理动态类加载器缓存 - -### MethodUtils -方法调用工具类,提供: -- [invokeClass()](src\main\java\cn\wubo\loader\util\MethodUtils.java#L28-L39): 调用类中的方法 -- [invokeBean()](src\main\java\cn\wubo\loader\util\MethodUtils.java#L74-L76): 调用Spring Bean中的方法 -- [proxy()](src\main\java\cn\wubo\loader\util\MethodUtils.java#L105-L107): 创建代理对象并应用切面 - -### SpringContextUtils -Spring上下文工具类,提供: -- Bean的注册和注销 -- 控制器的动态注册 -- Spring上下文访问功能 +- 内存中编译,无需生成.class文件 +- 支持自定义编译选项 +- 支持加载外部JAR包依赖 + +主要类: +- [DynamicCompiler](src\main\java\cn\wubo\dynamic\loader\utility\compiler\DynamicCompiler.java#L15-L139): 核心编译器,提供编译和加载功能 +- [CompilerOptions](src\main\java\cn\wubo\dynamic\loader\utility\compiler\CompilerOptions.java#L5-L51): 编译选项构建器 +- [ByteArrayClassLoader](src\main\java\cn\wubo\dynamic\loader\utility\compiler\ByteArrayClassLoader.java#L7-L51): 字节数组类加载器 + +### 2. AOP代理 ([aspect](src\main\java\cn\wubo\dynamic\loader\utility\aspect\AspectHandler.java#L11-L11)包) +基于CGLIB实现的面向切面编程支持: +- [IAspect](src\main\java\cn\wubo\dynamic\loader\utility\aspect\IAspect.java#L7-L36): 切面接口,定义前置、后置和异常处理方法 +- [SimpleAspect](src\main\java\cn\wubo\dynamic\loader\utility\aspect\SimpleAspect.java#L10-L44): 简单实现,提供方法执行时间统计 +- [DynamicAspect](src\main\java\cn\wubo\dynamic\loader\utility\aspect\DynamicAspect.java#L4-L26): 动态代理工厂类 +- [AspectHandler](src\main\java\cn\wubo\dynamic\loader\utility\aspect\AspectHandler.java#L8-L42): 方法拦截处理器 + +### 3. Spring Bean管理 (`bean`包) +提供对Spring容器中Bean的动态注册和注销功能: +- [DynamicBean](src\main\java\cn\wubo\dynamic\loader\utility\bean\DynamicBean.java#L15-L102): 动态Bean管理工具类 +- 支持控制器Bean的注册和注销 +- 支持请求映射的动态更新 ## 快速开始 -### 引入依赖 -增加 JitPack 仓库 +### 增加 JitPack 仓库 ```xml @@ -75,159 +45,52 @@ Spring上下文工具类,提供: ``` - -1.1.0版本后升级到jdk17 SpringBoot3+ -1.2.0重构核心代码 -继续使用jdk 8请查看jdk8分支 +### 引入依赖 ```xml com.gitee.wb04307201 - loader-util - 1.2.0 + dynamic-loader-utility + 1.2.1 ``` -### 使用 -#### 编译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"); +String sourceCode = "public class HelloWorld { public void sayHello() { System.out.println(\"Hello, World!\"); } }"; +try { + Class clazz = DynamicCompiler.compileAndLoad(sourceCode); + Object instance = clazz.newInstance(); + Method method = clazz.getMethod("sayHello"); + method.invoke(instance); +} catch (Exception e) { + e.printStackTrace(); } ``` -#### 5. DynamicController 动态编译加载Controller并执行 +### 使用AOP代理 ```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); -} +// 创建目标对象 +MyService target = new MyService(); +// 创建切面 +SimpleAspect aspect = new SimpleAspect(); +// 创建代理对象 +MyService proxy = DynamicAspect.proxy(target, aspect); +// 调用方法,将自动应用切面逻辑 +proxy.doSomething(); ``` -```http request -GET http://localhost:8080/test/hello?name=world -Accept: application/json -Hello,world! -``` -#### 动态增加切面代理 +### 动态Bean管理 ```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 +// 注册Bean +DynamicBean.registerSingleton(beanFactory, "myBean", MyBeanClass.class); +// 注销Bean +DynamicBean.unregisterSingleton(beanFactory, "myBean"); ``` -可以通过继承IAspect接口实现自定义切面,并通过MethodUtils.proxy(Class clazz, Class aspectClass)方法调用切面 - -## 如何在服务器上运行 +## 生产环境进行动态编译 因为本地和服务器的差异导致classpath路径不同, 进而使服务上动态编译class时会发生找不到import类的异常, 因此需要对maven编译配置和启动命令做出一定的修改 @@ -276,17 +139,3 @@ void testAspect() { ```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 & - diff --git a/hutool-all-5.8.29.jar b/hutool-all-5.8.29.jar deleted file mode 100644 index 13e345b80225592474f2fd0df95451aa9af6c48d..0000000000000000000000000000000000000000 Binary files a/hutool-all-5.8.29.jar and /dev/null differ diff --git a/img.png b/img.png deleted file mode 100644 index dc621159784662ddca8fcef9461e2dcc2c3b7ffd..0000000000000000000000000000000000000000 Binary files a/img.png and /dev/null differ diff --git a/pom.xml b/pom.xml index 46f7f95dd12f6189f31a73fbda38a182e9230e57..83560e013b1ea68b93816bd6c653de7f0a48cfbf 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.gitee.wb04307201 - loader-util + dynamic-loader-utility 1.0-SNAPSHOT @@ -19,7 +19,7 @@ org.springframework.boot spring-boot-dependencies - 3.3.1 + 3.5.3 pom import @@ -33,15 +33,15 @@ logback-classic 1.5.6 + + com.github.javaparser + javaparser-core + 3.27.0 + - - org.springframework.boot - spring-boot-autoconfigure - compile - org.springframework.boot spring-boot-starter-web @@ -54,17 +54,15 @@ ch.qos.logback logback-classic + + com.github.javaparser + javaparser-core + org.springframework.boot spring-boot-starter-test test - - org.apache.groovy - groovy - 4.0.21 - test - \ No newline at end of file diff --git a/src/main/java/cn/wubo/loader/util/aspect/AspectHandler.java b/src/main/java/cn/wubo/dynamic/loader/utility/aspect/AspectHandler.java similarity index 97% rename from src/main/java/cn/wubo/loader/util/aspect/AspectHandler.java rename to src/main/java/cn/wubo/dynamic/loader/utility/aspect/AspectHandler.java index b3bd20687966cfb803bb0bfdb4052155c1cdebdb..0c7e7af4fd83ac66259cf3c14addf2fed1e4f378 100644 --- a/src/main/java/cn/wubo/loader/util/aspect/AspectHandler.java +++ b/src/main/java/cn/wubo/dynamic/loader/utility/aspect/AspectHandler.java @@ -1,4 +1,4 @@ -package cn.wubo.loader.util.aspect; +package cn.wubo.dynamic.loader.utility.aspect; import org.springframework.cglib.proxy.MethodInterceptor; diff --git a/src/main/java/cn/wubo/dynamic/loader/utility/aspect/DynamicAspect.java b/src/main/java/cn/wubo/dynamic/loader/utility/aspect/DynamicAspect.java new file mode 100644 index 0000000000000000000000000000000000000000..95282c4eed751397fb4fbfc89d636f8338bc5f75 --- /dev/null +++ b/src/main/java/cn/wubo/dynamic/loader/utility/aspect/DynamicAspect.java @@ -0,0 +1,27 @@ +package cn.wubo.dynamic.loader.utility.aspect; + +import org.springframework.cglib.proxy.Enhancer; + +public class DynamicAspect { + + /** + * 创建代理对象,将目标对象和切面对象进行代理包装 + * + * @param 目标对象的类型 + * @param 切面对象的类型,必须实现IAspect接口 + * @param target 目标对象,需要被代理的实际对象 + * @param aspectTarget 切面对象,用于处理代理逻辑的对象 + * @return 返回代理后的对象,类型与目标对象相同 + */ + public static T proxy(T target, E aspectTarget) { + // 创建CGLIB增强器实例 + Enhancer enhancer = new Enhancer(); + // 设置代理对象的父类为target的类 + enhancer.setSuperclass(target.getClass()); + // 设置回调处理器,用于处理代理逻辑 + enhancer.setCallback(new AspectHandler(target, aspectTarget)); + // 创建并返回代理对象 + return (T) enhancer.create(); + } + +} diff --git a/src/main/java/cn/wubo/loader/util/aspect/IAspect.java b/src/main/java/cn/wubo/dynamic/loader/utility/aspect/IAspect.java similarity index 95% rename from src/main/java/cn/wubo/loader/util/aspect/IAspect.java rename to src/main/java/cn/wubo/dynamic/loader/utility/aspect/IAspect.java index bd8527aaf579bf154ce33000d4b34e0c40e9d64b..34edaac69e5fd03571d65da2c8db0dac86cba6e6 100644 --- a/src/main/java/cn/wubo/loader/util/aspect/IAspect.java +++ b/src/main/java/cn/wubo/dynamic/loader/utility/aspect/IAspect.java @@ -1,4 +1,4 @@ -package cn.wubo.loader.util.aspect; +package cn.wubo.dynamic.loader.utility.aspect; import java.lang.reflect.Method; diff --git a/src/main/java/cn/wubo/loader/util/aspect/SimpleAspect.java b/src/main/java/cn/wubo/dynamic/loader/utility/aspect/SimpleAspect.java similarity index 96% rename from src/main/java/cn/wubo/loader/util/aspect/SimpleAspect.java rename to src/main/java/cn/wubo/dynamic/loader/utility/aspect/SimpleAspect.java index 2976d2bf22c323160deb85569587100d26e8f097..c30ab29f3d942aa9f05efa008f5ae4dd91ec214f 100644 --- a/src/main/java/cn/wubo/loader/util/aspect/SimpleAspect.java +++ b/src/main/java/cn/wubo/dynamic/loader/utility/aspect/SimpleAspect.java @@ -1,4 +1,4 @@ -package cn.wubo.loader.util.aspect; +package cn.wubo.dynamic.loader.utility.aspect; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StopWatch; diff --git a/src/main/java/cn/wubo/dynamic/loader/utility/bean/DynamicBean.java b/src/main/java/cn/wubo/dynamic/loader/utility/bean/DynamicBean.java new file mode 100644 index 0000000000000000000000000000000000000000..2118c9b1f1c579e72baccbeaba136967e5400d17 --- /dev/null +++ b/src/main/java/cn/wubo/dynamic/loader/utility/bean/DynamicBean.java @@ -0,0 +1,103 @@ +package cn.wubo.dynamic.loader.utility.bean; + +import cn.wubo.dynamic.loader.utility.exception.BeanRuntimeException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class DynamicBean { + + /** + * 取消注册单例bean + * + * @param dbf Bean工厂实例,用于管理bean的注册和销毁 + * @param beanName 要取消注册的bean名称 + */ + public static void unregisterSingleton(DefaultListableBeanFactory dbf, String beanName) { + // 检查bean是否存在,如果存在则移除bean定义并销毁单例实例 + if (dbf.containsBean(beanName)) { + dbf.removeBeanDefinition(beanName); + dbf.destroySingleton(beanName); + } + } + + /** + * 向Spring容器中注册一个单例Bean定义 + * + * @param dbf Spring的Bean工厂,用于注册Bean定义 + * @param beanName 要注册的Bean的名称 + * @param type 要注册的Bean的类型 + */ + public static void registerSingleton(DefaultListableBeanFactory dbf, String beanName, Class type) { + // 创建Bean定义并设置相关属性 + GenericBeanDefinition patchBeanDefinition = new GenericBeanDefinition(); + patchBeanDefinition.setBeanClass(type); + patchBeanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON); + patchBeanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); + patchBeanDefinition.setPrimary(true); + + // 向Bean工厂注册Bean定义 + dbf.registerBeanDefinition(beanName, patchBeanDefinition); + } + + /** + * 从Spring MVC的RequestMappingHandlerMapping中注销指定控制器bean的请求映射 + * + * @param dbf Bean工厂实例,用于获取目标bean和RequestMappingHandlerMapping + * @param beanName 要注销的控制器bean名称 + */ + public static void unregisterController(DefaultListableBeanFactory dbf, String beanName) { + // 获取目标控制器对象 + Object targetObj = dbf.getBean(beanName); + // 获取Spring MVC的请求映射处理器映射器 + RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) dbf.getBean("requestMappingHandlerMapping"); + + // 遍历目标对象的所有方法,查找并注销对应的请求映射 + ReflectionUtils.doWithMethods(targetObj.getClass(), method -> { + Method mostSpecificMethod = ClassUtils.getMostSpecificMethod(method, targetObj.getClass()); + try { + // 通过反射获取RequestMappingHandlerMapping的getMappingForMethod方法 + Method declaredMethod = requestMappingHandlerMapping.getClass().getDeclaredMethod("getMappingForMethod", Method.class, Class.class); + declaredMethod.setAccessible(true); + // 调用getMappingForMethod方法获取请求映射信息 + RequestMappingInfo requestMappingInfo = (RequestMappingInfo) declaredMethod.invoke(requestMappingHandlerMapping, mostSpecificMethod, targetObj.getClass()); + // 如果存在请求映射信息,则将其从映射器中注销 + if (requestMappingInfo != null) requestMappingHandlerMapping.unregisterMapping(requestMappingInfo); + } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) { + throw new BeanRuntimeException(e.getMessage(), e); + } + }); + } + + /** + * 注销控制器Bean并重新检测处理器方法 + * + * @param dbf Bean工厂实例,用于获取和注册Bean + * @param beanName 要注销的Bean名称 + * @param type Bean的类型Class对象 + */ + public static void unregisterController(DefaultListableBeanFactory dbf, String beanName, Class type) { + // 重新注册单例Bean + registerSingleton(dbf, beanName, type); + + // 获取RequestMappingHandlerMapping实例,用于处理请求映射 + RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping) dbf.getBean("requestMappingHandlerMapping"); + try { + // 通过反射获取父类的detectHandlerMethods方法,用于重新检测处理器方法 + Method method = requestMappingHandlerMapping.getClass().getSuperclass().getSuperclass().getDeclaredMethod("detectHandlerMethods", Object.class); + method.setAccessible(true); + method.invoke(requestMappingHandlerMapping, beanName); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new BeanRuntimeException(e.getMessage(), e); + } + } + +} diff --git a/src/main/java/cn/wubo/dynamic/loader/utility/compiler/ByteArrayClassLoader.java b/src/main/java/cn/wubo/dynamic/loader/utility/compiler/ByteArrayClassLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..6b68a8913be85a088006949ad6896c5df805018c --- /dev/null +++ b/src/main/java/cn/wubo/dynamic/loader/utility/compiler/ByteArrayClassLoader.java @@ -0,0 +1,52 @@ +package cn.wubo.dynamic.loader.utility.compiler; + +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class ByteArrayClassLoader extends URLClassLoader { + + private static final Map classes = new ConcurrentHashMap<>(); + + public ByteArrayClassLoader(ClassLoader parent) { + super(new URL[0],parent); + } + + private static class SingletonHolder { + private static final ByteArrayClassLoader INSTANCE = + new ByteArrayClassLoader(Thread.currentThread().getContextClassLoader()); + } + + public static ByteArrayClassLoader getInstance() { + return SingletonHolder.INSTANCE; + } + + public void addClass(String name, byte[] bytes) { + classes.put(name, bytes); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + Class clazz = findLoadedClass(name); + if (clazz != null) { + return clazz; + } + + byte[] bytes = classes.get(name); + if (bytes != null) { + return defineClass(name, bytes, 0, bytes.length); + } + return super.findClass(name); + } + + // 提供清理方法 + public void clear() { + classes.clear(); + } + + @Override + public void addURL(URL url) { + super.addURL(url); + } +} diff --git a/src/main/java/cn/wubo/dynamic/loader/utility/compiler/CompilerOptions.java b/src/main/java/cn/wubo/dynamic/loader/utility/compiler/CompilerOptions.java new file mode 100644 index 0000000000000000000000000000000000000000..a1cfe299c8a81e2daed19598fc35a97020e67df2 --- /dev/null +++ b/src/main/java/cn/wubo/dynamic/loader/utility/compiler/CompilerOptions.java @@ -0,0 +1,52 @@ +package cn.wubo.dynamic.loader.utility.compiler; + +import java.util.ArrayList; +import java.util.List; + +public class CompilerOptions { + + private List options = new ArrayList<>(); + + public static CompilerOptions create() { + return new CompilerOptions(); + } + + public CompilerOptions sourceVersion(String version) { + options.add("-source"); + options.add(version); + return this; + } + + public CompilerOptions targetVersion(String version) { + options.add("-target"); + options.add(version); + return this; + } + + public CompilerOptions enableDebug() { + options.add("-g"); + return this; + } + + public CompilerOptions disableDebug() { + options.add("-g:none"); + return this; + } + + public CompilerOptions addOption(String option) { + options.add(option); + return this; + } + + public CompilerOptions addOptions(String... opts) { + for (String opt : opts) { + options.add(opt); + } + return this; + } + + public List build() { + return new ArrayList<>(options); + } + +} diff --git a/src/main/java/cn/wubo/dynamic/loader/utility/compiler/DynamicCompiler.java b/src/main/java/cn/wubo/dynamic/loader/utility/compiler/DynamicCompiler.java new file mode 100644 index 0000000000000000000000000000000000000000..a382fea41934d015f966ddf687bd4470ad7686c1 --- /dev/null +++ b/src/main/java/cn/wubo/dynamic/loader/utility/compiler/DynamicCompiler.java @@ -0,0 +1,140 @@ +package cn.wubo.dynamic.loader.utility.compiler; + +import cn.wubo.dynamic.loader.utility.exception.CompilerRuntimeException; +import com.github.javaparser.StaticJavaParser; +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.PackageDeclaration; + +import javax.tools.*; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +public class DynamicCompiler { + + /** + * 解析Java源代码中的类名(包括包名) + * + * @param sourceCode Java源代码字符串 + * @return 完整的类名(包含包名前缀),如果无包名则只返回类名 + * @throws IllegalArgumentException 当源代码中找不到类定义时抛出 + */ + public static String parseClassName(String sourceCode) { + // 解析源代码为编译单元 + CompilationUnit compilationUnit = StaticJavaParser.parse(sourceCode); + + // 获取包名,如果不存在则返回空字符串 + String packageName = compilationUnit.getPackageDeclaration() + .map(PackageDeclaration::getNameAsString) + .orElse(""); + + // 获取第一个类型声明的类名 + String className = compilationUnit.getTypes().stream() + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("No className found in sourceCode")) + .getNameAsString(); + + // 根据是否有包名返回完整的类名 + return packageName.isEmpty() ? className : packageName + "." + className; + } + + + /** + * 编译Java源代码 + * + * @param className 类名 + * @param sourceCode 源代码内容 + * @param options 编译选项列表 + * @throws IOException 当编译过程中发生IO异常时抛出 + */ + public static void compile(String className, String sourceCode, List options) throws IOException { + // 获取系统Java编译器 + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + if (compiler == null) { + throw new IllegalStateException("Failed to get Java compiler. Please ensure you are running in a JDK environment."); + } + + // 创建诊断收集器和编译单元 + DiagnosticCollector diagnostics = new DiagnosticCollector<>(); + + List compilationUnits = new ArrayList<>(); + compilationUnits.add(new MemoryJavaFileObject(className, sourceCode)); + + // 创建文件管理器和内存文件管理器 + JavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null); + MemoryFileManager memoryFileManager = new MemoryFileManager<>(fileManager); + + // 创建并执行编译任务 + JavaCompiler.CompilationTask task = compiler.getTask( + null, memoryFileManager, diagnostics, options, null, compilationUnits); + + boolean success = task.call(); + fileManager.close(); + + // 如果编译失败,收集错误信息并抛出运行时异常 + if (!success) { + String errorMessage = diagnostics.getDiagnostics().stream().map(Object::toString).reduce("", (acc, x) -> acc + "\r\n" + x); + throw new CompilerRuntimeException(errorMessage); + } + } + + + /** + * 加载指定类名的类 + * + * @param className 要加载的类的全限定名 + * @return 加载成功的Class对象 + * @throws ClassNotFoundException 当找不到指定类时抛出此异常 + */ + public static Class load(String className) throws ClassNotFoundException { + return ByteArrayClassLoader.getInstance().findClass(className); + } + + + /** + * 编译并加载Java源代码 + * + * @param sourceCode 要编译和加载的Java源代码字符串 + * @return 编译并加载后得到的Class对象 + * @throws IOException 当编译过程中发生IO错误时抛出 + * @throws ClassNotFoundException 当无法找到或加载编译后的类时抛出 + */ + public static Class compileAndLoad(String sourceCode) throws IOException, ClassNotFoundException { + return compileAndLoad(sourceCode, CompilerOptions.create()); + } + + + /** + * 编译并加载Java源代码 + * + * @param sourceCode 要编译的Java源代码字符串 + * @param options 编译器选项配置 + * @return 编译并加载成功的Class对象 + * @throws IOException 当编译过程中发生IO错误时抛出 + * @throws ClassNotFoundException 当无法找到或加载编译后的类时抛出 + */ + public static Class compileAndLoad(String sourceCode, CompilerOptions options) throws IOException, ClassNotFoundException { + String className = parseClassName(sourceCode); + compile(className, sourceCode, options.build()); + return load(className); + } + + + /** + * 添加JAR文件路径到类加载器中 + * + * @param jarPath JAR文件的路径字符串 + */ + public static void addJarPath(String jarPath) { + try { + // 将JAR文件路径转换为URL格式并添加到字节数组类加载器中 + ByteArrayClassLoader.getInstance().addURL(new URL("jar:file:" + new File(jarPath).getAbsolutePath() + "!/")); + } catch (MalformedURLException e) { + throw new CompilerRuntimeException(e.getMessage(), e); + } + } + +} diff --git a/src/main/java/cn/wubo/dynamic/loader/utility/compiler/MemoryClassFileObject.java b/src/main/java/cn/wubo/dynamic/loader/utility/compiler/MemoryClassFileObject.java new file mode 100644 index 0000000000000000000000000000000000000000..95ba34ad3c846779f29bb550e404e7de7945f88b --- /dev/null +++ b/src/main/java/cn/wubo/dynamic/loader/utility/compiler/MemoryClassFileObject.java @@ -0,0 +1,30 @@ +package cn.wubo.dynamic.loader.utility.compiler; + +import javax.tools.SimpleJavaFileObject; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; + +public class MemoryClassFileObject extends SimpleJavaFileObject{ + + private final String name; + + public MemoryClassFileObject(String name) { + super(URI.create("string:///" + name.replace('.', '/') + Kind.CLASS.extension), + Kind.CLASS); + this.name = name; + } + + @Override + public OutputStream openOutputStream() { + return new ByteArrayOutputStream() { + @Override + public void close() throws IOException { + super.close(); + ByteArrayClassLoader.getInstance().addClass(name, this.toByteArray()); + } + }; + } +} + diff --git a/src/main/java/cn/wubo/dynamic/loader/utility/compiler/MemoryFileManager.java b/src/main/java/cn/wubo/dynamic/loader/utility/compiler/MemoryFileManager.java new file mode 100644 index 0000000000000000000000000000000000000000..ba90bda4b6b3c76bfba6a8f765e85bb6b8610ea3 --- /dev/null +++ b/src/main/java/cn/wubo/dynamic/loader/utility/compiler/MemoryFileManager.java @@ -0,0 +1,23 @@ +package cn.wubo.dynamic.loader.utility.compiler; + +import javax.tools.FileObject; +import javax.tools.ForwardingJavaFileManager; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import java.io.IOException; + +public class MemoryFileManager extends ForwardingJavaFileManager { + + protected MemoryFileManager(T fileManager) { + super(fileManager); + } + + @Override + public JavaFileObject getJavaFileForOutput(Location location, String name, + JavaFileObject.Kind kind, FileObject sibling) throws IOException { + if (kind == JavaFileObject.Kind.CLASS) { + return new MemoryClassFileObject(name); + } + return super.getJavaFileForOutput(location, name, kind, sibling); + } +} diff --git a/src/main/java/cn/wubo/dynamic/loader/utility/compiler/MemoryJavaFileObject.java b/src/main/java/cn/wubo/dynamic/loader/utility/compiler/MemoryJavaFileObject.java new file mode 100644 index 0000000000000000000000000000000000000000..39f7d819d5cc34d5515bfaac4db10b2abbeba766 --- /dev/null +++ b/src/main/java/cn/wubo/dynamic/loader/utility/compiler/MemoryJavaFileObject.java @@ -0,0 +1,21 @@ +package cn.wubo.dynamic.loader.utility.compiler; + +import javax.tools.SimpleJavaFileObject; +import java.net.URI; + +public class MemoryJavaFileObject extends SimpleJavaFileObject{ + + private final String javaSourceCode; + + public MemoryJavaFileObject(String name, String javaSourceCode) { + super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), + Kind.SOURCE); + this.javaSourceCode = javaSourceCode; + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + return javaSourceCode; + } +} + diff --git a/src/main/java/cn/wubo/dynamic/loader/utility/exception/BeanRuntimeException.java b/src/main/java/cn/wubo/dynamic/loader/utility/exception/BeanRuntimeException.java new file mode 100644 index 0000000000000000000000000000000000000000..0b76e57e34b0163ecbe04f386398339bfa9b96df --- /dev/null +++ b/src/main/java/cn/wubo/dynamic/loader/utility/exception/BeanRuntimeException.java @@ -0,0 +1,8 @@ +package cn.wubo.dynamic.loader.utility.exception; + +public class BeanRuntimeException extends RuntimeException { + + public BeanRuntimeException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/cn/wubo/dynamic/loader/utility/exception/CompilerRuntimeException.java b/src/main/java/cn/wubo/dynamic/loader/utility/exception/CompilerRuntimeException.java new file mode 100644 index 0000000000000000000000000000000000000000..cbebdf902fd9fd46ac6e86598a1258306616bbca --- /dev/null +++ b/src/main/java/cn/wubo/dynamic/loader/utility/exception/CompilerRuntimeException.java @@ -0,0 +1,11 @@ +package cn.wubo.dynamic.loader.utility.exception; + +public class CompilerRuntimeException extends RuntimeException { + public CompilerRuntimeException(String message) { + super(message); + } + + public CompilerRuntimeException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/cn/wubo/loader/util/LoaderUtils.java b/src/main/java/cn/wubo/loader/util/LoaderUtils.java deleted file mode 100644 index 082977d0625706b374e11e537c915abbff440e91..0000000000000000000000000000000000000000 --- a/src/main/java/cn/wubo/loader/util/LoaderUtils.java +++ /dev/null @@ -1,149 +0,0 @@ -package cn.wubo.loader.util; - -import cn.wubo.loader.util.class_loader.DynamicClassLoader; -import cn.wubo.loader.util.class_loader.JavaBuilder; -import cn.wubo.loader.util.class_loader.JavaMemClass; -import cn.wubo.loader.util.exception.LoaderRuntimeException; -import lombok.extern.slf4j.Slf4j; - -import java.io.File; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Map; - -@Slf4j -public class LoaderUtils { - - /** - * 编译Java源代码并加载到内存中。 - * - * @param javaSourceCode Java源代码字符串。 - * @param fullClassName 完全限定类名。 - */ - public static void compiler(String javaSourceCode, String fullClassName) { - // 使用JavaBuilder编译Java源代码并获取内存中的类映射 - Map javaMemClassMap = JavaBuilder.builder().compiler(javaSourceCode, fullClassName).getJavaMemClassMap(); - // 获取动态类加载器实例 - DynamicClassLoader classLoader = DynamicClassLoader.getInstance(); - // 将编译后的类信息添加到类加载器中 - for (Map.Entry entry : javaMemClassMap.entrySet()) { - classLoader.addClassMap(entry.getKey(), entry.getValue().getBytes()); - } - } - - /** - * 动态编译并加载Java源代码。 - *

- * 该方法接收Java源代码和完整的类名作为参数,通过动态编译源代码并使用自定义类加载器加载生成的类,实现动态代码的执行。 - * 这种方式常用于运行时生成和执行代码,例如在某些框架中用于动态生成代理类。 - * - * @param javaSourceCode Java源代码字符串。 - * @param fullClassName 完整的类名,包括包名。 - * @return 编译并加载后的类的Class对象。 - * @throws LoaderRuntimeException 如果编译或加载过程中发生错误,将抛出此运行时异常。 - */ - public static Class compilerOnce(String javaSourceCode, String fullClassName) { - // 使用JavaBuilder编译Java源代码,生成内存中的类对象。 - Map javaMemClassMap = JavaBuilder.builder().compiler(javaSourceCode, fullClassName).getJavaMemClassMap(); - - try (DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(Thread.currentThread().getContextClassLoader())) { - // 将内存中的类信息添加到自定义类加载器的类映射中。 - for (Map.Entry entry : javaMemClassMap.entrySet()) { - dynamicClassLoader.addClassMap(entry.getKey(), entry.getValue().getBytes()); - } - // 使用自定义类加载器加载指定的类。 - return dynamicClassLoader.loadClass(fullClassName); - } catch (IOException | ClassNotFoundException e) { - // 抛出自定义异常,以更清晰地指示编译或加载过程中的错误。 - throw new LoaderRuntimeException(e.getMessage(), e); - } - } - - /** - * 通过类名加载类。 - * - * @param fullClassName 完全限定类名。 - * @return 加载的类。 - */ - public static Class load(String fullClassName) { - try { - // 获取动态类加载器实例并加载类 - DynamicClassLoader classLoader = DynamicClassLoader.getInstance(); - return classLoader.loadClass(fullClassName); - } catch (ClassNotFoundException e) { - // 抛出运行时异常,携带错误信息 - throw new LoaderRuntimeException(e.getMessage(), e); - } - } - - /** - * 将JAR路径添加到类路径中。 - * - * @param jarPath JAR文件路径。 - */ - public static void addJarPath(String jarPath) { - try { - // 获取动态类加载器实例并添加JAR路径 - DynamicClassLoader.getInstance().addURL(new URL("jar:file:" + new File(jarPath).getAbsolutePath() + "!/")); - } catch (MalformedURLException e) { - // 抛出运行时异常,携带错误信息 - throw new LoaderRuntimeException(e.getMessage(), e); - } - } - - /** - * 注册单例 bean 到 Spring 应用上下文中。 - * - * 此方法用于将指定的类作为单例 bean 注册到 Spring 应用上下文中。如果该类已经注册为 bean,则先销毁已存在的 bean 实例,然后重新注册。 - * 这确保了应用上下文中只有一个该类的实例存在。 - * - * @param clazz 要注册为单例 bean 的类。 - * @return 注册后的 bean 名称。 - */ - public static String registerSingleton(Class clazz) { - // 通过类名生成 bean 名称。 - String beanName = SpringContextUtils.beanName(clazz.getName()); - // 检查是否已存在同名的 bean。如果存在,则先销毁该 bean。 - if (Boolean.TRUE.equals(SpringContextUtils.containsBean(beanName))) { - SpringContextUtils.destroy(beanName); - } - // 注册指定类为单例 bean。 - SpringContextUtils.registerSingleton(beanName, clazz); - // 返回注册后的 bean 名称。 - return beanName; - } - - /** - * 注册控制器类到Spring上下文中。 - * - * 本方法用于将给定的控制器类注册到Spring应用程序上下文中。如果该类已经注册,则先注销原有的实例,再重新注册。 - * 这确保了Spring上下文中总是存在最新版本的控制器类实例。 - * - * @param clazz 待注册的控制器类。此参数不应为null。 - * @return 注册后的bean名称。如果给定的类已经被注册,则返回更新后的bean名称。 - */ - public static String registerController(Class clazz) { - // 通过类名生成bean名称 - String beanName = SpringContextUtils.beanName(clazz.getName()); - // 检查是否已经存在同名的bean - if (Boolean.TRUE.equals(SpringContextUtils.containsBean(beanName))) - // 如果存在,则先注销原有的bean - SpringContextUtils.unregisterController(beanName); - // 注册新的控制器类 - SpringContextUtils.registerController(beanName, clazz); - // 返回注册后的bean名称 - return beanName; - } - - /** - * 清理动态类加载器的缓存。 - * - * 该方法调用了DynamicClassLoader的clear方法,旨在清理动态加载的类,以释放内存资源。 - * 当系统不再需要这些动态加载的类,或者需要重新加载类时,可以调用此方法。 - */ - public static void clear() { - DynamicClassLoader.clear(); - } -} - diff --git a/src/main/java/cn/wubo/loader/util/MethodUtils.java b/src/main/java/cn/wubo/loader/util/MethodUtils.java deleted file mode 100644 index e3a4f45314fcdb2a8edcf2fb57f4ed3c4522f10c..0000000000000000000000000000000000000000 --- a/src/main/java/cn/wubo/loader/util/MethodUtils.java +++ /dev/null @@ -1,142 +0,0 @@ -package cn.wubo.loader.util; - -import cn.wubo.loader.util.aspect.AspectHandler; -import cn.wubo.loader.util.aspect.IAspect; -import cn.wubo.loader.util.aspect.SimpleAspect; -import cn.wubo.loader.util.exception.LoaderRuntimeException; -import org.springframework.cglib.proxy.Enhancer; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Arrays; - -/** - * 执行方法和代理切面工具类 - */ -public class MethodUtils { - - private MethodUtils() { - } - - /** - * 执行类中的方法 - * - * @param clazz 目标类 - * @param methodName 目标方法名 - * @param args 参数 - * @return 返回值,Object类型 - */ - public static Object invokeClass(Class clazz, String methodName, Object... args) { - try { - Object o = clazz.getConstructor().newInstance(); // 创建目标类的实例对象 - Class[] parameterTypes = Arrays.stream(args).map(Object::getClass).toList().toArray(new Class[]{}); // 获取参数的类型 - Method method = clazz.getDeclaredMethod(methodName, parameterTypes); // 获取目标方法 - method.setAccessible(true); // 设置方法可访问 - return method.invoke(o, args); // 调用目标方法并返回结果 - } catch (InstantiationException | IllegalAccessException | InvocationTargetException | - NoSuchMethodException e) { - throw new LoaderRuntimeException(e.getMessage(), e); // 抛出运行时异常 - } - } - - - /** - * 执行对象中的方法 - * - * @param target 目标对象 - * @param methodName 目标方法名 - * @param args 参数 - * @return 返回值,泛型 - */ - public static R invokeClass(T target, String methodName, Object... args) { - try { - // 获取参数类型数组 - Class[] parameterTypes = Arrays.stream(args).map(Object::getClass).toList().toArray(new Class[]{}); - // 获取目标对象对应方法的实例 - Method method = target.getClass().getDeclaredMethod(methodName, parameterTypes); - // 设置方法可访问性 - method.setAccessible(true); - // 调用目标方法并返回结果 - return (R) method.invoke(target, args); - } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { - throw new LoaderRuntimeException(e.getMessage(), e); - } - } - - - /** - * 执行Bean中的方法 - * - * @param beanName 目标bean名称 - * @param methodName 目标方法名 - * @param args 参数 - * @return 返回值,泛型 - */ - public static R invokeBean(String beanName, String methodName, Object... args) { - return (R) invokeBeanReturnObject(beanName, methodName, args); - } - - - /** - * 执行Bean中的方法 - * - * @param beanName 目标bean名称 - * @param methodName 目标方法名 - * @param args 参数 - * @return 返回值, Object类型 - */ - public static Object invokeBeanReturnObject(String beanName, String methodName, Object... args) { - try { - Object obj = SpringContextUtils.getBean(beanName); - Class[] parameterTypes = Arrays.stream(args).map(Object::getClass).toList().toArray(new Class[]{}); - Method method = obj.getClass().getMethod(methodName, parameterTypes); - method.setAccessible(true); - return method.invoke(obj, args); - } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { - throw new LoaderRuntimeException(e.getMessage(), e); - } - } - - /** - * 使用切面代理对象,默认使用SimpleAspect切面 - * - * @param target 目标对象 - * @return 被代理的切面,Object类型 - */ - public static T proxy(T target) { - return proxy(target, new SimpleAspect()); - } - - - /** - * 使用切面代理对象 - * - * @param target 目标对象 - * @param aspectClass 切面类 - * @return 被代理的切面,Object类型 - */ - public static T proxy(T target, Class aspectClass) { - try { - return proxy(target, aspectClass.getConstructor().newInstance()); - } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | - InvocationTargetException e) { - throw new LoaderRuntimeException(e.getMessage(), e); - } - } - - - /** - * 使用切面代理对象 - * - * @param target 目标对象 - * @param aspectTarget 切面对象 - * @return 被代理的切面 - */ - public static T proxy(T target, E aspectTarget) { - Enhancer enhancer = new Enhancer(); - enhancer.setSuperclass(target.getClass()); - enhancer.setCallback(new AspectHandler(target, aspectTarget)); - return (T) enhancer.create(); - } - -} diff --git a/src/main/java/cn/wubo/loader/util/SpringContextUtils.java b/src/main/java/cn/wubo/loader/util/SpringContextUtils.java deleted file mode 100644 index 50ddf180a26a15f9eaf3b6dac69b133af5ce2930..0000000000000000000000000000000000000000 --- a/src/main/java/cn/wubo/loader/util/SpringContextUtils.java +++ /dev/null @@ -1,194 +0,0 @@ -package cn.wubo.loader.util; - -import cn.wubo.loader.util.exception.LoaderRuntimeException; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.stereotype.Component; -import org.springframework.util.ClassUtils; -import org.springframework.util.ReflectionUtils; -import org.springframework.web.servlet.mvc.method.RequestMappingInfo; -import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Map; - -@Component(value = "loaderUtilSpringContextUtils") -public class SpringContextUtils implements BeanFactoryAware { - - private static DefaultListableBeanFactory listableBeanFactory; - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - listableBeanFactory = (DefaultListableBeanFactory) beanFactory; - } - - /** - * 注册一个单例bean。此方法为泛型方法,允许注册任何类型的单例对象。 - * 通过传入类类型,系统可以在运行时创建该类型的单例实例。 - * - * @param type 类类型,表示要注册为单例的类。 - * @param 类型参数,表示泛型类型。 - */ - public static void registerSingleton(Class type) { - // 使用类的名称作为bean的名称来注册单例 - registerSingleton(beanName(type.getName()), type); - } - - /** - * 注册一个单例对象到Spring Bean工厂。 - * - * 该方法通过以下步骤实现单例注册: - * 1. 使用提供的类型创建一个对象实例。 - * 2. 对该对象进行自动装配,使其能够与其他Spring管理的bean进行依赖注入。 - * 3. 将该对象注册为一个单例bean,使用提供的bean名称进行标识。 - * - * @param beanName 将要注册的bean的名称。 - * @param type bean的类型,用于创建对象实例。 - * @param 泛型参数,指定bean的类型。 - */ - public static void registerSingleton(String beanName, Class type) { - // 根据提供的类型创建bean实例。 - T obj = listableBeanFactory.createBean(type); - // 对创建的bean实例进行自动装配。 - listableBeanFactory.autowireBean(obj); - // 将装配好的bean实例注册为单例。 - listableBeanFactory.registerSingleton(beanName, obj); - } - - /** - * 注册控制器类并触发Spring MVC的映射处理方法。 - * - * 本方法主要用于在运行时动态注册控制器类,使得这些类能够被Spring MVC框架识别并处理相应的HTTP请求。 - * 通过调用registerSingleton方法将控制器类注册为Spring Bean,确保Spring容器能够管理这个类的实例。 - * 随后,通过获取RequestMappingHandlerMapping实例并调用其detectHandlerMethods方法,来触发Spring MVC对新注册控制器的映射处理。 - * 这一过程对于动态加载控制器类,例如在插件开发或者热部署场景中,是非常关键的。 - * - * @param beanName 控制器类在Spring容器中的Bean名称。 - * @param type 控制器类的类型。 - * @throws LoaderRuntimeException 如果在尝试检测处理器方法过程中发生异常,则抛出此运行时异常。 - * @param 控制器类的类型参数。 - */ - public static void registerController(String beanName, Class type) { - // 注册控制器类为Spring Bean。 - registerSingleton(beanName, type); - - // 获取RequestMappingHandlerMapping实例,用于处理请求映射。 - RequestMappingHandlerMapping requestMappingHandlerMapping = getBean("requestMappingHandlerMapping"); - - try { - // 通过反射获取父类的父类(即AbstractHandlerMethodMapping)中的detectHandlerMethods方法。 - // 这是因为RequestMappingHandlerMapping自身并没有提供公开的detectHandlerMethods方法。 - Method method = requestMappingHandlerMapping.getClass().getSuperclass().getSuperclass().getDeclaredMethod("detectHandlerMethods", Object.class); - // 设置方法可访问,以绕过Java的访问控制。 - method.setAccessible(true); - // 调用detectHandlerMethods方法,传入控制器Bean的名称,以触发映射处理。 - method.invoke(requestMappingHandlerMapping, beanName); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - // 如果在处理过程中发生异常,则抛出自定义的运行时异常。 - throw new LoaderRuntimeException(e.getMessage(), e); - } - } - - /** - * 取消注册一个控制器(bean)。 - * 该方法通过获取特定bean实例,分析并移除其上的@RequestMapping映射,从而实现控制器的注销功能。 - * 主要用于在运行时动态调整应用程序的路由配置。 - * - * @param beanName 需要注销的控制器bean的名称。 - */ - public static void unregisterController(String beanName) { - // 根据bean名称获取bean实例。 - Object obj = getBean(beanName); - // 获取bean实例的类类型。 - Class type = obj.getClass(); - - // 获取RequestMappingHandlerMapping的实例,用于处理@RequestMapping的映射注销。 - RequestMappingHandlerMapping requestMappingHandlerMapping = getBean("requestMappingHandlerMapping"); - - // 遍历类型上的所有方法,寻找并处理@RequestMapping注解的方法。 - ReflectionUtils.doWithMethods(type, method -> { - // 获取最具体的方法,用于处理重载方法的情况。 - Method mostSpecificMethod = ClassUtils.getMostSpecificMethod(method, type); - try { - // 获取RequestMappingHandlerMapping中用于获取方法映射的方法。 - Method declaredMethod = requestMappingHandlerMapping.getClass().getDeclaredMethod("getMappingForMethod", Method.class, Class.class); - // 设置该方法可访问,因为它是protected的。 - declaredMethod.setAccessible(true); - // 调用getMappingForMethod方法,获取对应方法的RequestMappingInfo对象。 - RequestMappingInfo requestMappingInfo = (RequestMappingInfo) declaredMethod.invoke(requestMappingHandlerMapping, mostSpecificMethod, type); - // 如果RequestMappingInfo不为空,则从映射中注销该方法。 - if (requestMappingInfo != null) requestMappingHandlerMapping.unregisterMapping(requestMappingInfo); - } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) { - // 抛出运行时异常,封装原始异常信息。 - throw new LoaderRuntimeException(e.getMessage(), e); - } - }); - // 销毁单例bean,确保它在容器中被重新创建而不是重新使用。 - listableBeanFactory.destroySingleton(beanName); - } - - /** - * 判断是否包含指定类型的bean - * - * @param type 要判断的bean类型 - * @return 如果包含指定类型的bean则返回true,否则返回false - */ - public static Boolean containsBean(Class type) { - return listableBeanFactory.containsBean(beanName(type.getName())); - } - - /** - * 判断给定的bean名称是否存在于listableBeanFactory中 - * - * @param beanName 要判断的bean名称 - * @return 如果bean名称存在于listableBeanFactory中,则返回true;否则返回false - */ - public static Boolean containsBean(String beanName) { - return listableBeanFactory.containsBean(beanName); - } - - /** - * 销毁指定名称的单例对象。 - * - * @param beanName 要销毁的单例对象的名称 - */ - public static void destroy(String beanName) { - listableBeanFactory.destroySingleton(beanName); - } - - /** - * 销毁指定类型的单例对象。 - * - * @param type 指定的类型 - */ - public static void destroy(Class type) { - listableBeanFactory.destroySingleton(beanName(type.getName())); - } - - /** - * 生成bean的名称 - * - * @param className 类名 - * @return bean的名称 - */ - public static String beanName(String className) { - String[] path = className.split("\\."); - String beanName = path[path.length - 1]; - return Character.toLowerCase(beanName.charAt(0)) + beanName.substring(1); - } - - public static T getBean(String name) { - return (T) listableBeanFactory.getBean(name); - } - - public static T getBean(Class type) { - return listableBeanFactory.getBean(type); - } - - public static Map getBeans(Class type) { - return listableBeanFactory.getBeansOfType(type); - } -} diff --git a/src/main/java/cn/wubo/loader/util/class_loader/DynamicClassLoader.java b/src/main/java/cn/wubo/loader/util/class_loader/DynamicClassLoader.java deleted file mode 100644 index 56b20bd60180b6dc3c3220f634777d4901d80cca..0000000000000000000000000000000000000000 --- a/src/main/java/cn/wubo/loader/util/class_loader/DynamicClassLoader.java +++ /dev/null @@ -1,98 +0,0 @@ -package cn.wubo.loader.util.class_loader; - -import cn.wubo.loader.util.exception.LoaderRuntimeException; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; - -import java.io.IOException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.HashMap; -import java.util.Map; - -/** - * 动态类加载器,扩展自URLClassLoader,支持动态加载类。 - * 通过将类的字节码与类名映射,实现动态加载和查找类的功能。 - */ -@Slf4j -public class DynamicClassLoader extends URLClassLoader { - - /** - * 类映射,存储类的完全限定名和对应的Class对象。 - */ - private Map> classMap = new HashMap<>(); - - /** - * 构造函数,使用当前线程的上下文类加载器作为父类加载器。 - * - * @param parent 父类加载器 - */ - public DynamicClassLoader(ClassLoader parent) { - super(new URL[0], parent); - } - - /** - * 单例模式,获取动态类加载器的实例。 - */ - @Getter - private static DynamicClassLoader instance = new DynamicClassLoader(Thread.currentThread().getContextClassLoader()); - - /** - * 清理并重新初始化动态类加载器。 - * 该方法旨在重新配置动态类加载器,以便它可以继续加载新的类,而不会受到之前加载类的影响。 - * 这是通过关闭当前的动态类加载器实例并创建一个新的实例来实现的。 - * 在这个过程中,它使用当前线程的上下文类加载器作为新实例的基础。 - * 如果在关闭当前实例或创建新实例的过程中发生IO异常,将会抛出一个LoaderRuntimeException。 - */ - public static void clear() { - try { - // 关闭当前的动态类加载器实例 - instance.close(); - // 创建一个新的动态类加载器实例,基于当前线程的上下文类加载器 - instance = new DynamicClassLoader(Thread.currentThread().getContextClassLoader()); - } catch (IOException e) { - // 抛出运行时异常,带有IO异常的信息和根异常 - throw new LoaderRuntimeException(e.getMessage(), e); - } - } - - /** - * 添加URL到类加载器的搜索路径。 - * - * @param url 要添加的URL - */ - @Override - public void addURL(URL url) { - super.addURL(url); - } - - /** - * 将类的字节码添加到类映射中,以供动态加载。 - * - * @param fullClassName 类的完全限定名 - * @param classData 类的字节码数据 - */ - public void addClassMap(String fullClassName, byte[] classData) { - classMap.put(fullClassName, defineClass(fullClassName, classData, 0, classData.length)); - } - - /** - * 根据类的完全限定名查找并加载类。 - * 首先尝试从类映射中加载类,如果未找到,则调用父类加载器的findClass方法。 - * - * @param fullClassName 类的完全限定名 - * @return 加载的Class对象 - * @throws ClassNotFoundException 如果类未找到 - */ - @Override - protected Class findClass(String fullClassName) throws ClassNotFoundException { - // 尝试从类映射中加载类 - Class clazz = classMap.get(fullClassName); - // 如果类在映射中不存在,则记录debug信息 - if (clazz == null) log.debug("[动态编译]classMap未找到类:" + fullClassName); - else return clazz; // 如果类在classMap中找到,则直接返回 - // 如果classMap中未找到类,则调用父ClassLoader的findClass方法尝试加载类 - return super.findClass(fullClassName); - } -} - diff --git a/src/main/java/cn/wubo/loader/util/class_loader/JavaBuilder.java b/src/main/java/cn/wubo/loader/util/class_loader/JavaBuilder.java deleted file mode 100644 index 85d77096303672b1e8596266cd6b1bb8ea94bb0f..0000000000000000000000000000000000000000 --- a/src/main/java/cn/wubo/loader/util/class_loader/JavaBuilder.java +++ /dev/null @@ -1,80 +0,0 @@ -package cn.wubo.loader.util.class_loader; - -import cn.wubo.loader.util.exception.LoaderRuntimeException; -import lombok.extern.slf4j.Slf4j; - -import javax.tools.*; -import java.io.Writer; -import java.util.Collections; -import java.util.Map; - -@Slf4j -public class JavaBuilder { - // Java编译器实例 - private JavaCompiler compiler; - // 用于收集编译过程中的诊断信息 - private DiagnosticCollector diagnosticCollector; - // 编译结果输出的writer - private Writer out; - // 内存文件管理器,用于在内存中管理编译后的类文件 - private MemFileManager memFileManager; - // 编译选项 - private Iterable options; - // 需要编译的类名集合 - private Iterable classes; - - /** - * JavaBuilder的构造函数。 - * 初始化Java编译器和相关组件。 - */ - public JavaBuilder() { - this.compiler = ToolProvider.getSystemJavaCompiler(); - this.diagnosticCollector = new DiagnosticCollector<>(); - this.memFileManager = new MemFileManager(compiler.getStandardFileManager(diagnosticCollector, null, null)); - } - - /** - * 静态方法,用于创建JavaBuilder实例。 - * 这是一种常见的Builder模式,用于实例化对象。 - * - * @return JavaBuilder的实例 - */ - public static JavaBuilder builder() { - return new JavaBuilder(); - } - - /** - * 编译给定的Java源代码。 - * - * @param javaSourceCode Java源代码字符串 - * @param fullClassName 完全限定类名 - * @return 当前JavaBuilder实例,支持方法链调用 - */ - public JavaBuilder compiler(String javaSourceCode, String fullClassName) { - log.debug("开始执行编译"); - JavaMemSource file = new JavaMemSource(fullClassName, javaSourceCode); - Iterable compilationUnits = Collections.singletonList(file); - log.debug("获取编译任务"); - JavaCompiler.CompilationTask task = compiler.getTask(out, memFileManager, diagnosticCollector, options, classes, compilationUnits); - log.debug("执行编译"); - boolean result = task.call(); - if (!result) { - String errorMessage = diagnosticCollector.getDiagnostics().stream().map(Object::toString).reduce("", (acc, x) -> acc + "\r\n" + x); - log.debug("编译失败: {}", errorMessage); - throw new LoaderRuntimeException("编译失败: " + errorMessage); - } - log.debug("编译成功"); - return this; - } - - /** - * 获取编译后的类的映射。 - * 这个映射将类名映射到内存中的类对象,可以用于后续的类加载和使用。 - * - * @return 编译后的类的映射 - */ - public Map getJavaMemClassMap() { - return memFileManager.getJavaMemClassMap(); - } -} - diff --git a/src/main/java/cn/wubo/loader/util/class_loader/JavaMemClass.java b/src/main/java/cn/wubo/loader/util/class_loader/JavaMemClass.java deleted file mode 100644 index 7eb0cf4fdba82f617d929188d351a9b4c1d36953..0000000000000000000000000000000000000000 --- a/src/main/java/cn/wubo/loader/util/class_loader/JavaMemClass.java +++ /dev/null @@ -1,49 +0,0 @@ -package cn.wubo.loader.util.class_loader; - -import javax.tools.SimpleJavaFileObject; -import java.io.ByteArrayOutputStream; -import java.io.OutputStream; -import java.net.URI; - -/** - * JavaMemClass 是为了在内存中处理Java类文件而设计的。 - * 它继承自SimpleJavaFileObject,用于表示类文件的内容。 - */ -public class JavaMemClass extends SimpleJavaFileObject { - - /** - * 用于存储类文件字节码的 ByteArrayOutputStream。 - */ - protected final ByteArrayOutputStream classByteArrayOutputStream = new ByteArrayOutputStream(); - - /** - * 构造函数初始化JavaMemClass对象。 - * - * @param name 类的全限定名,使用点(.)分隔。 - * @param kind 类文件的类型,例如SOURCE或CLASS。 - */ - public JavaMemClass(String name, Kind kind) { - super(URI.create("string:///" + name.replace('.', '/') - + kind.extension), kind); - } - - /** - * 获取类文件的字节码。 - * - * @return 类文件的字节码数组。 - */ - public byte[] getBytes() { - return classByteArrayOutputStream.toByteArray(); - } - - /** - * 打开一个输出流,用于写入类文件的内容。 - * - * @return 一个ByteArrayOutputStream,用于写入类文件的字节码。 - */ - @Override - public OutputStream openOutputStream() { - return classByteArrayOutputStream; - } -} - diff --git a/src/main/java/cn/wubo/loader/util/class_loader/JavaMemSource.java b/src/main/java/cn/wubo/loader/util/class_loader/JavaMemSource.java deleted file mode 100644 index 0f4623c8e265fb13528175a3ea28fa4abe902e13..0000000000000000000000000000000000000000 --- a/src/main/java/cn/wubo/loader/util/class_loader/JavaMemSource.java +++ /dev/null @@ -1,39 +0,0 @@ -package cn.wubo.loader.util.class_loader; - -import javax.tools.SimpleJavaFileObject; -import java.net.URI; - -/** - * JavaMemSource类继承自SimpleJavaFileObject,用于在内存中表示Java源代码。 - * 该类的主要作用是提供一种方式来存储和访问Java源代码字符串,而不需要将源代码写入到实际的文件中。 - */ -public class JavaMemSource extends SimpleJavaFileObject{ - - /** - * 存储Java源代码的字符串。 - */ - private String javaSourceCode; - - /** - * 构造函数初始化JavaMemSource对象。 - * - * @param name Java源文件的全限定名,使用'.'作为包名的分隔符。 - * @param javaSourceCode Java源代码的字符串表示。 - */ - public JavaMemSource(String name, String javaSourceCode) { - super(URI.create("string:///" + name.replace('.', '/')+ Kind.SOURCE.extension), Kind.SOURCE); - this.javaSourceCode = javaSourceCode; - } - - /** - * 获取Java源代码的字符序列。 - * - * @param ignoreEncodingErrors 是否忽略编码错误。该参数在此实现中未使用,因为源代码以字符串形式存储。 - * @return 返回Java源代码的字符序列。 - */ - @Override - public CharSequence getCharContent(boolean ignoreEncodingErrors) { - return javaSourceCode; - } -} - diff --git a/src/main/java/cn/wubo/loader/util/class_loader/MemFileManager.java b/src/main/java/cn/wubo/loader/util/class_loader/MemFileManager.java deleted file mode 100644 index c051502b3d6f838ff6be6adf252ecdd56846be6b..0000000000000000000000000000000000000000 --- a/src/main/java/cn/wubo/loader/util/class_loader/MemFileManager.java +++ /dev/null @@ -1,59 +0,0 @@ -package cn.wubo.loader.util.class_loader; - -import javax.tools.FileObject; -import javax.tools.ForwardingJavaFileManager; -import javax.tools.JavaFileManager; -import javax.tools.JavaFileObject; -import java.util.HashMap; -import java.util.Map; - -/** - * 在内存中管理编译后Java类的文件管理器。 - * 该类通过继承ForwardingJavaFileManager,实现了一个内存中的Java类管理机制, - * 主要用于在不将类文件写入磁盘的情况下,存储和管理编译后的Java类。 - */ -public class MemFileManager extends ForwardingJavaFileManager { - - /** - * 用于存储编译后的Java类的内存映射。 - * 键为类名,值为JavaMemClass对象,后者包含类的字节码和其他元数据。 - */ - private Map javaMemClassMap = new HashMap<>(); - - /** - * 构造函数,初始化MemFileManager。 - * - * @param fileManager 基础文件管理器,通常是一个与文件系统交互的文件管理器。 - */ - protected MemFileManager(JavaFileManager fileManager) { - super(fileManager); - } - - /** - * 重写getJavaFileForOutput方法,用于在内存中创建并返回一个新的JavaFileObject。 - * 这个方法是编译器在需要写入.class文件时调用的,我们在这里拦截这个调用, - * 并将.class文件的内容存储在JavaMemClass对象中,而不是写入磁盘。 - * - * @param location 位置标识,用于指示.class文件应该放置的位置,这里忽略,因为我们在内存中管理类。 - * @param className 类的全限定名。 - * @param kind 类文件的类型,例如SOURCE、CLASS等。 - * @param sibling 如果存在,表示与新类文件相邻的现有文件对象。 - * @return JavaMemClass对象,它在内存中代表了.class文件。 - */ - @Override - public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) { - JavaMemClass javaMemClass = new JavaMemClass(className, kind); - javaMemClassMap.put(className, javaMemClass); - return javaMemClass; - } - - /** - * 提供对javaMemClassMap的访问,允许外部获取和检查编译后的类。 - * - * @return 当前内存中所有编译后类的映射。 - */ - public Map getJavaMemClassMap() { - return javaMemClassMap; - } -} - diff --git a/src/main/java/cn/wubo/loader/util/exception/LoaderRuntimeException.java b/src/main/java/cn/wubo/loader/util/exception/LoaderRuntimeException.java deleted file mode 100644 index 2cdfc7ed145b6991583350e3fc562e4c321db351..0000000000000000000000000000000000000000 --- a/src/main/java/cn/wubo/loader/util/exception/LoaderRuntimeException.java +++ /dev/null @@ -1,11 +0,0 @@ -package cn.wubo.loader.util.exception; - -public class LoaderRuntimeException extends RuntimeException { - public LoaderRuntimeException(String message) { - super(message); - } - - public LoaderRuntimeException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/test/java/LoderUtilsTest.java b/src/test/java/LoderUtilsTest.java deleted file mode 100644 index 3d21a80b47841d1fe0c1b0bd8cfcf012132599a3..0000000000000000000000000000000000000000 --- a/src/test/java/LoderUtilsTest.java +++ /dev/null @@ -1,195 +0,0 @@ -import cn.wubo.loader.util.LoaderUtils; -import cn.wubo.loader.util.MethodUtils; -import cn.wubo.loader.util.SpringContextUtils; -import groovy.lang.GroovyClassLoader; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; - -import java.io.IOException; - -@SpringBootTest(classes = SpringContextUtils.class) -@AutoConfigureMockMvc -class LoderUtilsTest { - - @Test - 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"); - Assertions.assertEquals(str, "Hello,world!"); - } - - @Test - void testClassDelay() { - Class clazz = LoaderUtils.load("cn.wubo.loader.util.TestClass"); - String str = (String) MethodUtils.invokeClass(clazz, "testMethod", "world"); - Assertions.assertEquals(str, "Hello,world!"); - } - - @Test - void testInnerClass() { - String javaSourceCode = """ - package cn.wubo.loader.util; - - import lombok.AllArgsConstructor; - import lombok.Data; - import lombok.NoArgsConstructor; - - public class TestClass1 { - - public Object testMethod(String name){ - return new User(name); - } - - @Data - @AllArgsConstructor - @NoArgsConstructor - public static class User { - private String name; - } - } - """; - LoaderUtils.compiler(javaSourceCode, "cn.wubo.loader.util.TestClass1"); - Class clazz = LoaderUtils.load("cn.wubo.loader.util.TestClass1"); - Object obj = MethodUtils.invokeClass(clazz, "testMethod", "world"); - Assertions.assertEquals(obj.toString(), "TestClass1.User(name=world)"); - } - - @Test - 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"); - Assertions.assertFalse(str.isEmpty()); - } - - @Test - 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"); - Assertions.assertEquals(str, "Hello,world!"); - } - - @Test - void testBeans() { - String javaSourceCode = """ - package cn.wubo.loader.util; - - public class TestClass3 { - - public String testMethod(String name){ - return String.format("Hello,%s!",name); - } - } - """; - - LoaderUtils.compiler(javaSourceCode, "cn.wubo.loader.util.TestClass3"); - Class clazz = LoaderUtils.load("cn.wubo.loader.util.TestClass3"); - String beanName = LoaderUtils.registerSingleton(clazz); - Assertions.assertEquals(beanName, "testClass3"); - - String javaSourceCode2 = """ - package cn.wubo.loader.util; - - import cn.wubo.loader.util.MethodUtils; - - public class TestClass4 { - - public String testMethod(String name) { - return MethodUtils.invokeBean("testClass3", "testMethod", "world"); - } - } - """; - LoaderUtils.compiler(javaSourceCode2, "cn.wubo.loader.util.TestClass4"); - Class clazz2 = LoaderUtils.load("cn.wubo.loader.util.TestClass4"); - String beanName2 = LoaderUtils.registerSingleton(clazz2); - Assertions.assertEquals(beanName2, "testClass4"); - String str2 = MethodUtils.invokeBean(beanName2, "testMethod", "world"); - Assertions.assertEquals(str2, "Hello,world!"); - } - - @Test - void testGroovy() { - String javaSourceCode = """ - package cn.wubo.loader.util; - - public class TestClass5 { - - public String testMethod(String name){ - return String.format("Hello,%s!",name); - } - } - """; - try (GroovyClassLoader groovyClassLoader = new GroovyClassLoader()) { - groovyClassLoader.parseClass(javaSourceCode); - Class clazz = groovyClassLoader.parseClass(javaSourceCode); - String str = (String) MethodUtils.invokeClass(clazz, "testMethod", "world"); - Assertions.assertEquals(str, "Hello,world!"); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Test - 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); - } - } - - @Test - 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"); - Assertions.assertEquals(str, "Hello,world!"); - } -} diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml deleted file mode 100644 index 5e107958a45be527f7bda13e740ad98dc5e19492..0000000000000000000000000000000000000000 --- a/src/test/resources/application.yml +++ /dev/null @@ -1,3 +0,0 @@ -logging: - level: - root: debug