一、结构型模式

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

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

在 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 动态代理目标类实现了接口通过反射,在运行时生成实现了目标接口的代理类,所有方法调用都被转发到 InvocationHandler 的 invoke 方法中,在该方法内执行增强逻辑和目标方法。
CGLIB 动态代理目标类未实现接口通过 ASM 字节码框架,在运行时生成目标类的子类作为代理,重写目标方法,在重写的方法中执行增强逻辑和父类(目标类)的方法。

8.3 优点

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

8.4 扩展

详细内容可以看这篇博客

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

九、其他相关设计模式

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

Logo

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

更多推荐