一、代理模式简介
代理模式(Proxy Pattern)是一种结构型设计模式,它提供了一个代理对象,用来控制对另一个对象的访问。这种模式通常用于在访问对象时引入额外的功能,而不改变对象的接口。代理模式的核心思想是为其他对象提供一种代理,以控制对这个对象的访问。
在现实生活中,代理模式的典型例子是房屋中介。购房者并不会直接联系房主,而是通过中介进行房屋的购买,这个中介相当于代理。
二、代理模式的结构
代理模式的典型结构如下:
- Subject:定义了 RealSubject 和 Proxy 的公共接口。
- RealSubject:定义了代理类所代表的真实对象。
- Proxy:保存一个指向 RealSubject 对象的引用,并且可以访问、控制、甚至可以改变它的行为。
在 Java 中,代理模式常见的应用场景包括远程代理、虚拟代理、保护代理和智能引用代理。
类图如下:
三、代理模式的使用场景
- 远程代理:为一个对象在不同地址空间提供局部代表。
- 虚拟代理:根据需要创建开销较大的对象。
- 保护代理:控制对原始对象的访问。代理对象可以控制对原始对象操作的权限。
- 智能引用代理:在访问对象时增加一些额外的操作,比如缓存访问结果、统计访问次数等。
四、动态代理与静态代理的区别
代理模式可以分为静态代理和动态代理两种形式。
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 开发中是一个非常实用的模式,尤其是在权限控制、日志记录、性能优化等方面。通过代理模式,开发者可以在不修改原始代码的情况下,动态地增加功能,同时保持系统的稳定性和扩展性。
在设计模式中,代理模式、装饰器模式、中介者模式和外观模式有着各自的应用场景和设计意图。理解它们之间的区别,并在实际项目中合理应用,是提高系统设计质量的重要一步。
标签:动态,对象,代理,模式,日志,设计模式,public From: https://blog.csdn.net/weixin_39996520/article/details/141402579