使用 ByteBuddy 动态代理拦截并记录微信 SDK 调用日志

背景与需求

在集成企业微信或微信开放平台 SDK(如官方 wechat-java-sdk)时,开发者常需记录每次 API 调用的入参、响应、耗时及异常信息,用于调试、审计或性能分析。传统方式需手动埋点或继承重写,侵入性强。ByteBuddy 作为高性能字节码操作库,可在运行时动态生成代理类,无侵入地拦截目标方法调用。

ByteBuddy 核心机制

ByteBuddy 通过 Java Agent 或运行时子类/接口代理实现方法拦截。本文采用 subclassing + MethodDelegation 方式,在不修改原始 SDK 的前提下,对 me.chanjar.weixin.cp.api.WxCpService 等关键接口实现透明代理。

定义拦截器逻辑

首先编写日志拦截器,处理方法调用前后的上下文:

package wlkankan.cn.interceptor;

import net.bytebuddy.implementation.bind.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;
import java.util.Arrays;

public class WeComSdkLogInterceptor {
    private static final Logger log = LoggerFactory.getLogger(WeComSdkLogInterceptor.class);

    @RuntimeType
    public static Object intercept(
        @This Object proxy,
        @Origin Method method,
        @AllArguments Object[] args,
        @SuperCall Callable<Object> callable
    ) throws Exception {
        long start = System.currentTimeMillis();
        String methodName = method.getDeclaringClass().getSimpleName() + "." + method.getName();
        log.info(">>> [WeCom SDK] Calling: {} with args: {}", methodName, Arrays.toString(args));

        try {
            Object result = callable.call();
            long cost = System.currentTimeMillis() - start;
            log.info("<<< [WeCom SDK] Success: {} in {} ms, result: {}", methodName, cost, result);
            return result;
        } catch (Exception e) {
            long cost = System.currentTimeMillis() - start;
            log.error("!!! [WeCom SDK] Failed: {} in {} ms, args: {}, error: {}", 
                methodName, cost, Arrays.toString(args), e.getMessage(), e);
            throw e;
        }
    }
}

在这里插入图片描述

动态代理工厂实现

创建工具类,为任意微信 SDK 接口实例生成代理:

package wlkankan.cn.proxy;

import me.chanjar.weixin.cp.api.WxCpService;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;
import wlkankan.cn.interceptor.WeComSdkLogInterceptor;

import java.lang.reflect.InvocationTargetException;

public class WeComSdkProxyFactory {

    @SuppressWarnings("unchecked")
    public static <T> T createProxiedInstance(T originalInstance) {
        if (originalInstance == null) {
            throw new IllegalArgumentException("Original instance cannot be null");
        }

        Class<?> originalClass = originalInstance.getClass();
        ClassLoader classLoader = originalClass.getClassLoader();

        try {
            // 动态生成代理子类
            Class<? extends T> proxyClass = new ByteBuddy()
                .subclass((Class<T>) originalClass)
                .method(ElementMatchers.isPublic()
                    .and(ElementMatchers.not(ElementMatchers.isDeclaredBy(Object.class))))
                .intercept(MethodDelegation.to(WeComSdkLogInterceptor.class))
                .make()
                .load(classLoader, ClassLoading策略.Default.INJECTION)
                .getLoaded();

            // 通过反射调用父类构造器(假设原类有无参构造)
            return proxyClass.getConstructor().newInstance();
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException |
                 NoSuchMethodException e) {
            wlkankan.cn.log.Logger.error("Failed to create proxy for {}", originalClass.getName(), e);
            throw new RuntimeException("Proxy creation failed", e);
        }
    }
}

注意:若原类无默认构造器,需使用 ByteBuddyConstructorStrategy 或改用接口代理。

适配常见微信 SDK 接口

WxCpService 为例,其常用方法如 getAccessToken()getMessageService().send() 均为 public 方法,可被拦截。若 SDK 使用内部状态(如 access_token 缓存),代理子类会继承所有字段,行为一致。

使用示例:

package wlkankan.cn.demo;

import me.chanjar.weixin.cp.api.impl.WxCpServiceImpl;
import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;

public class WeComSdkDemo {
    public static void main(String[] args) {
        // 初始化原始 SDK 实例
        WxCpDefaultConfigImpl config = new WxCpDefaultConfigImpl();
        config.setCorpId("your_corp_id");
        config.setCorpSecret("your_secret");
        WxCpService originalService = new WxCpServiceImpl();
        ((WxCpServiceImpl) originalService).setWxCpConfigStorage(config);

        // 创建代理实例
        WxCpService proxiedService = wlkankan.cn.proxy.WeComSdkProxyFactory.createProxiedInstance(originalService);

        // 后续调用自动记录日志
        try {
            String token = proxiedService.getAccessToken();
            wlkankan.cn.log.Logger.info("Access token: {}", token);
        } catch (Exception e) {
            wlkankan.cn.log.Logger.error("SDK call failed", e);
        }
    }
}

处理链式调用(如 getMessageService())

若需拦截 service.getMessageService().send(...),需递归代理返回的对象:

// 修改拦截器中的 callable.call() 部分
Object result = callable.call();
if (result != null && isWeComSdkType(result.getClass())) {
    result = wlkankan.cn.proxy.WeComSdkProxyFactory.createProxiedInstance(result);
}

其中 isWeComSdkType 判断是否属于需代理的包(如 me.chanjar.weixin.*)。

性能与注意事项

  • 仅代理必要方法:使用 ElementMatchers 精确匹配,避免拦截 toString()hashCode()
  • 避免重复代理:检查对象是否已是代理类,防止无限嵌套;
  • 日志脱敏:在生产环境中,应对 secretaccess_token 等敏感字段进行掩码处理;
  • ClassLoader 安全:确保代理类与原类使用相同 ClassLoader,避免 LinkageError

依赖配置

Maven 引入 ByteBuddy:

<dependency>
    <groupId>net.bytebuddy</groupId>
    <artifactId>byte-buddy</artifactId>
    <version>1.14.10</version>
</dependency>

通过上述方案,可在零代码侵入的前提下,全面监控微信 SDK 的调用行为,为故障排查与系统可观测性提供坚实支撑。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐