1. 程式人生 > >深入理解 Java 動態代理機制

深入理解 Java 動態代理機制

Java 有兩種代理方式,一種是靜態代理,另一種是動態代理。對於靜態代理,其實就是通過依賴注入,對物件進行封裝,不讓外部知道實現的細節。很多 API 就是通過這種形式來封裝的。

代理模式結構圖(圖片來自《大話設計模式》)

下面看下兩者在概念上的解釋:

靜態代理
靜態代理類:由程式設計師建立或者由第三方工具生成,再進行編譯;在程式執行之前,代理類的.class檔案已經存在了。

靜態代理類通常只代理一個類。

靜態代理事先知道要代理的是什麼。

動態代理
動態代理類:在程式執行時,通過反射機制動態生成。

動態代理類通常代理介面下的所有類。

動態代理事先不知道要代理的是什麼,只有在執行的時候才能確定。

動態代理的呼叫處理程式必須事先InvocationHandler介面,及使用Proxy類中的newProxyInstance方法動態的建立代理類。

Java動態代理只能代理介面,要代理類需要使用第三方的CLIGB等類庫。

動態代理的好處
Java動態代理的優勢是實現無侵入式的程式碼擴充套件,也就是方法的增強;讓你可以在不用修改原始碼的情況下,增強一些方法;在方法的前後你可以做你任何想做的事情(甚至不去執行這個方法就可以)。此外,也可以減少程式碼量,如果採用靜態代理,類的方法比較多的時候,得手寫大量程式碼。

動態代理例項
靜態代理的例項這裡就不說了,比較簡單。在 java 的 java.lang.reflect 包下提供了一個 Proxy 類和一個 InvocationHandler 介面,通過這個類和這個介面可以生成 JDK 動態代理類和動態代理物件。下面講講動態代理的實現。

先定義一個介面:

public interface Person {
    void setName(String name);
}
再定義一個學生 Student 類來實現 Person 介面,每一個學生都有一個自己的名字:

public class Student implements Person {
    
    private String mName;

    public Student(String name) {
        mName = name;
    }

    public void setName(String name) {
        mName = name;
    }
}


 Student 類中,定義了一個私有變數 mName,用來表示 Student 的名字。接下去定義一個代理 handler,就是用來幫我們處理代理的 :

public class PersonHandler<T> implements InvocationHandler {
    // 代理的目標物件
    private T mTarget;

    public PersonHandler(T target) {
        mTarget = target;
    }

    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        // 呼叫開始前的操作
        ProxyUtil.start();
        // 呼叫方法,通過反射的形式來呼叫 mTarget 的 method 
        Object result = method.invoke(mTarget, objects);
        // 列印改名前的名字
        ProxyUtil.log(objects[0].toString());
        // 呼叫結束後的操作
        ProxyUtil.finish();
        return result;
    }
}


可以發現,我們在呼叫程式碼前後都做了一些操作,甚至可以直接攔截該方法,不讓其執行。其中定義了一個 ProxyUtil 類,方便我們做一些操作:

public class ProxyUtil {
    private static final String TAG = "ProxyUtil";

    public static void start() {
        Log.d(TAG, "start: " + System.currentTimeMillis());
    }

    public static void finish() {
        Log.d(TAG, "finish: " + System.currentTimeMillis());
    }

    public static void log(String name) {
        Log.d(TAG, "log: " + name);
    }
}


接下去開始編寫代理的實現:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //建立一個例項物件,這個物件是被代理的物件
        Person zhangsan = new Student("張三");

        //建立一個與代理物件相關聯的 InvocationHandler
        PersonHandler stuHandler = new PersonHandler<>(zhangsan);

        //建立一個代理物件 stuProxy 來代理 zhangsan,代理物件的每個執行方法都會替換執行 Invocation 中的 invoke 方法
        Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);

        //代理執行 setName 的方法
        stuProxy.setName("王五");
}


看下列印輸出:

可以發現代理成功了。並且我們在呼叫方式的之前之後,都做了一些操作。Spring 的 AOP 其就是通過動態代理的機制實現的。

其中,我們將 stuProxy 的類名打印出來:

Log.d(TAG, "onCreate: " + stuProxy.getClass().getCanonicalName());


 發現其名字竟然是 $Proxy0。具體原因下面會解釋。

原始碼分析
上面我們利用 Proxy 類的 newProxyInstance 方法建立了一個動態代理物件,檢視該方法的原始碼: 

/**
     * Returns an instance of a proxy class for the specified interfaces
     * that dispatches method invocations to the specified invocation
     * handler.
     *
     * <p>{@code Proxy.newProxyInstance} throws
     * {@code IllegalArgumentException} for the same reasons that
     * {@code Proxy.getProxyClass} does.
     *
     * @param   loader the class loader to define the proxy class
     * @param   interfaces the list of interfaces for the proxy class
     *          to implement
     * @param   h the invocation handler to dispatch method invocations to
     * @return  a proxy instance with the specified invocation handler of a
     *          proxy class that is defined by the specified class loader
     *          and that implements the specified interfaces
     * @throws  IllegalArgumentException if any of the restrictions on the
     *          parameters that may be passed to {@code getProxyClass}
     *          are violated
     * @throws  SecurityException if a security manager, <em>s</em>, is present
     *          and any of the following conditions is met:
     *          <ul>
     *          <li> the given {@code loader} is {@code null} and
     *               the caller's class loader is not {@code null} and the
     *               invocation of {@link SecurityManager#checkPermission
     *               s.checkPermission} with
     *               {@code RuntimePermission("getClassLoader")} permission
     *               denies access;</li>
     *          <li> for each proxy interface, {@code intf},
     *               the caller's class loader is not the same as or an
     *               ancestor of the class loader for {@code intf} and
     *               invocation of {@link SecurityManager#checkPackageAccess
     *               s.checkPackageAccess()} denies access to {@code intf};</li>
     *          <li> any of the given proxy interfaces is non-public and the
     *               caller class is not in the same {@linkplain Package runtime package}
     *               as the non-public interface and the invocation of
     *               {@link SecurityManager#checkPermission s.checkPermission} with
     *               {@code ReflectPermission("newProxyInPackage.{package name}")}
     *               permission denies access.</li>
     *          </ul>
     * @throws  NullPointerException if the {@code interfaces} array
     *          argument or any of its elements are {@code null}, or
     *          if the invocation handler, {@code h}, is
     *          {@code null}
     */
    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
     // 判空,判斷 h 物件是否為空,為空就丟擲 NullPointerException
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
       // 進行包訪問許可權、類載入器等許可權檢查
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            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 方法的原始碼:

/**
     * Generate a proxy class.  Must call the checkProxyAccess method
     * to perform permission checks before calling this.
     */
    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
     // 數量超過 65535 就丟擲異常,665535 這個就不用說了吧
        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 = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
 關鍵點在於  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<ClassLoader, Class<?>[], 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<Class<?>, 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());
            }
        }
    }


這裡會通過反射獲取介面的各種修飾符,包名等,然後根據規則命名代理類,最後呼叫 ProxyGenerator.generateProxyClass 生成了代理類。

 ProxyGenerator.generateProxyClass 具體實現在 eclipse 上開啟後,說是找不到原始碼:

 不過,從其他地方找到了部分程式碼:

public static byte[] generateProxyClass(final String name,  
                                           Class[] interfaces)  
   {  
       ProxyGenerator gen = new ProxyGenerator(name, interfaces);  
    // 這裡動態生成代理類的位元組碼,由於比較複雜就不進去看了  
       final byte[] classFile = gen.generateClassFile();  
  
    // 如果saveGeneratedFiles的值為true,則會把所生成的代理類的位元組碼儲存到硬碟上  
       if (saveGeneratedFiles) {  
           java.security.AccessController.doPrivileged(  
           new java.security.PrivilegedAction<Void>() {  
               public Void run() {  
                   try {  
                       FileOutputStream file =  
                           new FileOutputStream(dotToSlash(name) + ".class");  
                       file.write(classFile);  
                       file.close();  
                       return null;  
                   } catch (IOException e) {  
                       throw new InternalError(  
                           "I/O exception saving generated file: " + e);  
                   }  
               }  
           });  
       }  
  
    // 返回代理類的位元組碼  
       return classFile;  
   }  


我們可以自己試試 ProxyGenerator.generateProxyClass 的功能。

public class ProxyGeneratorUtils {
    /**
     * 把代理類的位元組碼寫到硬碟上 
     * @param path 儲存路徑 
     */
    public static void writeProxyClassToHardDisk(String path) {
// 獲取代理類的位元組碼  
        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy11", Student.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();
            }
        }
    }
}


main 方法裡面進行呼叫 :

public class Main {
    public static void main(String[] args) {
        ProxyGeneratorUtils.writeProxyClassToHardDisk("$Proxy0.class");
    }
}
可以發現,在根目錄下生成了一個  $Proxy0.class 檔案,檔案內容反編譯後如下:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.Person;

public final class $Proxy0 extends Proxy implements Person
{
  private static Method m1;
  private static Method m2;
  private static Method m3;
  private static Method m0;
  
  /**
  *注意這裡是生成代理類的構造方法,方法引數為InvocationHandler型別,看到這,是不是就有點明白
  *為何代理物件呼叫方法都是執行InvocationHandler中的invoke方法,而InvocationHandler又持有一個
  *被代理物件的例項,不禁會想難道是....? 沒錯,就是你想的那樣。
  *
  *super(paramInvocationHandler),是呼叫父類Proxy的構造方法。
  *父類持有:protected InvocationHandler h;
  *Proxy構造方法:
  *    protected Proxy(InvocationHandler h) {
  *         Objects.requireNonNull(h);
  *         this.h = h;
  *     }
  *
  */
  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }
  
  //這個靜態塊本來是在最後的,我把它拿到前面來,方便描述
   static
  {
    try
    {
      //看看這兒靜態塊兒裡面有什麼,是不是找到了giveMoney方法。請記住giveMoney通過反射得到的名字m3,其他的先不管
      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("proxy.Person")www.thd540.com .getMethod("giveMoney", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException www.jimeiyulept.com  localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
 
  /**
  * 
  *這裡呼叫代理物件的giveMoney方法,直接就呼叫了InvocationHandler中的invoke方法,並把m3傳了進去。
  *this.h.invoke(this, m3, null);這裡簡單,明瞭。
  *來,再想想,代理物件持有一個InvocationHandler物件,InvocationHandler物件持有一個被代理的物件,
  *再聯絡到InvacationHandler中的invoke方法。嗯,就是這樣。
  */
  public final void giveMoney()
    throws 
  {
    try
    {
      this.h.invoke(this,www.dasheng178.com m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowable www.ysyl157.com Exception(localThrowable);
    }
  }

  //注意,這裡為了節省篇幅,省去了toString,hashCode、equals方法的內容。原理和giveMoney方法一毛一樣。

}


jdk 為我們的生成了一個叫 $Proxy0(這個名字後面的0是編號,有多個代理類會一次遞增)的代理類,這個類檔案時放在記憶體中的,我們在建立代理物件時,就是通過反射獲得這個類的構造方法,然後建立的代理例項。通過對這個生成的代理類原始碼的檢視,我們很容易能看出,動態代理實現的具體過程。

我們可以對 InvocationHandler 看做一箇中介類,中介類持有一個被代理物件,在 invoke 方法中呼叫了被代理物件的相應方法,而生成的代理類中持有中介類,因此,當我們在呼叫代理類的時候,就是再呼叫中介類的 invoke 方法,通過反射轉為對被代理物件的呼叫。

代理類呼叫自己方法時,通過自身持有的中介類物件來呼叫中介類物件的 invoke 方法,從而達到代理執行被代理物件的方法。也就是說,動態代理通過中介類實現了具體的代理功能。

生成的代理類:$Proxy0 extends Proxy implements Person,我們看到代理類繼承了 Proxy 類,所以也就決定了 java 動態代理只能對介面進行代理,Java 的繼承機制註定了這些動態代理類們無法實現對 class 的動態代理。

參考文章:

1、java的動態代理機制詳解

2、徹底理解JAVA動態代理

3、java 1.8 動態代理原始碼分析

4、java動態代理實現與原理詳細分析