第六篇 - 手寫基於接口實現動態代理

在這裏插入圖片描述
Github源碼下載地址:https://github.com/chenxingxing6/sourcecode/tree/master/code-proxy

在這裏插入圖片描述


一、前言

我們知道常見的動態代理有兩種實現方式,基於jdk實現動態代理,基於cglib實現動態代理。本篇博客將自已模仿jdk實現接口動態代理,自己手寫源碼,實現一個接口的動態代理。


二、實現接口動態代理

如果不瞭解jdk動態代理的,先去了解一下,在來看此文章…

2.1動態代理原理圖

在這裏插入圖片描述
Proxy通過(interfaces/invocationHandler)生成代理類 $Proxy0;

Proxy通過ClassLoader來加載生成的代理類$Proxy0的字節碼文件;


2.2自己實現

2.2.1 目錄結構

在這裏插入圖片描述

2.2.2 實現原理圖

在這裏插入圖片描述

2.2.3 自定義InvocationHandler
package com.my;

import java.lang.reflect.Method;

/**
 * @Author: cxx
 * @Date: 2019/10/2 11:11
 */
public interface MyInvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

2.2.4 自定義MyClassLoader
package com.my;

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

/**
 * @Author: cxx
 * @Date: 2019/10/2 14:24
 */
public class MyClassLoader extends ClassLoader{
    private String baseDir;
    public MyClassLoader() {
        this.baseDir = this.getClass().getResource("").getFile();
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String className = this.getClass().getPackage().getName() + "." + name;
        if (baseDir != null && baseDir != ""){
            File f = new File(baseDir + "/" + name +".class");
            ByteArrayOutputStream out = null;
            FileInputStream fis = null;
            if (f.exists()){
                try {
                    fis = new FileInputStream(f);
                    out = new ByteArrayOutputStream();
                    byte[] b = new byte[1024];
                    int len;
                    while ((len = fis.read(b)) != -1){
                        out.write(b, 0, len);
                    }
                    return defineClass(className, out.toByteArray(), 0, out.size());
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    if (fis != null){
                        try {
                            fis.close();
                        }catch (Exception e){
                            e.printStackTrace();
                        }
                    }
                    if (out != null){
                        try {
                            out.close();
                        }catch (Exception e){
                            e.printStackTrace();
                        }
                    }
                    // 查看.class文件
                    f.delete();
                }
            }
        }
        return null;
    }
}

爲什麼要定義一個自定義的類加載器呢?它的作用是什麼呢?

想手寫JDK動態代理,那麼我們將自己在內存中生成動態代理類加載到jvm,重寫了findClass方法,就是爲了在指定路徑下加載指定的字節碼文件。

2.2.5 自定義MyProxy

自己實現代理類MyProxy,步驟分爲以下5步

1.生成java源碼
2.將源碼輸出到java文件中
3.將java文件編譯成class文件
4.將class加載進jvm
5.返回代理類對象
在這裏插入圖片描述

package com.my;

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

/**
 * @Author: cxx
 * @Date: 2019/10/2 11:14
 */
public class MyProxy implements Serializable {
    private static final String ln = "\r\n";
    public static Object newProxyInstance(MyClassLoader classLoader, Class<?>[] interfaces, MyInvocationHandler myInvocationHandler){
        File file = null;
       try {
           // 1.獲取java源碼,並輸出到文件
           String source = generateSrc(interfaces);
           String filePath = MyProxy.class.getResource("").getPath();
           file = new File(filePath + "$Proxy0.java");
           FileWriter fw = new FileWriter(file);
           fw.write(source);
           fw.flush();
           fw.close();

           // 2.對源碼進行編譯
           JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
           StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
           Iterable iterable = fileManager.getJavaFileObjects(file);
           JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, iterable);
           task.call();
           fileManager.close();

           // 3.加載class文件到jvm
           Class proxyClass = classLoader.findClass("$Proxy0");

           // 4.返回代理對象
           Constructor c = proxyClass.getConstructor(MyInvocationHandler.class);
           return c.newInstance(myInvocationHandler);
       }catch (Exception e){
           e.printStackTrace();
       }finally {
           file.delete();
       }
       return null;
    }

    /**
     * 獲取源碼,動態生成java類,看$ProxyMy
     * @param interfaces
     * @return
     */
    private static String generateSrc(Class<?>[] interfaces) {
        StringBuffer sb = new StringBuffer();
        sb.append("package com.my;" + ln);
        sb.append("import java.lang.reflect.Method;" + ln);
        sb.append("public class $Proxy0 implements " + interfaces[0].getName() + "{" + ln);
        sb.append("private MyInvocationHandler h;"+ln);
        sb.append("public $Proxy0(MyInvocationHandler h) { " + ln);
        sb.append("this.h = h;"+ln);
        sb.append("}" + ln);
        for (Method m : interfaces[0].getMethods()) {
            sb.append("public " + m.getReturnType().getName() + " "
                    + m.getName() + "(String id) {" + ln);
            sb.append("try{" + ln);
            sb.append("Method m = " + interfaces[0].getName()
                    + ".class.getMethod(\"" + m.getName()
                    + "\",String.class);" + ln);
            sb.append("this.h.invoke(this,m,new Object[]{id});" + ln);
            sb.append("}catch(Throwable e){" + ln);
            sb.append("e.printStackTrace();" + ln);
            sb.append("}"+ln);
            sb.append("}"+ln);
        }
        sb.append("}" + ln);
        return sb.toString();
    }
}
2.2.6 最終生成$MyProxy.java

生成代理類
在這裏插入圖片描述

package com.my;

import com.IUserService;

import java.lang.reflect.Method;

/**
 * @Author: cxx
 * @Date: 2019/10/2 11:29
 * 動態生成代理類:如下
 */
public class $ProxyMy implements IUserService {
    private MyInvocationHandler invocationHandler;
    public $ProxyMy(MyInvocationHandler invocationHandler) {
        this.invocationHandler = invocationHandler;
    }
    public void delete(String id) {
        try {
            Method m = IUserService.class.getMethod("delete", String.class);
            invocationHandler.invoke(this, m, new Object[]{id});
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }
}

2.2.7 測試
package com.my;

import com.IUserService;
import com.UserService;

import java.lang.reflect.Method;

/**
 * @Author: cxx
 * @Date: 2019/10/2 11:13
 */
public class MyTest {
    public static void main(String[] args) {
        IUserService userServiceProxy = (IUserService) MyProxy.newProxyInstance(
                new MyClassLoader(),
                UserService.class.getInterfaces(),
                new MyInvocationHandler() {
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("before");
                        method.invoke(new UserService(), args);
                          System.out.println("after");
                        return null;
                    }
                }
        );
        userServiceProxy.delete("100");
    }
}

結果

Connected to the target VM, address: ‘127.0.0.1:49209’, transport: ‘socket’
Disconnected from the target VM, address: ‘127.0.0.1:49209’, transport: ‘socket’
before
刪除用戶 id:100
after


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章