Java设计模式及应用场景之《代理模式》

Daisy ·
更新时间:2024-11-13
· 575 次阅读

文章目录一、代理模式定义二、代理模式的结构和说明三、代理模式的分类四、代理模式示例五、动态代理1、JDK动态代理JDK动态代理使用步骤JDK动态代理示例JDK动态代理实现原理JDK动态代理局限性2、CGLIB动态代理CGLIB动态代理使用步骤CGLIB动态代理示例CGLIB动态代理实现原理CGLIB动态代理注意事项六、三种代理方式的对比七、代理模式的应用场景及案例 一、代理模式定义

Provide a surrogate or placeholder for another object to control access to it.
为其它对象提供一种代理以控制对这个对象的访问。

  代理模式是通过创建一个代理对象,用代理对象去代表真实的对象。当客户端去操作这个代理对象的时候,实际上功能最终还是会由真实的对象来完成,只不过是通过代理操作的,也就是客户端操作代理,代理操作真实对象。
  代理对象夹在客户端和真实对象中间,相当于一个中转,那么在中转的时候,我们就可以实现很多的花招,比如,权限控制事务记录日志等。

二、代理模式的结构和说明

1、通过聚合方式实现代理模式(代理对象和真实对象实现相同的接口,代理对象持有真实对象的实例)

在这里插入图片描述
2、通过继承方式实现代理模式(代理对象继承自真实对象)
在这里插入图片描述

聚合比继承更适合代理模式,如果需要叠加多种代理需求,使用聚合方式更加灵活。

Subject 目标接口 RealSubject 具体的目标对象 Proxy 代理对象 三、代理模式的分类 虚拟代理(Virtual Proxy)
如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。 保护代理(Protect Proxy)
控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。 远程代理(Remote Proxy)
为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又称为大使(Ambassador)。远程代理在Java中最典型的应用就是RMI技术。 缓冲代理(Cache Proxy)
为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。 智能引用代理(Smart Reference Proxy)
当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数记录下来等。 copy on write 代理(Copy on write Proxy)
只有对象确实改变了,才会真的去拷贝(或克隆)这个对象。 防火墙代理(Firewall Proxy)
保护对象不被恶意用户访问和操作。 同步代理(Synchronization Proxy)
使多个用户可以同时访问目标对象而不会出现冲突。 四、代理模式示例

  让我们来模拟一个保护代理的场景。有一个订单系统,要求是,一旦订单被创建,只有订单的创建人才能修改订单的订购数量,其他人则不能修改。

OrderApi 订单对象的接口定义(目标接口)

/** * 订单对象的接口定义(目标接口) */ public interface OrderApi { /** * 修改订单数量 * @param orderNum 订单数量 * @param user 修改人 */ void changeOrderNum(int orderNum,String user); }

Order 订单对象(具体的目标对象)

/** * 订单对象(具体的目标对象) */ @Data public class Order implements OrderApi{ /** * 订单订购的产品名称 */ private String productName; /** * 订单订购的数量 */ private int orderNum; /** * 创建订单的人员 */ private String orderUser; /** * 构造方法,传入构建需要的数据 * @param productName 订单订购的产品名称 * @param orderNum 订单订购的数量 * @param orderUser 创建订单的人员 */ public Order(String productName,int orderNum,String orderUser){ this.productName = productName; this.orderNum = orderNum; this.orderUser = orderUser; } @Override public void changeOrderNum(int orderNum, String user) { this.orderNum = orderNum; } @Override public String toString() { return "Order{" + "productName='" + productName + '\'' + ", orderNum=" + orderNum + ", orderUser='" + orderUser + '\'' + '}'; } }

OrderProxy 订单的代理对象

/** * 订单的代理对象 */ public class OrderProxy implements OrderApi{ /** * 持有被代理的具体的目标对象 */ private Order order; /** * 构造方法,传入被代理的具体的目标对象 * @param realSubject 被代理的具体的目标对象 */ public OrderProxy(Order realSubject){ this.order = realSubject; } @Override public void changeOrderNum(int orderNum, String user) { //控制访问权限,只有创建订单的人员才能够修改 if(Objects.equals(user, order.getOrderUser())){ order.changeOrderNum(orderNum,user); }else{ System.out.println("对不起"+user+",您无权修改订单中的产品名称。"); } } @Override public String toString() { return "OrderProxy{" + "order=" + order + '}'; } }

以上,保护代理已经设计好了,下边我们来模拟一下使用。

public class Client { public static void main(String[] args) { //张三先登录系统创建了一个订单 OrderApi order = new OrderProxy(new Order("泡面",1,"张三")); //李四想要来修改,那就会报错 order.changeOrderNum(3, "李四"); //输出order System.out.println("李四修改后订单记录没有变化:"+order); //张三修改就不会有问题 order.changeOrderNum(3, "张三"); //再次输出order System.out.println("张三修改后,订单记录:"+order); } }

执行main方法后,得到下边的输出:

对不起李四,您无权修改订单中的产品名称。
李四修改后订单记录没有变化:OrderProxy{order=Order{productName=‘泡面’, orderNum=1, orderUser=‘张三’}}
张三修改后,订单记录:OrderProxy{order=Order{productName=‘泡面’, orderNum=3, orderUser=‘张三’}}

五、动态代理

  像前边定义的这种,代理类在程序运行前就已经存在,代理类所实现的接口和所代理的方法都被固定,这种代理方式被称为静态代理
  静态代理会存在这样的缺点:如果Subject(目标接口)中方法发生变化,那么真实类和代理类都需要跟着变化;如果需要被代理的真实类有很多,就需要相应的创建很多个代理类。
  那么,有没有一种机制能够让系统在运行时动态创建代理类,并可以对多个真实类进行代理操作?有,这就是我们将要介绍的这种动态代理(Dynamic Proxy)。
  动态代理是一种较为高级的代理模式,它在事务管理、AOP(Aspect-OrientedProgramming,切面编程)等领域都发挥了重要的作用。

1、JDK动态代理

  从JDK 1.3开始,Java语言提供了对动态代理的支持,Java语言实现动态代理时需要用到位于java.lang.reflect包中的一些类,一般主要用到到以下两个类:

InvocationHandler 接口
InvocationHandler接口是代理处理程序类的实现接口,该接口作为代理实例的调用处理者的公共父类,每一个代理类的实例都可以提供一个相关的具体调用处理者(InvocationHandler接口的子类)。
客户端在调用动态代理对象的方法时,调用请求会将请求自动转发给InvocationHandler对象的invoke()方法,由invoke()方法来实现对请求的统一处理。

Proxy
Proxy类提供了用于创建动态代理类和实例对象的方法。

JDK动态代理使用步骤 创建一个实现接口 InvocationHandler 的类,重写invoke方法。 通过 Proxy 的静态方法 newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) 创建一个代理对象。 通过代理对象调用方法。 JDK动态代理示例

假设我们的订单系统需要实现一个日志功能,在用户做一些关键操作时,将操作信息记录到数据库中。

UserService 用户业务层的接口定义(目标接口)

/** * 用户业务层的接口定义 */ public interface UserService { /** * 修改密码 */ void changePwd(); }

UserServiceImpl 用户业务层的具体处理类(真实的对象)

/** * 用户业务层的具体处理类 */ public class UserServiceImpl implements UserService{ @Override public void changePwd() { System.out.println("用户修改密码"); } }

以上,目标接口和实现类已经有了,接下来我们来定义一个处理类,声明一下代理中具体要执行的动作。

LogHandler 使用JDK动态代理时具体处理者

/** * 使用JDK动态代理时具体处理者 */ public class LogHandler implements InvocationHandler { /** * 被代理的真实对象 */ private Object object; public LogHandler(Object object) { this.object = object; } /** * @param proxy 代理对象,Proxy.newProxyInstance方法的返回对象 * @param method 调用的方法 * @param args 方法中的参数 * @return 调用方法的返回值 * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(String.format("%s.%s 方法被调用 -- 保存到数据库", object.getClass().getSimpleName(),method.getName())); return method.invoke(object, args); } }

我们来看下怎么来使用JDK动态代理

public class Client { public static void main(String[] args) { // 创建一个用户业务的真实处理对象 UserService userService = new UserServiceImpl(); // 创建处理程序实现类(InvocationHandler) LogHandler logHandler = new LogHandler(userService); Class cls = userService.getClass(); /** * 创建代理对象 * 三个参数分别为:类加载器、目标接口、处理程序实现类(InvocationHandler) */ UserService userServiceProxy = (UserService) Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), logHandler); // 执行修改密码方法 userServiceProxy.changePwd(); } }

执行main方法后,得到下边的输出:

UserServiceImpl.changePwd 方法被调用 – 保存到数据库
用户修改密码

如果我们的目标接口中方法有变动,如新增一个方法:

UserService 用户业务层的接口定义(目标接口)

/** * 用户业务层的接口定义 */ public interface UserService { /** * 修改密码 */ void changePwd(); /** * 修改收货地址(新增方法) */ void changeAddr(); }

UserServiceImpl 用户业务层的具体处理类(具体的目标对象)

/** * 用户业务层的具体处理类 */ public class UserServiceImpl implements UserService{ @Override public void changePwd() { System.out.println("用户修改密码"); } @Override public void changeAddr() { System.out.println("用户修改收货地址"); } }

我们新增的方法加好了,我们再来看看动态代理能否对新方法有效

public class Client { public static void main(String[] args) { // 创建一个用户业务的真实处理对象 UserService userService = new UserServiceImpl(); // 创建处理程序实现类(InvocationHandler) LogHandler logHandler = new LogHandler(userService); Class cls = userService.getClass(); /** * 创建代理对象 * 三个参数分别为:类加载器、目标接口、处理程序实现类(InvocationHandler) */ UserService userServiceProxy = (UserService) Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), logHandler); // 执行修改密码方法 userServiceProxy.changePwd(); // 执行修改收货地址方法 userServiceProxy.changeAddr(); } }

执行main方法后,得到下边的输出:

UserServiceImpl.changePwd 方法被调用 – 保存到数据库
用户修改密码
UserServiceImpl.changeAddr 方法被调用 – 保存到数据库
用户修改收货地址

从输出中,我们可以看出,动态代理对新增的方法同样有效。

那么,如果我们需要代理的类非常多怎么办?来,安排上。

OrderService 订单业务层的接口定义(目标接口)

/** * 订单业务层的接口定义 */ public interface OrderService { /** * 保存订单 */ void saveOrder(); }

OrderServiceImpl 订单业务层的具体处理类(具体的目标对象)

/** * 订单业务层的具体处理类 */ public class OrderServiceImpl implements OrderService{ @Override public void saveOrder() { System.out.println("保存订单信息"); } }

新的接口和具体实现类也加好了,我们再来看看动态代理能否有效

public class Client { public static void main(String[] args) { // 创建一个订单业务的真实处理对象 OrderService orderService = new OrderServiceImpl(); // 创建处理程序实现类(InvocationHandler) LogHandler logHandler = new LogHandler(orderService); Class cls = orderService.getClass(); /** * 创建代理对象 * 三个参数分别为:类加载器、目标接口、处理程序实现类(InvocationHandler) */ OrderService orderServiceProxy = (OrderService) Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), logHandler); // 执行代理对象的保存订单方法 orderServiceProxy.saveOrder(); } }

执行main方法后,得到下边的输出:

OrderServiceImpl.saveOrder 方法被调用 – 保存到数据库
保存订单信息

同样有效,这样一来,我们就不需要像静态代理那样创建那么多的代理类了。

JDK动态代理实现原理

  利用Java的反射技术(Java Reflection),在运行时创建一个实现给定目标接口的新类(也称“动态代理类”),将产生的新类进行编译生成class文件,将class字节码文件Load进内存,最后创建一个此动态代理类的对象。

JDK动态代理局限性

  使用JDK动态代理,要求委托类必须实现一个接口,但事实上并不是所有类都有接口,对于没有实现接口的类,便无法使用该方式实现动态代理。

2、CGLIB动态代理

  前面介绍了JDK动态代理的局限性,它要求被代理类必须实现一个接口。
  而CGLIB动态代理则没有此类强制性要求。CGLib动态代理是代理类去继承目标类,然后重写目标类的方法。既然是继承,则被代理类不能为final,并且 final 和static 修饰的方法也不会被拦截。
  使用CGLIB动态代理,需要引入cglib.jar这个Jar包,一般主要用到到以下两个类:

MethodInterceptor 接口
MethodInterceptor 接口是一个方法拦截器接口,接口中只有一个intercept回调方法,我们要实现的业务在这个方法中添加即可。 Enhancer
CGLIB通过Enhancer类来动态创建代理类和实例对象。 CGLIB动态代理使用步骤 创建一个实现接口 MethodInterceptor 的类,重写 intercept 方法。 通过 Enhancer 的 create 方法创建一个代理对象。 通过代理对象调用方法。 CGLIB动态代理示例

我们还拿前面JDK动态代理中的示例来做下演示。为了演示CGLIB对没有实现接口的类也可以实现动态代理,我们这里先把接口去掉。

OrderServiceImpl 订单业务层的具体处理类

/** * 订单业务层的具体处理类 */ public class OrderServiceImpl{ public void saveOrder() { System.out.println("保存订单信息"); } }

我们来创建一个方法拦截器。

LogMethodInterceptor 使用CGLIB动态代理时具体处理者

/** * 使用CGLIB动态代理时具体处理者 */ public class LogMethodInterceptor implements MethodInterceptor { /** * @param obj CGLib动态生成的代理类实例 * @param method 被代理的方法 * @param args 方法的参数 * @param methodProxy 代理类对方法的代理 * @return * @throws Throwable */ @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println(String.format("%s.%s 方法被调用 -- 保存到数据库", method.getDeclaringClass().getSimpleName(),method.getName())); return methodProxy.invokeSuper(obj,args); } }

我们来看下怎么来使用CGLIB动态代理

public class Client { public static void main(String[] args) { // 创建拦截器(MethodInterceptor) LogMethodInterceptor logMethodInterceptor = new LogMethodInterceptor(); // 创建Enhancer对象,类似于JDK动态代理的Proxy类 Enhancer enhancer = new Enhancer(); // 设置被代理类,也就是父类 enhancer.setSuperclass(OrderServiceImpl.class); // 设置回调函数 enhancer.setCallback(logMethodInterceptor); // 这里的create方法就是正式创建代理类 OrderServiceImpl orderServiceProxy = (OrderServiceImpl) enhancer.create(); // 执行代理对象的保存订单方法 orderServiceProxy.saveOrder(); } }

执行main方法后,得到下边的输出:

OrderServiceImpl.saveOrder 方法被调用 – 保存到数据库
保存订单信息

CGLIB动态代理实现原理

  CGLIB会让生成的代理类继承被代理类,并在代理类中对代理方法进行强化处理(前置处理、后置处理等)。在CGLIB底层,其实是借助了ASM这个非常强大的Java字节码生成框架。生成字节码文件,再将生成的字节码文件Load进内存,最后创建一个动态代理类对象。
  使用字节码技术生成代理类,比使用Java反射效率要高。

CGLIB动态代理注意事项

  CGLib实现动态代理,不受代理类必须实现接口的限制,但是它不能对 final 类进行代理,也不能拦截被 final 和 static 修饰的方法。

六、三种代理方式的对比
代理方式 优点 缺点
静态代理 实现简单,容易理解。 被代理对象增删等变动方法时,代理类也需要改变。并且,如果被代理的类很多,也需要相应创建很多个代理类。
JDK动态代理 动态创建代理类,代码复用率高 。 只能够代理实现了接口的委托类。
CGLIB动态代理 动态创建代理类,且被代理类无需实现接口。使用字节码技术生成代理类,比使用Java反射效率要高。 不能对 final 类,以及 final 和 static 修饰的方法进行代理。
七、代理模式的应用场景及案例 需要为一个对象在不同的地址空间提供局部代表的时候,可以使用远程代理。 需要按需创建开销很大的对象的时候,可以使用虚拟代理。 需要控制对象访问权限的时候,可以使用保护代理。 需要对访问对象执行一些附加操作的时候,可以使用智能引用代理。 Hibernate 的延迟加载(Lazy Load)就是虚拟代理的典型应用。 Spring AOP就是动态代理的典型应用。
作者:晓呆同学



JAVA 代理 代理模式 java设计模式

需要 登录 后方可回复, 如果你还没有账号请 注册新账号