Java代理模式

代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

代理模式的主要优点有:

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  • 代理对象可以扩展目标对象的功能;
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;


其主要缺点是:

  • 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
  • 增加了系统的复杂度;

代理模式的结构与实现

代理模式的结构比较简单,主要是通过定义一个继承抽象主题的代理来包含真实主题,从而实现对真实主题的访问,下面来分析其基本结构和实现方法。

1. 模式的结构

代理模式的主要角色如下。

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


其结构图如图 1 所示。

 

2. 模式的实现

代理模式的实现代码如下:

package proxy;
public class ProxyTest
{
    public static void main(String[] args)
    {
        Proxy proxy=new Proxy();
        proxy.Request();
    }
}
//抽象主题
interface Subject
{
    void Request();
}
//真实主题
class RealSubject implements Subject
{
    public void Request()
    {
        System.out.println("访问真实主题方法...");
    }
}
//代理
class Proxy implements Subject
{
    private RealSubject realSubject;
    public void Request()
    {
        if (realSubject==null)
        {
            realSubject=new RealSubject();
        }
        preRequest();
        realSubject.Request();
        postRequest();
    }
    public void preRequest()
    {
        System.out.println("访问真实主题之前的预处理。");
    }
    public void postRequest()
    {
        System.out.println("访问真实主题之后的后续处理。");
    }
}

程序运行的结果如下:

访问真实主题之前的预处理。
访问真实主题方法...
访问真实主题之后的后续处理。

代理模式的应用实例

【例1】韶关“天街e角”公司是一家婺源特产公司的代理公司,用代理模式实现。

分析:本实例中的“婺源特产公司”经营许多婺源特产,它是真实主题,提供了显示特产的 display() 方法,可以用窗体程序实现(点此下载该实例所要显示的图片)。而韶关“天街e角”公司是婺源特产公司特产的代理,通过调用婺源特产公司的 display() 方法显示代理产品,当然它可以增加一些额外的处理,如包裝或加价等。客户可通过“天街e角”代理公司间接访问“婺源特产公司”的产品,图 2 所示是公司的结构图。

韶关“天街e角”公园的结构图
图2 韶关“天街e角”公司的结构图

程序代码如下:
package proxy;
import java.awt.*;
import javax.swing.*;
public class WySpecialtyProxy
{
    public static void main(String[] args)
    {
        SgProxy proxy=new SgProxy();
        proxy.display();
    }
}
//抽象主题:特产
interface Specialty
{
    void display();
}
//真实主题:婺源特产
class WySpecialty extends JFrame implements Specialty
{
    private static final long serialVersionUID=1L;
    public WySpecialty()
    {
        super("韶关代理婺源特产测试");
        this.setLayout(new GridLayout(1,1));
        JLabel l1=new JLabel(new ImageIcon("src/proxy/WuyuanSpecialty.jpg"));
        this.add(l1);   
        this.pack();       
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);      
    }
    public void display()
    {
        this.setVisible(true);
    }
}
//代理:韶关代理
class SgProxy implements Specialty
{
    private WySpecialty realSubject=new WySpecialty();
    public void display()
    {
        preRequest();
        realSubject.display();
        postRequest();
    }
    public void preRequest()
    {
          System.out.println("韶关代理婺源特产开始。");
    }
    public void postRequest()
    {
          System.out.println("韶关代理婺源特产结束。");
    }
}

代理模式的应用场景

前面分析了代理模式的结构与特点,现在来分析以下的应用场景。

  • 远程代理,这种方式通常是为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问。例如,用户申请某些网盘空间时,会在用户的文件系统中建立一个虚拟的硬盘,用户访问虚拟硬盘时实际访问的是网盘空间。
  • 虚拟代理,这种方式通常用于要创建的目标对象开销很大时。例如,下载一幅很大的图像需要很长时间,因某种计算比较复杂而短时间无法完成,这时可以先用小比例的虚拟代理替换真实的对象,消除用户对服务器慢的感觉。
  • 安全代理,这种方式通常用于控制不同种类客户对真实对象的访问权限。
  • 智能指引,主要用于调用目标对象时,代理附加一些额外的处理功能。例如,增加计算真实对象的引用次数的功能,这样当该对象没有被引用时,就可以自动释放它。
  • 延迟加载,指为了提高系统的性能,延迟对目标的加载。例如,Hibernate 中就存在属性的延迟加载和关联表的延时加载。

代理模式的扩展

在前面介绍的代理模式中,代理类中包含了对真实主题的引用,这种方式存在两个缺点。

  1. 真实主题与代理主题一一对应,增加真实主题也要增加代理。
  2. 设计代理以前真实主题必须事先存在,不太灵活。采用动态代理模式可以解决以上问题,如 SpringAOP,其结构图如图 4 所示。

 

动态代理模式的结构图
图4 动态代理模式的结构图
 
 

当然代理模式中,用的最广泛的,用的最多的是  动态代理模式。

动态代理:就是实现阶段不用关系代理是哪个,而在运行阶段指定具体哪个代理。

抽象接口的类图如下:

      --图来自设计模式之禅

所以动态代理模式要有一个InvocationHandler接口 和 GamePlayerIH实现类。其中 InvocationHandler是JD提供的动态代理接口,对被代理类的方法进行代理。

代码实现如下

抽象主题类或者接口:

复制代码
 1 package com.yemaozi.proxy.dynamic;
 2 
 3 /*
 4  * 动态代理:就是实现阶段不用关系代理是哪个,而在运行阶段指定具体哪个代理。
 5  */
 6 public interface IGamePlayer {
 7     //登录游戏
 8     public void login(String username, String password);
 9     
10     //击杀Boss
11     public void killBoss();
12     
13     //升级
14     public void upGrade();
15 }
复制代码

 

需要被代理类:

复制代码
 1 package com.yemaozi.proxy.dynamic;
 2 
 3 public class GamePlayer implements IGamePlayer {
 4     
 5     private String name = "";
 6     
 7     public GamePlayer(String name){
 8         this.name = name;
 9     }
10     
11     public void login(String username, String password) {
12         System.out.println("登录名为 "+username+" 进入游戏," + name + " 登录成功!");
13     }
14         
15     public void killBoss() {
16         System.out.println(this.name + " 击杀了Boss!");
17     }
18 
19     public void upGrade() {
20         System.out.println(this.name + "升级了!");
21     }
22 
23 }
复制代码

 

动态代理处理器类:

复制代码
 1 package com.yemaozi.proxy.dynamic;
 2 
 3 import java.lang.reflect.InvocationHandler;
 4 import java.lang.reflect.Method;
 5 
 6 public class GamePlayerInvocationHandler implements InvocationHandler{
 7 
 8     //被代理的对象
 9     private Object obj;
10     
11     //将需要代理的实例通过处理器类的构造方法传递给代理。
12     public GamePlayerInvocationHandler(Object obj){
13         this.obj = obj;
14     }
15     
16     public Object invoke(Object proxy, Method method, Object[] args)
17             throws Throwable {
18         Object result = null;
19         if("login".equalsIgnoreCase(method.getName())){
20             //这个在主题方法不受任何影响的情况下,在主题方法前后添加新的功能,或者增强主题方法,
21             //从侧面切入从而达到扩展的效果的编程,就是面向切面编程(AOP Aspect Oriented Programming)。
22             //AOP并不是新技术,而是相对于面向对象编程的一种新的编程思想。在日志,事务,权限等方面使用较多。
23             System.out.println("代理登录游戏!");
24             result = method.invoke(this.obj, args);
25             return result;
26         }
27         result = method.invoke(this.obj, args);
28         return result;
29     }
30 
31 }
复制代码

由于代理是动态产生的,所以不需要再声明代理类。

 

动态代理场景类:

复制代码
package com.yemaozi.proxy.dynamic;

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

public class Client {
    public static void main(String[] args) {
        IGamePlayer gp = new GamePlayer("张三");
        InvocationHandler gpHandler = new GamePlayerInvocationHandler(gp);
        //获取真实主题类的ClassLoader
        ClassLoader classLoader = gp.getClass().getClassLoader();
        //动态产生一个代理者。
        Class<?>[] cls = new Class[]{IGamePlayer.class};
        IGamePlayer proxyGp = (IGamePlayer) Proxy.newProxyInstance(classLoader, cls, gpHandler);
        proxyGp.login("zhangsan", "123456");
        proxyGp.killBoss();
        proxyGp.upGrade();
    }
}

执行结果:
代理登录游戏!
登录名为 zhangsan 进入游戏,张三 登录成功!
张三 击杀了Boss!
张三升级了!

//在此,我们没有创建代理类,但是确实有代理类帮我们完成事情。
复制代码

其中,在此代理模式中,不仅代理是动态产生的(即在运行的时候生成),而且还在代理的时候,也增加了一些处理。在此处增加的处理,其实就是另一种编程思想-----面向切面编程思想(AOP Aspect Oriented Programming)。

带有AOP的动态代理模式类图:

        --图来自设计模式之禅

从上图中,可以看出有两个相对独立的模块(Subject和InvocationHandler)。动态代理实现代理的职责,业务逻辑Subject实现相关的逻辑功能,两者之间没有必然的相互耦合的关系。然而,通知Advice从另一个切面切入,最终在上层模块就是Client耦合,完成逻辑的封装。

代码清单如下

抽象主题或者接口:

复制代码
1 package com.yemaozi.proxy.dynamic_aop;
2 
3 public interface Subject {
4     public void doSomething(String str);
5     //...可以多个逻辑处理方法。。。
6 }
复制代码

 

真实主题:

复制代码
 1 package com.yemaozi.proxy.dynamic_aop;
 2 
 3 public class RealSubject implements Subject{
 4 
 5     public void doSomething(String str) {
 6         //do something...
 7         System.out.println("do something..." + str);
 8     }
 9 
10 }
复制代码

 

通知接口:

复制代码
1 package com.yemaozi.proxy.dynamic_aop;
2 
3 //通知接口及定义、
4 public interface IAdvice {
5     public void exec();
6 }
复制代码

 

前置通知:

复制代码
1 package com.yemaozi.proxy.dynamic_aop;
2 
3 public class BeforeAdvice implements IAdvice {
4     //在被代理的方法前来执行,从而达到扩展功能。
5     public void exec() {
6         System.out.println("前置通知被执行!");
7     }
8 }
复制代码

 

后置通知:

复制代码
1 package com.yemaozi.proxy.dynamic_aop;
2 
3 public class AfterAdvice implements IAdvice {
4     
5     //在被代理的方法后来执行,从而达到扩展功能。
6     public void exec() {
7         System.out.println("后置通知被执行!");
8     }
9 }
复制代码

 

动态代理的处理器类:

所有的方法通过invoke方法类实现。

复制代码
 1 package com.yemaozi.proxy.dynamic_aop;
 2 
 3 import java.lang.reflect.InvocationHandler;
 4 import java.lang.reflect.Method;
 5 
 6 public class MyInvocationHandler implements InvocationHandler {
 7 
 8     //被代理的对象
 9     private Subject realSubject;
10     //通过MyInvocationHandler的构造方法将被代理对象传递过来。
11     public MyInvocationHandler(Subject realSubject){
12         this.realSubject = realSubject;
13     }
14     //执行被代理类的方法。
15     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
16         //在执行方法前,执行前置通知。
17         IAdvice beforeAdvice = new BeforeAdvice();
18         beforeAdvice.exec();
19         Object result = method.invoke(this.realSubject, args);
20         //在执行方法后,执行后置通知。
21         IAdvice afterAdvice = new AfterAdvice();
22         afterAdvice.exec();
23         //前置通知,和后置通知,都是要看具体实际的业务需求来进行添加。
24         return result;
25     }
26 
27 }
复制代码

 

动态代理类:

复制代码
 1 package com.yemaozi.proxy.dynamic_aop;
 2 
 3 import java.lang.reflect.InvocationHandler;
 4 import java.lang.reflect.Proxy;
 5 
 6 public class DynamicProxy {
 7     
 8     /**
 9      * public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler handler)
10      * loader:
11      *             一个ClassLoader对象,定义了由哪个ClassLoader对象,来对生产的代理进行加载。
12      * interfaces:
13      *             一个Interfaces数组,表示我将要给我所代理的对象提供一组什么样的接口,
14      *             如果提供一组接口给它,那么该代理对象就宣称实现了该接口,从而可以调用接口中的方法。
15      *             即,查找出真是主题类的所实现的所有的接口。
16      * handler:
17      *             一个InvocationHandler对象,表示当我这个动态代理对象在调用方法时,会关联到该InvocationHandler对象。
18      *             该InvocationHandler与主题类有着关联。
19      */
20     public static <T> T newProxyInstance(ClassLoader classLoader, Class<?>[] interfaces, InvocationHandler handler){
21         @SuppressWarnings("unchecked")
22         T t = (T) Proxy.newProxyInstance(classLoader, interfaces, handler);
23         return t;
24     }
25 }
复制代码

从动态的产生动态代理类。

 

动态代理场景类:

复制代码
 1 package com.yemaozi.proxy.dynamic_aop;
 2 
 3 import java.lang.reflect.InvocationHandler;
 4 
 5 public class AOPClient {
 6     
 7     public static void main(String[] args) {
 8         Subject realSubject = new RealSubject();
 9         InvocationHandler handler = new MyInvocationHandler(realSubject); 
10         ClassLoader classLoader = realSubject.getClass().getClassLoader();
11         Class<?>[] interfaces = realSubject.getClass().getInterfaces();
12         Subject proxySubect = DynamicProxy.newProxyInstance(classLoader, interfaces, handler);
13         proxySubect.doSomething("这是一个Dynamic AOP示例!!!");
14     }
15 }
16 
17 执行结果:
18 前置通知被执行!
19 do something...这是一个Dynamic AOP示例!!!
20 后置通知被执行!
复制代码

动态代理中invoke的动态调用:

动态代理类DynamicProxy是个纯粹的动态创建代理类通用类。

所以在具体业务中,可以在进一步封装具体的具有业务逻辑意义的DynamicProxy类。

代码如下

具体业务的动态代理:

复制代码
 1 package com.yemaozi.proxy.dynamic_aop;
 2 
 3 import java.lang.reflect.InvocationHandler;
 4 //具体业务的动态代理。
 5 public class SubjectDynamicProxy extends DynamicProxy {
 6     public static <T> T newProxyInstance(Subject subject){
 7         ClassLoader classLoader = subject.getClass().getClassLoader();
 8         Class<?>[] interfaces = subject.getClass().getInterfaces();
 9         InvocationHandler handler = new MyInvocationHandler(subject);
10         T t = newProxyInstance(classLoader, interfaces, handler);
11         return t;
12     }
13 }
复制代码

动态代理在现在用的是非常的多的,如像Spring AOP ,DBCP连接池,AspectJ等。。。

posted @ 2019-04-11 20:45  一心二念  阅读(353)  评论(0编辑  收藏  举报