1. 程式人生 > >Java動態代理概述和手寫實現動態代理

Java動態代理概述和手寫實現動態代理

一:前提

Spring中最重要的兩種思想:控制反轉IOC(Inversion of Control)和麵向切面程式設計AOP(Aspect-Oriented Programming),而AOP最重要的原理就是動態代理,今天我們談一下動態代理。動態代理顧名思義是代替別人做某些事,它自己不幹,讓代理幫他做。別的不多說,直接上程式碼(舉的例子可能不恰當)!

二:基於JDK的動態代理

1:因為Java的動態代理是基於介面的,所以先定義一個頂級介面Person

package dynamicProxy.jdkDynamic;
/**
 * 頂級介面
 */
public interface Person {
    public void speak();
}

2:寫一個Person的實現類GuangDongPerson 

package dynamicProxy.jdkDynamic;

/**
 * 實現類
 * */
public class GuangDongPerson implements Person {

    @Override
    public void speak() {
        System.out.println("我說的是~¥#~&~#");
    }
}

3:找一個翻譯作為代理類,進行翻譯普通話。

package dynamicProxy.jdkDynamic;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Translation implements InvocationHandler {

    private Person target;

    public Translation(Person target) {
        this.target = target;
    }

    public static Object getInstance(Person target){
        Class<? extends Person> clazz = target.getClass();
        System.out.println(clazz.getName());
        Object poxy = Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new Translation(target));
        System.out.println(poxy.getClass().getName());
        return poxy;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("我是翻譯,你說話我翻譯,請說話!");
        Object invoke = method.invoke(target,args);
        System.out.println("他說的是xxxxxxxxxx");
        return invoke;
    }
}

說明:

1:Proxy.newInstance()方法

        * 第一個引數為目標類的類載入器

        * 第二個引數為目標類的介面集合,JDK動態代理目標類必須實現一個介面,這是為了保證生成的代理類和目標類之前的強一致性關係

        * 第三個引數為InvocationHandler介面的實現類,這裡直接通過匿名內部類實現,重寫了invoke方法

  2:InvocationHandler介面實現類

        * 實現該類必須重寫invoke()方法,動態代理實現在代理類中該介面的實現類呼叫該方法,並在該方法中反射執行該方法完成整個動態代理流程;

        * 第一個引數為生成的動態代理物件;

        * 第二個引數為動態代理在客戶端執行的方法;

        * 第三個引數為該方法的引數列表;

        * 通過反射來完成方法呼叫;

4:測試類

package dynamicProxy.jdkDynamic;

import dynamicProxy.jdkDynamic.custom.TLGuangDongPerson;
import dynamicProxy.jdkDynamic.custom.TLPerson;
import dynamicProxy.jdkDynamic.custom.TLTranslation;
import sun.misc.ProxyGenerator;

import java.io.FileOutputStream;

public class Test {

    public static void main(String[] args) throws Exception{

        Person translationInstance = (Person)Translation.getInstance(new GuangDongPerson());
        translationInstance.speak();

        byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Person.class});
        FileOutputStream outputStream = new FileOutputStream("E:\\$Proxy0.class");
        outputStream.write(bytes);
        outputStream.close();
//        TLPerson translationInstance = (TLPerson)TLTranslation.getInstance(new TLGuangDongPerson());
//        translationInstance.speak();
//
//        byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Person.class});
//        FileOutputStream outputStream = new FileOutputStream("E:\\$Proxy0.class");
//        outputStream.write(bytes);
//        outputStream.close();
    }
}

5:測試結果

dynamicProxy.jdkDynamic.Test
//未被代理之前的類
dynamicProxy.jdkDynamic.GuangDongPerson
//代理之後的類
com.sun.proxy.$Proxy0

我是翻譯,你說話我翻譯,請說話!
我說的是~¥#~&~#
他說的是xxxxxxxxxx

Process finished with exit code 0

注:看到上面的輸出,可以很明顯的看代理之後的類程式設計了$Proxy0,下面是此類的反編譯結果

import dynamicProxy.jdkDynamic.Person;
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 Person {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void speak() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("dynamicProxy.jdkDynamic.Person").getMethod("speak");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

總結:Java動態代理使用反射機制進行代理,可以看到該虛擬類繼承Proxy類並實現了目標類頂層介面,並反射獲取了目標類中的所有方法的Method物件,同時,虛擬類也同時重寫了目標類的所有方法,並通過super.h.invoke(this, m3, (Object[])null)的方式進行呼叫。

三:手寫實現JDK動態代理

步驟:

1:TLPerson類,定義newInstance()方法,進行代理物件建立

  • 在該方法中首先需要輸出代理類的.java檔案
  • 編譯該.java檔案生成.class檔案
  • 通過類載入器載入該.class檔案
  • 生成該.class檔案的事例物件作為代理物件返回

2:TLInvocationHandler介面,定義invoke()方法,在代理類中進行目標方法呼叫

  • 在.java檔案中,定義構造器,傳遞該類引用
  • 通過SelfInvocationHandler實現類物件呼叫invoke()方法,實現代理方式的方法呼叫

3:TLClassLoader,自定義類載入器,繼承JDK的ClassLoader類,實現自定義的載入方式

具體操作:

1:建立頂級介面

package dynamicProxy.jdkDynamic.custom;

public interface TLPerson {
    public void speak();
}

2:建立實現類

package dynamicProxy.jdkDynamic.custom;

public class TLGuangDongPerson implements TLPerson{
    @Override
    public void speak() {
        System.out.println("我說的是~¥#~&~#");
    }
}

3:使用自己的TLClassLoader代替JDK的ClassLoader,重寫findClass(String name)方法。

package dynamicProxy.jdkDynamic.custom;

import dynamicProxy.custom.SelfClassLoader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;

public class TLClassLoader extends ClassLoader {
    private File classPathFile;

    public TLClassLoader() {
        String classPath = TLClassLoader.class.getResource("").getPath();
        this.classPathFile = new File(classPath);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String className = TLClassLoader.class.getPackage().getName() + "." + name;
        if (null != classPathFile) {
            File classFile = new File(classPathFile, name + ".class");
            if (classFile.exists()) {
                FileInputStream in = null;
                ByteArrayOutputStream out = null;
                try {
                    in = new FileInputStream(classFile);
                    out = new ByteArrayOutputStream();
                    byte[] bytes = new byte[1024];
                    int len;
                    while((len = in.read(bytes)) != -1) {
                        out.write(bytes, 0, len);
                    }
                    return defineClass(className, out.toByteArray(), 0, out.size());
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    try {
                        if (null != in) {
                            in.close();
                        }
                        if (null != out) {
                            out.close();
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        return null;
    }
}

4:使用自己的TLInvocationHandler代替JDK的InvocationHandler,重寫invoke(Object proxy, Method method, Object[] args)方法。

package dynamicProxy.jdkDynamic.custom;

import java.lang.reflect.Method;

public interface TLInvocationHandler {

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

5:使用自己的TLProxy代替JDK的Proxy

package dynamicProxy.jdkDynamic.custom;

import com.sun.org.apache.regexp.internal.RE;
import dynamicProxy.custom.SelfInvocationHandler;
import dynamicProxy.custom.SelfProxy;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class TLProxy {
    private static final String LN = "\r\n";
    public static Object newProxyInstance(TLClassLoader loader, Class<?>[] interfaces, TLInvocationHandler h) throws Exception {
        // 動態生成原始碼
        String srcClass = generateSrc(interfaces);
        // 輸出Java檔案
        String filePath = TLProxy.class.getResource("").getPath()  + "$ProxyO.java";
        System.out.println(filePath);
        FileWriter fileWriter = new FileWriter(filePath);
        fileWriter.write(srcClass);
        fileWriter.flush();
        fileWriter.close();
        // 編譯Java檔案為class檔案
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        Iterable iterable = fileManager.getJavaFileObjects(filePath);
        JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, iterable);
        task.call();
        fileManager.close();
        // 載入編譯生成的class檔案到JVM
        Class<?> proxyClass = loader.findClass("$ProxyO");
        Constructor<?> constructor = proxyClass.getConstructor(TLInvocationHandler.class);
        // 刪掉虛擬代理類
        File file = new File(filePath);
        file.delete();
        // 返回位元組碼重組以後的代理物件
        return constructor.newInstance(h);
    }
    private static String generateSrc(Class<?>[] interfaces) {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("package dynamicProxy.jdkDynamic.custom;" + LN );
        stringBuilder.append("import dynamicProxy.jdkDynamic.custom.TLPerson;" + LN);
        stringBuilder.append("import java.lang.reflect.Method;" + LN);
        stringBuilder.append("public class $ProxyO implements " + interfaces[0].getName() + "{" + LN);
        stringBuilder.append("TLInvocationHandler h;" + LN);
        stringBuilder.append("public $ProxyO(TLInvocationHandler h) {" + LN);
        stringBuilder.append("this.h = h;" + LN);
        stringBuilder.append("}" + LN);

        for (Method method : interfaces[0].getMethods()) {
            stringBuilder.append("public " + method.getReturnType().getName() + " " + method.getName() + "() {" + LN);
            stringBuilder.append("try {" + LN);
            stringBuilder.append("Method m = " + interfaces[0].getName() + ".class.getMethod(\"" + method.getName() + "\", new Class[]{});" + LN);
            stringBuilder.append("this.h.invoke(this, m, null);" + LN);
            stringBuilder.append("} catch(Throwable able) {" + LN);
            stringBuilder.append("able.getMessage();" + LN);
            stringBuilder.append("}" + LN);
            stringBuilder.append("}" + LN );
        }
        stringBuilder.append("}" + LN);
        return stringBuilder.toString();
    }
}

6:建立手寫翻譯類

package dynamicProxy.jdkDynamic.custom;

import dynamicProxy.jdkDynamic.Translation;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class TLTranslation implements TLInvocationHandler {
    private TLPerson target;

    public TLTranslation(TLPerson target) {
        this.target = target;
    }

    public static Object getInstance(TLPerson target) {
        try {
            Class<? extends TLPerson> clazz = target.getClass();
            System.out.println(clazz.getName());
            Object poxy = TLProxy.newProxyInstance(new TLClassLoader(), clazz.getInterfaces(), new TLTranslation(target));
            System.out.println(poxy.getClass().getName());
            return poxy;
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
        System.out.println("我是翻譯,你說話我翻譯,請說話!");
        Object invoke = method.invoke(target,args);
        System.out.println("他說的是xxxxxxxxxx");
        return invoke;
    }
}

7:測試類

package dynamicProxy.jdkDynamic;

import dynamicProxy.jdkDynamic.custom.TLGuangDongPerson;
import dynamicProxy.jdkDynamic.custom.TLPerson;
import dynamicProxy.jdkDynamic.custom.TLTranslation;
import sun.misc.ProxyGenerator;

import java.io.FileOutputStream;

public class Test {

    public static void main(String[] args) throws Exception{

//        Person translationInstance = (Person)Translation.getInstance(new GuangDongPerson());
//        translationInstance.speak();
//
//        byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Person.class});
//        FileOutputStream outputStream = new FileOutputStream("E:\\$Proxy0.class");
//        outputStream.write(bytes);
//        outputStream.close();
        TLPerson translationInstance = (TLPerson)TLTranslation.getInstance(new TLGuangDongPerson());
        translationInstance.speak();

        byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Person.class});
        FileOutputStream outputStream = new FileOutputStream("E:\\$Proxy0.class");
        outputStream.write(bytes);
        outputStream.close();
    }
}

8:測試結果

dynamicProxy.jdkDynamic.custom.TLGuangDongPerson
/F:/project/TimeTest/target/classes/dynamicProxy/jdkDynamic/custom/$ProxyO.java
dynamicProxy.jdkDynamic.custom.$ProxyO
我是翻譯,你說話我翻譯,請說話!
我說的是~¥#~&~#
他說的是xxxxxxxxxx

Process finished with exit code 0

這僅是一個理解動態代理的思路,可能會給你一點啟發,共同進步!