You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在无Spring依赖的ByteBuddy JavaAgent中拦截@RestController类

哈哈,这个需求我太熟了——既要精准拦截Spring的@RestController类,又不想让Agent绑死Spring依赖,核心就是绕开类加载,直接在字节码层面做注解检测。下面给你拆解具体实现步骤:

核心思路

我们不需要引入Spring的任何依赖包,只需要用@RestController的全限定名字符串"org.springframework.web.bind.annotation.RestController"作为匹配标识,通过字节码操作工具(比如ByteBuddy或ASM)在类加载前扫描类的注解元数据,判断是否需要添加Advice。

具体实现(推荐用ByteBuddy,省心高效)

ByteBuddy是JavaAgent场景下最易用的字节码库,它的匹配器支持直接通过注解全限定名匹配,而且完全不会触发Spring注解类的加载。

1. 编写Agent核心逻辑

import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;

import java.lang.instrument.Instrumentation;

public class RestControllerLoggingAgent {
    public static void premain(String agentArgs, Instrumentation inst) {
        new AgentBuilder.Default()
                // 匹配带有@RestController注解的类
                .type(ElementMatchers.isAnnotatedWith(
                        ElementMatchers.named("org.springframework.web.bind.annotation.RestController")
                ))
                // 给匹配类的所有非静态方法添加日志Advice
                .transform((builder, typeDesc, classLoader, module) ->
                        builder.method(ElementMatchers.not(ElementMatchers.isStatic()))
                                .intercept(MethodDelegation.to(LoggingAdvice.class))
                )
                .installOn(inst);
    }
}

2. 编写日志Advice类

这个类是实际做日志逻辑的地方,要符合ByteBuddy的Advice规范:

import java.lang.reflect.Method;
import net.bytebuddy.asm.Advice;

public class LoggingAdvice {
    @Advice.OnMethodEnter
    public static void logEntry(@Advice.Origin Method method) {
        System.out.printf("[Agent] Entering %s#%s%n", 
                          method.getDeclaringClass().getName(), method.getName());
    }

    @Advice.OnMethodExit(onThrowable = Throwable.class)
    public static void logExit(@Advice.Origin Method method, 
                               @Advice.Thrown Throwable throwable) {
        if (throwable == null) {
            System.out.printf("[Agent] Exiting %s#%s%n", 
                              method.getDeclaringClass().getName(), method.getName());
        } else {
            System.out.printf("[Agent] Exiting %s#%s with exception: %s%n", 
                              method.getDeclaringClass().getName(), method.getName(), 
                              throwable.getMessage());
        }
    }
}
用ASM手动实现的思路(更轻量,适合追求极小体积的场景)

如果不想引入ByteBuddy依赖,可以用JDK自带的ASM库手动解析类字节码:

1. 自定义ClassFileTransformer

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Opcodes;

import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;

public class RestControllerTransformer implements ClassFileTransformer {
    private static final String REST_CONTROLLER_ANNOTATION = 
        "Lorg/springframework/web/bind/annotation/RestController;";

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain, byte[] classfileBuffer) {
        // 先判断类是否带有@RestController注解
        ClassReader cr = new ClassReader(classfileBuffer);
        boolean isRestController = false;

        cr.accept(new AnnotationVisitor(Opcodes.ASM9) {
            @Override
            public void visit(String name, Object value) {
                if (REST_CONTROLLER_ANNOTATION.equals(name)) {
                    isRestController = true;
                }
                super.visit(name, value);
            }
        }, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);

        if (isRestController) {
            // 这里可以用ASM的ClassWriter给类的方法添加日志逻辑
            // 具体ASM修改方法的代码可以参考官方文档,核心是插入日志字节码
            return modifyClassWithLogging(classfileBuffer);
        }
        return classfileBuffer;
    }

    private byte[] modifyClassWithLogging(byte[] classfileBuffer) {
        // 实现ASM修改类的逻辑,比如给方法前后插入日志
        // 示例代码省略,可参考ASM的MethodVisitor相关API
        return classfileBuffer;
    }
}
关键注意事项
  • 不要触发Spring类加载:绝对不能用Class.forName("org.springframework.web.bind.annotation.RestController"),否则会强制Agent依赖Spring,甚至在非Spring环境下报错。
  • 打包注意:用ByteBuddy的话,打包Agent时可以用ShadowJar把ByteBuddy核心类打包进去,但不要包含任何Spring类。
  • 类加载器兼容:Spring应用可能用自定义类加载器(比如Tomcat的WebappClassLoader),ByteBuddy默认会处理类加载器隔离问题,ASM需要额外注意确保能正确读取类字节码。

内容的提问来源于stack exchange,提问作者Solve Johnsen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 09:46:33