diff --git a/README.md b/README.md index 35238c927fb0c6ac237368c72deae0fdfd703f8a..8160838dbe03ea5cd7d81e3f61bf992b399b9789 100644 --- a/README.md +++ b/README.md @@ -28,32 +28,12 @@ com.gitee.wb04307201 loader-util - 1.1.3 + 1.1.4 ``` ## 第三步 如何使用 -## 1. DynamicBean 动态编译加载Bean并执行 -> 使用DynamicBean需要配置@ComponentScan,包括cn.wubo.loader.util.SpringContextUtils文件 -```java - @GetMapping(value = "/test/bean") - public String testBean(){ - String javaSourceCode = "package cn.wubo.loader.util;\n" + - "\n" + - "public class TestClass {\n" + - " \n" + - " public String testMethod(String name){\n" + - " return String.format(\"Hello,%s!\",name);\n" + - " }\n" + - "}"; - String fullClassName = "cn.wubo.loader.util.TestClass"; - String methodName = "testMethod"; - String beanName = DynamicBean.init(DynamicClass.init(javaSourceCode,fullClassName)).load(); - return (String) MethodUtils.invokeBean(beanName,methodName,"world"); - } -``` - -## 2. DynamicClass 动态编译加载Class并执行 +## 1. DynamicClass 动态编译Class并执行 ```java @GetMapping(value = "/test/class") public String testClass(){ @@ -73,7 +53,7 @@ } ``` -## 3. DynamicJar 动态加载外部jar并执行 +## 2. DynamicJar 动态加载外部jar并执行 ```java @GetMapping(value = "/test/jar") public String testJar(){ @@ -82,10 +62,11 @@ } ``` -## 4. DynamicGroovy 动态编译加载Groovy并执行 +## 3. DynamicBean 动态编译加载Bean并执行 +> 使用DynamicBean需要配置@ComponentScan,包括cn.wubo.loader.util.SpringContextUtils文件 ```java - @GetMapping(value = "/loadAndInvokeGroovy") - public String loadAndInvokeGroovy() { + @GetMapping(value = "/test/bean") + public String testBean(){ String javaSourceCode = "package cn.wubo.loader.util;\n" + "\n" + "public class TestClass {\n" + @@ -94,9 +75,33 @@ " return String.format(\"Hello,%s!\",name);\n" + " }\n" + "}"; + String fullClassName = "cn.wubo.loader.util.TestClass"; String methodName = "testMethod"; - Class clasz = DynamicGroovy.init(javaSourceCode).load(); - return (String) MethodUtils.invokeClass(clasz, methodName, "world"); + String beanName = DynamicBean.init(DynamicClass.init(javaSourceCode,fullClassName)).load(); + return (String) MethodUtils.invokeBean(beanName,methodName,"world"); + } +``` + +## 4.可通过Groovy动态编译Class +添加依赖 +```xml + + org.apache.groovy + groovy + 4.0.21 + +``` +编译class +```java + @GetMapping(value = "/loadAndInvokeGroovy") + public String loadAndInvokeGroovy() { + try (GroovyClassLoader groovyClassLoader = new GroovyClassLoader()) { + groovyClassLoader.parseClass(javaSourceCode); + Class clasz = groovyClassLoader.parseClass(javaSourceCode); + return (String) MethodUtils.invokeClass(clasz, methodName, "world"); + } catch (IOException e) { + throw new RuntimeException(e); + } } ``` @@ -132,7 +137,7 @@ private String name; } } - """; + """; return DynamicController.init(DynamicClass.init(javaSourceCode, fullClassName)).load(); } ``` @@ -232,6 +237,6 @@ java -jar -Dloader.path=lib/ loader-util-test-0.0.1-SNAPSHOT.jar 解决办法: - **idea启动的话**,打开Project Strcutre,添加tools.jar - ![img.png](q1.png) + ![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/img.png b/img.png index 4140f9ca637ee582f8377c4c73150d0446ddb9fe..dc621159784662ddca8fcef9461e2dcc2c3b7ffd 100644 Binary files a/img.png and b/img.png differ diff --git a/pom.xml b/pom.xml index 4ac82be17527461d93e8c83fd3a9a2b4c6210d01..b90d66e5f989aaeeaeed8f2eb9332b3dbb511128 100644 --- a/pom.xml +++ b/pom.xml @@ -19,24 +19,19 @@ org.springframework.boot spring-boot-dependencies - 3.2.2 + 3.2.5 pom import org.projectlombok lombok - 1.18.30 + 1.18.32 ch.qos.logback logback-classic - 1.4.14 - - - org.apache.groovy - groovy - 4.0.18 + 1.5.6 diff --git a/q1.png b/q1.png deleted file mode 100644 index dc621159784662ddca8fcef9461e2dcc2c3b7ffd..0000000000000000000000000000000000000000 Binary files a/q1.png and /dev/null differ diff --git a/src/main/java/cn/wubo/loader/util/bean_loader/DynamicBean.java b/src/main/java/cn/wubo/loader/util/bean_loader/DynamicBean.java index 8d4c50b326d67bc16d45f5711b156d7cea381eec..48a5cd470c57995f03ba09c3b1ac67aa3ead62e1 100644 --- a/src/main/java/cn/wubo/loader/util/bean_loader/DynamicBean.java +++ b/src/main/java/cn/wubo/loader/util/bean_loader/DynamicBean.java @@ -25,7 +25,9 @@ public class DynamicBean { } /** - * 加载动态类并注册到Spring容器中 + * 加载动态类并注册到Spring容器中。 + * 此方法首先尝试获取动态类的bean名称,然后检查该bean是否已在Spring容器中存在。 + * 如果已存在,则销毁该bean。接着,加载动态类的Class对象,并将其作为单例bean注册到Spring容器中。 * * @return 注册的bean名称 */ @@ -41,6 +43,4 @@ public class DynamicBean { // 返回注册的bean名称 return beanName; } - - } 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 index 1050cc4b026087900f0685dbf5f91b7693cc9bec..0b239d3905944dd55b07ea49c677fb9496911ffe 100644 --- a/src/main/java/cn/wubo/loader/util/class_loader/DynamicClassLoader.java +++ b/src/main/java/cn/wubo/loader/util/class_loader/DynamicClassLoader.java @@ -21,31 +21,36 @@ public class DynamicClassLoader extends SecureClassLoader { } /** - * 添加类信息 + * 该方法用于添加一个类的完全限定名及其对应的数据到类信息存储结构中。 * - * @param fullClassName 类的完全限定名 - * @param classData 类的数据 + * @param fullClassName 类的完全限定名,指明了类在项目中的唯一标识。 + * @param classData 类的数据,以字节数组的形式表示,包含了类的二进制信息。 */ public void addClass(String fullClassName, byte[] classData) { + // 将类的完全限定名和对应的字节数组存储到classBytes中 classBytes.put(fullClassName, classData); } /** * 重写父类方法,用于查找指定的类。 + * 这个方法首先尝试从一个映射(classBytes)中获取指定类名的字节码数据。 + * 如果找到了字节码数据,则使用这些数据定义并返回一个类对象。 + * 如果没有找到相应的字节码数据,则抛出ClassNotFoundException。 * - * @param fullClassName 指定的类名 - * @return 查找到的类对象 - * @throws ClassNotFoundException 如果找不到指定的类,则抛出该异常 + * @param fullClassName 指定的类名,包括包名。 + * @return 查找到的类对象。 + * @throws ClassNotFoundException 如果找不到指定的类,则抛出该异常。 */ @Override protected Class findClass(String fullClassName) throws ClassNotFoundException { - // 获取指定类的字节码数据 + // 尝试从classBytes映射中获取指定类的字节码数据 byte[] classData = classBytes.get(fullClassName); if (classData == null) { - // 如果找不到指定的类,则抛出ClassNotFoundException异常 + // 如果未能找到类的字节码,则抛出异常 throw new ClassNotFoundException("[动态编译]找不到类: " + fullClassName); } - // 定义并返回指定的类对象 + // 定义并返回一个类对象,使用找到的字节码数据 return defineClass(fullClassName, classData, 0, classData.length); } + } 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 index 708f756c7a2da108975903cf2ab986518a2bcef4..45042c8d68e0e07fd4f19cc6bd3beff5b19281ed 100644 --- a/src/main/java/cn/wubo/loader/util/class_loader/JavaMemClass.java +++ b/src/main/java/cn/wubo/loader/util/class_loader/JavaMemClass.java @@ -22,22 +22,25 @@ public class JavaMemClass extends SimpleJavaFileObject { /** * 获取字节数组 * - * @return 字节数组 + * 该方法不需要接受任何参数,它将返回一个字节数组。 + * 主要用于将内部缓存的字节信息转换为字节数组输出。 + * + * @return byte[] 返回一个包含字节信息的数组 */ public byte[] getBytes() { + // 将内部存储的字节流转换为字节数组并返回 return classByteArrayOutputStream.toByteArray(); } - /** * 重写openOutputStream方法 + * 这个方法重写了父类中的openOutputStream方法,目的是提供一个特定的输出流返回。 * - * @return 返回classByteArrayOutputStream对象 + * @return 返回classByteArrayOutputStream对象 - 这是一个OutputStream的实例, + * 用于允许外部写入数据到类的内部缓存中。 */ @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 index f49db4e4febfaa595afc33b9392e4e880c8f701a..4df5c3500577ccf1b059af0e3d9937c415ba2393 100644 --- a/src/main/java/cn/wubo/loader/util/class_loader/JavaMemSource.java +++ b/src/main/java/cn/wubo/loader/util/class_loader/JavaMemSource.java @@ -21,15 +21,17 @@ public class JavaMemSource extends SimpleJavaFileObject{ } /** - * 获取源代码的注释。 + * 此方法用于获取源代码的注释内容。 + * 它会返回源代码文件中的字符内容,包括注释部分。 * - * @param ignoreEncodingErrors 是否忽略编码错误。 - * @return 源代码的字符内容。 + * @param ignoreEncodingErrors 指定是否忽略编码错误。如果设置为true, + * 在读取源代码文件时将忽略任何编码错误, + * 否则遇到编码错误将抛出异常。 + * @return 返回源代码的字符序列,包括代码中的所有注释。 */ @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 index 2ad832d276b064db59ef354325c96b3e2885a9ef..61387edfedbdbaaac9dfc1bc9bf8e22ed1b6728c 100644 --- a/src/main/java/cn/wubo/loader/util/class_loader/MemFileManager.java +++ b/src/main/java/cn/wubo/loader/util/class_loader/MemFileManager.java @@ -22,18 +22,23 @@ public class MemFileManager extends ForwardingJavaFileManager { /** * 根据给定参数获取用于输出Java代码的JavaFileObject对象。 + * 这个方法在编译过程中被调用,用于生成和返回一个JavaFileObject实例, + * 该实例代表了将要被输出的Java源代码或编译后的字节码文件。 * - * @param location 代码发生的位置 - * @param className 类名 - * @param kind Java文件对象的类型 - * @param sibling 与新创建的JavaFileObject具有相同父级文件对象的兄弟文件对象 - * @return 用于输出Java代码的JavaFileObject对象 + * @param location 代码发生的位置。指定生成文件的位置。 + * @param className 类名。指定将要生成的文件所对应的类名。 + * @param kind Java文件对象的类型。指定生成文件的类型(如源代码、字节码等)。 + * @param sibling 与新创建的JavaFileObject具有相同父级文件对象的兄弟文件对象。 + * 可用于指定新文件在文件系统中的相对位置。 + * @return 用于输出Java代码的JavaFileObject对象。返回一个JavaMemClass实例, + * 该实例用于存储将要输出的Java类的字节码。 */ @Override public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) { - // 这里创建的JavaMemClass实例会被JavaCompiler用来存储编译好的类的字节码数据 + // 创建一个JavaMemClass实例,用于存储将要编译的类的字节码 JavaMemClass javaMemClass = new JavaMemClass(className, kind); - // 存储编译好的类 + // 将新创建的JavaMemClass实例与对应的类名存储到compiledClasses中, + // 以便于后续访问和使用 compiledClasses.put(className, javaMemClass); return javaMemClass; } @@ -44,7 +49,9 @@ public class MemFileManager extends ForwardingJavaFileManager { * @return 所有编译好的类的字节码数据的Map,键为类名,值为类的字节码数据 */ public Map getAllCompiledClassesData() { + // 创建一个空的Map用于存储类的字节码数据 Map classDataMap = new HashMap<>(); + // 遍历编译好的所有类,将每个类的字节码数据添加到Map中 for (Map.Entry entry : compiledClasses.entrySet()) { // 将每个编译好的类的字节码数据存入Map中 classDataMap.put(entry.getKey(), entry.getValue().getBytes()); diff --git a/src/main/java/cn/wubo/loader/util/controller_loader/DynamicController.java b/src/main/java/cn/wubo/loader/util/controller_loader/DynamicController.java index 2eb43a206b74ec8917bece2e32fbec96c9f0f0c1..9190a4a785fd0e0aaf6e25ffffc65f244f192939 100644 --- a/src/main/java/cn/wubo/loader/util/controller_loader/DynamicController.java +++ b/src/main/java/cn/wubo/loader/util/controller_loader/DynamicController.java @@ -19,22 +19,22 @@ public class DynamicController { } /** - * 加载动态类并注册到Spring容器中 + * 加载动态类并注册到Spring容器中。 + * 此方法首先尝试获取动态类的bean名称,然后检查该bean是否已在Spring容器中注册。 + * 如果已注册,则注销该bean;未注册则将动态类加载为Class对象,并将其注册为Spring容器中的控制器。 * * @return 注册的bean名称 */ public String load() { // 获取动态类的bean名称 String beanName = SpringContextUtils.beanName(dynamicClass.getFullClassName()); - // 如果Spring容器中已存在该bean,则注销该bean + // 检查Spring容器中是否已存在该bean,若存在则注销 if (Boolean.TRUE.equals(SpringContextUtils.containsBean(beanName))) SpringContextUtils.unregisterController(beanName); // 加载动态类并获取其Class对象 Class type = dynamicClass.compiler().load(); - // 将该Class对象注册为Spring容器中的控制器 + // 将动态类的Class对象注册为Spring控制器 SpringContextUtils.registerController(beanName, type); - // 返回注册的bean名称 + // 返回bean名称 return beanName; } - - } diff --git a/src/main/java/cn/wubo/loader/util/groovy_loader/DynamicGroovy.java b/src/main/java/cn/wubo/loader/util/groovy_loader/DynamicGroovy.java deleted file mode 100644 index 93d76801d26355d6a411613bd1ad012f161d200d..0000000000000000000000000000000000000000 --- a/src/main/java/cn/wubo/loader/util/groovy_loader/DynamicGroovy.java +++ /dev/null @@ -1,44 +0,0 @@ -package cn.wubo.loader.util.groovy_loader; - - -import cn.wubo.loader.util.exception.LoaderRuntimeException; -import groovy.lang.GroovyClassLoader; -import lombok.extern.slf4j.Slf4j; - -import java.io.IOException; - -@Slf4j -public class DynamicGroovy { - - private String javaSourceCode; - - public DynamicGroovy(String javaSourceCode) { - this.javaSourceCode = javaSourceCode; - } - - /** - * 初始化 DynamicGroovy 工具类 - * - * @param javaSourceCode Java 源代码字符串 - * @return DynamicGroovy 对象 - */ - public static DynamicGroovy init(String javaSourceCode) { - log.debug("初始化 groovy javaSourceCode:{}", javaSourceCode); - return new DynamicGroovy(javaSourceCode); - } - - - /** - * 加载并解析一个Groovy类或脚本的Class对象。 - * - * @return 解析后的Class对象 - */ - public Class load() { - try (GroovyClassLoader groovyClassLoader = new GroovyClassLoader()) { - return groovyClassLoader.parseClass(javaSourceCode); - } catch (IOException e) { - throw new LoaderRuntimeException(e.getMessage(), e); - } - } - -} diff --git a/src/main/java/cn/wubo/loader/util/jar_loader/DynamicJar.java b/src/main/java/cn/wubo/loader/util/jar_loader/DynamicJar.java index 84c8aa099ff7052c4e6f08486e12925656265535..7593636f050e66d8f5186139e9b66c01ff676438 100644 --- a/src/main/java/cn/wubo/loader/util/jar_loader/DynamicJar.java +++ b/src/main/java/cn/wubo/loader/util/jar_loader/DynamicJar.java @@ -31,22 +31,24 @@ public class DynamicJar { return new DynamicJar(filePath); } - /** * 加载指定的类名。 * - * @param fullClassName 待加载的类名 - * @return 加载的类对象 + * @param fullClassName 待加载的类的完整名称,包括包名。 + * @return 加载的类对象。如果指定的类成功加载,则返回对应的Class对象。 + * @throws LoaderRuntimeException 如果类加载失败,将抛出此运行时异常。 */ public Class load(String fullClassName) { try { - URL url = new File(filePath).toURI().toURL(); // 将文件路径转换为URL格式 + // 将文件路径转换为URL格式,为创建URLClassLoader做准备 + URL url = new File(filePath).toURI().toURL(); try (URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url})) { - return urlClassLoader.loadClass(fullClassName); // 加载指定的类 - } // 创建URLClassLoader + // 使用URLClassLoader加载指定的类 + return urlClassLoader.loadClass(fullClassName); + } // URLClassLoader的资源被自动关闭 } catch (ClassNotFoundException | IOException e) { - throw new LoaderRuntimeException(e.getMessage(), e); // 抛出加载异常 + // 当类找不到或发生IO异常时,抛出自定义的加载异常 + throw new LoaderRuntimeException(e.getMessage(), e); } } - }