一、结构型模式

在面向对象的世界里,如何优雅地组织类与对象、构建更大的结构,是每一位开发者都会反复思考的问题。直接堆砌类和继承固然简单,但当业务复杂度上升、类间关系变得盘根错节时,这种方式就会让代码变得臃肿、难以维护。

结构型设计模式正是为了解决这一痛点而诞生的一套思想体系。它们关注如何将类或对象按某种布局组合成更大的结构,通过组合、代理、适配和装饰等手段,让代码更具灵活性、可复用性和可维护性。

在 Java 开发中,结构型模式主要包含以下 7 种经典实现:

  1. 代理模式 (Proxy):为其他对象提供一种代理以控制对这个对象的访问,实现延迟加载、权限控制等。
  2. 适配器模式 (Adapter):将一个类的接口转换成客户期望的另一个接口,让原本不兼容的类可以协同工作。Java 设计模式・适配器模式篇:从思想到代码实现-CSDN博客
  3. 装饰器模式 (Decorator):动态地给一个对象添加额外的职责,比生成子类更灵活地扩展功能。Java 设计模式・装饰器模式篇:从思想到代码实现-CSDN博客
  4. 桥接模式 (Bridge):将抽象部分与它的实现部分分离,使它们都可以独立地变化,避免类爆炸。Java 设计模式・桥接模式篇:从思想到代码实现-CSDN博客
  5. 外观模式 (Facade):为子系统中的一组接口提供一个统一的高层接口,使子系统更容易使用。Java 设计模式・外观模式篇:从思想到代码实现-CSDN博客
  6. 组合模式 (Composite):将对象组合成树形结构以表示 “部分 - 整体” 的层次关系,使客户端对单个对象和组合对象的使用具有一致性。Java 设计模式・组合模式篇:从思想到代码实现-CSDN博客
  7. 享元模式 (Flyweight):运用共享技术有效地支持大量细粒度的对象,减少内存占用。​Java 设计模式・享元模式篇:从思想到代码实现-CSDN博客

二、代理模式

2.1 介绍

代理模式的本质是 “中间人”:

  • 客户端不直接调用目标对象,而是通过代理对象间接访问。
  • 代理对象可以在调用目标对象的前后插入额外逻辑,实现功能增强或访问控制,而无需修改目标对象的代码。

2.2 角色

  • 抽象主题(Subject)类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。

  • 真实主题(Real Subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。

  • 代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

三、静态代理

注:本文代码类名为中文,方便理解

3.1 抽象主题

public interface 卖货 {
    void sell();
}

3.2 真实主题

public class 网店商家 implements 卖货{

    @Override
    public void sell() {
        System.out.println("网店商家卖货中...");
    }
}

3.3 代理

public class 网购平台 implements 卖货{

    网店商家 shop = new 网店商家();

    @Override
    public void sell() {
        System.out.println("网购平台正在处理中...");
        shop.sell();
    }
}

3.4 客户端

public class 客户端 {
    public static void main(String[] args) {
        网购平台 platform = new 网购平台();
        platform.sell();
    }
}

客户端接触的是代理类,而不是直接接触真实主题,同时代理类还对原方法进行了增强

四、JDK动态代理

4.1 方法分析

Java中提供了一个动态代理类Proxy,Proxy并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象。也就是说我们不需要手动再写一个代理类了

  • newProxyInstance 第一个参数是代理类指定的类加载器,从被代理类中获取,第二个参数是代理类要实现的接口,从被代理类中获取,第三个参数是InvocationHandler类型的对象,“方法调用处理器”
  • 所有对代理对象的方法调用,最终都会被转发到这个 InvocationHandler 对象的 invoke() 方法中执行,第一个参数是生成的代理对象,第二个是被调用的方法,第三个是调用此方法时传入的参数

4.2 代码实现

接下来我们把代理类改造成代理工厂

public class 网购平台 {

    网店商家 shop = new 网店商家();

    public 卖货 getProxy(){
        卖货 goods = (卖货) Proxy.newProxyInstance(
                shop.getClass().getClassLoader(),
                shop.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("网购平台正在代理...");
                        return method.invoke(shop, args);
                    }
                }
        );
        return goods;
    }
}

客户端

public class 客户端 {
    public static void main(String[] args) {
        网购平台 platform = new 网购平台();
        卖货 goods = platform.getProxy();
        goods.sell();
    }
}

其他类不变

4.2 对比

动态代理通过 Proxy.newProxyInstance()运行时动态生成代理类,无需手动编写代理类代码,一个 InvocationHandler 可以代理多个实现了相同接口的目标类,扩展性极强

如果接口(卖货)新增了方法,代理逻辑无需任何修改,invoke() 方法会自动处理所有方法调用,而静态代理就要去实现新增的接口

对比维度 静态代理 JDK 动态代理
实现方式 手动编写代理类,实现与目标类相同的接口 通过 Proxy.newProxyInstance() 在运行时动态生成代理类,无需手动编写
代理数量 一个代理类只能代理一个目标类 一个 InvocationHandler 可以代理多个实现了相同接口的目标类
接口变更影响 接口新增方法时,所有代理类必须实现新方法,否则编译报错 接口新增方法时,代理逻辑无需修改,invoke() 方法会自动处理所有方法调用
代码复用性 增强逻辑(如日志、事务)散落在各个代理类中,难以复用和统一维护 增强逻辑集中在 InvocationHandler 中,一处修改,所有代理生效
性能开销 编译期生成,无额外性能开销 运行时通过反射生成代理类并调用方法,存在一定性能开销
适用场景 目标类数量少、接口稳定的简单场景 需要对大量类进行统一增强(如 AOP、日志、事务)或在运行时确定代理对象的复杂系统

五、CGLIB代理

5.1 介绍

如果没有定义抽象主题接口,只定义了真实主题。很显然JDK代理是无法使用了,因为JDK动态代理要求必须定义接口,对接口进行代理。

CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。

5.2 代码实现

引入依赖

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>

真实主题

public class 网店商家 {
    public void sell() {
        System.out.println("网店商家卖货中...");
    }
}

代理工厂

public class 网购平台 implements MethodInterceptor {
    网店商家 shop = new 网店商家();

    public 网店商家 getProxy() {
        //创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
        Enhancer enhancer =new Enhancer();
        //设置父类的字节码对象
        enhancer.setSuperclass(shop.getClass());
        //设置回调函数
        enhancer.setCallback(this);
        //创建代理对象
        网店商家 obj = (网店商家) enhancer.create();
        return obj;
    }


    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("网购平台正在代理...");
        return methodProxy.invokeSuper(o,objects);
    }
}

客户端

public class 客户端 {
    public static void main(String[] args) {
        网购平台 platform = new 网购平台();
        网店商家 goods = platform.getProxy();
        goods.sell();
    }
}

5.3 对比

使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。

在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。

对比维度 JDK 动态代理 CGLIB 代理
实现方式 JDK 反射,运行时生成接口实现类 ASM 字节码生成,运行时生成目标类的子类
对接口的要求 必须实现接口 无需实现接口
代理目标 只能代理实现了接口的类 可代理未实现接口的类
性能 反射调用,略低 直接调用子类方法,较高
扩展性 高,支持统一增强 高,支持无接口类代理
典型应用 Spring AOP(当 Bean 实现接口时) Spring AOP(当 Bean 未实现接口时)

六、优缺点

6.1 优点

  • 职责清晰:目标对象专注核心业务,代理对象处理日志、事务、权限等横切关注点。
  • 高扩展性:无需修改目标对象代码,即可通过新增代理类或代理逻辑来扩展功能。
  • 保护目标对象:客户端通过代理间接访问目标对象,有效隔离直接耦合,提升系统安全性。
  • 透明性:代理对象与目标对象实现相同接口,客户端可无感知地使用代理替换目标对象。

6.2 缺点

  • 增加复杂度:引入额外的代理类和对象,增加了系统的抽象层次和理解难度。
  • 性能开销:动态代理在运行时生成类和通过反射调用,会带来一定的性能损耗。
  • 调试困难:代理逻辑在运行时生成,出现问题时排查难度高于直接调用。

七、使用场景

  • 远程(Remote)代理本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。

  • 防火墙(Firewall)代理当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。

  • 保护(Protect or Access)代理控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。

  • 虚拟(Virtual)代理延迟创建开销很大的对象,先用一个轻量级的代理对象占位,直到真正需要时才实例化真实对象,从而优化系统启动速度和内存占用。

  • 缓冲(Cache)代理为目标对象的操作结果提供临时缓存,在后续相同请求时直接返回缓存结果,避免重复计算或远程调用,提升响应速度。

  • 智能引用(Smart Reference)代理在访问对象时执行附加操作,例如统计对象的引用次数、记录访问日志、自动释放资源等,比简单指针更智能。

八、Spring AOP

8.1 介绍

AOP 的本质是:在目标方法执行的前后,插入额外的增强逻辑

  • 客户端不再直接调用目标对象的方法,而是调用代理对象的同名方法。
  • 代理对象在调用目标方法的前后,执行预先定义的增强逻辑(如 @Before@After@Around)。

8.2 实现

Spring AOP 会根据目标类是否实现了接口,自动选择不同的代理方式:

代理方式 适用场景 实现原理
JDK 动态代理 目标类实现了接口 通过反射,在运行时生成实现了目标接口的代理类,所有方法调用都被转发到 InvocationHandlerinvoke 方法中,在该方法内执行增强逻辑和目标方法。
CGLIB 动态代理 目标类未实现接口 通过 ASM 字节码框架,在运行时生成目标类的子类作为代理,重写目标方法,在重写的方法中执行增强逻辑和父类(目标类)的方法。

8.3 优点

  • 解耦:将横切关注点(日志、事务)与核心业务逻辑分离,使代码更清晰、易于维护。
  • 增强:在不修改业务代码的前提下,为系统统一添加功能。
  • 透明:客户端无需感知代理的存在,调用方式与直接调用目标对象完全一致。

8.4 扩展

详细内容可以看这篇博客

深入浅出 AOP:织入时机、JDK 动态代理与 CGLIB 原理及 Spring 选择策略-CSDN博客

九、其他相关设计模式

Java 设计模式・总结目录篇:从思想到代码实现-CSDN博客

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐