一文讲清反射&动态代理&AOP

关注微信公众号 一只蜘蛛 及时获取原文+最新技术文章

本文会用一个业务场景——“下单并支付”,来讲清楚动态代理,顺便介绍其用到的反射机制以及高级的动态代理 AOP。

讲动态代理前,先说说什么是代理?为什么要有代理?

假设现在有一个大明星叫杨超越,它有唱歌和跳舞的本领,作为大明星是要用唱歌和跳舞来赚钱的,但是每次做节目,唱歌的时候要准备话筒、收钱,再唱歌;跳舞的时候也要准备场地、收钱、再唱歌。

杨超越觉得我擅长的做的事情是唱歌,和跳舞,但是每次唱歌和跳舞之前或者之后都要做一些繁琐的事情,有点烦。

于是杨超越就找个一个经济公司,请了一个代理人,代理杨超越处理这些事情,如果有人想请杨超越演出,直接找代理人就可以了。如下图所示

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们说杨超越的代理是中介公司派的,那中介公司怎么知道,要派一个有唱歌和跳舞功能的代理呢?

解决这个问题,Java 使用的是接口,杨超越想找代理,在 Java 中需要杨超越实现了一个接口,接口中规定要唱歌和跳舞的方法。Java 就可以通过这个接口为杨超越生成一个代理对象,只要接口中有的方法代理对象也会有。(可以理解为杨超越本身有个本子,上面记着自己会做的事情,代理人拿到这个本子自然就知道超越都会干啥,以及自己是一个怎么样的代理人-自己要代理啥)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

下面会以这个为例,用具体的代码讲解动态代理。

动态代理?那什么是静态代理呢?

  • 公共接口(明星的业务能力)
public interface Star {
    void sing(String songName);
    void dance(String danceName);
}
  • 真实明星(业务实现人/类):杨超越
public class YangChaoYue implements Star {
    @Override
    public void sing(String songName) {
        System.out.println("杨超越正在演唱《" + songName + "》");
    }

    @Override
    public void dance(String danceName) {
        System.out.println("杨超越正在表演《" + danceName + "》");
    }
}
  • 静态代理:助理“小静”
public class AssistantStaticProxy implements Star {

    private final Star realStar;   // 真正的杨超越

    public AssistantStaticProxy(Star realStar) {
        this.realStar = realStar;
    }

    @Override
    public void sing(String songName) {
        prepare("话筒");
        collectMoney();
        realStar.sing(songName);   // 杨超越唱歌
        afterWork();
    }

    @Override
    public void dance(String danceName) {
        prepare("场地");
        collectMoney();
        realStar.dance(danceName); // 杨超越跳舞
        afterWork();
    }

    private void prepare(String item) {
        System.out.println("[助理] 准备" + item);
    }

    private void collectMoney() {
        System.out.println("[助理] 收钱");
    }

    private void afterWork() {
        System.out.println("[助理] 演出结束,收尾款\n");
    }
}

可以看到这个代理人实现了 超越姐的业务能力 接口(拿到了本子),且自己也和超越姐达成协议(构造方法获取了超越姐,这样之后就可以通知她要干什么),然后实现了 所有业务能力,且增加了要代理的内容(在超越姐唱歌前后办一些事情)。

说白了就是对 业务实现类的一个对象 中的方法进行二次封装。对象 体现在构造代理类时传入了真实的业务对象;二次封转体现在 同名方法内除了真实的业务对象方法调用外增加了其他功能。

  • 代理人真实代理场景
public class StaticProxyShow {
    public static void main(String[] args) {
        // 超越姐准备就绪
        Star yang = new YangChaoYue();
        // 代理人也准备好了
        // 这里用Star接收代理人,是因为无论超越姐还是代理人都是服务于Star这个业务能力接口的
        // 在业务视角,代理人和超越姐是平级的
        Star assistant = new AssistantStaticProxy(yang);
        
        // 代理人收到观众点歌开始工作
        assistant.sing("《卡路里》");
        assistant.dance("创造101");
    }
}
  • 静态代理:公司给杨超越配了“小静”——一个只能服务杨超越的贴身助理,想再签王一博?得再招一个“小王”。

动态代理是怎么样的呢?

这里的初始明星业务与上面的一样,只是在代理人的设计上不一样了。

  • 统一经纪人处理器(一个类代理所有明星)
public class AgentHandler implements InvocationHandler {

    private final Object realStar;   // 可以是杨超越,也可以是别人

    public AgentHandler(Object realStar) {
        this.realStar = realStar;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        if ("sing".equals(methodName)) {
            prepare("话筒");
        } else if ("dance".equals(methodName)) {
            prepare("场地");
        }
        collectMoney();

        Object result = method.invoke(realStar, args); // 真正干活

        afterWork();
        return result;
    }

    private void prepare(String item) {
        System.out.println("[经纪人] 准备" + item);
    }

    private void collectMoney() {
        System.out.println("[经纪人] 收钱");
    }

    private void afterWork() {
        System.out.println("[经纪人] 演出结束,收尾款\n");
    }
}
  • 动态代理人真实代理场景
public class DynamicProxyShow {
    public static void main(String[] args) {
        Star yang = new YangChaoYue();

        Star agent = (Star) Proxy.newProxyInstance(
                Star.class.getClassLoader(),
                new Class[]{Star.class},
                new AgentHandler(yang)
        );

        agent.sing("卡路里");
        agent.dance("创造101");
    }
}

我们再来深入理解一下动态代理的相关 API:

  • 核心 API
public static Object newProxyInstance(
        ClassLoader loader,      // ① 类加载器
        Class<?>[] interfaces,   // ② 要实现的接口列表
        InvocationHandler h      // ③ 方法调用处理器
)

这里补充一下 ClassLoader 吧:

Star.class.getClassLoader() 就是 “让代理类跟 Star 接口在同一个户口本上”

保证运行时 agent instanceof Star 为 true,不会出现“认不得”的尴尬。

  • 类加载器 ClassLoader:搬运工:负责把 。class 文件(或内存字节码)搬到 JVM 的方法区

  • 类对象 。class:已经搬进来的那份“图纸”

  • getClassLoader():问这张图纸:“当初是哪位搬运工把你搬进来的?”

为什么代理类要用 同一位搬运工?

  • 安全限制
    Java 的类加载器有“双亲委派”机制,不同搬运工之间是隔离的。
    如果代理类用了一个跟 Star 不一样的搬运工,运行时就会抛出
    ClassCastException: $Proxy0 cannot be cast to Star

  • 可见性
    只有同一个搬运工(或它的父搬运工)加载的类,才能互相看见。
    否则代理类虽然实现了 Star,但 JVM 认为那是“两个不同的 Star”。

运行时到底发生了什么?(流程拆解)
  1. JVM 在内存中 动态生成一个类 $Proxy0 implements Star
    伪代码大致长这样:
// 这段代码不是手写的,是 JVM 在内存里拼出来然后加载的
public final class $Proxy0 extends Proxy implements Star {
    // 方法句柄,缓存起来省得每次都反射查找
    private static Method m1;  // sing
    private static Method m2;  // dance

    // 构造函数:把 InvocationHandler 传进去
    public $Proxy0(InvocationHandler h) {
        super(h);   // ← 把 AgentHandler 赋给 Proxy.h
    }

    @Override
    public void sing(String songName) {
        super.h.invoke(this, m1, new Object[]{songName});
    }

    @Override
    public void dance(String danceName) {
        super.h.invoke(this, m2, new Object[]{danceName});
    }
}
  • $Proxy0 的字节码通过 loader 加载到 JVM

  • 通过反射创建 $Proxy0 实例,并把 AgentHandler 传进去

  • 以后任何 agent.sing(...) 都会触发 AgentHandler.invoke(...)

概括一下:
  • 静态代理:公司每签一个明星就招一个“小静”“小王”——类爆炸。

  • 动态代理:公司培训了一个 万能经纪人模板InvocationHandler),
    通过 Proxy.newProxyInstance运行时 把模板“打印”成任何明星的专属经纪人,
    既省人力,又能随时加新招式(rap、篮球)而不用改现有代码。

基于上文的动态代理,我们再来看看 JDK 的反射机制

哪里用到了反射?

反射的底层流程是怎么样的?

  1. 用户态 → JVM 入口
// 你写的代码
method.invoke(realStar, "卡路里");
  • 这行代码在 Java 层 只是普通调用;

  • 真正干活的是 native 方法 Method.invoke0(...)

  • 进入 HotSpot 源码(C++ 层)
JavaCalls::call()
  └─ Reflection::invoke_method()
       ├─ 1. 权限检查(publicprivate?)
       ├─ 2. 包装参数(把 Object[] 转成 C++ 对应类型)
       ├─ 3. 找到真正的 **目标方法指针**Method*)
       ├─ 4. 选择调用方式
       │    ├─ 4.1 如果已经 **JIT 编译过** → 直接跳转到 **编译后的机器码**
       │    └─ 4.2 否则 → 走 **解释器** 的入口 stub
       └─ 5. 执行完成后,把返回值再包回 Java 对象
  • 关键数据结构

JIT 优化:反射为什么越来越快?

  • 第 1 次 调用:走 JNI + 解释器,确实慢。

  • 第 N 次 调用:HotSpot 会生成 动态反射手柄MethodAccessor 的 ASM 版本),
    method.invoke 直接 内联成普通 invokevirtual,性能接近 直接调用(差距 < 2 倍)。

一张图总结反射底层流程

Java 层
  method.invoke(realStar, args)
        │
        ▼
JNI 入口 Method.invoke0()
        │
        ▼
HotSpot C++
  Reflection::invoke_method()
        ├─ 权限检查
        ├─ 参数解包
        ├─ 找 Method* → vtable/itable 定位
        ├─ 选择解释器 or JIT 代码
        └─ 执行 & 返回

总结概括一下

反射机制是 JVM 在运行时借助 Class 元数据(运行时常量池、方法表、字段表等) 提供的自省(introspection)能力:

通过 java.lang.Classjava.lang.reflect.* API,先 定位并解析 目标类的 Class 对象,再 按需获取MethodFieldConstructor 等元数据对象,最后 绕过编译期绑定,在 解释器或 JIT 生成的适配 stub 中动态完成成员访问或方法调用。

在动态代理场景下,这一过程使框架能够在 运行期 为接口生成 $Proxy0 代理类,并将接口方法统一路由到用户提供的 InvocationHandler,实现横切逻辑的织入。

AOP 是什么?他与动态代理有什么关系?

AOP 就是把“与业务无关、却在各处重复出现的横切逻辑”抽出来,做成一个可插拔的“切面”,然后在 运行期 通过动态代理(+反射)织入到目标方法的前后、环绕或异常返回点。

想象一个场景:

  • 订单服务、支付服务、库存服务……每个方法都要写:
    – 记录日志
    – 开启 / 提交事务
    – 监控耗时
    – 权限校验

这些代码 一模一样,却 侵入 在每个业务类里 → 维护噩梦
AOP 把这些“横切关注点”统一收编到一个 切面(Aspect) 里,业务类保持纯粹。

核心概念(对照动态代理秒懂)

AOP 术语动态代理里的对应物解释
Join Point每一个可能被拦截的方法调用所有 method.invoke() 的点
Pointcut用表达式筛出来的那一批方法比如 execution(* com.xxx.service..*(..))
AdviceInvocationHandler.invoke() 里写的逻辑@Before、@After、@Around 等
Aspect把 Pointcut + Advice 打包成一个类你写的 @Aspect 注解类
Weaving把代理对象塞给调用方Spring 启动时或运行时完成

Spring AOP 的实现套路(最常用)

  1. 有接口 → 默认用 JDK 动态代理

    • 生成 $Proxy 类,实现业务接口。

    • 调用链路:@Autowired OrderService → 注入的是代理 → 代理内部 method.invoke() 前后执行 Advice。

  2. 无接口 / 指定 CGLIB → 用 CGLIB 字节码增强

    • 生成目标类的子类(OrderService$$EnhancerBySpringCGLIB$$...)。

    • 通过 MethodInterceptor(类似 InvocationHandler)织入逻辑。

  3. Weaving 时机

    • Spring 容器启动时,通过 BeanPostProcessor 把原始 bean 包一层代理再返回。

    • 所以 单例 bean 在容器里存的就是代理对象

代码 30 秒速览

@Aspect
@Component
public class LogAspect {

    // 1. 定义切点:所有 service 包下的 public 方法
    @Pointcut("execution(public * com.demo.service..*(..))")
    public void serviceMethods(){}

    // 2. 定义 Advice:环绕
    @Around("serviceMethods()")
    public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.nanoTime();
        try {
            return pjp.proceed();          // 相当于 method.invoke(...)
        } finally {
            long cost = (System.nanoTime() - start) / 1_000_000;
            log.info("{} took {} ms", pjp.getSignature(), cost);
        }
    }
}

  • 业务类 OrderService 干干净净,没有任何日志代码。

  • Spring 启动时自动生成代理,把 logAround 织进去。

与动态代理 / 反射的关系总结
  1. 底层机制 就是动态代理(JDK 或 CGLIB),反射负责拿到 Method 并调用。

  2. AOP 框架 帮你把“写代理类、写 InvocationHandler、缓存 Method、处理异常”这些脏活累活全包圆了。

  3. 最终效果:业务代码专注业务,横切逻辑集中管理,可插拔、可复用、可测试。

所以说,在运行时 动态代理利用反射机制获取类的元数据生成代理类$Proxy,而 AOP 则是对动态代理的进一步封装

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只蜘猪

感谢!!!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值