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,能让我们在面对复杂代码结构时更游刃有余,写出更具扩展性的代码。

相关推荐
小马爱打代码9 小时前
设计模式:依赖倒转原则 - 依赖抽象,解耦具体实现
设计模式
Koma-forever9 小时前
java设计模式-适配器模式
java·设计模式·适配器模式
自在如风。11 小时前
Java 设计模式:原型模式详解
java·设计模式·原型模式
快乐源泉13 小时前
【设计模式】观察者,只旁观?不,还可随之变化
后端·设计模式·go
浅陌sss15 小时前
设计模式 --- 策略模式
设计模式
FirstMrRight17 小时前
策略模式随笔~
后端·设计模式
NorthCastle18 小时前
设计模式-结构型模式-代理模式
java·设计模式·代理模式
小马爱打代码18 小时前
设计模式:里氏代换原则 - 继承设计的稳定之道
设计模式
快乐源泉20 小时前
【设计模式】桥接,是设计模式?对,其实你用过
后端·设计模式·go
Auroral15620 小时前
创建型模式:抽象工厂模式
设计模式