说说Java代理模式

fed2riloh12y 8年前
   <p>代理实现可以分为静态代理和动态代理。</p>    <h2><strong>静态代理</strong></h2>    <p>静态代理模式其实很常见,比如买火车票这件小事:黄牛相当于是火车站的代理,我们可以通过黄牛买票,但只能去火车站进行改签和退票。在代码实现中相当于为一个委托对象realSubject提供一个代理对象proxy,通过proxy可以调用realSubject的部分功能,并添加一些额外的业务处理,同时可以屏蔽realSubject中未开放的接口。</p>    <p><img src="https://simg.open-open.com/show/b878d785ad52a116d2c29b9e07090a87.png"></p>    <p>1、RealSubject 是委托类,Proxy 是代理类;</p>    <p>2、Subject 是委托类和代理类的接口;</p>    <p>3、request() 是委托类和代理类的共同方法;</p>    <p>具体代码实现如下:</p>    <pre>  <code class="language-java">interface Subject {      void request();  }    class RealSubject implements Subject {      public void request(){          System.out.println("RealSubject");      }  }    class Proxy implements Subject {      private Subject subject;        public Proxy(Subject subject){          this.subject = subject;      }      public void request(){          System.out.println("begin");          subject.request();          System.out.println("end");      }  }    public class ProxyTest {      public static void main(String args[]) {          RealSubject subject = new RealSubject();          Proxy p = new Proxy(subject);          p.request();      }  }</code></pre>    <p>静态代理实现中,一个委托类对应一个代理类,代理类在编译期间就已经确定。</p>    <h2><strong>动态代理</strong></h2>    <p>动态代理中,代理类并不是在Java代码中实现,而是在运行时期生成,相比静态代理,动态代理可以很方便的对委托类的方法进行统一处理,如添加方法调用次数、添加日志功能等等,动态代理分为jdk动态代理和cglib动态代理,下面我们通过一个例子看看如何实现jdk动态代理。</p>    <p>1、定义业务逻辑</p>    <pre>  <code class="language-java">public interface Service {        //目标方法       public abstract void add();    }     public class UserServiceImpl implements Service {        public void add() {            System.out.println("This is add service");        }    }</code></pre>    <p>2、利用 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口定义代理类的实现。</p>    <pre>  <code class="language-java">class MyInvocatioHandler implements InvocationHandler {      private Object target;        public MyInvocatioHandler(Object target) {          this.target = target;      }        @Override      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {          System.out.println("-----before-----");          Object result = method.invoke(target, args);          System.out.println("-----end-----");          return result;      }      // 生成代理对象      public Object getProxy() {          ClassLoader loader = Thread.currentThread().getContextClassLoader();          Class<?>[] interfaces = target.getClass().getInterfaces();          return Proxy.newProxyInstance(loader, interfaces, this);      }  }</code></pre>    <p>3、使用动态代理</p>    <pre>  <code class="language-java">public class ProxyTest {      public static void main(String[] args) {          Service service = new UserServiceImpl();          MyInvocatioHandler handler = new MyInvocatioHandler(service);          Service serviceProxy = (Service)handler.getProxy();          serviceProxy.add();      }  }</code></pre>    <p>执行结果:</p>    <pre>  <code class="language-java">-----before-----  This is add service  -----end-----</code></pre>    <p>代理对象的生成过程由Proxy类的newProxyInstance方法实现,分为3个步骤:</p>    <p>1、 ProxyGenerator.generateProxyClass 方法负责生成代理类的字节码,生成逻辑比较复杂,有兴趣的同学可以继续分析源码 <a href="/misc/goto?guid=4959714390635768505" rel="nofollow,noindex">sun.misc.ProxyGenerator</a> ;</p>    <pre>  <code class="language-java">// proxyName:格式如 "com.sun.proxy.$Proxy.1";  // interfaces:代理类需要实现的接口数组;  // accessFlags:代理类的访问标识;  byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);</code></pre>    <p>2、native方法 Proxy.defineClass0 负责字节码加载的实现,并返回对应的Class对象。</p>    <pre>  <code class="language-java">Class clazz = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);</code></pre>    <p>3、利用 clazz.newInstance 反射机制生成代理类的对象;</p>    <p><strong>反编译代理类</strong></p>    <p>为了更清楚的理解动态代理,通过以下方式把代理类字节码生成class文件。</p>    <pre>  <code class="language-java">byte[] classFile = ProxyGenerator.generateProxyClass("com.sun.proxy.$Proxy.1", service.getClass().getInterfaces());  FileOutputStream out = new FileOutputStream("com.sun.proxy.$Proxy.1.class");  out.write(classFile);  out.flush();</code></pre>    <p>使用 <a href="/misc/goto?guid=4958847400135189856" rel="nofollow,noindex">反编译工具 jad</a> jad com.sun.proxy.$Proxy.1 看看代理类如何实现,反编译出来的java代码如下:</p>    <pre>  <code class="language-java">public final class $proxy1 extends Proxy implements Service {        public $proxy1(InvocationHandler invocationhandler) {          super(invocationhandler);      }        public final boolean equals(Object obj) {          try {              return ((Boolean)super.h.invoke(this, m1, new Object[] {                  obj              })).booleanValue();          }          catch(Error _ex) { }          catch(Throwable throwable) {              throw new UndeclaredThrowableException(throwable);          }      }        public final String toString() {          try {              return (String)super.h.invoke(this, m2, null);          }          catch(Error _ex) { }          catch(Throwable throwable) {              throw new UndeclaredThrowableException(throwable);          }      }        public final void add() {          try {              super.h.invoke(this, m3, null);              return;          }          catch(Error _ex) { }          catch(Throwable throwable) {              throw new UndeclaredThrowableException(throwable);          }      }        public final int hashCode() {          try {              return ((Integer)super.h.invoke(this, m0, null)).intValue();          }          catch(Error _ex) { }          catch(Throwable throwable) {              throw new UndeclaredThrowableException(throwable);          }      }        private static Method m1;      private static Method m2;      private static Method m3;      private static Method m0;        static {          try {              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]);              m3 = Class.forName("zzzzzz.Service").getMethod("add", new Class[0]);              m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);          }          catch(NoSuchMethodException nosuchmethodexception) {              throw new NoSuchMethodError(nosuchmethodexception.getMessage());          }          catch(ClassNotFoundException classnotfoundexception) {              throw new NoClassDefFoundError(classnotfoundexception.getMessage());          }      }  }</code></pre>    <p>从上述代码可以发现:</p>    <p>1、生成的$proxy1继承自Proxy类,并实现了Service接口。</p>    <p>2、执行代理对象的方法,其实就是执行InvocationHandle对象的invoke方法,传入的参数分别是当前代理对象,当前执行的方法和参数。</p>    <pre>  <code class="language-java">super.h.invoke(this, m3, null);</code></pre>    <p><strong>jdk动态代理使用的局限性</strong><br> 通过反射类<code>Proxy</code>和<code>InvocationHandler</code>回调接口实现的jdk动态代理,要求委托类必须实现一个接口,但事实上并不是所有类都有接口,对于没有实现接口的类,便无法使用该方方式实现动态代理。</p>    <p> </p>    <p>来自:http://www.jianshu.com/p/a1d094fc6c00</p>    <p> </p>