Java動態代理深度解析

Java動態代理深度解析

引言

提及動態代理,不少人可能都沒有直接去使用過。可是隻要用過Spring,那動態代理就是一個是個繞不過的坎,由於Spring的核心特性之一AOP就是基於動態代理來實現的,那麼什麼狀況下須要用到動態代理呢?java

場景

考慮這樣一個教師的接口:app

public interface Teacher {
    void teach();
}

假設咱們有一個TeacherChan的實現類,陳老師教的是攝影:ide

public class TeacherChan implements Teacher {

    @Override
    public void teach() {
        System.out.println("你們好,我是陳老師,我教你們攝影!");
    }
    
}

另外還有一個TeacherCang的實現類,蒼老師教的是生物:函數

public class TeacherCang implements Teacher {

    @Override
    public void teach() {
        System.out.println("你們好,我是蒼老師,我教你們生物!");
    }
    
}

不論是陳老師仍是蒼老師,只要實現了Teacher這個接口,給咱們傳道授業解惑,爲了禮貌起見,咱們總應該給人家問聲好吧。而問好這件事不須要老師主動要求,能夠交給代理來作,每次有老師來上課,代理自動作了問好這件事。而代理類又分爲靜態代理和動態代理,靜態代理在編寫代碼時已經肯定了要代理的類,只能代理單一的類型,在此略過,今天重點講動態代理。測試

Java動態代理

Java動態代理建立代理類的方法爲:ui

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

其中ClassLoader是用來定義代理類的class文件的,用系統默認的就好,interfaces是要代理的接口,InvocationHandler是用來實際執行代理方法的接口,經常使用作法是實現該接口,並將須要代理的類實例對象傳進去。
實現本身的方法執行器:this

public class JdkDynamicProxy implements InvocationHandler {

    private Object proxied;

    public JdkDynamicProxy(Object object) {
        this.proxied = object;
    }

    /**
     * proxy爲建立的代理類實例,method是本次被代理的方法,args是方法的參數
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("-------------------老師好[by jdk動態代理]-------------------");
        // 執行代理方法
        Object obj = method.invoke(proxied, args);
        System.out.println("-------------------老師再見[by jdk動態代理]-------------------");
        // 返回方法執行結果
        return obj;
    }

}

建立代理類對象並執行:spa

// 代理陳老師
Teacher proxy1 = (Teacher) Proxy.newProxyInstance(Teacher.class.getClassLoader(),
        new Class[]{Teacher.class}, new JdkDynamicProxy(new TeacherChan()));
proxy1.teach();
// 代理蒼老師
Teacher proxy2 = (Teacher) Proxy.newProxyInstance(Teacher.class.getClassLoader(),
        new Class[]{Teacher.class}, new JdkDynamicProxy(new TeacherCang()));
proxy2.teach();

輸出結果:設計

-------------------老師好[by jdk動態代理]-------------------
你們好,我是陳老師,我教你們攝影!
-------------------老師再見[by jdk動態代理]-------------------
-------------------老師好[by jdk動態代理]-------------------
你們好,我是蒼老師,我教你們生物!
-------------------老師再見[by jdk動態代理]-------------------

實際上,Java會過濾掉接口全部final、native等方法,併爲剩下的全部符合條件的方法生成代理方法。並且,熟悉Spring的朋友應該知道,Spring的AOP機制的實現不只使用了Java的動態代理,並且還引入了CGLib。由於Java的動態代理只能代理接口,而不能代理原始的類。那麼爲何Java不能代理類呢,答案是Java的單繼承機制。代理

深刻Java動態代理的實現

Java的動態代理是怎麼實現的呢?其實很簡單,就是運行時生成一個代理類,該類實現了須要代理的接口,並返回這個代理類的實例對象給調用者。調試進入Proxy.newProxyInstance()的方法內部,能夠看到在Proxy內部生成class字節碼的方法:

// 生成的代理類名前綴
private static final String proxyClassNamePrefix = "$Proxy";
// 生成的代理類名序號
private static final AtomicLong nextUniqueNumber = new AtomicLong();

// 序號值加1
long num = nextUniqueNumber.getAndIncrement();
// 代理類名:$ProxyN
String proxyName = proxyPkg + proxyClassNamePrefix + num;

...

// 生成代理類的class字節碼
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);

不難看出,生成的代理類的類名是&dollar;ProxyN的形式,因此咱們常常會看到&dollar;Proxy0這個類,就是動態代理在運行時生成的。

既然知道了動態代理是怎麼生成代理類的了,那咱們不妨把它生成的類打印出來看看,到底裏面是怎麼實現的。

// 調用Java生成字節碼文件的方法
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
        "com.test.$proxy0.class", new Class[]{Teacher.class}, Modifier.FINAL);
// 輸出文件到本地
FileOutputStream out = new FileOutputStream(new File("/temp/$Proxy0.class"));
out.write(proxyClassFile);
out.flush();
out.close();

用java反編譯軟件打開生成的&dollar;Proxy0.class文件,內容以下:

package com.test.$proxy0;

import com.demos.java.basedemo.proxy.bean.Teacher;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

final class class extends Proxy
  implements Teacher
{
  private static Method m1;
  private static Method m2;
  private static Method m3;
  private static Method m0;

  public class(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }

  public final boolean equals(Object paramObject)
    throws 
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final String toString()
    throws 
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

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

  public final int hashCode()
    throws 
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  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.demos.java.basedemo.proxy.bean.Teacher").getMethod("teach", 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 localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

能夠看出,代理類不只代理了teach()這個方法,還代理了toString()、equals()和hashCode()方法,由於java中全部的類都是繼承Object類的,因此天然有這些方法。其中還有一個地方要特別注意:生成的代理類繼承了Proxy這個類,所以它只能經過實現接口來代理其餘的類,如今知道爲何Java動態代理只能代理接口了。

既然都講到這個份上了,固然不能不繼續深刻了。接下來是真正壓軸的環節,實現本身的動態代理類。

手動實現動態代理

首先分析一下實現動態代理須要的步驟:

1.拼接java類實現,並生成class字節碼
2.加載class字節碼到JVM
3.實現本身的InvocationHandler處理器
4.對外提供接口

既然知道了Java動態代理的原理,咱們不妨借鑑Java生成的class文件格式,同時去掉默認繼承的Proxy,使得咱們本身的動態代理既能夠代理接口,也能夠代理類。先寫出代理類的格式(假設代理的是類TeacherChan):

public class $Proxy0 extends TeacherChan {

private InvocationHandler handler;

private static Method m0;

static {
    try {
        // 利用反射獲取TeacherChan的teach()方法
        m0 = TeacherChan.class.getMethod("teach", new Class[]{});
    } catch (NoSuchMethodException ne) {
        throw new NoSuchMethodError(ne.getMessage());
    }
}

// 構造方法中傳入代理類處理器
public $Proxy0(InvocationHandler handler) {
    this.handler = handler;
}

public void teach() {
    try {
        // 收集teach()方法傳入的參數,此處參數爲空
        Object[] args = new Object[]{};
        // 執行代理類的teach()
        Object result = handler.invoke(this, m0, args);
        // 若是有返回值,此處要返回result
    } catch (Error|RuntimeException e) {
        throw e;
    } catch (Throwable t) {
        throw new UndeclaredThrowableException(t);
    }
}

}

好了,生成類的格式大概就是這樣設計,實際編寫代碼時須要處理參數、返回值和異常的狀況,略微有點繁瑣。下面是動態類生成器:

public class MyProxyGenerator {

    // 換行符
    public static final String LINE_SEPARATOR = "\r\n";
    // 動態代理類包名
    public static final String PROXY_CLASS_PACKAGE = "com.demos.proxy";
    // 動態代理類名前綴
    public static final String PROXY_CLASS_NAME_PREFIX = "$Proxy";
    // 動態代理類文件索引
    public static final AtomicLong INDEX_GENERATOR = new AtomicLong();
    // 動態代理生成文件臨時目錄
    public static final String PROXY_CLASS_FILE_PATH = "/temp";

    /**
     * 生成代理類並加載到JVM
     * @param clazz
     * @param methods
     * @throws Exception
     */
    public static Class<?> generateAndLoadProxyClass(Class<?> clazz, Method[] methods) throws Exception {
        long index = INDEX_GENERATOR.getAndIncrement();
        // 代理類類名
        String className = PROXY_CLASS_NAME_PREFIX + index;
        String fileName = PROXY_CLASS_FILE_PATH + File.separator + className + ".java";
        FileWriter writer = null;
        try {
            // 生成.java文件
            writer = new FileWriter(new File(fileName));
            writer.write(generateClassCode(PROXY_CLASS_PACKAGE, className, clazz, methods));
            writer.flush();
            // 編譯.java文件
            compileJavaFile(fileName);
            // 加載class到JVM
            String classPath = PROXY_CLASS_FILE_PATH + File.separator + className + ".class";
            Class<?> proxyClass = MyClassLoader.getInstance().findClass(classPath, PROXY_CLASS_PACKAGE + "." + className);
            return proxyClass;
        } finally {
            if (writer != null) {
                writer.close();
            }
        }
    }

    /**
     * 編譯.java文件
     * @param fileName
     * @throws IOException
     */
    private static void compileJavaFile(String fileName) throws IOException {
        compileByTools(fileName);
//        compileByExec(fileName);
    }

    /**
     * 使用Runtime執行javac命令
     * 注意: 須要指定classpath, 不然找不到依賴的類
     * 建議使用compileByTools()
     * @param fileName
     * @throws IOException
     */
    @Deprecated
    private static void compileByExec(String fileName) throws IOException {
        // 獲取當前的classpath
        String classpath = MyProxyGenerator.class.getResource("/").getPath();
        // 運行命令: javac -classpath ${classpath} ${filepath}
        String command = "javac -classpath " + classpath + " " + fileName;
        Process process = Runtime.getRuntime().exec(command);
        // 等待執行, 並輸出錯誤日誌
        try {
            InputStream errorStream = process.getErrorStream();
            InputStreamReader inputStreamReader = new InputStreamReader(errorStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String line = null;
            while ((line = bufferedReader.readLine()) != null) {
                System.out.println(line);
            }
            int exitVal = process.waitFor();
            System.out.println("Process exitValue: " + exitVal);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 使用JDK自帶的JavaCompiler
     * @param fileName
     * @throws IOException
     */
    private static void compileByTools(String fileName) throws IOException {
        // 獲取系統Java編譯器
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        // 獲取標準文件管理器實例
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        try {
            Iterable units = fileManager.getJavaFileObjects(fileName);
            JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, units);
            task.call();
        } finally {
            fileManager.close();
        }
    }

    /**
     * 拼接 class 代碼片斷
     * @param packageName   代理類包名
     * @param clazz         要代理的類型
     * @return
     */
    private static String generateClassCode(String packageName, String className, Class<?> clazz, Method[] methods) throws Exception {

        StringBuilder classCodes = new StringBuilder();

        /*--------------------包名和依賴 start--------------------*/
        classCodes.append("package ").append(packageName).append(";").append(LINE_SEPARATOR);
        classCodes.append(LINE_SEPARATOR);
        classCodes.append("import java.lang.reflect.*;").append(LINE_SEPARATOR);
        classCodes.append(LINE_SEPARATOR);
        /*--------------------包名和依賴 start--------------------*/

        /*--------------------類定義 start--------------------*/
        classCodes.append("public class ").append(className);
        if (clazz.isInterface()) {
            classCodes.append(" implements ");
        } else {
            classCodes.append(" extends ");
        }
        classCodes.append(clazz.getName()).append(" {").append(LINE_SEPARATOR);
        classCodes.append(LINE_SEPARATOR);
        /*--------------------類定義 end--------------------*/

        /*--------------------聲明變量InvocationHandler start--------------------*/
        classCodes.append("private InvocationHandler handler;").append(LINE_SEPARATOR);
        classCodes.append(LINE_SEPARATOR);
        /*--------------------聲明變量InvocationHandler end--------------------*/

        /*--------------------聲明代理方法 start--------------------*/
        for (int i = 0; i < methods.length; i++) {
            classCodes.append("private static Method m").append(i).append(";").append(LINE_SEPARATOR);
        }
        classCodes.append(LINE_SEPARATOR);
        /*--------------------聲明代理方法 end--------------------*/

        /*--------------------代理方法對象初始化 start--------------------*/
        classCodes.append("static {").append(LINE_SEPARATOR);
        classCodes.append("    ").append("try {").append(LINE_SEPARATOR);
        for (int i = 0; i < methods.length; i++) {
            Method method = methods[i];
            classCodes.append("    ").append("    ").append("m").append(i).append(" = ").append(clazz.getName())
                    .append(".class.getMethod(\"").append(method.getName()).append("\", new Class[]{");
            // 方法參數
            Parameter[] params = method.getParameters();
            if (params.length != 0) {
                for (int j = 0; j < params.length; j++) {
                    if (j != 0) {
                        classCodes.append(", ");
                    }
                    Parameter param = params[j];
                    classCodes.append(param.getType().getName()).append(".class");
                }
            }
            classCodes.append("});").append(LINE_SEPARATOR);
        }
        classCodes.append("    ").append("} catch (NoSuchMethodException ne) {").append(LINE_SEPARATOR);
        classCodes.append("    ").append("    ").append("throw new NoSuchMethodError(ne.getMessage());").append(LINE_SEPARATOR);
        classCodes.append("    ").append("}").append(LINE_SEPARATOR);
        classCodes.append("}").append(LINE_SEPARATOR);
        classCodes.append(LINE_SEPARATOR);
        /*--------------------代理方法對象初始化 end--------------------*/

        /*--------------------定義構造函數 start--------------------*/
        classCodes.append("public ").append(className).append("(InvocationHandler handler) {").append(LINE_SEPARATOR);
        classCodes.append("    ").append("this.handler = handler;").append(LINE_SEPARATOR);
        classCodes.append("}").append(LINE_SEPARATOR);
        classCodes.append(LINE_SEPARATOR);
        /*--------------------定義構造函數 end--------------------*/

        /*--------------------填充其餘函數 start--------------------*/
        classCodes.append(generateMethodCode(clazz, methods));
        /*--------------------填充其餘函數 end--------------------*/

        // 類結束
        classCodes.append("}").append(LINE_SEPARATOR);

        return classCodes.toString();
    }

    /**
     * 拼接 method 代碼片斷
     * @param clazz
     * @param methods
     * @return
     * @throws Exception
     */
    private static String generateMethodCode(Class<?> clazz, Method[] methods) throws Exception {

        StringBuilder methodCodes = new StringBuilder();

        for (int i = 0; i < methods.length; i++) {
            Method method = methods[i];
            // 返回類型
            String returnType = method.getReturnType().getName();
            // 參數列表
            Parameter[] params = method.getParameters();
            // 異常列表
            Class<?>[] exceptionTypes = method.getExceptionTypes();

            /*--------------------方法定義 start--------------------*/
            methodCodes.append("public ").append(returnType).append(" ").append(method.getName());
            methodCodes.append("(");
            // 填充參數
            if (params.length != 0) {
                for (int j = 0; j < params.length; j++) {
                    if (j != 0) {
                        methodCodes.append(", ");
                    }
                    Parameter param = params[j];
                    methodCodes.append(param.getType().getName()).append(" ").append(param.getName());
                }
            }
            methodCodes.append(")");
            // 填充異常
            if (exceptionTypes.length != 0) {
                methodCodes.append(" throws ");
                for (int j = 0; j < exceptionTypes.length; j++) {
                    if (j != 0) {
                        methodCodes.append(", ");
                    }
                    methodCodes.append(exceptionTypes[j].getName());
                }
            }
            methodCodes.append(" {").append(LINE_SEPARATOR);
            /*--------------------方法定義 end--------------------*/

            /*--------------------方法體 start--------------------*/
            methodCodes.append("    ").append("try {").append(LINE_SEPARATOR);
            // 方法參數
            methodCodes.append("    ").append("    ").append("Object[] args = new Object[]{");
            if (params.length != 0) {
                for (int j = 0; j < params.length; j++) {
                    if (j != 0) {
                        methodCodes.append(", ");
                    }
                    Parameter param = params[j];
                    methodCodes.append(param.getName());
                }
            }
            methodCodes.append("};").append(LINE_SEPARATOR);
            // 執行InvocationHandler.invoke()
            methodCodes.append("    ").append("    ").append("Object result = handler.invoke(this, m").append(i)
                    .append(", args);").append(LINE_SEPARATOR);
            // 返回結果
            if (!"void".equals(returnType)) {
                methodCodes.append("    ").append("    ").append("return (").append(returnType).append(") result;").append(LINE_SEPARATOR);
            }
            // 異常處理
            methodCodes.append("    ").append("} catch (Error|RuntimeException");
            for (Class<?> exceptionType : exceptionTypes) {
                methodCodes.append("|").append(exceptionType.getName());
            }
            methodCodes.append(" e) {").append(LINE_SEPARATOR);
            methodCodes.append("    ").append("    ").append("throw e;").append(LINE_SEPARATOR);
            methodCodes.append("    ").append("} catch (Throwable t) {").append(LINE_SEPARATOR);
            methodCodes.append("    ").append("    ").append("throw new UndeclaredThrowableException(t);").append(LINE_SEPARATOR);
            methodCodes.append("    ").append("}").append(LINE_SEPARATOR);
            /*--------------------方法體 end--------------------*/

            // 方法結束
            methodCodes.append("}").append(LINE_SEPARATOR).append(LINE_SEPARATOR);
        }

        return methodCodes.toString();
    }

}

實際上只是拼接前面給出的代理類實現而已,代碼量有點大,但並不難理解。

下一步,實現本身的類加載器,來加載生成的class字節碼:

public class MyClassLoader extends ClassLoader {

    private static MyClassLoader loader;

    private MyClassLoader() {

    }

    public static MyClassLoader getInstance() {
        if (loader == null) {
            synchronized (MyClassLoader.class) {
                // 獲得鎖首先檢查loader是否已經存在, 避免重複建立
                if (loader == null) {
                    loader = new MyClassLoader();
                }
            }
        }
        return loader;
    }

    /**
     * 加載class文件,並返回類型對象
     *
     * @param filePath
     * @param className
     * @return
     * @throws ClassNotFoundException
     */
    public Class<?> findClass(String filePath, String className) throws ClassNotFoundException {
        try {
            // 讀取指定class文件的字節碼
            byte[] classBytes = Files.readAllBytes(Paths.get(filePath));
            // 加載類並返回class類型對象
            Class<?> clazz = defineClass(className, classBytes, 0, classBytes.length);
            return clazz;
        } catch (IOException e) {
            e.printStackTrace();
        }
        throw new ClassNotFoundException(className);
    }

}

到這一步,複雜的工做基本作完了,接下來只剩下自定義處理器類和對外接口了

自定義處理器類與Java動態代理的方式相同:

public class MyInvocationHandler implements InvocationHandler {

    private Object proxied;

    public MyInvocationHandler(Object object) {
        this.proxied = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("-------------------老師好[by 自定義動態代理]-------------------");
        Object obj = method.invoke(proxied, args);
        System.out.println("-------------------老師再見[by 自定義動態代理]-------------------");
        return obj;
    }

}

對外接口:

public class MyDynamicProxy {

    public static <T> T newProxyInstance(Class<T> clazz, InvocationHandler handler) throws Exception {
        // 要代理的方法: public & !final
        Method[] proxyMethods = Arrays.stream(clazz.getMethods())
                .filter(method -> !Modifier.isFinal(method.getModifiers()))
                .collect(Collectors.toList())
                .toArray(new Method[0]);
        // 生成的代理類
        Class<?> proxyClass = MyProxyGenerator.generateAndLoadProxyClass(clazz, proxyMethods);
        // 代理類的構造方法
        Constructor c = proxyClass.getConstructor(InvocationHandler.class);
        // 建立代理類對象
        Object proxyObj = c.newInstance(handler);
        return (T) proxyObj;
    }

}

搞定,測試一下效果:

TeacherChan proxy1 = MyDynamicProxy.newProxyInstance(
        TeacherChan.class, new MyInvocationHandler(new TeacherChan()));
proxy1.teach();

TeacherCang proxy2 = MyDynamicProxy.newProxyInstance(
        TeacherCang.class, new MyInvocationHandler(new TeacherCang()));
proxy2.teach();

輸出:

-------------------老師好[by 自定義動態代理]-------------------
你們好,我是陳老師,我教你們攝影!
-------------------老師再見[by 自定義動態代理]-------------------
-------------------老師好[by 自定義動態代理]-------------------
你們好,我是蒼老師,我教你們生物!
-------------------老師再見[by 自定義動態代理]-------------------

完美!大功告成!

相關文章
相關標籤/搜索