设计模式——代理模式

更多内容,前往 IT-BLOG

在现实生活中,一个对象不能直接访问另一个对象,这时需要找中介来访问目标对象,此时的中介就是代理对象。例如:租房子时,我们无法与房东取得联系,只能通过某网站与中介进行交易,获取自己心仪的房间等等。在软件设计中,使用代理模式的例子也很多,例如:访问阿里的 maven 仓库,其就是海外 maven 仓库的代理。还有因为安全原因需要屏蔽客户端直接访问真是对象,如某单位的内部数据等。

一、代理模式基本介绍


【1】代理模式:为一个对象提供一个替身,以控制对目标对象的访问。即通过代理对象访问目标对象。这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,及扩展目标对象的功能。
【2】被代理的对象可以是远程对象,创建开销大的对象或需要安全控制的对象。
【3】代理模式有不同的形式,主要有三种:静态代理、动态代理(又称JDK代理、接口代理)和 Cglib 代理(可以在内存动态的创建对象,目标对象也不需要实现接口,它也属于动态代理的范畴,但比较特殊)
【4】代理模式的主要优点:①、代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用。②、代理对象可以扩展目标对象的功能。③、代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度。
【5】代理模式的主要缺点:①、在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢。②、增加了系统的复杂度。

二、静态代理


静态代理在使用时,需要定义接口或父类,被代理对象与代理对象一起实现相同的接口或者继承相同的父类。

静态代理 类图 如下:
 【1】抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
 【2】真实主题(Real Subject)类:实现了出现主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
 【3】代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

  

静态代理 代码 实例:
【1】抽象主题类:代理类与被代理类都需要继承的接口

1 //购票接口
2 public interface Ticketing {
3     //购票
4     public String buy();
5 }

【2】真实主题类:目标类

1 //火车售票官方系统
2 public class RailwaySite implements Ticketing{
3     @Override
4     public String buy() {
5         String ticket = " 调用官方系统购票,票价=120 ";
6         System.out.println();
7         return ticket;
8     }
9 }

【3】代理类:需要实现被代理类的接口,使用代理方法调用目标对象的方法,同时实现对目标方法的扩展。

 1 //实现与目标系统一致的接口
 2 public class ProxyTicketSystem implements Ticketing{
 3     //组合 被代理对象
 4     private Ticketing ticket;
 5     //构造器
 6     public ProxyTicketSystem(Ticketing ticket) {
 7         this.ticket = ticket;
 8     }
 9     
10     @Override
11     public String buy() {
12         System.out.println("代理(智行火车票 系统启动");
13         String ticketInfo = ticket.buy();
14         ticketInfo+="第三方系统服务费 20 总计:140元";
15         System.out.println("代理(智行火车票 系统结束");
16         return ticketInfo;
17     }
18 
19 }

【4】客户端:需要创建被代理对象和代理对象,并进行组合调用。

 1 public class Client {
 2     public static void main(String[] args) {
 3         //被代理类
 4         RailwaySite railwaySite = new RailwaySite();
 5         //获取代理类
 6         ProxyTicketSystem proxy = new ProxyTicketSystem(railwaySite);
 7         //调用代理方法
 8         String buy = proxy.buy();
 9         /**
10          *   代理(智行火车票 系统启动
11          *  代理(智行火车票 系统结束
12          *   调用官方系统购票,票价=120 第三方系统服务费 20 总计:140元
13          */
14         System.out.println(buy);
15     }
16 }

静态代理的 优缺点:

1优点:在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展。
【2】缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类。
【3】一旦接口增加方法,代理对象与目标对象都要维护。

三、动态代理


动态代理基本介绍:1)、代理对象,不需要实现接口,但是目标对象要实现接口,否则不能使用动态代理。
2)、代理对象的生成,是利用 JDK 的 API,动态的在内存中构建代理对象。
3)、动态代理又叫:JDK 代理、接口代理。

JDK 中生成代理对象的 API:代理类所在包:java.lang.reflect.Proxy JDK 实现代理只需要使用 newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )

动态代理类图如下:与动态代理 代码实例相互参考实现。与静态最大的不同在于代理类的不同。代理类无需实现目标类的接口,同时组合的是 Object 通用对象,无需组合目标接口对象,适合所有类(实现了接口)的代理方式,非常通用。
 

动态代理代码实例:【1】抽象主题类:被代理类(目标类)都需要继承的接口

1 public interface ITicket {
2     //购票
3     public String buy();
4 }

【2】真实主题类:目标类

1 public class RailTicketImpl implements ITicket{
2 
3     @Override
4     public String buy() {
5         String ticket = " 调用官方系统购票,票价=120 ";
6         System.out.println();
7         return ticket;
8     }
9 }

【3】代理类:也是动态代理与静态的区别之处,动态代理主要通过 JDK的 Proxy.newProxyInstance方法返回代理对象,且调用 methodinvoke内置方法,并将其结果返回。代理类实现了与目标类的解耦,适合为实现任意接口的所有类做代理。

 1 //代理类  能够实现所有类(必须实现接口)的代理
 2 public class ProxyTicket {
 3     //注入目标类的接口
 4     private Object target;
 5     //构造器
 6     public ProxyTicket(Object target) {
 7         super();
 8         this.target = target;
 9     }
10     
11     public Object getInstance() {
12         // ClassLoader loader =指定当前目标对象使用的类加载器,获取加载器的方法固定
13                 // Class<?>[] interfaces = 目标类实现的接口 使用泛型方法确认类型
14                 //InvocationHandler h = 事件处理,执行目标对象的方法,会触发事件处理器方法,会把当前执行的目标对象方法作为参数传入
15         return Proxy.newProxyInstance(target.getClass().getClassLoader(),
16                 target.getClass().getInterfaces(), new InvocationHandler() {
17                     //Object  proxy  传入代理对象
18                     //method 代理的方法  
19                     // args 代理参数 
20                     @Override
21                     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
22                         System.out.println("代理对象 方法入口");
23                         //调用代理方法时 传入目标对象和参数
24                         Object invoke = method.invoke(target, args);
25                         return invoke;
26                     }
27                 });
28     }
29 }

【4】客户端类:目标对象和代理对象的返回值都必须使用接口接收,否则会出现转换异常。这也是为什么目标类必须实现接口的原因。

 1 public class Client {
 2     public static void main(String[] args) {
 3         //定义目标类(被代理类)
 4         ITicket railTicketImpl = new RailTicketImpl();
 5         //创建代理类
 6         ProxyTicket proxyTicket = new ProxyTicket(railTicketImpl);
 7         //获取代理对象  需要强转对象类型
 8         ITicket ticket = (ITicket)proxyTicket.getInstance();
 9         //调用目标方法,使用 debugger 调试时会发现其调用了 invoke 方法
10         ticket.buy();
11     }
12 }

JDK代理为什么不能直接对非接口的类进行代理由于 Java的单继承,动态生成的代理类已经继承了 Proxy类的,就不能再继承其他的类,所以只能靠实现被代理类的接口的形式,故 JDK的动态代理必须有接口。

四、Cglib 代理


1)、静态代理和动态代理都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理——这就是Cglib 代理
2)、Cglib 代理也叫子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展,有些书也将 Cglib 代理归属于动态代理。
3)、Cglib 是一个非常强大的高性能的代码生成包,它可以在运行期扩展 java 类与实现java 接口。它广泛的被许多 AOP 框架使用,例如:Spring AOP,实现方法的拦截。
4)、在 AOP编程中如何选择代理:目标对象需要实现接口,用 JDK 代理。目标对象不需要实现接口,用 Cglib 代理。
5)、Cglib 包在底层是通过使用字节码处理框架 ASM 来转换字节码并生成新的类。

【1】Cglib 依赖的 jar 包:
 

【2】在内存中动态构建子类,需要注意代理的类不能为 final ,否则会出现:java.lang.IllegalArgumentException 错误。
【3】目标方法不能使用 final/static 修饰,否则不会被拦截,即不会执行。
动态代理 类图 如下

【动态代理代码实例如下】:
【1】被代理类:无需实现接口,很平常的一个类

1 public class RailTicketImpl{
2     public String buy() {
3         String ticket = " 调用官方系统购票,票价=120 ";
4         System.out.println(ticket);
5         return ticket;
6     }
7 }

【2】代理类:需要实现 jar 包中的 MethodInterceptor 接口,重写 intercept 方法,此方法用于拦截代理对象的方法调用。同时需要创建一个代理对象返回方法:getProxyInstall() 可自行定义,其内部通过工具类 enhancer.create() 创建并返回代理对象,代理对象中需要传入父类即目标类等等参数。

 1 public class ProxyFactory implements MethodInterceptor{
 2     //组合目标对象
 3     private Object target;
 4     //构造器
 5     public ProxyFactory(Object target) {
 6         this.target = target;
 7     }
 8     //写一个返回代理对象的方法:target 的代理类
 9     public Object getProxyInstall() {
10         //1.创建一个工具类
11         Enhancer enhancer = new Enhancer();
12         //2.将目标类设置为 父类 , 因为我们创建的是子类
13         enhancer.setSuperclass(target.getClass());
14         //3. 设置回调函数
15         enhancer.setCallback(this);
16         //4. 创建子类对象,即代理对象
17         return enhancer.create();
18     }
19     
20     //重写  intercept 方法,会调用目标对象的方法
21     @Override
22     public Object intercept(Object obj, Method method, Object[] args, MethodProxy arg3) throws Throwable {
23         System.out.println("Cglib代理模式 ~~ 开始");
24         Object returnVal = method.invoke(target, args);
25         System.out.println("Cglib代理模式 ~~ 提交");
26         return returnVal;
27     }
28 }

【3】客户端类:创建目标类和代理类,并调用目标方法,跟踪代码会发现调用了 intercept方法,也就是方法会被 intercept 拦截。此时代理对象的返回值,就可以为目标类。

 1 public class Client {
 2     public static void main(String[] args) {
 3         //目标类
 4         RailTicketImpl ticket = new RailTicketImpl();
 5         //代理工厂类
 6         ProxyFactory proxyFactory = new ProxyFactory(ticket);
 7         //获取代理类 需要强转类型
 8         RailTicketImpl proxyInstall = (RailTicketImpl)proxyFactory.getProxyInstall();
 9         //调用目标方法
10         proxyInstall.buy();
11         /**
12          * 输入如下:
13          * Cglib代理模式 ~~ 开始
14          *  调用官方系统购票,票价=120 
15          * Cglib代理模式 ~~ 提交
16          */
17     }
18 }

五、代理模式的变体(了解)


【1】防火墙(Firewall)代理:内网通过代理穿透防火墙,实现对公网的访问。
【2】缓存(Cache)代理:例如,当请求图片或文件等资源时,先到缓存代理取,如果取到资源则返回,如果取不到资源,再到公网或者数据库中取,然后缓存。
【3】远程(Remote)代理:可以把远程对象在本地 cope 一份来调用。远程代理通过网络和真正的远程对象同步。
【4】同步(Synchronization)代理:主要使用在多线程编程中,完成多线程间同步工作。
【5】智能引用(Smart Reference)代理 等等

 
posted @ 2020-11-19 14:46  Java程序员进阶  阅读(144)  评论(0编辑  收藏  举报