Java设计模式之代理模式:静态代理VS动态代理,与其他模式的对比分析和案例解析

一、代理模式简介

代理模式(Proxy Pattern)是一种结构型设计模式,它提供了一个代理对象,用来控制对另一个对象的访问。这种模式通常用于在访问对象时引入额外的功能,而不改变对象的接口。代理模式的核心思想是为其他对象提供一种代理,以控制对这个对象的访问。

在现实生活中,代理模式的典型例子是房屋中介。购房者并不会直接联系房主,而是通过中介进行房屋的购买,这个中介相当于代理。

二、代理模式的结构

代理模式的典型结构如下:

  1. Subject:定义了 RealSubject 和 Proxy 的公共接口。
  2. RealSubject:定义了代理类所代表的真实对象。
  3. Proxy:保存一个指向 RealSubject 对象的引用,并且可以访问、控制、甚至可以改变它的行为。

在 Java 中,代理模式常见的应用场景包括远程代理、虚拟代理、保护代理和智能引用代理。
类图如下:
在这里插入图片描述

三、代理模式的使用场景
  1. 远程代理:为一个对象在不同地址空间提供局部代表。
  2. 虚拟代理:根据需要创建开销较大的对象。
  3. 保护代理:控制对原始对象的访问。代理对象可以控制对原始对象操作的权限。
  4. 智能引用代理:在访问对象时增加一些额外的操作,比如缓存访问结果、统计访问次数等。
四、动态代理与静态代理的区别

代理模式可以分为静态代理和动态代理两种形式。

1. 静态代理

静态代理是在编译时确定代理类,通过在代理类中预先定义代理对象和目标对象之间的关系。实现相对简单,但扩展性和灵活性有一定局限。

1.1 静态代理的实现

假设在电商交易系统中,我们有一个订单处理服务接口OrderService,实现类负责订单的具体处理。我们希望在订单处理前后记录日志,可以使用静态代理来实现。

1.1.1 代码示例
// 定义订单处理服务接口
public interface OrderService {
    void processOrder(String orderId);
}

// 订单处理服务实现类
public class OrderServiceImpl implements OrderService {
    @Override
    public void processOrder(String orderId) {
        System.out.println("Processing order: " + orderId);
    }
}

// 静态代理类
public class OrderServiceProxy implements OrderService {
    private OrderServiceImpl orderService;

    public OrderServiceProxy(OrderServiceImpl orderService) {
        this.orderService = orderService;
    }

    @Override
    public void processOrder(String orderId) {
        // 在处理订单前记录日志
        System.out.println("Logging: Before processing order " + orderId);
        // 调用实际的订单处理方法
        orderService.processOrder(orderId);
        // 在处理订单后记录日志
        System.out.println("Logging: After processing order " + orderId);
    }
}

// 客户端调用示例
public class Main {
    public static void main(String[] args) {
        OrderServiceImpl orderService = new OrderServiceImpl();
        OrderServiceProxy proxy = new OrderServiceProxy(orderService);
        proxy.processOrder("12345");
    }
}
1.1.2 运行结果分析

执行上述代码,输出结果如下:

Logging: Before processing order 12345
Processing order: 12345
Logging: After processing order 12345

通过静态代理,我们成功在订单处理前后插入了日志记录的逻辑。

1.2 静态代理的优缺点
  • 优点
    • 实现简单,代理类和目标对象之间的关系清晰。
    • 易于理解和调试。
  • 缺点
    • 如果接口有多个实现类,每个实现类都需要创建一个对应的代理类,代码冗余。
    • 如果接口增加了新方法,代理类需要同步修改,增加了维护成本。
    • 缺乏灵活性,代理逻辑固定在代理类中,不易扩展。
2. 动态代理

动态代理是在运行时创建代理类,能够动态处理目标对象的方法调用。Java中的动态代理主要依赖java.lang.reflect.Proxy类(JDK动态代理)和CGLIB库(CGLIB动态代理)实现。相比静态代理,动态代理更加灵活。

2.1 动态代理的实现

继续以电商交易系统为例,我们希望在订单处理前后记录日志,但这次使用动态代理来实现。

2.1.1 JDK动态代理

JDK动态代理只能代理实现了接口的类。它通过Proxy.newProxyInstance方法在运行时生成代理对象。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 动态代理处理器
public class LoggingInvocationHandler implements InvocationHandler {
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在方法调用前记录日志
        System.out.println("Logging: Before method " + method.getName());
        // 调用实际的目标对象方法
        Object result = method.invoke(target, args);
        // 在方法调用后记录日志
        System.out.println("Logging: After method " + method.getName());
        return result;
    }
}

// 客户端调用示例
public class Main {
    public static void main(String[] args) {
        // 创建目标对象
        OrderServiceImpl orderService = new OrderServiceImpl();
        // 创建动态代理
        OrderService proxy = (OrderService) Proxy.newProxyInstance(
                orderService.getClass().getClassLoader(),
                orderService.getClass().getInterfaces(),
                new LoggingInvocationHandler(orderService)
        );
        // 调用代理对象的方法
        proxy.processOrder("12345");
    }
}
2.1.2 CGLIB动态代理

CGLIB动态代理通过生成目标类的子类来创建代理对象,因此即使目标类没有实现接口,也可以使用CGLIB进行代理。

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

// CGLIB动态代理处理器
public class LoggingInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // 在方法调用前记录日志
        System.out.println("Logging: Before method " + method.getName());
        // 调用实际的目标对象方法
        Object result = proxy.invokeSuper(obj, args);
        // 在方法调用后记录日志
        System.out.println("Logging: After method " + method.getName());
        return result;
    }
}

// 客户端调用示例
public class Main {
    public static void main(String[] args) {
        // 创建目标对象
        OrderServiceImpl orderService = new OrderServiceImpl();
        // 创建CGLIB动态代理
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(OrderServiceImpl.class);
        enhancer.setCallback(new LoggingInterceptor());
        OrderServiceImpl proxy = (OrderServiceImpl) enhancer.create();
        // 调用代理对象的方法
        proxy.processOrder("12345");
    }
}
2.1.3 运行结果分析

无论是JDK动态代理还是CGLIB动态代理,执行上述代码后的输出结果类似:

Logging: Before method processOrder
Processing order: 12345
Logging: After method processOrder

动态代理在运行时生成代理对象,并在方法执行前后插入日志逻辑。与静态代理不同的是,动态代理更具灵活性,特别是当接口或目标类有多个方法或复杂逻辑时。

2.2 动态代理的优缺点
  • 优点
    • 灵活性高,能够动态处理目标对象的方法调用。
    • 代码复用性强,一个代理处理器可以代理多个对象。
    • 如果接口增加新方法,代理类无需修改,减少了维护成本。
  • 缺点
    • 调试困难,尤其是异常堆栈信息较复杂时。
    • 性能相对较低,因为动态代理在运行时进行反射调用。
JDK动态代理与CGLIB动态代理的比较
  • JDK动态代理
    • 仅适用于实现了接口的类。
    • 使用java.lang.reflect.Proxy类,性能较CGLIB略高,但在处理大量方法时会受到一定限制。
  • CGLIB动态代理
    • 可以代理没有实现接口的类。
    • 使用底层字节码生成库,性能较好,但在使用时会增加项目的依赖和复杂性。
3. 动态代理与静态代理的使用场景
  • 静态代理:适用于接口较少、代理逻辑简单的场景,例如单个服务的权限控制或简单的日志记录。
  • 动态代理:适用于需要代理多个接口或方法,且代理逻辑复杂的场景,例如系统级别的事务管理、复杂的AOP切面处理。
五、代理模式与其他模式的区别

1. 代理模式 vs 装饰器模式

  • 目的:代理模式的主要目的是控制对对象的访问,而装饰器模式则是动态地增加或扩展对象的功能。
  • 设计意图:代理模式关注的是如何控制对象的访问,装饰器模式则关注对象功能的扩展。
  • 结构区别:代理模式通常只有一个代理对象,而装饰器模式可以有多个装饰器层次。

示例

 // 代理模式中的订单处理
class LoggingOrderProxy implements OrderService {
    private RealOrderService realOrderService;

    public LoggingOrderProxy(RealOrderService realOrderService) {
        this.realOrderService = realOrderService;
    }

    public void processOrder(String orderId) {
        logOrder(orderId);
        realOrderService.processOrder(orderId);
    }

    private void logOrder(String orderId) {
        System.out.println("Logging order: " + orderId);
    }
}

// 装饰器模式中的订单处理
class DiscountOrderDecorator extends RealOrderService {
    private RealOrderService realOrderService;

    public DiscountOrderDecorator(RealOrderService realOrderService) {
        this.realOrderService = realOrderService;
    }

    public void processOrder(String orderId) {
        applyDiscount(orderId);
        realOrderService.processOrder(orderId);
    }

    private void applyDiscount(String orderId) {
        System.out.println("Applying discount to order: " + orderId);
    }
}

在这个示例中,代理模式和装饰器模式都扩展了订单处理功能,但它们的设计意图和应用方式有所不同。

2. 代理模式 vs 中介者模式

  • 职责不同:代理模式的职责是控制对单个对象的访问,中介者模式的职责是协调多个对象之间的交互。
  • 复杂度:中介者模式通常更复杂,因为它要处理多个对象之间的关系,而代理模式只需关注一个对象。

3. 代理模式 vs 外观模式

  • 抽象层次不同:外观模式提供一个简化的接口来访问子系统,而代理模式则提供对单个对象的访问控制。
  • 功能范围:外观模式通常用于简化系统接口,代理模式则用于对象级别的控制。
六、代理模式的优缺点

优点

  • 控制复杂度:通过代理对象来间接访问真实对象,能够减少直接访问复杂对象的复杂度。
  • 扩展功能:可以在不修改真实对象代码的情况下,通过代理对象增加功能。

缺点

  • 性能开销:代理模式会增加一些内存消耗和处理时间,因为它需要保存一份代理对象。
  • 复杂度增加:使用代理模式会使代码变得更加复杂,尤其是嵌套代理时。
七、代理模式在Spring AOP中的应用

在Java的开源框架中,代理模式(Proxy Pattern)被广泛应用,其中最为典型的就是Spring AOP(面向切面编程)。Spring AOP通过代理对象拦截方法调用,注入横切关注点(如事务管理、日志记录等),从而实现业务逻辑与非功能性需求的解耦。

1.1 动态代理的实现

Spring AOP在实现代理时,主要依赖两种方式:JDK动态代理和CGLIB代理。

  • JDK动态代理:当目标对象实现了接口时,Spring AOP默认使用JDK动态代理。
  • CGLIB代理:当目标对象没有实现接口时,Spring AOP会使用CGLIB代理,它通过生成目标类的子类来创建代理对象。

以下我们通过一个实际的Spring AOP示例,来详细讲解代理模式在Spring中的应用。

1.2 代码示例分析
1.2.1 目标对象接口和实现类

首先,我们定义一个简单的服务接口及其实现类。

public interface UserService {
    void createUser(String username);
}

public class UserServiceImpl implements UserService {
    @Override
    public void createUser(String username) {
        System.out.println("User " + username + " has been created.");
    }
}
1.2.2 切面类

接下来,我们定义一个切面类,用于在方法执行前后添加日志。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;

@Aspect
public class LoggingAspect {

    @Before("execution(* com.example.UserService.createUser(..))")
    public void logBefore() {
        System.out.println("Before creating user");
    }

    @After("execution(* com.example.UserService.createUser(..))")
    public void logAfter() {
        System.out.println("After creating user");
    }
}

在这个切面类中,我们使用了@Before@After注解来定义在createUser方法执行前后的日志操作。

1.2.3 Spring配置

为了使Spring能够识别并应用切面类,我们需要进行相应的配置。

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="userService" class="com.example.UserServiceImpl"/>

    <bean id="loggingAspect" class="com.example.LoggingAspect"/>

    <aop:config>
        <aop:aspect ref="loggingAspect">
            <aop:pointcut id="createUserPointcut" expression="execution(* com.example.UserService.createUser(..))"/>
            <aop:before method="logBefore" pointcut-ref="createUserPointcut"/>
            <aop:after method="logAfter" pointcut-ref="createUserPointcut"/>
        </aop:aspect>
    </aop:config>
</beans>

在这个XML配置文件中,我们使用了标签来配置AOP,指定在哪些方法调用前后执行切面逻辑。

1.2.4 运行结果分析

当我们通过Spring容器获取UserService的代理对象并调用createUser方法时,AOP代理会拦截方法调用,并执行我们在切面类中定义的日志操作。

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userService");

userService.createUser("JohnDoe");

执行上面的代码,输出结果如下:

Before creating user
User JohnDoe has been created.
After creating user

从输出结果可以看出,createUser方法的执行被代理对象拦截,且在方法执行前后插入了日志操作。通过代理模式,Spring AOP实现了业务逻辑与日志记录的解耦,使得代码更加模块化和可维护。

1.3 代理模式的优势

在Spring AOP中使用代理模式,开发者可以非常方便地将横切关注点(如事务、日志、安全性)独立出来,避免这些非功能性需求侵入核心业务逻辑。代理模式使得代码更加清晰,并且能够在不修改业务代码的情况下,灵活地添加或移除横切关注点。

在这个示例中,LoggingAspect 使用了 AOP 代理来在订单处理之前记录日志。这种方式极大地简化了业务逻辑的实现,同时保持了代码的整洁性和可维护性。

八、总结

代理模式在 Java 开发中是一个非常实用的模式,尤其是在权限控制、日志记录、性能优化等方面。通过代理模式,开发者可以在不修改原始代码的情况下,动态地增加功能,同时保持系统的稳定性和扩展性。

在设计模式中,代理模式、装饰器模式、中介者模式和外观模式有着各自的应用场景和设计意图。理解它们之间的区别,并在实际项目中合理应用,是提高系统设计质量的重要一步。

  • 10
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

J老熊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值