第27篇:Java代理类详解


📌 系列导航《Java 100 天进阶之路》完整目录 |
⬅️ 上一篇:第26篇:Java注解详解 |
➡️ 下一篇:第28篇:Java反射机制原理详解


一、核心知识点

  • 代理模式的定义与作用:为另一个对象提供替身,控制对其访问
  • 静态代理:手动编写代理类,编译期确定
  • 动态代理:JDK 动态代理(基于接口)、CGLIB 动态代理(基于继承)
  • 应用场景:AOP(日志、事务、权限)、延迟加载、远程调用
  • JDK 动态代理核心:InvocationHandler 接口、Proxy.newProxyInstance()
  • CGLIB 核心:MethodInterceptor 接口、Enhancer

二、通俗讲解(1分钟开心学)

1. 什么是代理?

代理就是找一个“替身”或“中介”,帮你完成一些额外工作(如日志、权限校验),同时核心业务逻辑还是由你来做。

生活类比
明星拍戏(核心业务),经纪人(代理)负责谈合同、安排行程、挡记者。明星只专心拍戏,其他杂事交给代理。

2. 静态代理 vs 动态代理

  • 静态代理:代理类在编译期就已经写好了。每个目标类需要一个专属代理类,如果目标类很多,代理类也会很多。
  • 动态代理:代理类在运行时动态生成,不需要为每个目标类单独写代理类。JDK 动态代理要求目标类实现接口;CGLIB 不需要接口,通过生成子类实现。

3. JDK 动态代理原理

通过 Proxy.newProxyInstance() 在内存中生成一个实现了目标接口的代理类,所有方法调用都会转发给 InvocationHandlerinvoke 方法,你可以在 invoke 中加增强逻辑,再调用目标对象。

4. CGLIB 动态代理原理

通过 Enhancer 生成目标类的子类,子类重写目标类的方法,在方法拦截器中添加增强逻辑。因为使用继承,所以不能代理 final 类或 final 方法。

三、实操代码案例 + 场景说明

场景:为业务方法添加日志记录和性能监控,不修改原有代码。

1. 静态代理示例

// 接口
interface UserService {
    void addUser(String name);
}

// 目标类
class UserServiceImpl implements UserService {
    @Override
    public void addUser(String name) {
        System.out.println("添加用户:" + name);
    }
}

// 静态代理类
class UserServiceProxy implements UserService {
    private UserService target;
    
    public UserServiceProxy(UserService target) {
        this.target = target;
    }
    
    @Override
    public void addUser(String name) {
        System.out.println("[静态代理] 开始日志");
        long start = System.currentTimeMillis();
        target.addUser(name);
        System.out.println("[静态代理] 耗时:" + (System.currentTimeMillis() - start) + "ms");
    }
}

// 使用
UserService service = new UserServiceProxy(new UserServiceImpl());
service.addUser("张三");

2. JDK 动态代理(基于接口)

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

class LogHandler implements InvocationHandler {
    private Object target;
    
    public LogHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("[JDK代理] 方法调用前:" + method.getName());
        long start = System.currentTimeMillis();
        Object result = method.invoke(target, args);
        System.out.println("[JDK代理] 耗时:" + (System.currentTimeMillis() - start) + "ms");
        return result;
    }
}

// 使用
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
    target.getClass().getClassLoader(),
    target.getClass().getInterfaces(),
    new LogHandler(target)
);
proxy.addUser("李四");

3. CGLIB 动态代理(需要引入 cglib 依赖)

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

class NoInterfaceClass {
    public void sayHello() {
        System.out.println("Hello CGLIB");
    }
}

class CglibInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("[CGLIB] 前置增强");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("[CGLIB] 后置增强");
        return result;
    }
}

// 使用
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(NoInterfaceClass.class);
enhancer.setCallback(new CglibInterceptor());
NoInterfaceClass proxy = (NoInterfaceClass) enhancer.create();
proxy.sayHello();

四、避坑要点

错误/误区 后果 正确做法
JDK 动态代理的目标类没有实现接口 Proxy.newProxyInstance 抛异常 改用 CGLIB,或给目标类添加接口
InvocationHandler.invoke 中调用 proxy 自身的方法 无限递归,栈溢出 调用 method.invoke(target, args),不要用 proxy
CGLIB 代理 final 类或 final 方法 代理失败或方法不会被增强 避免代理 final 成员
动态代理对象类型转换错误 ClassCastException 确保代理对象类型与声明类型一致(接口类型)

五、面试高频考点

Q1:JDK 动态代理和 CGLIB 的区别?

JDK 动态代理基于接口,只能代理实现了接口的类;CGLIB 基于继承,生成目标类的子类,不能代理 final 类和方法。JDK 原生支持,CGLIB 需要额外库。

Q2:动态代理的典型应用场景?

Spring AOP(切面编程)、声明式事务、权限控制、日志记录、性能监控、延迟加载(如 Hibernate)。

Q3:InvocationHandlerinvoke 方法参数中的 proxy 有什么作用?

proxy 是动态生成的代理对象本身,可以用于反射调用方法(如 proxy.toString()),但小心无限递归。通常不需要使用。

六、练习题

  1. 动手:使用 JDK 动态代理为任意接口的方法添加执行时间打印。
  2. 设计:解释为什么 Spring AOP 默认使用 JDK 动态代理,什么时候会切换到 CGLIB?
  3. 代码补全:完成一个缓存代理,当方法参数相同时,直接从缓存返回结果,避免重复计算。

📊 你的学习进度

  • 当前:第27篇 / 共44篇 · 第四阶段:注解、反射、代理、日期(第26~31篇)
  • ✅ 已完成:第1~26篇
  • 📖 正在学:第27篇
  • ⏳ 待学习:第28~44篇

👉 📚 完整目录 & 学习指南 | 🔥 订阅本专栏,不错过每一篇

💡 本专栏每篇都包含:避坑表 + 面试高频考点 + 练习题。每天30分钟,100天拿offer!


下一篇文章预告

《Java反射机制原理详解》

内容简介:反射核心能力、获取Class的三种方式、调用私有方法/字段、反射性能与安全性。

💡 学完这篇,你将彻底掌握反射,理解Spring/MyBatis等框架的底层原理。

📌 《Java 100 天进阶之路 | 从入门到上岗就业》 每天一篇,建议收藏 + 关注,一起100天拿offer!
👉 点击关注我,更新后第一时间收到推送!


更多推荐