【AOP系列】自己動手實現一個JDK的Proxy類(四)

【AOP系列】自己動手實現一個JDK的Proxy類

思路:

1.通過反射拿到一個類的Class對象。然後調用getMethods()方法。

2.拿到原來的方法後,開始原方法的所有信息,如(方法訪問標識符,方法返回值,方法名稱,方法參數)

3.可以使用字符串的形式拼接出原來的方法,並在方法前和方法後加入自己的代碼邏輯。

4.把拼接好的代理類寫入到一個Java文件中。這樣我們就生成了代理的類。(有點像代碼生成器)

5.編譯這個.java的文件。並生成對應的class文件。

6.使用類加載器加載生成的class類。並返回。此時的對象,在拼接的時候已經對原有方法進行了增強。

實現

InvocationHandler.java

public interface InvocationHandler {
    Object invoke(Method method, Object... args)throws Throwable;
}

注意,我們寫的這個接口和JDK的區別,少了第一個參數Object target

Log.java

public class Log implements InvocationHandler {

    Object target;//要代理的對象

    public Log(Object obj){
        this.target=obj;
    }

    @Override
    public Object invoke(Method method, Object[] args) throws Throwable {
        //打印方法名
        System.out.print(method.getName()+":");
        //打印參數
        for (Object object : args) {
            System.out.print(object);
        }
        //換行
        System.out.println();
        //調用原對象的方法
        Object o=method.invoke(target, args);
        //這裏也可以在方法調用完之後插入一些邏輯
        return o;
    }

}

寫一個類實現上面的接口,並在invoke前添加輸出語句。

由於自己寫Proxy類的實現,需生成byte[]加載到內存,需要自己定義ClassLoder,so

MyClassLoader.java

public class MyClassLoader extends ClassLoader {

    //指定路徑
    private String classPath;
	//構造函數
    public MyClassLoader(String classPath){
        this.classPath =classPath;
    }

    /**
     * 重寫findClass方法
     * @param name 是我們這個類的全路徑
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class clazz = null;
        // 獲取該class文件字節碼數組
        byte[] classData = getData();
        if (classData != null) {
            // 將class的字節碼數組轉換成Class類的實例
            clazz = defineClass(name, classData, 0, classData.length);
        }
        return clazz;
    }

    /**
     * 將class文件轉化爲字節碼數組
     * @return
     */
    private byte[] getData() throws ClassNotFoundException {
        File file = new File(classPath);
        if (!file.exists())throw new ClassNotFoundException();
        FileInputStream inputStream = null;
        try {
            inputStream = new FileInputStream(file);
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int size;
            while ((size = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, size);
            }
            return outputStream.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

最後是最重要的Proxy類

public class Proxy {

    //當前項目目錄
    private static String userDir = System.getProperty("user.dir");
    //當前包
    private static String pack = Proxy.class.getPackage().getName();
    private static String packPath = pack.replace(".","/");
    //環境目錄
    private static String envDir = "/src/main/java/";
    //當前包的絕對路徑
    private static String currPackPath = userDir  + envDir + packPath;

    // 所有代理類名的前綴(模擬JDK中的命名)
    private static final String proxyClassNamePrefix = "$Proxy";
    // 下一個用於生成唯一代理類名的數字,用於生成代碼類名 $Proxy  + 自增號(模擬JDK中的命名)
    private static final AtomicLong nextUniqueClassNumber = new AtomicLong();
    // 在生成代碼過程中,由於會生成大量參數,命名較爲繁瑣,使用(arg + 自增號)命名
    private static final AtomicLong nextUniqueArgsNumber = new AtomicLong();

    /**
     * 生成代碼類(僅支持接口類型)
     *
     * @param clazz   需要代理的接口類
     * @param handler 已經實現了InvocationHandler接口的實體類
     */
    public static Object newProxyInstance(Class clazz, InvocationHandler handler) throws Exception {
        //換行
        String rt = "\r\n";
        //接口名稱
        String interfaceName = clazz.getName();
        //生成代碼類名      $Proxy  + 自增號
        String className = proxyClassNamePrefix + nextUniqueClassNumber.getAndIncrement();
        //類所在包
        String packCode = "package " + pack + ";" + rt + rt;
        //導包代碼
        String importMethod = "import java.lang.reflect.Method;" + rt + rt;
        //類的關係
        String relation = (clazz.isInterface() ? " implements " : " extends ") + interfaceName;
//        String relation = clazz.isInterface() ? " implements " + interfaceName : " ";
        //class類定義代碼
        String clazzCode = "public class " + className + relation + "{" + rt;
        //屬性
        String fieldCode = "    InvocationHandler handler;" + rt;
        //構造方法
        String constructorCode = "    public  " + className + "(InvocationHandler handler){" + rt;
        //構造方法內的代碼
        String innerCode1 = "        this.handler = handler;" + rt;
        //構造方法結束
        String right = "    }" + rt;
        //拼接 前面的代碼
        String header = packCode
                + importMethod
                + clazzCode
                + rt
                + fieldCode
                + rt
                + constructorCode
                + innerCode1
                + right
                + rt;
        //用於拼接代理類的方法代碼
        StringBuilder builder = new StringBuilder(header);
        //通過反射拿到代理類接口的所有方法
        Method[] methods = clazz.getMethods();
        //便利每一個方法,並代理每一個方法
        Stream.of(methods).forEach(method -> {
            //方法名
            String methodName = typeOf(method.getName());
            int modifier = method.getModifiers();
            if (Modifier.isNative(modifier)||Modifier.isFinal(modifier)) {
                //如果是Native方法則不代理。
            } else {
                //方法返回值類型
                String returnName = typeOf(method.getReturnType().getName());
                //方法參數列表
                Class<?>[] parameterTypes = method.getParameterTypes();
                //用於拼接形參列表
                String formalArgs = "";
                //用於拼接實參列表
                String realArgs = "";
                //用於拼接實參Class類型的字符串
                String realArgsClass = "";
                //便利參數列表,並拼接出上面三個參數
                for (int i = 0; i < parameterTypes.length; i++) {
                    //參數類型
                    String parameterTypeName = typeOf(parameterTypes[i].getName());

                    //參數名 arg0,arg1,arg2,arg3  ...
                    String argName = "arg" + nextUniqueArgsNumber.getAndIncrement();
                    //形參   Object arg0,String arg1,Integer arg2  ...
                    formalArgs += (parameterTypeName + " " + argName);
                    realArgs += argName;
                    realArgsClass += parameterTypeName + ".class";
                    //如果不是最後一個都加上個","  如果最後一個就不再添加","
                    if (i != (parameterTypes.length - 1)) {
                        formalArgs += ",";
                        realArgs += ",";
                        realArgsClass += ",";
                    }
                }
                //如果參數長度爲0,那麼傳null
                String arg = parameterTypes.length > 0 ? realArgs : "new Object()";
                //拼接方法體代碼
                String methodStr = clazz.isInterface() ?   "    @Override" + rt : "";
                methodStr += ("    public " + returnName + " " + methodName + "(" + formalArgs + "){" + rt);
                methodStr += ("        try{ " + rt);
                methodStr += ("            Method method = " + interfaceName + ".class.getMethod(\"" + methodName + "\"");
                //根據參數長度,確定是否添加參數
                if (parameterTypes.length > 0) {
                    methodStr += ("," + realArgsClass);
                }
                methodStr += (");" + rt);
                //返回值 如果爲"void",則生成沒有返回值的方法調用
                if (returnName.equals("void")) {
                    methodStr += ("            handler.invoke(method," + arg + ");" + rt);
                } else {
                    methodStr += ("            return (" + returnName + ")handler.invoke(method," + arg + ");" + rt);
                }
//            System.out.println(methodStr);
                //捕獲所有異常,轉換成RuntimeException異常
                methodStr += ("        }catch (Throwable e){e.printStackTrace();throw new RuntimeException(e.getMessage());}" + rt + right + rt);
                //代理對象的一個方法代碼拼接完成,與原有代碼拼接在一起
                builder.append(methodStr);
            }
        });
        builder.append("}");
        //代理類的所有代碼拼接完成
        String code = builder.toString();


        String javaFileName = userDir + envDir + packPath + "/" + className + ".java";
        File javaFile = new File(javaFileName);
        String classFileName = userDir + envDir + packPath + "/" + className + ".class";
        File classFile = new File(classFileName);

        //寫入代碼
        FileWriter fileWriter = new FileWriter(new File(javaFileName));
        fileWriter.write(code);
        fileWriter.close();

        //運行時編譯器  編譯代理類的Java源代碼代碼->class文件
        JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileManager = javaCompiler.getStandardFileManager(null, null, null);
        Iterable<? extends JavaFileObject> javaFileObjects = fileManager.getJavaFileObjects(javaFileName);
        JavaCompiler.CompilationTask task = javaCompiler.getTask(null, fileManager, null, null, null, javaFileObjects);
        task.call();
        fileManager.close();

        //使用自定義ClassLoader加載Class類
        String packet = pack + "." + className;
        MyClassLoader myClassLoader = new MyClassLoader(classFileName);
        Class<?> targetProxy = myClassLoader.findClass(packet);

        //使用反射創建代理類對象
        Constructor constructor = targetProxy.getConstructor(InvocationHandler.class);
        Object object = constructor.newInstance(handler);

        //刪除操作時生成的Java源代碼和Class文件
        if (javaFile.exists()) javaFile.delete();
//        if (classFile.exists())classFile.delete();
        return object;
    }

    public static String typeOf(String className) {
        //如果是數組類型,則轉換
        if (className.startsWith("[")) {
            //如果是對象數組
            if (className.startsWith("[L"))
                return className.substring(2, className.length() - 1) + "[]";
            //如果是Java8種基礎類型
            if (className.startsWith("[S"))
                return "short[]";
            if (className.startsWith("[B"))
                return "byte[]";
            if (className.startsWith("[C"))
                return "char[]";
            if (className.startsWith("I"))
                return "int[]";
            if (className.startsWith("[J"))
                return "long[]";
            if (className.startsWith("[F"))
                return "float[]";
            if (className.startsWith("[D"))
                return "double[]";
            if (className.startsWith("[Z"))
                return "boolean[]";
        }
        //否則普通類型,不做轉換
        return className;
    }

    public static void main(String[] args) throws Exception {
        ArrayList list = (ArrayList) newProxyInstance(ArrayList.class,new Log(new ArrayList<>()));
        list.add("a");
        ArrayList arrayList = (ArrayList) newProxyInstance(ArrayList.class,new Log(new ArrayList<>()));
        arrayList.add("a");
//        Integer integer = (Integer) newProxyInstance(Integer.class,new Log(new Integer(10)));
//        integer.compareTo(5);
    }
}

OK寫完了。代碼有點長。寫的有點亂了。沒有整理,把很多東西都放在一個方法裏了。

其實不難發現這裏的實現方式,和JDK的還是有一些不一樣的。
JDK是直接根據class文件的格式直接構造class類型的byte[],
由於Class文件結構較爲複雜,
而這裏則是先用字符串拼接一個Java文件,
再用JavaCompiler去編譯這個java文件。生成一個class文件。
再通過MyClassLoader把這個生成好的class文件Load到內存。得到Class對象。
雖然實現方式不同,但是最終目的效果是差不多的。

運行結果

在這裏插入圖片描述

連ArrarList都能被代理了???

不是說JDK的Proxy代理只能代理接口嗎。爲啥ArrayList都能被代理了。

思考。

爲啥$Proxy0一定要繼承Proxy呢?

在代理類中,與Proxy有關的只有Proxy的變量protected InvocationHandler h;這個InvocationHandler實例的引用,在調用接口方法時實際調用的是super.h.invoke(this, method, args), 如果僅僅爲了這個變量,完全可以通過別的方法傳過去。

既然我不知道爲啥一定要繼承這個Proxy。那我自己實現時我就不繼承Proxy了。這樣我生成的代理類就可以繼承其他類了。既然可以繼承其他類。那是不是就實現了對類的代理?

注意這兩行代碼。

String relation = (clazz.isInterface() ? " implements " : " extends ") + interfaceName;
//class類定義代碼
String clazzCode = "public class " + className + relation + "{" + rt;

className我依然模仿了JDK的命名方式$Proxy+遞增序號,而繼承關係則是,根據接口的類型不同,選擇是實現,還是繼承。這樣我的Proxy就可以 支持普通類的代理了。雖然看上去,上面已經實現了對List接口和ArrayList類的動態代理。但是真的就沒問題嗎?真的就可以代理所有的接口和類了嗎?

注意Proxy類中的這一行代碼

String relation = (clazz.isInterface() ? " implements " : " extends ") + interfaceName;

如果是接口用implements 否則是類則extends,下面我們使用上面的Proxy嘗試去代理Integer類。

嘗試代理Integer類。

在這裏插入圖片描述

他說Integer是被final修飾過的。就是說被final定義的類是無法代理的;

一個錯誤的思路

其實我們可以換個思路。既然不能被繼承,那我們就不繼承,只把原來類中的方法重新實現一遍不就好了嗎?

生成代理類的時候直接不繼承,而是把Integer類的所有方法實現一遍。反正都是動態生成的。

好的,那我們試試。把

String relation = (clazz.isInterface() ? " implements " : " extends ") + interfaceName;

註釋掉,換成

String relation = clazz.isInterface() ? " implements " + interfaceName : " ";

這樣的語義是:如果是接口的話就實現。如果不是接口的話,就不做任何處理,只是生成對應一模一樣的方法。

再次運行
在這裏插入圖片描述

現在清楚了,雖然生成了一模一樣的類,但是現在就不算代理了,因爲兩個類已經沒啥聯繫了,所以生成的Proxy類當然不能轉換成Integer了

思考

JDK爲啥不能實現普通類的動態代理?

因爲生成的代理類繼承Proxy,(這是網上很多人給的答案)

但是$Proxy0爲啥要繼承Proxy呢?

或許$Proxy0也可以不繼承Proxy其實也是不影響的。(學術淺薄,僅僅猜測)。-_-~

大佬們。在此對這個問題提出質疑。

JDK動態代理爲什麼不能代理類 作者:luoxn28

jdk的動態代理及爲什麼需要接口 作者:XyGoodCode

jdk的動態代理及爲什麼需要接口 作者:可愛馬蓮花

或許是JDK的設計者們是這樣考慮的??

因爲無法知道被代理的類是否被final修飾了。如果被final修飾了,就實現不了代理。

而接口是不能被final關鍵字修飾的。

注意 接口中成員變量已默認是final了,而接口的定義和方法的定義是不能被final修飾的。

參考文章

java動態代理原理及解析 作者:簡單世界

Java 動態代理機制分析及擴展 作者:王忠平和何平

JDK 動態代理分析 作者:沉木

{
	"author": "大火yzs",
	"title": "【AOP系列】自己動手實現一個JDK的Proxy類",
	"tag": "AOP,List,JDK動態代理",
	"createTime": "2020-03-23  1:35"
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章