fix: method body write failed with jakarta renamed

ByteBuddy Advice cache the class method list at first, the method signature will be changed when use jakarta rename, if follow advice need enhance the javax method, it will not find method signature. because ByteBuddy cached javax method, but follow advice need find the jakarta method, boom~
This commit is contained in:
ReaJason
2026-02-08 20:44:00 +08:00
parent ca26e37c21
commit b40fc59668
6 changed files with 54 additions and 57 deletions
@@ -17,11 +17,11 @@ public final class ProcessorRegistry {
private static final List<Processor<DynamicType.Builder<?>>> BUILDER_PROCESSORS = Arrays.asList( private static final List<Processor<DynamicType.Builder<?>>> BUILDER_PROCESSORS = Arrays.asList(
new ListenerBuilderModifier(), new ListenerBuilderModifier(),
new ValveBuilderModifier(), new ValveBuilderModifier(),
new JakartaBuilderModifier(),
new DebugOffBuilderModifier() new DebugOffBuilderModifier()
); );
private static final List<Processor<byte[]>> BYTE_PROCESSORS = Arrays.asList( private static final List<Processor<byte[]>> BYTE_PROCESSORS = Arrays.asList(
new JakartaPostProcessor(),
new JettyHandlerPostProcessor(), new JettyHandlerPostProcessor(),
new ShrinkPostProcessor() new ShrinkPostProcessor()
); );
@@ -1,22 +0,0 @@
package com.reajason.javaweb.memshell.generator.processors;
import com.reajason.javaweb.buddy.ServletRenameVisitorWrapper;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.memshell.generator.Processor;
import net.bytebuddy.dynamic.DynamicType;
/**
* @author ReaJason
* @since 2025/12/7
*/
public class JakartaBuilderModifier implements Processor<DynamicType.Builder<?>> {
@Override
public DynamicType.Builder<?> process(DynamicType.Builder<?> builder, ShellConfig shellConfig, ShellToolConfig shellToolConfig) {
if (shellConfig.isJakarta()) {
builder = builder.visit(ServletRenameVisitorWrapper.INSTANCE);
}
return builder;
}
}
@@ -0,0 +1,16 @@
package com.reajason.javaweb.memshell.generator.processors;
import com.reajason.javaweb.asm.ClassRenameUtils;
import com.reajason.javaweb.memshell.config.ShellConfig;
import com.reajason.javaweb.memshell.config.ShellToolConfig;
import com.reajason.javaweb.memshell.generator.Processor;
public class JakartaPostProcessor implements Processor<byte[]> {
@Override
public byte[] process(byte[] input, ShellConfig shellConfig, ShellToolConfig shellToolConfig) {
if (shellConfig.isJakarta()) {
return ClassRenameUtils.relocateJakarta(input);
}
return input;
}
}
@@ -17,6 +17,7 @@ import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FixedValue; import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.implementation.StubMethod;
import net.bytebuddy.matcher.ElementMatchers; import net.bytebuddy.matcher.ElementMatchers;
import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.named;
@@ -46,12 +47,9 @@ public class ListenerBuilderModifier implements Processor<DynamicType.Builder<?>
TypeDescription typeDefinition, String newClassName) { TypeDescription typeDefinition, String newClassName) {
MethodList<MethodDescription.InDefinedShape> methods = typeDefinition.getDeclaredMethods(); MethodList<MethodDescription.InDefinedShape> methods = typeDefinition.getDeclaredMethods();
if (methods.filter(ElementMatchers.named("getResponseFromRequest") if (methods.filter(named("getResponseFromRequest").and(takesArguments(1))).isEmpty()) {
.and(ElementMatchers.takesArguments(Object.class)) throw new GenerationException("please add [getResponseFromRequest(Object request)] method," +
.and(ElementMatchers.returns(Object.class))) " the method body will be auto adapted for multi server");
.isEmpty()) {
throw new GenerationException("[public Object getResponseFromRequest(Object request)] method not found" +
" make sure arg and return type is Object.class");
} else { } else {
builder = builder builder = builder
.visit(MethodCallReplaceVisitorWrapper.newInstance( .visit(MethodCallReplaceVisitorWrapper.newInstance(
@@ -59,13 +57,11 @@ public class ListenerBuilderModifier implements Processor<DynamicType.Builder<?>
.visit(Advice.to(implInterceptor).on(named("getResponseFromRequest"))); .visit(Advice.to(implInterceptor).on(named("getResponseFromRequest")));
} }
if (methods.filter(named("getFieldValue") if (methods.filter(named("getFieldValue").and(takesArguments(Object.class, String.class))).isEmpty()) {
.and(takesArguments(Object.class, String.class)))
.isEmpty()) {
builder = builder.defineMethod("getFieldValue", Object.class, Visibility.PUBLIC, Ownership.STATIC) builder = builder.defineMethod("getFieldValue", Object.class, Visibility.PUBLIC, Ownership.STATIC)
.withParameters(Object.class, String.class) .withParameters(Object.class, String.class)
.throwing(Exception.class) .throwing(Exception.class)
.intercept(FixedValue.nullValue()) .intercept(StubMethod.INSTANCE)
.visit(Advice.to(ShellCommonUtil.GetFieldValueInterceptor.class).on(named("getFieldValue"))); .visit(Advice.to(ShellCommonUtil.GetFieldValueInterceptor.class).on(named("getFieldValue")));
} }
return builder; return builder;
@@ -28,12 +28,6 @@ class ListenerGeneratorTest {
} }
} }
public static class J {
public HttpServletResponse getResponseFromRequest(HttpServletRequest request) {
return null;
}
}
public static class FakeRequest { public static class FakeRequest {
public Object response = "i'm a good boy"; public Object response = "i'm a good boy";
} }
@@ -44,12 +38,6 @@ class ListenerGeneratorTest {
Assertions.assertThrows(GenerationException.class, () -> ListenerBuilderModifier.modifier(builder, Tomcat.ListenerInterceptor.class, TypeDescription.ForLoadedType.of(Object.class), "hello.world")); Assertions.assertThrows(GenerationException.class, () -> ListenerBuilderModifier.modifier(builder, Tomcat.ListenerInterceptor.class, TypeDescription.ForLoadedType.of(Object.class), "hello.world"));
} }
@Test
void testGetResponseFromRequestSignatureError() {
DynamicType.Builder<?> builder = new ByteBuddy().redefine(J.class);
Assertions.assertThrows(GenerationException.class, () -> ListenerBuilderModifier.modifier(builder, Tomcat.ListenerInterceptor.class, TypeDescription.ForLoadedType.of(J.class), "hello.world"));
}
@Test @Test
@SneakyThrows @SneakyThrows
void test() { void test() {
@@ -1,5 +1,6 @@
package com.reajason.javaweb.asm; package com.reajason.javaweb.asm;
import org.jetbrains.annotations.NotNull;
import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter; import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes; import org.objectweb.asm.Opcodes;
@@ -14,12 +15,7 @@ import org.objectweb.asm.commons.SimpleRemapper;
public class ClassRenameUtils { public class ClassRenameUtils {
public static byte[] renameClass(byte[] classBytes, String newName) { public static byte[] renameClass(byte[] classBytes, String newName) {
ClassReader reader = null; ClassReader reader = getClassReader(classBytes);
try {
reader = new ClassReader(classBytes);
} catch (Exception e) {
throw new RuntimeException("invalid class bytes");
}
String oldClassName = reader.getClassName(); String oldClassName = reader.getClassName();
String newClassName = newName.replace('.', '/'); String newClassName = newName.replace('.', '/');
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
@@ -29,12 +25,7 @@ public class ClassRenameUtils {
} }
public static byte[] relocateClass(byte[] classBytes, String relocateClassPackage, String relocatePrefix) { public static byte[] relocateClass(byte[] classBytes, String relocateClassPackage, String relocatePrefix) {
ClassReader reader = null; ClassReader reader = getClassReader(classBytes);
try {
reader = new ClassReader(classBytes);
} catch (Exception e) {
throw new RuntimeException("invalid class bytes");
}
String oldClassName = relocateClassPackage.replace('.', '/'); String oldClassName = relocateClassPackage.replace('.', '/');
String newClassName = relocatePrefix.replace('.', '/'); String newClassName = relocatePrefix.replace('.', '/');
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS); ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS);
@@ -51,4 +42,32 @@ public class ClassRenameUtils {
reader.accept(adapter, 0); reader.accept(adapter, 0);
return writer.toByteArray(); return writer.toByteArray();
} }
public static byte[] relocateJakarta(byte[] classBytes) {
ClassReader reader = getClassReader(classBytes);
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS);
ClassRemapper adapter = new ClassRemapper(writer, new Remapper(Opcodes.ASM9) {
@Override
public String map(String typeName) {
if (typeName.startsWith("javax/servlet/")
|| typeName.startsWith("javax/websocket/")) {
return typeName.replaceFirst("javax", "jakarta");
} else {
return typeName;
}
}
});
reader.accept(adapter, 0);
return writer.toByteArray();
}
private static @NotNull ClassReader getClassReader(byte[] classBytes) {
ClassReader reader = null;
try {
reader = new ClassReader(classBytes);
} catch (Exception e) {
throw new RuntimeException("invalid class bytes");
}
return reader;
}
} }