1. 程式人生 > >經典三層框架初識(二)---Spring 3.2動態代理

經典三層框架初識(二)---Spring 3.2動態代理

上面的遺留問題需要我們使用這次講的動態代理來解決.由於在網上看到一個關於動態代理富含原始碼解釋的文章,覺得很棒.這裡就通過轉載的方式和大家分享一下.

動態代理JDK代理:

  • 動態代理有以下特點:

    • 代理物件,不需要實現介面
    • 代理物件的生成,是利用JDK的API,動態的在記憶體中建立代理物件(需要我們指定建立代理物件/目標物件實現的介面的型別)

    • 動態代理也叫做:JDK代理,介面代理
  • JDK中生成代理物件的API

    • 代理類所在包:java.lang.reflect.Proxy.JDK實現代理只需要使用newProxyInstance方法,但是該方法需要接收三個引數,完整的寫法是:static Object newProxyInstance(ClassLoader loader, Class<?>[ ] interfaces,InvocationHandler h )
    • 方法是在Proxy類中是靜態方法,且接收的三個引數依次為:
      • ClassLoader loader  //指定當前目標物件使用類載入器
      • Class<?>[ ] interfaces  //目標物件實現的介面的型別,使用泛型方式確認型別
      • InvocationHandler h  //事件處理器
  • 下面進行程式碼演示:

    • 介面類IUserDao
    • /**
       * 介面
       */
      public interface IUserDao {
      
          void save();
      
      }

      目標物件UserDao

    • /**
       * 介面實現
       * 目標物件
       */
      public class UserDao implements IUserDao {
      
          public void save() {
      
              System.out.println("----儲存資料成功!----");
          }
      
      }

      代理工廠類:ProxyFactory

    • /**
       * 建立動態代理物件
       * 動態代理不需要實現介面,但是需要指定介面型別
       */
      public class ProxyFactory{
      
          //維護一個目標物件
          private Object target;
          public ProxyFactory(Object target){
              this.target=target;
          }
      
         //給目標物件生成代理物件
          public Object getProxyInstance(){
              return Proxy.newProxyInstance(
                      target.getClass().getClassLoader(),
                      target.getClass().getInterfaces(),
                      new InvocationHandler() {
                          @Override
                          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                              System.out.println("開始事務111");
                              //執行目標物件方法
                              Object returnValue = method.invoke(target, args);
                              System.out.println("提交事務111");
                              return returnValue;
                          }
                      }
              );
          }
      
      }

      測試類:App:

    • /**
       * 測試類
       */
      public class App {
          public static void main(String[] args) {
              // 目標物件
              IUserDao target = new UserDao();
              // 【原始的型別 class com.zhong.UserDao】
              System.out.println(target.getClass());
      
              // 給目標物件,建立代理物件
              IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
              // class $Proxy0   記憶體中動態生成的代理物件
              System.out.println(proxy.getClass());
      
              // 執行方法   【代理物件】
              proxy.save();
          }
      }

      在這裡我們會想:代理物件是誰,是如何生成這個代理物件的呢?接下來我們主要看這個方法  getProxyInstance() 

    • //給目標物件生成代理物件
          public Object getProxyInstance(){
              return Proxy.newProxyInstance(
                      target.getClass().getClassLoader(),
                      target.getClass().getInterfaces(),
                      new InvocationHandler() {
                          @Override
                          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                              System.out.println("開始事務111");
                              //執行目標物件方法
                              Object returnValue = method.invoke(target, args);
                              System.out.println("提交事務111");
                              return returnValue;
                          }
                      }
              );

      我們看到其返回了一個Proxy類的物件,即JDK的動態代理,是通過一個叫Proxy的類的靜態方法newProxyInstance來實現的,其那麼我們就去它的原始碼裡看一下它到底都做了些什麼?

    • public static Object newProxyInstance(ClassLoader loader,
                                                Class>[] interfaces,
                                                InvocationHandler h)
              throws IllegalArgumentException
          {
              //檢查h 不為空,否則拋異常
              Objects.requireNonNull(h);
       
              final Class>[] intfs = interfaces.clone();
              final SecurityManager sm = System.getSecurityManager();
              if (sm != null) {
                  checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
              }
       
              /*
               * 獲得與指定類裝載器和一組介面相關的代理類型別物件
               */
              Class> cl = getProxyClass0(loader, intfs);
       
              /*
               * 通過反射獲取建構函式物件並生成代理類例項
               */
              try {
                  if (sm != null) {
                      checkNewProxyPermission(Reflection.getCallerClass(), cl);
                  }
                  //獲取代理物件的構造方法(也就是$Proxy0(InvocationHandler h)) 
                  final Constructor> cons = cl.getConstructor(constructorParams);
                  final InvocationHandler ih = h;
                  if (!Modifier.isPublic(cl.getModifiers())) {
                      AccessController.doPrivileged(new PrivilegedAction() {
                          public Void run() {
                              cons.setAccessible(true);
                              return null;
                          }
                      });
                  }
                  //生成代理類的例項並把InvocationHandlerImpl的例項傳給它的構造方法
                  return cons.newInstance(new Object[]{h});
              } catch (IllegalAccessException|InstantiationException e) {
                  throw new InternalError(e.toString(), e);
              } catch (InvocationTargetException e) {
                  Throwable t = e.getCause();
                  if (t instanceof RuntimeException) {
                      throw (RuntimeException) t;
                  } else {
                      throw new InternalError(t.toString(), t);
                  }
              } catch (NoSuchMethodException e) {
                  throw new InternalError(e.toString(), e);
              }
          }

      上面的程式碼表明,首先通過getProxyClass0獲得這個代理類,然後通過c1.getConstructor()拿到建構函式,最後一步,通過cons.newInstance返回這個新的代理類的一個例項,注意:呼叫newInstance的時候,傳入的引數為h,即我們自己定義好的InvocationHandler類 。我們再進去getProxyClass0方法看一下:

    • /**
           * Generate a proxy class.  Must call the checkProxyAccess method
           * to perform permission checks before calling this.
           */
          private static Class> getProxyClass0(ClassLoader loader,
                                                 Class>... interfaces) {
              if (interfaces.length > 65535) {
                  throw new IllegalArgumentException("interface limit exceeded");
              }
       
              // If the proxy class defined by the given loader implementing
              // the given interfaces exists, this will simply return the cached copy;
              // otherwise, it will create the proxy class via the ProxyClassFactory
              return proxyClassCache.get(loader, interfaces);
          }

      這裡用到了快取,先從快取裡查一下,如果存在,直接返回,不存在就新建立。

      真相還是沒有來到,繼續,看一下proxyClassCache

    • /**
           * a cache of proxy classes
           */
          private static final WeakCache[], Class>>
              proxyClassCache = new WeakCache(new KeyFactory(), new ProxyClassFactory());

      再看下proxyClassCache.get方法,

    • public synchronized V get() { // serialize access
                  // re-check
                  Supplier supplier = valuesMap.get(subKey);
                  if (supplier != this) {
                      // something changed while we were waiting:
                      // might be that we were replaced by a CacheValue
                      // or were removed because of failure ->
                      // return null to signal WeakCache.get() to retry
                      // the loop
                      return null;
                  }
                  // else still us (supplier == this)
       
                  // create new value
                  V value = null;
                  try {
                      value = Objects.requireNonNull(valueFactory.apply(key, parameter));
                  } finally {
                      if (value == null) { // remove us on failure
                          valuesMap.remove(subKey, this);
                      }
                  }
                  // the only path to reach here is with non-null value
                  assert value != null;
       
                  // wrap value with CacheValue (WeakReference)
                  CacheValue cacheValue = new CacheValue(value);
       
                  // try replacing us with CacheValue (this should always succeed)
                  if (valuesMap.replace(subKey, this, cacheValue)) {
                      // put also in reverseMap
                      reverseMap.put(cacheValue, Boolean.TRUE);
                  } else {
                      throw new AssertionError("Should not reach here");
                  }
       
                  // successfully replaced us with new CacheValue -> return the value
                  // wrapped by it
                  return value;
              }
          }

      其中,value = Objects.requireNonNull(valueFactory.apply(key, parameter));

      提到了apply(),是Proxy類的內部類ProxyClassFactory實現其介面的一個方法,具體實現如下:

    • /**
           * A factory function that generates, defines and returns the proxy class given
           * the ClassLoader and array of interfaces.
           */
          private static final class ProxyClassFactory
              implements BiFunction[], Class>>
          {
              // prefix for all proxy class names
              private static final String proxyClassNamePrefix = "$Proxy";
       
              // next number to use for generation of unique proxy class names
              private static final AtomicLong nextUniqueNumber = new AtomicLong();
       
              @Override
              public Class> apply(ClassLoader loader, Class>[] interfaces) {
       
                  Map, Boolean> interfaceSet = new IdentityHashMap(interfaces.length);
                  for (Class> intf : interfaces) {
                      /*
                       * Verify that the class loader resolves the name of this
                       * interface to the same Class object.
                       */
                      Class> interfaceClass = null;
                      try {
                          interfaceClass = Class.forName(intf.getName(), false, loader);
                      } catch (ClassNotFoundException e) {
                      }
                      if (interfaceClass != intf) {
                          throw new IllegalArgumentException(
                              intf + " is not visible from class loader");
                      }
                      /*
                       * Verify that the Class object actually represents an
                       * interface.
                       */
                      if (!interfaceClass.isInterface()) {
                          throw new IllegalArgumentException(
                              interfaceClass.getName() + " is not an interface");
                      }
                      /*
                       * Verify that this interface is not a duplicate.
                       */
                      if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                          throw new IllegalArgumentException(
                              "repeated interface: " + interfaceClass.getName());
                      }
                  }
       
                  String proxyPkg = null;     // package to define proxy class in
                  int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
       
                  /*
                   * Record the package of a non-public proxy interface so that the
                   * proxy class will be defined in the same package.  Verify that
                   * all non-public proxy interfaces are in the same package.
                   */
                  for (Class> intf : interfaces) {
                      int flags = intf.getModifiers();
                      if (!Modifier.isPublic(flags)) {
                          accessFlags = Modifier.FINAL;
                          String name = intf.getName();
                          int n = name.lastIndexOf('.');
                          String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                          if (proxyPkg == null) {
                              proxyPkg = pkg;
                          } else if (!pkg.equals(proxyPkg)) {
                              throw new IllegalArgumentException(
                                  "non-public interfaces from different packages");
                          }
                      }
                  }
       
                  if (proxyPkg == null) {
                      // if no non-public proxy interfaces, use com.sun.proxy package
                      proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
                  }
       
                  /*
                   * Choose a name for the proxy class to generate.
                   */
                  long num = nextUniqueNumber.getAndIncrement();
                  String proxyName = proxyPkg + proxyClassNamePrefix + num;
       
                  /*
                   * Generate the specified proxy class.
                   */
                  byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                      proxyName, interfaces, accessFlags);
                  try {
                      return defineClass0(loader, proxyName,
                                          proxyClassFile, 0, proxyClassFile.length);
                  } catch (ClassFormatError e) {
                      /*
                       * A ClassFormatError here means that (barring bugs in the
                       * proxy class generation code) there was some other
                       * invalid aspect of the arguments supplied to the proxy
                       * class creation (such as virtual machine limitations
                       * exceeded).
                       */
                      throw new IllegalArgumentException(e.toString());
                  }
              }
          }

      這裡我們看到了熟悉的方法Class.forName();要載入指定的介面,即是生成類,那就有對應的class位元組碼

    • /生成位元組碼
      byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);

      接下來我們也使用測試一下,使用這個方法生成的位元組碼是個什麼樣子:

    • package com.adam.java.basic;
      
      import java.io.FileOutputStream;
      import java.io.IOException;
      import sun.misc.ProxyGenerator;
      
      public class DynamicProxyTest {
      
          public static void main(String[] args) {
      
               IUserDao  userdao = new UserDao();
      
               ProxyFactory  handler = new  ProxyFactory  (
                      userdao);
      
              IUserDao   proxy = (IUserDao ) handler.getProxyInstance();
      
              proxy.save();
              
              String path = "C:/$Proxy0.class";
              byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0",
                      UserDao.class.getInterfaces());
              FileOutputStream out = null;
      
              try {
                  out = new FileOutputStream(path);
                  out.write(classFile);
                  out.flush();
              } catch (Exception e) {
                  e.printStackTrace();
              } finally {
                  try {
                      out.close();
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          }
      }

      不是原始的IUserDao裡的save()方法了,而是新生成的代理類的save()方法,我們將生成的$Proxy0.class檔案用jd-gui開啟

    • public final void save()
          throws 
        {
          try
          {
            this.h.invoke(this, m3, null);
            return;
          }
          catch (Error|RuntimeException localError)
          {
            throw localError;
          }
          catch (Throwable localThrowable)
          {
            throw new UndeclaredThrowableException(localThrowable);
          }
        }

      核心就在於this.h.invoke(this. m3, null);此處的h是啥呢?我們看看這個類的類名:

      public final class $Proxy0 extends Proxy implements IUserDao

      不難發現,新生成的這個類,繼承了Proxy類實現了IUserDao這個介面,而這個UserService就是我們指定的介面,所以,這裡我們基本可以斷定,JDK的動態代理,生成的新代理類就是繼承了Proxy基類,實現了傳入的介面的類。那這個h到底是啥呢?我們再看看這個新代理類,看看建構函式:

    • public $Proxy0(InvocationHandler paramInvocationHandler)  
          throws   
        {  
          super(paramInvocationHandler);  
      
        }

      這裡傳入了InvocationHandler型別的引數,而之前有一句程式碼:

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

      這是newInstance方法的最後一句,傳入的h,就是這裡用到的h,也就是我們最初自己定義的MyInvocationHandler類的例項。所以,我們發現,其實最後呼叫的save()方法,其實呼叫的是ProxyFactory的invoke()方法.繼續看:

    • 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("com.zhong.IUserDao").getMethod("save", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            return;
          }

      m3就是原介面的save()方法.

    • 通過跟蹤提示程式碼可以看出:當代理物件呼叫真實物件的方法時,其會自動的跳轉到代理物件關聯的handler物件的invoke方法來進行呼叫。

  • 總結:

    • 動態代理實現過程:
    • 1. 通過getProxyClass0()生成代理類。JDK生成的最終真正的代理類,它繼承自Proxy並實現了我們定義的介面.
    • 2. 通過Proxy.newProxyInstance()生成代理類的例項物件,建立物件時傳入InvocationHandler型別的例項。
    • 3. 呼叫新例項的方法,即此例中的save(),即原InvocationHandler類中的invoke()方法。
  • 代理物件不需要實現介面,但是目標物件一定要實現介面,否則不能用動態代理