1. 引言
1.1 背景
代理模式(Proxy Pattern)是一种常用的设计模式,它允许通过一个代理对象来控制对另一个对象的访问。在面向对象编程的框架中,代理模式被广泛应用,尤其在Spring框架的AOP(面向切面编程)功能中,用于实现横切关注点的模块化,如事务管理、日志记录、安全检查等。
通过代理模式,我们能够将复杂的功能从主要业务逻辑中剥离出来,使代码更简洁易读。同时,它还能够在不修改原有代码的基础上,动态地增加新功能或优化现有功能,使代码更灵活可扩展。
1.2 目的
本文将详细介绍代理模式的基本概念、实现步骤。通过本篇文章,你将能够理解代理模式的工作原理,并学会如何在实际项目中有效地利用它。
2. 何为代理模式?
代理模式就像是生活中的中介或代理人,他们代表我们处理一些事务,让我们能够更方便、更高效地完成任务。想象一下,你想要买一套房子,但你没有时间或者不熟悉购房流程,这时候你可能会找一个房产中介来帮你处理这些事情。房产中介就是这个场景中的“代理”,而你则是“客户端”,房子和卖家则是“真实对象”。
在这个例子中,房产中介(代理)会:
- 代表你与卖家沟通:中介会代替你与卖家协商价格、查看房子状况等,这样你就不需要亲自去做这些事情。
- 提供额外的服务:中介可能会提供法律咨询、贷款协助等额外服务,帮助你更顺利地完成购房过程。
- 控制访问:中介可能会筛选掉一些不符合你要求的房子,只向你推荐合适的房源,这样你就不需要自己去处理大量的信息。
- 延迟处理:如果你暂时没有时间去看房,中介可以先帮你预约,等到你有时间再去,这样就避免了资源的浪费。
通过房产中介这个代理,你可以在不直接与卖家接触的情况下,完成购房的任务,同时还能享受到额外的服务和便利。
在软件开发中,代理模式也是类似的道理。比如,一个处理敏感数据的应用程序,可以通过代理来控制对数据的访问,确保只有授权的用户才能查看或修改数据。或者,一个需要处理大量计算的应用,可以通过代理来实现计算的延迟加载,只在真正需要时才进行计算,从而提高系统的效率。
2.1 代理模式的主要角色
- Subject(主体):定义了RealSubject和Proxy的共同接口,这样在任何使用RealSubject的地方都可以使用Proxy。
- RealSubject(真实主体):定义了Proxy所代表的真实对象。
- Proxy(代理):持有一个RealSubject的引用,并提供与RealSubject相同的接口,这样代理就可以代替RealSubject。代理对象可以在调用RealSubject的方法前后执行额外的操作。
// 1. 主接口
public interface Subject {
void house();
}
// 2. 真实对象类
public class RealSubject implements Subject {
@Override
public void house() {
System.out.println("买家:筛选出想要的房子");
}
}
// 3. 代理类
public class Proxy implements Subject {
private RealSubject realSubject;
@Override
public void house() {
if (realSubject == null) {
realSubject = new RealSubject();
}
preRequest();
realSubject.house();
postRequest();
}
private void preRequest() {
System.out.println("房产中介: 帮你筛选掉一些不符合要求的房子,只向你推荐合适的房源");
}
private void postRequest() {
System.out.println("房产中介: 提供法律咨询、贷款协助等额外服务,帮助你更顺利地完成购房过程");
}
}
// 4. 客户端代码
public class Client {
public static void main(String[] args) {
Subject proxy = new Proxy();
proxy.house();
}
}
2.2 代理模式的应用场景
- 远程代理:代表一个位于不同地址空间的对象。
- 虚拟代理:根据需要创建开销很大的对象。
- 保护代理:控制对原始对象的访问,用于对象应该有不同的访问权限的情况。
- 智能引用:在访问对象时执行额外的操作,例如计算引用次数。
2.3 代理模式的主要类型
代理模式通常可以分为两种主要类型:静态代理和动态代理。
静态代理(Static Proxy)
静态代理是指在编译时就已经确定的代理类。静态代理类需要手动编写,它实现了与目标对象相同的接口,并在代理类中持有一个目标对象的引用。
静态代理的优点是实现简单,容易理解,但缺点是每当需要代理一个新的接口或类时,都需要手动创建一个新的代理类,这会导致代码冗余和维护成本增加。
代理模式的默认实现通常是静态代理,因为静态代理是最直观和最容易理解的方式:
// 1. 主接口
public interface Subject {
void hourse();
}
// 2. 真实对象类
public class RealSubject implements Subject {
@Override
public void hourse() {
System.out.println("买家:筛选出想要的房子");
}
}
// 3. 代理类
public class Proxy implements Subject {
private RealSubject realSubject;
@Override
public void hourse() {
if (realSubject == null) {
realSubject = new RealSubject();
}
preRequest();
realSubject.hourse();
postRequest();
}
private void preRequest() {
System.out.println("房产中介: 帮你筛选掉一些不符合要求的房子,只向你推荐合适的房源");
}
private void postRequest() {
System.out.println("房产中介: 提供法律咨询、贷款协助等额外服务,帮助你更顺利地完成购房过程");
}
}
// 4. 客户端代码
public class Client {
public static void main(String[] args) {
Subject proxy = new Proxy();
proxy.hourse();
}
}
动态代理(Dynamic Proxy)
动态代理是指在运行时动态生成的代理类。动态代理不需要手动编写代理类,而是通过Java的反射机制在运行时创建代理对象。
动态代理的优点是灵活性高,可以为任意接口或类创建代理对象,而无需手动编写代理类。
Java提供了两种动态代理的实现方式:基于接口的动态代理(使用java.lang.reflect.Proxy类)和基于子类的动态代理(使用CGLIB库)。
我们还是基于买房卖房中介的场景,假设我们有一个房地产中介系统,其中有两个角色:买家和卖家。买家和卖家都可以通过中介进行交易。我们希望在交易过程中添加一些额外的功能,比如记录交易日志、检查交易资格等。
1. 基于接口的动态代理(买家)
基于接口的动态代理是Java标准库中提供的实现方式,使用java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public interface Buyer {
void buyHouse(String houseId);
}
public class RealBuyer implements Buyer {
@Override
public void buyHouse(String houseId) {
System.out.println("买方:需要买的房子的ID是: " + houseId);
}
}
public class BuyerInvocationHandler implements InvocationHandler {
private Object realBuyer;
public BuyerInvocationHandler(Object realBuyer) {
this.realBuyer = realBuyer;
}
@Override
public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
System.out.println("买方中介BuyerInvocationHandler:检查买方资格");
Object result = method.invoke(realBuyer, args);
System.out.println("买方中介BuyerInvocationHandler:日志记录买方的需求");
return result;
}
}
2. 基于子类的动态代理(卖家)
基于子类的动态代理通常使用第三方库,如CGLIB(Code Generation Library)。CGLIB通过生成目标类的子类来实现代理。
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class Seller {
public void sellHouse(String houseId) {
System.out.println("卖家: 需要售卖的房子ID: " + houseId);
}
}
public class SellerMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("卖方中介SellerMethodInterceptor:检查卖方资格");
Object result = methodProxy.invokeSuper(obj, args);
System.out.println("卖方中介SellerMethodInterceptor:日志记录卖方的需求");
return result;
}
}
3. 这样,买方和卖方和中介(代理类)的关系就串起来了,形成一个完整的房地产中介系统
public class Client {
public static void main(String[] args) {
// 买家代理
RealBuyer realBuyer = new RealBuyer();
InvocationHandler buyerHandler = new BuyerInvocationHandler(realBuyer);
Buyer buyerProxy = (Buyer) Proxy.newProxyInstance(
realBuyer.getClass().getClassLoader(),
realBuyer.getClass().getInterfaces(),
buyerHandler);
buyerProxy.buyHouse("123");
// 卖家代理
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Seller.class);
enhancer.setCallback(new SellerMethodInterceptor());
Seller sellerProxy = (Seller) enhancer.create();
sellerProxy.sellHouse("456");
}
}
3. Spring框架中代理模式实现AOP
在Spring框架中,代理模式被广泛应用于AOP(面向切面编程)。在此之前,先简单的过一下什么是AOP编程吧,好有个清晰的认知。
3.1 何为面向切面编程?
AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,旨在通过允许程序员模块化横切关注点(cross-cutting concerns)来提高代码的模块性。横切关注点是指那些影响多个模块的功能,如日志记录、事务管理、安全性检查等,这些功能通常会跨越多个模块,导致代码重复和耦合度增加。
3.2 AOP的核心概念
AOP通过以下几个核心概念来实现横切关注点的模块化:
-
切面(Aspect):一个模块化的横切关注点。切面可以包含多个通知(advice)和切入点(pointcut)。
-
通知(Advice):定义了切面在特定连接点(join point)上执行的动作。通知有多种类型,如前置通知(before advice)、后置通知(after advice)、环绕通知(around advice)等。
-
切入点(Pointcut):定义了通知应该应用的连接点的集合。切入点通过匹配特定的方法或代码位置来确定通知的执行时机。
-
连接点(Join Point):程序执行过程中的一个特定点,如方法调用、异常抛出等。在AOP中,连接点是通知可以插入的地方。
-
引入(Introduction):允许向现有类添加新的方法或字段,从而在不修改现有代码的情况下扩展类的功能。
-
目标对象(Target Object):包含连接点的对象,也就是被代理的对象。
-
代理(Proxy):在目标对象上应用切面后创建的对象。代理对象负责在调用目标对象的方法时插入通知。
3.3 AOP的主要优点包括
代码重用:通过将横切关注点模块化为切面,可以在多个模块中重用这些功能。
降低耦合度:将横切关注点从业务逻辑中分离出来,降低了代码的耦合度。
提高可维护性:模块化的横切关注点使得代码更易于理解和维护。
3.4 AOP的实现流程
Spring AOP支持两种类型的代理:基于接口的动态代理和基于子类的动态代理(CGLIB)。
我们依旧以买家卖家和中介为例,分为四步实现:
1. 定义买卖双方的接口和各自的实现类
2. 创建一个切面类
3. 配置Spring上下文
4. 在客户端测试代理模式是否生效
首先,我们定义买家和卖家接口及其实现类:
public interface Buyer {
void buyHouse(String houseId);
}
public class RealBuyer implements Buyer {
@Override
public void buyHouse(String houseId) {
System.out.println("买方:需要买的房子的ID是: " + houseId);
}
}
public interface Seller {
void sellHouse(String houseId);
}
public class RealSeller implements Seller {
@Override
public void sellHouse(String houseId) {
System.out.println("卖家: 需要售卖的房子ID: " + houseId);
}
}
接下来,我们创建切面类,用于在买家和卖家操作前后添加额外的逻辑:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class TransactionAspect {
@Before("execution(* Buyer.buyHouse(..)) || execution(* Seller.sellHouse(..))")
public void beforeTransaction() {
System.out.println("TransactionAspect: 检查是否有交易资格");
}
@After("execution(* Buyer.buyHouse(..)) || execution(* Seller.sellHouse(..))")
public void afterTransaction() {
System.out.println("TransactionAspect: 日志记录交易双方的需求");
}
}
我们需要配置Spring上下文,启用AOP和组件扫描:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
@Bean
public Buyer buyer() {
return new RealBuyer();
}
@Bean
public Seller seller() {
return new RealSeller();
}
@Bean
public TransactionAspect transactionAspect() {
return new TransactionAspect();
}
}
最后,我们编写一个测试类来验证代理模式是否生效:
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Client {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Buyer buyer = context.getBean(Buyer.class);
Seller seller = context.getBean(Seller.class);
buyer.buyHouse("123");
seller.sellHouse("456");
}
}
虽然Spring AOP默认使用JDK动态代理来实现AOP,但也可以通过配置强制使用CGLIB来实现。
依旧以买家卖家和中介为例,分为四步实现:
1. 配置Spring上下文
2. 定义切面类
3. 定义目标类
4. 在客户端测试代理模式是否生效
首先,我们需要在Spring配置类中启用AOP并设置proxyTargetClass
属性为true
,以强制使用CGLIB代理。
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
// 其他配置
}
接下来,我们创建一个切面类,并使用@Aspect注解标记。这个切面类将包含在买卖双方操作前后执行的通知。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class TransactionAspect {
@Before("execution(* Buyer.buyHouse(..)) || execution(* Seller.sellHouse(..))")
public void beforeTransaction() {
System.out.println("TransactionAspect: 检查是否有交易资格");
}
@After("execution(* Buyer.buyHouse(..)) || execution(* Seller.sellHouse(..))")
public void afterTransaction() {
System.out.println("TransactionAspect: 日志记录交易双方的需求");
}
}
定义买家和卖家类,这些类不需要实现任何接口。
package com.example.service;
import org.springframework.stereotype.Service;
@Service
public class Buyer {
public void buyHouse(String houseId) {
System.out.println("买方:需要买的房子的ID是: " + houseId);
}
}
@Service
public class Seller {
public void sellHouse(String houseId) {
System.out.println("卖家: 需要售卖的房子ID: " + houseId);
}
}
最后,我们编写一个测试类来验证CGLIB代理是否生效。
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.example.service.Buyer;
import com.example.service.Seller;
public class Client {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Buyer buyer = context.getBean(Buyer.class);
Seller seller = context.getBean(Seller.class);
buyer.buyHouse("123");
seller.sellHouse("456");
}
}
4. 总结
讲了这么多,我们也可以看出来代理模式是一种强大的设计模式,它通过引入代理对象来控制对目标对象的访问,并可以在不改变目标对象的情况下添加额外的功能。代理模式在许多框架和库中得到了广泛应用,如Spring AOP、Hibernate等。通过合理使用代理模式,可以提高代码的模块化、可维护性和灵活性。