Java中23种设计模式之代理模式

一、什么是代理模式?

代理模式(Proxy Pattern)是一种结构型设计模式 ,其核心思想是:
通过引入一个代理对象作为中间层,控制对目标对象(真实对象)的访问,并在访问前后添加额外的处理逻辑

代理模式的本质是 "接力调用",代理对象和目标对象实现相同的接口,对外提供一致的访问方式,但代理对象会在调用目标对象的方法前后 "插队" 执行自己的逻辑。

二、代理模式的核心角色

代理模式涉及三个核心角色:

  1. 抽象主题(Subject):定义代理和目标对象的公共接口,确保两者具有相同的行为。
  2. 真实主题(Real Subject):实际处理业务逻辑的目标对象,如 "明星"。
  3. 代理主题(Proxy Subject):持有真实主题的引用,在调用真实主题的方法前后添加额外逻辑,如 "经纪人"。

三、案例场景:明星与经纪人

我们以 "明星表演" 为例:

  • 真实主题:明星(ConcreteStar)负责核心表演逻辑。
  • 代理主题:经纪人(StarAgentProxy)作为代理,在明星表演前后处理事务(如安排场地、结算费用)。
  • 抽象主题:定义 "表演" 接口(Star),约束明星和经纪人的共同行为。

四、代码实现(Java 静态代理)

1. 抽象主题接口(Star)

复制代码
// 定义明星的公共行为:表演
interface Star {
    void perform(); // 表演方法
}

2. 真实主题:具体明星(ConcreteStar)

复制代码
// 真实明星类,实现核心表演逻辑
class ConcreteStar implements Star {
    @Override
    public void perform() {
        System.out.println("明星开始精彩的表演!"); // 核心业务逻辑
    }
}

3. 代理主题:经纪人(StarAgentProxy)

复制代码
// 经纪人代理类,实现Star接口并持有明星引用
class StarAgentProxy implements Star {
    private final Star realStar; // 让经纪人代理类持有目标对象(真实明星)的引用
                                 //这样在下面就可以调用真实明星的方法

    // 构造方法:接收真实明星对象
    public StarAgentProxy(Star realStar) {
        this.realStar = realStar;
    }

    @Override
    public void perform() {
        // 前置处理:代理在调用真实对象前的逻辑
        System.out.println("经纪人:安排场地、宣传推广、对接流程...");
        
        // 调用真实明星的表演方法
        realStar.perform(); // 核心逻辑由真实对象处理
        
        // 后置处理:代理在调用真实对象后的逻辑
        System.out.println("经纪人:处理费用结算、安排下一场行程...");
    }
}

4. 客户端测试(StaticProxyTest)

复制代码
public class StaticProxyTest {
    public static void main(String[] args) {
        // 创建真实明星对象
        Star realStar = new ConcreteStar();
        
        // 创建经纪人代理对象,传入真实明星
        Star agentProxy = new StarAgentProxy(realStar);
        
        // 客户端通过代理对象调用方法(仿佛直接调用真实对象)
        agentProxy.perform();
    }
}

运行结果:

5.变量引用关系

在代码 Star agentProxy = new StarAgentProxy(realStar); 里:

  • realStarConcreteStar 类的一个实例,也就是目标对象。
  • agentProxyStarAgentProxy 类的一个实例,即代理对象。

StarAgentProxy 类的构造方法 public StarAgentProxy(Star star) { this.star = star; } 中,只是把 realStar 对象的引用传递给了 StarAgentProxy 类的成员变量 star,这意味着 StarAgentProxy 类的实例 agentProxy 持有了 realStar 对象的引用,而并非 agentProxy 本身就是 realStar 对象。

当执行 agentProxy.perform(); 时,由于 agentProxyStarAgentProxy 类的实例,所以调用的是 StarAgentProxy 类中重写的 perform() 方法。

五、代理模式的核心原理

  1. 接口约束 :代理和真实对象实现相同接口,确保客户端可以像使用真实对象一样使用代理对象(透明性)。
  2. 组合关系 :代理对象通过构造方法接收真实对象(has-a关系),而非继承(is-a),降低耦合度。
  3. 责任分离
    • 真实对象专注核心业务(如 "表演"),
    • 代理对象专注辅助逻辑(如 "预处理" 和 "后处理")。

六、继承实现代理的三大致命缺点

复制代码
// 明星接口(抽象主题)
interface Star {
    void perform(); // 表演方法
}

// 真实明星(目标对象)
class ConcreteStar implements Star {
    @Override public void perform() {
        System.out.println("明星开始精彩的表演!");
    }
}

// 经纪人代理类(继承真实明星)
class StarAgentProxy extends ConcreteStar { // 注意:这里用了继承
    @Override public void perform() {
        System.out.println("经纪人:安排场地、对接流程...");
        super.perform(); // 调用父类(明星)的方法
        System.out.println("经纪人:处理费用结算、安排下一场...");
    }
}

// 测试
public class InheritanceProxyTest {
    public static void main(String[] args) {
        Star agent = new StarAgentProxy(); // 代理对象是真实明星的子类
        agent.perform();
    }
}

1. 强耦合:代理类绑定具体实现,而非抽象

  • 问题StarAgentProxy 直接继承 ConcreteStar(具体类),而非 Star(接口)。
    如果明星类需要更换实现(比如新增一个 AdvancedStar 类),代理类必须跟着修改继承关系,违背了 "依赖抽象而非具体" 的设计原则。

    复制代码
    // 假设新增一个更高级的明星类
    class AdvancedStar implements Star { /* ... */ }
    // 此时代理类若想代理AdvancedStar,必须修改继承关系,违反开闭原则
    class StarAgentProxy extends AdvancedStar { /* ... */ } 

2. 单继承限制:无法代理多个对象或扩展其他功能

  • Java 特性:单继承机制导致代理类只能继承一个具体类,无法同时代理多个不同的目标对象(如同时代理 "歌手" 和 "演员" 两个不同类)。
  • 扩展性差:如果代理类需要额外功能(如日志记录),只能通过多层继承实现,导致类层次结构复杂,违背 "组合优于继承" 原则。

3. 违背接口隔离:代理类依赖目标类的具体实现

  • 本质问题 :代理模式的核心是 "控制对目标对象的访问",而继承方式让代理类直接拥有目标类的所有方法和属性(包括不需要的)。
    例如:如果 ConcreteStar 新增一个 sing() 方法,代理类会自动继承该方法,而代理类可能并不需要这个方法,导致接口污染。

七、代理模式的正确打开方式:组合(Has-A)而非继承(Is-A)

核心思想

代理类与目标对象实现相同接口 (基于抽象),通过构造方法持有目标对象的引用(组合关系),而非继承具体类。这样:

  • 代理类只依赖接口(低耦合),可代理任何实现该接口的目标对象
  • 遵循 "开闭原则",新增目标类时无需修改代理类
  • 灵活扩展,可在运行时动态替换目标对象

八、两种实现方式对比表

特性 继承式代理 组合式代理(正确方式)
耦合度 依赖具体类(强耦合) 依赖接口(低耦合)
开闭原则 新增目标类需修改代理类(违反) 可直接代理新目标类(符合)
扩展性 单继承限制,功能扩展困难 可自由组合其他功能(如日志)
适用场景 仅能代理特定类 可代理所有实现接口的类
设计原则 违背 "依赖抽象" 原则 严格遵循接口隔离原则

九、静态代理的优缺点

优点:

  1. 符合开闭原则:不修改真实对象代码,通过代理类扩展功能。
  2. 职责清晰:代理与真实对象分工明确,代码易维护。
  3. 保护真实对象:代理可控制对真实对象的访问(如权限校验、日志记录)。

缺点:

  1. 类爆炸问题:每个真实对象需对应一个代理类,若接口方法多或真实对象多,会导致代理类数量膨胀。
  2. 灵活性有限:代理类的逻辑固定,若需通用增强逻辑(如所有方法都加日志),静态代理难以复用。

大家思考一下:如果系统中业务接口很多,一个接口对应一个代理类,显然也是不合理的,会导致类爆炸。怎么解决这个问题?

**动态代理可以解决。**因为在动态代理中可以在内存中动态的为我们生成代理类的字节码。代理类不需要我们写了。类爆炸解决了,而且代码只需要写一次,代码也会得到复用。

十、动态代理

1.动态代理概述

静态代理虽然能在不修改目标对象的基础上增强其功能,但存在类爆炸的问题。即每有一个目标对象,就需要为其创建一个对应的代理类。而动态代理则很好地解决了这个问题,它可以在运行时动态地生成代理类,无需为每个目标对象都手动编写代理类。

在 Java 中,实现动态代理主要有两种方式:基于 java.lang.reflect.ProxyInvocationHandlerJDK 动态代理 ,以及基于 CGLIB 的动态代理。下面将分别详细介绍这两种方式。

2.JDK 动态代理

⑴.原理

JDK 动态代理是基于接口的代理方式。它通过 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口来实现。Proxy 类负责生成代理类的实例,而 InvocationHandler 接口则用于定义代理对象方法调用时的处理逻辑。

复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 定义一个公共接口,代表明星的表演行为
interface Star {
    // 表演方法
    void perform();
}

// 具体的明星类,实现 Star 接口
class ConcreteStar implements Star {
    @Override
    public void perform() {
        System.out.println("明星开始精彩的表演!");
    }
}

// 动态代理的调用处理器
class StarInvocationHandler implements InvocationHandler {
    // 持有目标对象的引用
    private final Object target;

    // 构造方法,接收目标对象作为参数
    public StarInvocationHandler(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 class JdkDynamicProxyTest {
    public static void main(String[] args) {
        // 创建具体的明星对象
        Star realStar = new ConcreteStar();
        // 创建动态代理的调用处理器,并传入目标对象
        StarInvocationHandler handler = new StarInvocationHandler(realStar);
        // 使用 Proxy 类的 newProxyInstance 方法创建动态代理对象
        Star proxy = (Star) Proxy.newProxyInstance(
                Star.class.getClassLoader(),
                new Class<?>[]{Star.class},
                handler
        );
        // 调用动态代理对象的方法
        proxy.perform();
    }
}

代码解释

  1. Star 接口 :定义了明星的核心行为 perform(),这是代理对象和目标对象都要实现的公共接口。
  2. ConcreteStar :具体的明星类,也就是目标对象,实现了 perform() 方法。
  3. StarInvocationHandler :实现了 InvocationHandler 接口,其中的 invoke 方法是核心,它**会在代理对象的方法被调用时自动执行。**在 invoke 方法中,可以在调用目标对象的方法前后添加额外的操作。

⑵.方法解释

Proxy.newProxyInstance 方法定义在 java.lang.reflect.Proxy 类中,其签名如下:

复制代码
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException

参数解释

  1. ClassLoader loader
  • 作用:指定用于加载代理类的类加载器。类加载器负责将代理类的字节码加载到 Java 虚拟机(JVM)中。通常使用目标对象所实现接口的类加载器,因为代理类需要与目标对象实现相同的接口,使用相同的类加载器可以确保代理类和接口在同一个类加载环境中。
  • 代码示例中的情况Star.class.getClassLoader() 获取了 Star 接口的类加载器。由于 Star 接口是目标对象 ConcreteStar 所实现的接口,使用这个类加载器可以保证代理类能够正确加载并实现 Star 接口。
  1. Class<?>[] interfaces
  • 作用:指定代理类要实现的接口数组。代理类会实现这些接口中定义的所有方法,这样代理对象就可以像目标对象一样被使用,因为它们实现了相同的接口。
  • 代码示例中的情况new Class<?>[]{Star.class} 表示代理类要实现 Star 接口。这里使用数组是因为代理类可以同时实现多个接口,例如 new Class<?>[]{Interface1.class, Interface2.class} 表示代理类将同时实现 Interface1Interface2 两个接口。
  1. InvocationHandler h
  • 作用 :指定代理对象方法调用的处理程序。当调用代理对象的方法时,实际上会调用 InvocationHandler 中的 invoke 方法。在 invoke 方法中,可以添加额外的逻辑,如在调用目标对象的方法前后进行一些操作,然后再调用目标对象的实际方法。
  • 代码示例中的情况handlerStarInvocationHandler 类的实例。StarInvocationHandler 实现了 InvocationHandler 接口,并重写了 invoke 方法,在该方法中添加了经纪人在明星表演前后的操作逻辑。

返回值

  • 作用 :返回一个实现了指定接口的代理对象。这个代理对象可以像目标对象一样调用接口中定义的方法,但在调用方法时会经过 InvocationHandler 的处理。
  • 代码示例中的情况(Star) Proxy.newProxyInstance(...) 将返回的代理对象强制转换为 Star 类型,因为代理类实现了 Star 接口。这样就可以通过 proxy 变量调用 Star 接口中定义的 perform 方法。

⑶.通过 Star 类型的引用变量调用 perform 方法时,为什么是调用了代理对象实现的 perform 方法。

在Java的动态代理机制中,当通过Star类型的引用变量调用perform()方法时,实际上调用的是代理对象的方法,而代理对象内部通过InvocationHandler将调用转发给真实对象,并允许在调用前后插入额外逻辑。以下是详细的步骤解析:

① 动态代理的核心机制

动态代理对象是通过Proxy.newProxyInstance动态生成的类实例,它实现了指定的接口(如Star)。

代理对象的所有方法调用都会被重定向到InvocationHandler的invoke方法。

②代理对象的创建过程

在JdkDynamicProxyTest类中:

复制代码
// 创建代理对象
Star proxy = (Star) Proxy.newProxyInstance(
    Star.class.getClassLoader(),
    new Class<?>[]{Star.class},
    handler
);
  • 类加载器Star.class.getClassLoader()用于加载代理类。

  • 接口列表new Class<?>[]{Star.class}表明代理类会实现Star接口。

  • 调用处理器handlerStarInvocationHandler实例,负责处理实际的方法调用。

③ 代理对象的方法调用流程

当调用proxy.perform()时:

代理对象拦截调用

动态生成的代理类覆盖了Star接口的所有方法(如perform())。当调用perform()时,代理类的方法实现会触发InvocationHandler.invoke()。

转发到InvocationHandler的invoke方法

代理类的方法实现类似以下伪代码:

复制代码
public void perform() {
    handler.invoke(
        this,                    // 代理对象自身
        methodPerform,           // 通过反射获取的Method对象(对应perform()方法)
        new Object[0]            // 方法参数(本例中无参数)
    );
}

invoke方法中处理逻辑
StarInvocationHandlerinvoke方法执行以下操作:

复制代码
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;
}

method.invoke(target, args)会调用真实对象(ConcreteStar实例)的perform()方法。 通过这种方式,代理对象在调用真实对象方法前后添加了额外逻辑。

④关键:代理对象与接口的关系

代理对象是接口的实现类

代理对象动态实现了Star接口,因此可以赋值给Star类型的变量。

方法调用的本质是动态分发

当通过Star接口调用perform()时,实际调用的是代理对象的实现方法,而代理对象的实现方法会委托给InvocationHandler。

⑤ 为什么不是直接调用真实对象的方法?

代理对象的目的是增强行为

动态代理的核心思想是"控制访问"------通过代理对象间接调用真实对象,从而在调用前后插入逻辑(如日志、事务管理等)。

透明性

调用者(如JdkDynamicProxyTest类)无需知道实际调用的是代理对象还是真实对象,只需面向接口编程。

总结

当调用proxy.perform()时,代理对象(动态生成的类)拦截了方法调用,并通过InvocationHandler将调用转发给真实对象。

InvocationHandler的invoke方法负责协调调用过程,既调用了真实对象的方法,又添加了额外逻辑。

动态代理通过接口实现透明性,使得调用者无需关心底层是直接调用还是代理增强。

⑷.invoke 方法

invoke 方法是 java.lang.reflect.InvocationHandler 接口中定义的唯一方法,当调用动态代理对象的方法时,实际上会执行 InvocationHandler 实现类中的 invoke 方法。下面详细解释 invoke 方法的各个参数以及方法体的逻辑。

复制代码
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable

参数解释

  1. Object proxy
  • 作用 :表示生成的动态代理对象本身。这个对象是通过 Proxy.newProxyInstance 方法创建出来的代理对象。不过在 invoke 方法中,通常不会直接使用 proxy 对象,因为如果在 invoke 方法中再次调用 proxy 对象的方法,会导致无限递归调用,因为每次调用代理对象的方法都会触发 invoke 方法。
  • 示例代码中的情况 :在当前的 StarInvocationHandler 类的 invoke 方法中,并没有使用 proxy 参数。
  1. Method method
  • 作用 :表示被调用的代理对象的方法。Method 类是 Java 反射机制中的一个重要类,它包含了方法的各种信息,如方法名、参数类型、返回值类型等。通过 method 对象,可以使用反射机制调用目标对象的对应方法。
  • 示例代码中的情况 :在 invoke 方法中,method.invoke(target, args) 这行代码使用 method 对象调用了目标对象 target 的对应方法。
  1. Object[] args
  • 作用 :表示调用代理对象方法时传递的参数数组。如果调用的方法没有参数,args 数组将为 null;如果有参数,args 数组中的元素将按照方法参数的顺序依次排列。
  • 示例代码中的情况 :在 Star 接口的 perform 方法没有参数,所以在调用 proxy.perform() 时,args 数组为 null。但如果 Star 接口的方法有参数,这些参数会被封装在 args 数组中传递给 invoke 方法。
复制代码
@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;
}
调用目标对象的方法
复制代码
Object result = method.invoke(target, args);

使用 Method 对象的 invoke 方法调用目标对象 target 的对应方法,并将调用结果存储在 result 变量中。invoke 方法的第一个参数是要调用方法的对象,这里是目标对象 target;第二个参数是调用方法时传递的参数数组,即 args

返回结果
复制代码
return result;

将目标对象方法的调用结果返回,这样调用代理对象方法的代码就可以获取到目标对象方法的返回值。




CGLIB 动态代理:无接口场景下的代理方案




一、为什么需要 CGLIB 动态代理?

前文介绍的 JDK 动态代理基于接口实现,要求目标对象必须实现至少一个接口。但现实中存在大量没有接口的类 (如遗留代码、第三方工具类),此时 JDK 代理失效。
CGLIB 动态代理 应运而生:它通过生成目标类的子类实现代理,无需目标类实现接口,弥补了 JDK 代理的局限性。

二、CGLIB 核心原理

1. 核心思想

  • 继承代理:CGLIB 在运行时动态生成目标类的子类(代理类),代理类重写目标类的方法。
  • 方法拦截 :通过回调函数(MethodInterceptor)拦截方法调用,在子类方法中添加增强逻辑(如前置 / 后置处理)。

2. 核心类

  • Enhancer:CGLIB 的核心类,用于设置代理类的父类(目标类)和回调函数,生成代理对象。
  • MethodInterceptor :核心接口,定义方法拦截逻辑,相当于 JDK 代理中的InvocationHandler
  • MethodProxy:CGLIB 的方法代理类,用于高效调用目标类的方法(比反射更快)。

三、代码实战:用 CGLIB 代理 "无接口明星"

场景说明

假设明星类ConcreteStar没有实现任何接口(现实中可能存在的遗留类),需要经纪人代理其表演流程。

1. 添加 CGLIB 依赖

Maven 项目需在pom.xml中添加:

复制代码
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

2. 目标类(无接口)

复制代码
// 没有实现任何接口的明星类
class ConcreteStar {
    public void perform() { // 核心业务方法
        System.out.println("明星开始精彩的表演!");
    }
    // 假设这是一个不需要代理的final方法(CGLIB无法代理final方法)
    public final void sing() { 
        System.out.println("明星演唱一首歌曲"); 
    }
}

3. CGLIB 代理类(方法拦截器)

复制代码
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

// 实现MethodInterceptor接口定义拦截逻辑
class StarCglibProxy implements MethodInterceptor {
    // 目标对象(被代理的真实对象)
    private final Object target;

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

    @Override
    public Object intercept(
        Object proxy,        // 代理对象(子类实例)
        Method method,      // 被调用的目标方法
        Object[] args,      // 方法参数
        MethodProxy proxyMethod  // CGLIB的方法代理,用于调用目标方法
    ) throws Throwable {
        // 前置处理:经纪人准备工作
        System.out.println("经纪人:安排场地、调试设备、对接流程...");
        
        // 方式1:通过反射调用目标方法(与JDK代理类似)
        // Object result = method.invoke(target, args);
        
        // 方式2:通过CGLIB的MethodProxy调用(更高效)
        Object result = proxyMethod.invokeSuper(proxy, args); // 调用父类(目标类)的方法
        
        // 后置处理:经纪人收尾工作
        System.out.println("经纪人:结算费用、安排下一场行程...");
        
        return result; // 返回目标方法的执行结果
    }
}

4. 客户端测试

复制代码
import net.sf.cglib.proxy.Enhancer;

public class CglibProxyTest {
    public static void main(String[] args) {
        // 1. 创建目标对象
        ConcreteStar realStar = new ConcreteStar();
        
        // 2. 创建CGLIB增强器
        Enhancer enhancer = new Enhancer();
        
        // 3. 设置代理类的父类(即目标类)
        enhancer.setSuperclass(realStar.getClass());
        
        // 4. 设置回调函数(方法拦截器)
        enhancer.setCallback(new StarCglibProxy(realStar));
        
        // 5. 生成代理对象(目标类的子类)
        ConcreteStar proxyStar = (ConcreteStar) enhancer.create();
        
        // 6. 调用代理对象的方法
        proxyStar.perform(); // 正常代理
        
        // 测试final方法:CGLIB无法代理,会直接调用目标类的方法
        proxyStar.sing(); // 输出:明星演唱一首歌曲(无代理逻辑)
    }
}

对于高版本的JDK,如果使用CGLIB,需要在启动项中添加两个启动参数:

填写 VM 选项

  1. 在左侧列表中选择你的运行配置(如 Client)。

  2. 在右侧的 VM options 输入框中,将两个参数合并为一行输入:

    --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/sun.net.util=ALL-UNNAMED

四、CGLIB 关键细节解析

1. intercept方法参数对比

参数名 含义 与 JDK 代理的区别
proxy 代理对象(目标类的子类实例) JDK 代理中是接口类型,CGLIB 中是子类实例
method 被调用的目标方法(反射对象) 与 JDK 代理的Method参数含义相同
args 方法参数数组 与 JDK 代理相同
proxyMethod CGLIB 的方法代理对象,用于高效调用目标方法 JDK 代理中无此参数,需通过method.invoke

2. 调用目标方法的两种方式

  • 反射调用(method.invoke:与 JDK 代理一致,兼容性强但性能稍低。
  • CGLIB 专用调用(proxyMethod.invokeSuper
    • 内部通过字节码生成技术直接调用父类方法,性能比反射快5-10 倍
    • 注意:proxyMethod.invoke(proxy, args)会递归调用代理类的方法(导致死循环),需用invokeSuper调用父类(目标类)方法。

3. 代理范围限制

  • 无法代理final :CGLIB 通过继承实现代理,final类不能被继承。
  • 无法代理final方法final方法不能被重写,会直接调用目标类的方法(如示例中的sing()方法)。
  • 构造方法无法代理:代理类继承目标类,构造方法由目标类初始化。

五、CGLIB vs JDK 动态代理:核心对比

特性 JDK 动态代理 CGLIB 动态代理
代理基础 接口(必须实现至少一个接口) 类(生成目标类的子类)
性能 反射调用,稍慢 字节码生成,调用效率更高
适用场景 有接口的目标对象 无接口、或需要代理类的场景
局限性 依赖接口,无法代理无接口类 无法代理 final 类 / 方法,继承可能破坏封装
Spring 中的应用 代理接口(如@Transactional接口方法) 代理类(如无接口的组件,或@Configuration类)

六、CGLIB 适用场景

  1. 无接口类代理 :处理遗留代码、第三方工具类(如HashMapArrayList等没有接口的类)。
  2. 高性能需求MethodProxy比反射调用更快,适合对性能敏感的场景(如高频调用的工具类)。
  3. AOP 底层实现 :Spring 框架中,当 Bean 没有实现接口时,默认使用 CGLIB 代理(可通过proxy-target-class配置强制使用)。
  4. 字节码增强:除了方法拦截,CGLIB 还可用于动态生成类、修改类结构(如添加字段、方法)。

七、CGLIB 代理的优缺点

优点:

  • 无接口依赖:弥补 JDK 代理的局限性,适用范围更广。
  • 高性能 :通过字节码生成和MethodProxy优化调用效率。
  • 灵活扩展:可代理类的所有非 final 方法,支持更细粒度的拦截。

缺点:

  • 继承限制:代理类是目标类的子类,可能破坏类的封装性(如覆盖父类方法)。
  • final 方法限制 :无法代理final修饰的类或方法。
  • 类加载问题:生成的代理类可能导致类加载器相关问题(如多模块环境)。

八、最佳实践

  1. 优先接口代理:如果目标类有接口,优先使用 JDK 动态代理(更符合面向接口设计)。
  2. 合理处理 final 成员 :避免在目标类中对关键方法使用final修饰,确保 CGLIB 可代理。
  3. 结合 Spring 配置 :在 Spring 中通过@EnableAspectJAutoProxy(proxyTargetClass = true)强制使用 CGLIB 代理类。
  4. 性能敏感场景 :对高频调用的方法,优先使用 CGLIB 的MethodProxy.invokeSuper而非反射调用。

九、总结:CGLIB 的核心价值

CGLIB 动态代理是 JDK 代理的重要补充,解决了无接口类的代理问题,在框架设计(如 Spring AOP)和遗留代码处理中不可或缺。其核心思想是通过继承 + 方法拦截实现代理,相比 JDK 代理更灵活但也有更多限制。

理解 CGLIB 与 JDK 代理的区别,能帮助我们在不同场景下选择合适的代理方案:

  • 有接口 → JDK 动态代理(优雅、符合设计原则)
  • 无接口 → CGLIB 代理(强制代理,牺牲部分封装性)

通过动态代理技术,我们实现了 "在不修改目标对象的前提下扩展功能",这正是 AOP(面向切面编程)的核心思想。掌握 CGLIB,能让我们在面对复杂代码结构时更游刃有余,写出更具扩展性的代码。

相关推荐
晨米酱1 天前
JavaScript 中"对象即函数"设计模式
前端·设计模式
数据智能老司机2 天前
精通 Python 设计模式——分布式系统模式
python·设计模式·架构
数据智能老司机2 天前
精通 Python 设计模式——并发与异步模式
python·设计模式·编程语言
数据智能老司机2 天前
精通 Python 设计模式——测试模式
python·设计模式·架构
数据智能老司机2 天前
精通 Python 设计模式——性能模式
python·设计模式·架构
使一颗心免于哀伤2 天前
《设计模式之禅》笔记摘录 - 21.状态模式
笔记·设计模式
数据智能老司机2 天前
精通 Python 设计模式——创建型设计模式
python·设计模式·架构
数据智能老司机2 天前
精通 Python 设计模式——SOLID 原则
python·设计模式·架构
烛阴3 天前
【TS 设计模式完全指南】懒加载、缓存与权限控制:代理模式在 TypeScript 中的三大妙用
javascript·设计模式·typescript
李广坤3 天前
工厂模式
设计模式