Java中代理的理解

一、什么是代理?—— 从生活例子说起

想象你是一个明星,每天有很多人找你拍广告、接活动。但你不希望直接面对这些琐事,于是你找了一个 经纪人(代理) 帮你处理这些请求。

  • 经纪人做什么?

    1. 过滤请求:比如拒绝不靠谱的广告。

    2. 增强功能:在接活动前先谈好价格,活动后结算费用。

    3. 隐藏细节:商家不知道你的电话号码,只能通过经纪人联系你。

代理的本质在目标对象外面包一层,拦截对它的访问,并添加额外逻辑


二、静态代理 vs 动态代理

1. 静态代理:手动写一个代理类

假设有一个 明星接口(Star)

public interface Star {
    void acceptAdvertisement();
}

明星本人(目标对象)

public class RealStar implements Star {
    @Override
    public void acceptAdvertisement() {
        System.out.println("明星本人接广告");
    }
}

经纪人(静态代理类)

public class StaticProxy implements Star {
    private Star target; // 被代理的明星

    public StaticProxy(Star target) {
        this.target = target;
    }

    @Override
    public void acceptAdvertisement() {
        System.out.println("经纪人谈价格");
        target.acceptAdvertisement(); // 调用明星本人的方法
        System.out.println("经纪人结算费用");
    }
}

使用静态代理

public static void main(String[] args) {
    Star realStar = new RealStar();
    Star proxy = new StaticProxy(realStar);
    proxy.acceptAdvertisement(); // 通过经纪人调用
}

缺点

  • 每个接口都要手动写一个代理类,代码冗余

  • 如果接口方法很多,代理类会变得臃肿。


2. 动态代理:运行时自动生成代理类

动态代理不需要手动写代理类,而是在 运行时 动态生成代理对象。
Java 提供了两种实现方式:

  • JDK 动态代理:基于接口(要求目标对象必须实现接口)

  • CGLIB 动态代理:基于继承(可代理普通类)


三、JDK 动态代理详解

1. 核心类和接口
  • java.lang.reflect.Proxy:生成代理对象的工具类。

  • java.lang.reflect.InvocationHandler:代理逻辑处理器。

2. 代码示例

目标接口和实现类(同上):

public interface Star {
    void acceptAdvertisement();
}

public class RealStar implements Star {
    @Override
    public void acceptAdvertisement() {
        System.out.println("明星本人接广告");
    }
}

动态代理处理器

public class StarHandler implements InvocationHandler {
    private Object target; // 被代理的对象

    public StarHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("经纪人谈价格");
        Object result = method.invoke(target, args); // 调用目标方法
        System.out.println("经纪人结算费用");
        return result;
    }
}

生成动态代理对象

public static void main(String[] args) {
    Star realStar = new RealStar();
    Star proxy = (Star) Proxy.newProxyInstance(
        realStar.getClass().getClassLoader(), // 类加载器
        realStar.getClass().getInterfaces(),  // 目标接口
        new StarHandler(realStar)             // 代理逻辑处理器
    );
    proxy.acceptAdvertisement(); // 输出:经纪人谈价格 → 明星本人接广告 → 经纪人结算费用
}

3. JDK 动态代理原理
  • 运行时生成代理类Proxy.newProxyInstance() 会动态生成一个类,名字类似 $Proxy0

  • 代理类实现目标接口:代理类会实现 Star 接口,并在每个方法中调用 InvocationHandler.invoke()

  • 隐藏细节:开发者无需关心代理类的具体实现。


四、CGLIB 动态代理

如果目标类没有实现接口,可以使用 CGLIB(Code Generation Library)动态代理。它通过继承目标类生成子类来实现代理。

1. 代码示例

目标类(无接口)

public class RealStar {
    public void acceptAdvertisement() {
        System.out.println("明星本人接广告");
    }
}

CGLIB 代理处理器

public class CglibProxy implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("经纪人谈价格");
        Object result = proxy.invokeSuper(obj, args); // 调用父类(目标类)方法
        System.out.println("经纪人结算费用");
        return result;
    }
}

生成代理对象

public static void main(String[] args) {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(RealStar.class); // 设置目标类
    enhancer.setCallback(new CglibProxy()); // 设置代理逻辑
    RealStar proxy = (RealStar) enhancer.create(); // 创建代理对象
    proxy.acceptAdvertisement(); // 输出同上
}

2. JDK vs CGLIB 对比
特性JDK 动态代理CGLIB 动态代理
目标对象要求必须实现接口可以代理普通类
性能生成代理较快,执行稍慢生成代理较慢,执行较快
原理基于接口实现基于继承生成子类
Spring 默认选择目标有接口时优先使用目标无接口时使用

五、动态代理的应用场景

1. AOP(面向切面编程)
  • 日志记录:在方法执行前后打印日志。

  • 事务管理:在方法执行前开启事务,执行后提交或回滚。

  • 权限校验:在方法执行前检查用户权限。

示例:Spring 的 @Transactional 事务注解就是通过动态代理实现的。


2. RPC 框架

远程调用(如 Dubbo、gRPC)中,客户端通过动态代理生成一个“假的”本地对象,调用该对象的方法时,实际会通过网络发送请求到服务端。

示例

// 用户以为调用的是本地方法,实际是通过代理发送网络请求
UserService userService = proxy.create(UserService.class);
User user = userService.getUserById(1);

3. Mock 测试

在单元测试中,用动态代理生成一个虚拟对象,模拟真实接口的行为。

示例

List<String> mockList = mock(List.class);
when(mockList.get(0)).thenReturn("test");
System.out.println(mockList.get(0)); // 输出 "test"

4. 延迟加载(Lazy Loading)

在 Hibernate 中,关联对象(如 User 的 Order 列表)可以通过动态代理实现延迟加载,只有实际访问时才会查询数据库。


六、动态代理的本质

动态代理的核心是 在运行时动态生成一个类,拦截对目标方法的调用,并插入额外逻辑。它解决了以下问题:

  1. 代码复用:避免为每个类编写重复的代理代码。

  2. 解耦:将通用逻辑(如日志、事务)与业务逻辑分离。

  3. 灵活性:动态代理可以应对接口或类的变化。


七、实际工作中的应用举例

假设你要为所有 Service 层的方法添加执行时间统计:

1. 定义切面(使用动态代理)
@Aspect
@Component
public class TimeAspect {
    @Around("execution(* com.example.service.*.*(..))")
    public Object logTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed(); // 调用原方法
        long time = System.currentTimeMillis() - start;
        System.out.println("方法执行耗时:" + time + "ms");
        return result;
    }
}

其中动态代理的体现:

1.一、动态代理的核心体现
1. 代理对象的生成
  • 原始对象com.example.service 包下的类(如 UserService)会被 Spring 容器初始化为普通 Bean。
  • 代理对象:Spring 检测到 @Aspect 切面后,会为这些 Bean 动态生成代理对象,替换原始 Bean。
    • 如果目标类实现了接口 → 使用 JDK 动态代理(生成一个实现相同接口的代理类)。
    • 如果目标类无接口 → 使用 CGLIB 动态代理(生成目标类的子类)。

2. 方法调用的拦截
当调用 UserService 的方法时,实际调用的是 代理对象的方法,代理对象会:
  1. 拦截方法调用:代理对象检测到方法匹配切入点表达式(execution(* com.example.service.*.*(..)))。
  2. 执行切面逻辑:调用你的 logTime() 方法,在其中插入耗时统计代码。
  3. 转发到原始方法:通过 joinPoint.proceed() 调用原始目标方法。

3. 整个过程示意图
开发者调用 userService.updateUser() 
    → 实际调用的是代理对象的方法 
        → 代理对象触发 TimeAspect.logTime() 
            → logTime() 中调用 joinPoint.proceed() 
                → 代理对象调用原始 userService.updateUser() 
                    → 返回结果,继续执行 logTime() 的剩余逻辑。

1.二、具体代码中的动态代理痕迹
1. 代理对象的类名
运行程序时,可以看到 Spring 生成的代理类名:
  • JDK 动态代理:类名类似 com.example.service.UserService$$EnhancerBySpringCGLIB$$12345678
  • CGLIB 动态代理:类名类似 $Proxy0(JDK)或 UserService$$EnhancerBySpringCGLIB$$...

2. 调试观察代理对象
在调试模式下,查看 UserService 实例的类名:
@Autowired
private UserService userService;

public void someMethod() {
    System.out.println(userService.getClass().getName()); 
    // 输出可能是 UserService$$EnhancerBySpringCGLIB$$...
}

1.三、动态代理的关键优势
1. 无需侵入目标代码
  • 你不需要修改 UserService 或其他 Service 类的代码,只需定义一个切面类。
  • 动态代理自动将增强逻辑“织入”目标方法。
2. 统一管理横切关注点
  • 日志、事务、权限等通用逻辑通过切面集中处理,避免代码重复。
3. 灵活应对变化
  • 修改切面逻辑(如调整日志格式)只需修改 TimeAspect,无需改动业务代码。

1.四、对比静态代理理解动态代理
假设不用动态代理,手动实现类似功能:
1. 静态代理代码
public class UserServiceProxy implements UserService {
    private UserService target;

    public UserServiceProxy(UserService target) {
        this.target = target;
    }

    @Override
    public void updateUser(User user) {
        long start = System.currentTimeMillis();
        target.updateUser(user); // 调用原始方法
        long time = System.currentTimeMillis() - start;
        System.out.println("方法执行耗时:" + time + "ms");
    }

    // 需要为每个方法手动重复耗时统计逻辑!
}
2. 动态代理的优势
  • 无需手动编写代理类:动态代理自动生成。
  • 通用性:一个切面可作用于多个类和方法。
  • 维护性:逻辑集中,修改方便。

1.五、总结
  1. Spring 自动为 com.example.service 下的 Bean 生成代理对象。
  2. 代理对象拦截方法调用,插入 logTime() 中的耗时统计逻辑。
  3. 开发者无需感知代理的存在,只需关注切面逻辑和目标业务代码。
 
2. Spring 的幕后操作
  • Spring 使用动态代理(JDK 或 CGLIB)生成 Service 的代理对象。

  • 当调用 userService.updateUser() 时,实际调用的是代理对象的方法,代理会先执行 logTime() 中的逻辑。


八、总结

  • 动态代理:在运行时生成代理类,拦截方法调用并添加额外逻辑。

  • JDK 动态代理:基于接口,要求目标类必须实现接口。

  • CGLIB:基于继承,可代理普通类。

  • 应用场景:AOP、RPC、Mock 测试、延迟加载等。

关键记忆点
动态代理 = 运行时生成“中介”类 + 拦截方法调用 + 插入增强逻辑

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值