您的位置:首页 > 编程语言 > Java开发

深度解析JAVA动态代理设计模式

2015-11-11 09:16 609 查看
基本概念:Proxy模式也叫代理模式,所谓代理,是指具有与代理元(被代理的对象)具有相同的接口的类,客户端必须通过代理与被代理的目标类交互,而代理一般在交互的过程中(交互前后),进行某些特别的处理(比如说,日志记录,事务控制,权限过滤等等)。

(一)静态代理

项目开发中,我们往往要在某些业务方法之前或者之后加上一些日志记录,但是为了使业务方法更加专注于业务流程的实现,我们希望够把这些记录日志的语句从业务方法中脱离出来,使得整个程序的结构更加清晰。那么有什么方案能够实现这个设计思路呢

下面我们写个例子看看使用静态代理的解决方案

首先我们设计一个IHello接口:

[java] view
plaincopyprint?

public interface IHello{  

     //假设这是一个业务方法  

   public void sayHello(String name);  

}  

然后我们写个类去实现上面这个接口:

[java] view
plaincopyprint?

public class Hello implements IHello{  

         public void sayHello(String name){  

          System.out.println("你好,"+name);  

         }  

}  

现在我们设计一个代理类为sayHello这个方法加上日志记录:

[java] view
plaincopyprint?

public class ProxyHello implements IHello{  

    private IHello hello;  

    public ProxyHello(IHello hello){  

         this.hello=hello;  

    }  

    public void sayHello(String name){  

         System.out.println("sayHello start......");  

         hello.sayHello(name);  

         System.out.println("sayHello end......");  

   }  

}  

最后我们写一个测试类:

[java] view
plaincopyprint?

public class Test{  

     public static void main(String[] args){  

          IHello hello=new ProxyHello(new Hello());  

          hello.sayHello("极度暴走");  

     }  

}  

运行结果:

sayHello start.....

你好,极度暴走

sayHello end ......

从上面的代码我们可以看出,hello对象(代理实例)是由ProxyHello这个代理所创建,无需修改Hello类中的sayHello方法便可为其添加日志记录,并且,当我们以后不需要日志记录时我们只要把生成hello对象的代码改成如下便可:

[java] view
plaincopyprint?

public class Test{  

     public static void main(String[] args){  

          IHello hello=new Hello();  

          hello.sayHello("极度暴走");  

     }  

}  

(二)动态代理

设想一下,如果我们要为项目的所有类的方法加上日志记录,那么根据静态代理的解决方案,我们需要为所有的类设计各自的代理类,这样显然是很麻烦的,并且会使整个程序代码的冗余度很高。这时我们就想,能不能有那么一个类专门用来为我们动态生成代理类,OK,动态代理就因此应运而生了

先来看下动态代理类的设计:

[java] view
plaincopyprint?

import java.lang.reflect.InvocationHandler;  

import java.lang.reflect.Method;  

import java.lang.reflect.Proxy;  

  

public class DynaProxy implements InvocationHandler{  

    //委托对象,即要被代理的对象  

    private Object delegate;  

    //生成指定委托类的代理实例  

    public  Object bind(Object delegate){  

        this.delegate=delegate;  

        return Proxy.newProxyInstance(this.delegate.getClass().getClassLoader(), this.delegate.getClass().getInterfaces(), this);  

    }  

    //实现InvocationHandler的invoke方法,每个代理实例的方法会通过此方法调用委托对象的业务方法,同时加上我们需要的一些操作(如:日志记录)  

    public Object invoke(Object proxy, Method method, Object[] args){  

        Object result=null;       

        try {  

            System.out.println("mehtod start......");  

            //反射调用委托对象,即被代理对象的业务方法  

            result=method.invoke(delegate, args);  

            System.out.println("mehtod end......");  

        } catch (Exception e) {  

            e.printStackTrace();  

        }  

        return result;  

    }  

}  

接下来我们设计一个测试类,从测试类中我们便可以看出动态代理的优势:

[java] view
plaincopyprint?

public void Test{  

    public static void main(){  

        IHello hello=new DynaProxy().bind(new Hello());  

        hello.sayHello("极度暴走");  

        //这里假设我们还有IGoogbye接口和Googbye类  

        IGoogbye googbye=new DynaProxy.bind(new Goodbye());  

        googbye.sayGoogbye("极度暴走");  

    }  

}  

运行结果:

method  start.....

你好,极度暴走

method  end ......

method  start.....

再见,极度暴走

method  end ......

看出优势了没?

简而言之,动态代理为我们动态创建了委托类的代理实例,所有的类都可以通过DynaProxy这个代理类去生产代理实例,而不用为了每个类写一个各自的代理类。

那么动态代理是如何帮我们创建代理实例的呢?

要解答这个问题,必须搞清楚以下几点:

 

1  Proxy.newProxyInstance接口

InvocationHandler接口并没有想象中的那么高深莫测,事实上,这个接口只定义了一个invoke方法,我们要求代理类去实现这个接口,也就是要求代理类要去实现这个invoke方法。上面的例子,我们在invoke这个方法中实现了在业务方法(method参数)前后加上了日志记录操作。很显然,我们最后想达到的效果是,当委托类(如Hello类)的某个方法被调用时,我们能够将这个方法对象(Method)传递给我们的invoke方法,然后通过调用invoke方法实现我们需要的代理操作。

那么invoke方法是什么时候被调用的呢?让我们看完下面的内容再来思考这个问题。

 

2 Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)方法

这个方法实现了代理实例的创建,通过查看JDK的源代码,我发现这个方法的实现主要分下面几个步骤

步骤一:生成一个代理实例的类对象

Class proxyClass = getProxyClass(loader, interfaces);

步骤二:获取proxyClass 对象中含有InvocationHandler.class参数的构造函数

Constructor cons =proxyClass  .getConstructor(InvocationHandler.class);

步骤三:调用上面的构造函数,将我们设计的InvocationHandler的实现类传递进去

return (Object) cons.newInstance(new Object[] { handler });

我们来分析一下第一步生成的类对象,这里我贴出生成的代理类的源代码(反编译得来的):

[java] view
plaincopyprint?

import java.lang.reflect.InvocationHandler;   

import java.lang.reflect.Method;   

import java.lang.reflect.Proxy;   

import java.lang.reflect.UndeclaredThrowableException;   

  

public final class $Proxy0 extends Proxy   

  implements IHello{   

  private static Method m0;   

  private static Method m1;   

  private static Method m2;   

  private static Method m3;   

  static   

  {  //创建我们传递进来的接口中的方法对象  

      m3 = Class.forName("com.IHello").getMethod("sayHello", new Class[0]);   

      //下面三个方法对象是一个类对象应具有的几个基础方法  

      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);   

      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });   

      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);   

      return;   

  }   

    

  //带有InvocationHandler参数的构造函数  

  //查看父类Proxy的源代码可以看出,Proxy类中拥有一个InvocationHandler类型的成员变量h  

  public $Proxy0(InvocationHandler paramInvocationHandler){   

    super(paramInvocationHandler);   

  }   

  

  //调用invoke方法实现代理业务  

  public final void sayHello(){      

      this.h.invoke(this, m3, null);   

      return;   

  }   

  

  public final boolean equals(Object paramObject){  

      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();   

  }   

  

  public final int hashCode()   {     

      return ((Integer)this.h.invoke(this, m0, null)).intValue();   

  }   

    

  public final String toString(){     

      return ((String)this.h.invoke(this, m2, null));   

  }    

}   

为了便于使代码变得更简介,更容易理解,上面的代码中我把所有的异常检测语句去掉了。

从代码中我们可以看出,代理实例类中关联着一个InvocationHandler类型的成员变量h,当代理实例的方法被调用时事实上是调用了h对象的invoke方法,

从而实现了上面我们所说的——“当委托类(如Hello类)的某个方法被调用时,我们能够将这个方法对象(Method)传递给我们的invoke方法,然后通过调用invoke方法实现我们需要的代理操作”。

(三)动态代理的拓展应用——Spring AOP

正如上面分析,我们可以通过动态代理来为我们的业务方法加上日志记录,同理,我们也可以通过动态代理实现对业务方法的事务控制(比如在方法前加上beginTransation,在方法后加上commit)。

Spring中使用AOP(面向切面编程)实现事务控制的原理便是:通过的动态代理的机制,动态生成我们需要的代理实例,然后通过依赖注入,在运行时将这些代理实例注入到对应的委托类的对象中。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: