diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0df9085c..0fd9e31d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,6 +14,7 @@ jetbrains-annotations = "26.0.2" alibaba-dubbo = "2.6.12" apache-dubbo = "2.7.6" tomcat = "8.5.85" +groovy = "2.4.21" # 与 Jenkins Script Console 的 Groovy 解析器版本对齐(仅测试用) byte-buddy = "1.18.12" # https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy commons-io = "2.22.0" # https://mvnrepository.com/artifact/commons-io/commons-io @@ -52,6 +53,7 @@ commons-lang3 = { module = "org.apache.commons:commons-lang3", version.ref = "co commons-codec = { module = "commons-codec:commons-codec", version.ref = "commons-codec" } logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } bcel = { module = "org.apache.bcel:bcel", version.ref = "bcel" } +groovy = { module = "org.codehaus.groovy:groovy", version.ref = "groovy" } java-websocket = { module = "org.java-websocket:Java-WebSocket", version.ref = "java-websocket" } okhttp3 = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp3" } fastjson2 = { module = "com.alibaba.fastjson2:fastjson2", version.ref = "fastjson2" } diff --git a/packer/build.gradle.kts b/packer/build.gradle.kts index f3b8784d..f7671b15 100644 --- a/packer/build.gradle.kts +++ b/packer/build.gradle.kts @@ -18,6 +18,7 @@ dependencies { implementation("commons-collections:commons-collections:3.2.1") implementation("org.apache.commons:commons-collections4:4.0") testImplementation(libs.junit.jupiter) + testImplementation(libs.groovy) testRuntimeOnly(libs.junit.platform.launcher) } diff --git a/packer/src/main/resources/memshell-party/XXL-Job-DefineClass.java b/packer/src/main/resources/memshell-party/XXL-Job-DefineClass.java index 7fccf517..41e8e42d 100644 --- a/packer/src/main/resources/memshell-party/XXL-Job-DefineClass.java +++ b/packer/src/main/resources/memshell-party/XXL-Job-DefineClass.java @@ -21,8 +21,8 @@ public class DemoGlueJobHandler extends IJobHandler { } public ReturnT execute(String param) throws Exception { - String base64Str = "{{base64Str}}"; - String className = "{{className}}"; + String base64Str = '{{base64Str}}'; + String className = '{{className}}'; try { Class.forName(className); } catch (ClassNotFoundException e) { diff --git a/packer/src/main/resources/memshell-party/shell.groovy b/packer/src/main/resources/memshell-party/shell.groovy index b878217d..38d5388d 100644 --- a/packer/src/main/resources/memshell-party/shell.groovy +++ b/packer/src/main/resources/memshell-party/shell.groovy @@ -14,8 +14,8 @@ class ClassDefiner extends ClassLoader { @Override public String toString() { - String className = "{{className}}"; - String base64Str = "{{base64Str}}"; + String className = '{{className}}'; + String base64Str = '{{base64Str}}'; ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); try { classLoader.loadClass(className).newInstance(); diff --git a/packer/src/test/java/com/reajason/javaweb/packer/groovy/GroovyClassDefinerPackerTest.java b/packer/src/test/java/com/reajason/javaweb/packer/groovy/GroovyClassDefinerPackerTest.java new file mode 100644 index 00000000..3770ac6e --- /dev/null +++ b/packer/src/test/java/com/reajason/javaweb/packer/groovy/GroovyClassDefinerPackerTest.java @@ -0,0 +1,42 @@ +package com.reajason.javaweb.packer.groovy; + +import com.reajason.javaweb.packer.ClassPackerConfig; +import groovy.lang.GroovyClassLoader; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author ReaJason + * @since 2026/9/24 + */ +class GroovyClassDefinerPackerTest { + + @Test + void pack() { + ClassPackerConfig config = new ClassPackerConfig(); + config.setClassName("com.reajason.javaweb.ErrorAbcdHandler"); + config.setClassBytesBase64Str("aGVsbG8="); + String script = new GroovyClassDefinerPacker().pack(config); + assertTrue(script.contains("'com.reajason.javaweb.ErrorAbcdHandler'")); + compile(script); + } + + @Test + void packClassNameWithLambdaSuffix() { + ClassPackerConfig config = new ClassPackerConfig(); + config.setClassName("le.gso.SzyHj.SOAPUtils$Proxy0$$Lambda$1"); + config.setClassBytesBase64Str("aGVsbG8="); + String script = new GroovyClassDefinerPacker().pack(config); + assertTrue(script.contains("'le.gso.SzyHj.SOAPUtils$Proxy0$$Lambda$1'")); + compile(script); + } + + private void compile(String script) { + try (GroovyClassLoader classLoader = new GroovyClassLoader()) { + classLoader.parseClass(script); + } catch (Exception e) { + throw new AssertionError("groovy script should compile, script: " + script, e); + } + } +} diff --git a/packer/src/test/java/com/reajason/javaweb/packer/xxljob/XxlJobJsonPackerTest.java b/packer/src/test/java/com/reajason/javaweb/packer/xxljob/XxlJobJsonPackerTest.java new file mode 100644 index 00000000..ac1c2930 --- /dev/null +++ b/packer/src/test/java/com/reajason/javaweb/packer/xxljob/XxlJobJsonPackerTest.java @@ -0,0 +1,53 @@ +package com.reajason.javaweb.packer.xxljob; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.reajason.javaweb.packer.ClassPackerConfig; +import groovy.lang.GroovyClassLoader; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author ReaJason + * @since 2026/9/24 + */ +class XxlJobJsonPackerTest { + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + void pack() throws Exception { + ClassPackerConfig config = new ClassPackerConfig(); + config.setClassName("com.reajason.javaweb.ErrorAbcdHandler"); + config.setClassBytesBase64Str("aGVsbG8="); + String glueSource = extractGlueSource(config); + assertTrue(glueSource.contains("'com.reajason.javaweb.ErrorAbcdHandler'")); + compile(glueSource); + } + + @Test + void packClassNameWithLambdaSuffix() throws Exception { + ClassPackerConfig config = new ClassPackerConfig(); + config.setClassName("le.gso.SzyHj.SOAPUtils$Proxy0$$Lambda$1"); + config.setClassBytesBase64Str("aGVsbG8="); + String glueSource = extractGlueSource(config); + assertTrue(glueSource.contains("'le.gso.SzyHj.SOAPUtils$Proxy0$$Lambda$1'")); + compile(glueSource); + } + + private String extractGlueSource(ClassPackerConfig config) throws Exception { + String payload = new XxlJobJsonPacker().pack(config); + JsonNode jsonNode = objectMapper.readTree(payload); + assertEquals("GLUE_GROOVY", jsonNode.get("glueType").asText()); + return jsonNode.get("glueSource").asText(); + } + + private void compile(String glueSource) { + try (GroovyClassLoader classLoader = new GroovyClassLoader()) { + classLoader.parseClass(glueSource); + } catch (Exception e) { + throw new AssertionError("glue source should compile, source: " + glueSource, e); + } + } +} diff --git a/packer/src/test/java/com/xxl/job/core/biz/model/ReturnT.java b/packer/src/test/java/com/xxl/job/core/biz/model/ReturnT.java new file mode 100644 index 00000000..dfa16afb --- /dev/null +++ b/packer/src/test/java/com/xxl/job/core/biz/model/ReturnT.java @@ -0,0 +1,10 @@ +package com.xxl.job.core.biz.model; + +import java.io.Serializable; + +/** + * 精简版 ReturnT,仅用于在测试中编译 GLUE 源码 + */ +public class ReturnT implements Serializable { + public static final ReturnT SUCCESS = new ReturnT<>(); +} diff --git a/packer/src/test/java/com/xxl/job/core/handler/IJobHandler.java b/packer/src/test/java/com/xxl/job/core/handler/IJobHandler.java new file mode 100644 index 00000000..ba470afd --- /dev/null +++ b/packer/src/test/java/com/xxl/job/core/handler/IJobHandler.java @@ -0,0 +1,10 @@ +package com.xxl.job.core.handler; + +import com.xxl.job.core.biz.model.ReturnT; + +/** + * 精简版 IJobHandler,仅用于在测试中编译 GLUE 源码 + */ +public abstract class IJobHandler { + public abstract ReturnT execute(String param) throws Exception; +}