java代理

2025/04/26 Java 共 3346 字,约 10 分钟

java代理

1. Java 中的代理分类

类型描述例子
静态代理编写代理类,手动调用目标方法自己写的代理类
动态代理运行时生成代理类JDK 动态代理、CGLIB 代理

2. 静态代理

特点

  • 代理类和被代理类都需要实现相同的接口。
  • 需要手动编写代理类。
  • 扩展性差,如果接口方法多,需要重复工作。

示例


public interface Service {
    void doSomething();
}

public class RealService implements Service {
    public void doSomething() {
        System.out.println("Real service is doing something");
    }
}

public class ServiceProxy implements Service {
    private RealService realService = new RealService();

    public void doSomething() {
        System.out.println("Before");
        realService.doSomething();
        System.out.println("After");
    }
}

3. JDK 动态代理

特点

  • 只能代理接口(interface)。
  • 运行时动态生成代理对象。
  • 通过 Proxy 类和 InvocationHandler 接口实现。

核心类

  • Proxy:生成代理对象。
  • InvocationHandler:处理方法调用的接口。

示例代码


UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
        target.getClass().getClassLoader(),
        target.getClass().getInterfaces(),
        (proxyObj, method, args) -> {
            System.out.println("Before");
            Object result = method.invoke(target, args);
            System.out.println("After");
            return result;
        }
);
proxy.sayHello();

面试重点

  • 为什么 JDK 动态代理只能代理接口? 因为 Java 的 Proxy 类生成的代理类是通过 implements 接口 来创建的,不是继承类。

  • JDK 动态代理的底层实现? 通过运行时生成一个新类(继承 Proxy 类并实现接口),字节码生成后加载到 JVM 中。

  • 一个类多个接口,怎么办

  • 在 JDK 动态代理 中,一个类可以实现多个接口,代理时是可以同时代理多个接口的 —— 本质上,代理对象实现的是这些接口的组合,并不会代理具体类本身(因为 JDK Proxy 只能基于接口)。

  • 如何保存生成的代理类? 设置系统属性:

      
    System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
    

4. CGLIB 动态代理

特点

  • 可以代理普通类(没有接口也能代理)。
  • 通过继承的方式生成子类代理。
  • CGLIB(Code Generation Library)是开源项目,Spring 默认也集成了。

核心类

  • Enhancer:生成代理对象。
  • MethodInterceptor:方法拦截器。

示例代码


Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
    System.out.println("Before");
    Object result = proxy.invokeSuper(obj, args);
    System.out.println("After");
    return result;
});
UserService proxy = (UserService) enhancer.create();
proxy.sayHello();

面试重点

  • 为什么 final 类不能被 CGLIB 代理? 因为 CGLIB 是通过继承来生成代理子类的,final 类不能被继承。
  • CGLIB 与 JDK 动态代理的区别?
 JDK 动态代理CGLIB 动态代理
是否需要接口是否
实现方式实现接口继承类
代理限制只能代理接口final类不能代理,final方法不会被代理
性能接口少时性能好类复杂时性能好
  • CGLIB 代理的原理? 基于 ASM 框架,在运行时生成字节码文件,创建子类并重写方法。ASM 是一个 Java 字节码操控库,允许你:
    • 动态生成类(不写 .java,只构建字节码)
    • 修改已有的类(AOP 增强、插桩、监控)
    • 分析 .class 文件结构(如反编译工具)

5. 动态代理在 Spring AOP 中的应用

Spring 本身支持两种代理:

代理方式用途
JDK 动态代理接口类的 AOP 代理(默认首选)
CGLIB 代理非接口类(纯类)或强制使用 CGLIB 的情况

Spring 代理选择策略

  • 有接口:默认用 JDK 动态代理。

  • 没有接口:使用 CGLIB 动态代理。

  • 可以通过配置强制使用 CGLIB:

      
    spring:
      aop:
        proxy-target-class: true
    

    或者在 JavaConfig 里:

      
    @EnableAspectJAutoProxy(proxyTargetClass = true)
    

6. QA

✅ 什么是动态代理? 动态代理是运行时生成代理对象,拦截并增强方法调用的一种机制。

✅ JDK 动态代理和 CGLIB 的区别? JDK 动态代理基于接口,CGLIB 基于继承(子类化目标类)。

✅ JDK 动态代理的底层原理? 通过 Proxy 类+InvocationHandler 动态生成实现接口的类(字节码生成+反射调用)。

✅ CGLIB 动态代理能代理 final 类吗? 不能,CGLIB 通过继承生成代理类,final 类无法继承。final方法也不能代理

✅ Spring AOP 是如何选择代理方式的? 默认有接口用 JDK 动态代理,没接口用 CGLIB;可以配置 proxy-target-class=true 强制使用 CGLIB。

✅ 如何保存 JDK 生成的代理类字节码? 设置系统属性:


System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");

✅ 动态代理的优缺点?

优点缺点
1. 解耦增强逻辑与业务逻辑 2. 运行时灵活增强1. 性能稍有开销 2. JDK Proxy必须有接口 3. CGLIB无法代理final类或方法

ByteBuddy介绍

  • CGLIB的替代品,mockito 2已经从cglib使用了byteBuddy。

  • ByteBuddy 相较于 CGLIB 在多个方面实现了 更现代、更强大、更易用 的字节码生成与修改能力。它已经成为 Mockito、Spring、Hibernate 等框架的主流选择(尤其是 Mockito 2+ 完全弃用了 CGLIB,转向 ByteBuddy)。api简单,jdk兼容好,社区维护好

为什么Spring需要 CGLIB 或 ByteBuddy?

🔹 框架解耦:Spring 不自己造轮子,而是集成稳定成熟的类库。

  • CGLIB 是一个强大的字节码增强工具,可以生成任意类的子类。
  • 后来 Spring 和 Mockito 都逐渐转向了 ByteBuddy,因为它功能更强,API 更简洁。

ByteBuddy生成类过程

  1. 在内存中构造新的类(或子类)
    • 使用 ByteBuddy().subclass(...) 等 API 定义结构
  2. 动态生成字节码
    • 内部使用 ASM 框架将类定义转换为 .class 字节码
  3. 通过 ClassLoader 加载到 JVM 中
    • 通常使用 ClassLoadingStrategy.Default.INJECTION 或自定义加载方式
  4. 返回这个“新类”的实例
    • 可直接用反射创建,也可以作为 mock 对象返回

动态代理使用场景

  • AOP(面向切面编程):可以通过代理来做日志记录、权限检查、事务管理等横切关注点。
  • Mock 框架:例如,Mockito 使用动态代理来模拟接口和类的行为,帮助进行单元测试。
  • 性能优化:动态代理可以在运行时为方法加上缓存等性能增强逻辑。

Search

    Table of Contents