APS 系列文章 01

一行代理告别反射:Java 动态代理新选择 APS

写 Java 久了,几乎人人都碰过这类需求:给某个方法加日志、做鉴权、统计耗时、织入事务切面。这时候「动态代理」是最顺手的工具,但 JDK 生态里一直摆着一个尴尬的两难:

  • java.lang.reflect.Proxy 只能代理接口。你的业务对象一旦是具体 class,要么先抽接口,要么放弃。
  • CGLib 能代理类,但基于 FastClass/MethodProxy 那一套,性能一般,还有 ClassLoader 泄漏的历史包袱。

更关键的是,无论选哪边,拦截器里想调用「原来的方法」都绕不开反射:

java 复制代码
// JDK 动态代理:Method.invoke 是反射调用
Object result = method.invoke(target, args);

反射慢在哪?Method.invoke 每次调用都要做访问检查、参数装箱,而且它是一个 JVM 无法内联的边界------这意味着本来能被 JIT 优化成几条机器指令的热点方法,一旦套上代理就变成了一次反射派发。

APS(Accelerated Proxy Solution) 想解决的就是这件事:它在运行时用 ASM 直接生成代理类的字节码 ,拦截器里调用原方法用的是一条 INVOKESPECIAL 指令直接调用父类方法------没有反射、没有 MethodHandle、能被 JIT 完全内联。

直接说结果:类代理比 CGLib 快约 3--5 倍接口代理和 java.lang.reflect.Proxy 打平、default 方法快约 6 倍

一个 API,类和接口通吃

APS 只有一个入口 AcceleratedProxy.proxy(...),无论目标是类还是接口:

java 复制代码
// 类代理:拦截 Greeter 的每个方法
Greeter proxy = AcceleratedProxy.proxy(Greeter.class,
        (obj, method, args) -> {
            System.out.println("before " + method.getName());
            Object result = AcceleratedProxy.invokeSuper(obj, method, args);
            System.out.println("after " + method.getName());
            return result;
        });

// 接口代理:Calculator 连实现类都没有,运行时直接给行为
Calculator calc = AcceleratedProxy.proxy(Calculator.class,
        (obj, method, args) -> (int) args[0] + (int) args[1]);

注意两处细节,它们是 APS 设计里最关键的约定:

  1. Interceptor 是一个函数式接口 ,签名只有一个方法 Object intercept(Object proxy, Method method, Object[] args),所以可以直接用 lambda。
  2. 调用原方法用 AcceleratedProxy.invokeSuper(proxy, method, args) ,而不是 method.invoke(...)。这一行在生成的字节码里就是一条直接调用父类方法的指令,是整个性能优势的来源。

完整示例

下面这段代码完整可跑(源码在 examples/src/main/java/io/github/lamspace/blog/ch01/):

java 复制代码
public class Greeter {
    public String hello(String name) { return "Hello, " + name; }
    public String getGreeting() { return "Hi there"; }
}
java 复制代码
public interface Calculator {
    int add(int a, int b);
}
java 复制代码
import io.github.lamspace.AcceleratedProxy;

public class Main {
    public static void main(String[] args) {
        // 1. 类代理:拦截 Greeter 的每个方法
        Greeter greeter = AcceleratedProxy.proxy(Greeter.class,
                (proxy, method, mArgs) -> {
                    System.out.println("before " + method.getName());
                    Object result = AcceleratedProxy.invokeSuper(proxy, method, mArgs);
                    System.out.println("after " + method.getName());
                    return result;
                });

        System.out.println(greeter.hello("World"));

        // 2. 接口代理:Calculator 没有实现类,运行时给行为
        Calculator calc = AcceleratedProxy.proxy(Calculator.class,
                (proxy, method, mArgs) -> {
                    System.out.println("calling " + method.getName());
                    return (int) mArgs[0] + (int) mArgs[1];
                });

        System.out.println(calc.add(10, 20));
    }
}

运行输出:

text 复制代码
before hello
after hello
Hello, World
---
calling add
30

APS 到底提供了什么

除了「快」,APS 还补齐了一堆原生方案没有的能力,这些是它区别于 CGLib / JDK Proxy 的地方:

能力 APS CGLib JDK Proxy
代理具体类
代理接口
调用原方法的机制 直接 INVOKESPECIAL MethodProxy + FastClass Method.invoke 反射
GC 安全(隐藏类)
选择性拦截(方法分组) Group CallbackFilter
多接口代理
构造器拦截
静态方法代理
热重载 / 热替换
注解驱动 API

这张表里每一行,本系列后续都会拆开讲。

环境与安装

APS 要求 Java 25+ ,编译期依赖 ASM 9.7.1。目前还没上 Maven Central,先 git clone 下来 mvn install 到本地仓库即可:

bash 复制代码
git clone https://github.com/LamSpace/aps.git
cd aps
mvn install -DskipTests

小结与预告

这一篇只做了一件事:让你用几行代码跑起来第一个 APS 代理,并记住一个核心约定------invokeSuper 而不是反射

下一篇我们看它的第一个杀手级特性:方法分组 Group ------如何只拦截你关心的方法,而让其余方法以零开销直通父类。

本文示例代码见 aps-blog/examples,框架源码与完整文档见 APS 仓库

相关推荐
IT爱学堂2 小时前
尚硅谷 - 2025年3月Java+AI大模型应用开发
java·开发语言·人工智能
小贤plus2 小时前
SpringBoot 三大核心注解精讲:@ControllerAdvice、@RestControllerAdvice、@Validated 分组校验(实战)
java
吠品3 小时前
Java byte数组与String互转:编码细节与踩坑记录
java·linux·服务器
晚风醉蝶3 小时前
1-6-插入排序-InsertionSort
java·数据结构·排序算法
yaoxin5211234 小时前
497. Java 反射 - 使用反射读取注解
java·开发语言·python
eralong4 小时前
Java 面向对象:继承、多态、接口
java·后端
哦虎!5 小时前
【数据库】事务
java·数据库·mysql
CDN3605 小时前
流媒体加速实践:出海东南亚短视频点播频繁缓冲,HLS 分片与 Nginx 配置调优
java·网络·nginx·流媒体加速
程序员黑豆5 小时前
Java中的null与NullPointerException完全指南:安全处理、实战排查与面试题
java·前端·ai编程