1. 程式人生 > >[Proxy] 深入理解jdk動態代理

[Proxy] 深入理解jdk動態代理

[Proxy] 深入理解jdk動態代理

分析

jdk的proxy主要有三個類,ProxyProxy.ProxyClassFactoryProxyGeneratorProxy是一個面向使用者的Client,主要是管理proxy class cache,jdk的proxy必須是interface,且必須傳入一個InvocationHandler介面的例項,規約比較強。Proxy.ProxyClassFactory主要是生產proxy class例項。ProxyGenerator是生產class例項的執行者。最終將生成proxy class 位元組碼,ProxyGenerator的saveGeneratedFiles屬性決定了是否儲存class檔案。檔案路徑是BaseDir+/com/sun/proxy/$xx.class。最終ClassLoader將會load此class位元組碼到JVM中。返回interface的動態代理例項。

自定義proxy

  1. 建立java檔案,儲存至工作路徑。
  2. 通過JavaCompiler將java檔案編譯成class檔案,儲存在工作路徑。
  3. 通過ClassLoader載入此class檔案
  4. 返回interface例項

Proxy

package com.yzz.study.proxy.custom;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.Map;
import java.util.WeakHashMap;

/**
 * author:yzz
 * date:2018/12/1
 * E-mail:
[email protected]
* com.yzz.study.proxy.custom * {@link java.lang.reflect.Proxy} */ public class Proxy { /** * 快取 {@link java.lang.reflect.Proxy} */ private static Map<String, Class> cachedProxyClassInstaceMap = new WeakHashMap<String, Class>(); protected InvocationHandler h; private Proxy(){ } protected Proxy(InvocationHandler h){ this.h = h; } /** * {@link java.lang.reflect.Proxy} * 這裡實現比較簡單的 Class<T>[] interfaces --> Class<T> ince * @param loader * @param ince * @param h * @param <T> * @return */ public static <T> T newInstance(MyClassLoader loader, Class<T> ince, InvocationHandler h) throws Exception { String className = "$proxy"+cachedProxyClassInstaceMap.size()+1; //1.從cache裡查詢 Class targetClass = cachedProxyClassInstaceMap.get(ince.getName()); if (null != targetClass){ return (T) targetClass; } //2.建立java file String javaFilePth = ProxyGenerator.createProxyJavaFile(loader,className,ince); //3.編譯 java file CompileJavaCode.compile(javaFilePth); //4.load class 檔案 Class<T> _class = (Class<T>) loader.findClass(className); //這裡加入InvocationHandler例項 Constructor constructor = _class.getConstructor(InvocationHandler.class); return (T) constructor.newInstance(h); } }

ProxyGenerator(負責建立java檔案)

package com.yzz.study.proxy.custom;

import javax.sound.sampled.Line;
import java.io.*;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * author:yzz
 * date:2018/12/1
 * E-mail:[email protected]
 * com.yzz.study.proxy.custom
 * 註釋:建立proxy class 檔案
 */
class ProxyGenerator {

    private static final String LINE = "\r\n";

    public static final String LEFT_CLOSE = "}";

    public static final String RIGHT_CLOSE = "{";

    public static final String INVOCATIONHANDLER = "InvocationHandler";

    public static final String PARAMATER = "var";

    public static String createProxyJavaFile(ClassLoader classLoader, String className, Class ince) {
        String filePath = classLoader.getClass().getResource("").getPath() + "/" + className + ".java";
        String packgeNmae = classLoader.getClass().getPackage().getName();
        StringBuffer sb = new StringBuffer();
        sb.append("package ").append(packgeNmae).append(";").append(LINE);
        sb.append("import ").append("java.lang.reflect.InvocationHandler;").append(LINE);
        sb.append("import java.lang.reflect.Method;").append(LINE);
        sb.append("import com.yzz.study.proxy.custom.Proxy;").append(LINE);
        createHead(sb, className,ince.getName());
        Map<String, String> methodNames = createField(sb, ince);
        createConstruct(sb, className);
        createMethods(sb, ince, methodNames);
        createStaticBlock(ince,methodNames,sb);
        sb.append(LEFT_CLOSE);
        try {
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filePath)));
            writer.write(sb.toString());
            writer.flush();
            writer.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return filePath;
    }

    public static void createHead(StringBuffer sb, String className,String inceName) {
        sb.append("public class ")
                .append(className)
                .append(" extends Proxy implements ")
                .append(inceName)
                .append(" ")
                .append(RIGHT_CLOSE)
                .append(" ")
                .append(LINE);
    }

    public static Map<String, String> createField(StringBuffer sb, Class ince) {
        Method[] ms = ince.getMethods();
        Map<String, String> map = new HashMap<String, String>();
        for (int i = 0; i < ms.length; i++) {
            Method method = ms[i];
            sb.append("private static Method ")
                    .append("m")
                    .append(i + 1)
                    .append(";").append(LINE);
            map.put(method.getName(), "m" + (i + 1));
        }
        return map;
    }

    public static void createConstruct(StringBuffer sb, String className) {
        sb.append("public ")
                .append(className)
                .append(" (")
                .append(INVOCATIONHANDLER)
                .append(" h )")
                .append(RIGHT_CLOSE)
                .append("super(h);")
                .append(LEFT_CLOSE)
                .append(LINE);
    }

    public static void createMethods(StringBuffer sb, Class ince, Map<String, String> methodNmaes) {
        Method[] ms = ince.getMethods();
        for (Method m : ms) {
            String methodName = m.getName();
            Class returnType = m.getReturnType();
            Class[] pts = m.getParameterTypes();
            createMethod(methodName, returnType, pts, sb, ince, methodNmaes.get(m.getName()));
        }
    }

    public static void createMethod(String methodName, Class returnType, Class[] pts, StringBuffer sb, Class ince, String mName) {
        sb.append(" public ")
                .append(returnType)
                .append(" ")
                .append(methodName)
                .append("(");
        String[] args = new String[pts.length];
        for (int i = 0; i < pts.length; i++) {
            Class c = pts[i];
            sb.append(c.getName())
                    .append(" ")
                    .append(PARAMATER)
                    .append(i);
            if (i != pts.length - 1) {
                sb.append(",");
            }
            args[i] = PARAMATER + i;
        }
        sb.append(")");
        sb.append(RIGHT_CLOSE);
        createMethodBody(mName, args, sb);
        sb.append(LEFT_CLOSE)
                .append(LINE);

    }

    /***
     *  public Object invoke(Object proxy, Method method, Object[] args)
     *  throws Throwable;
     * @return
     */
    public static void createMethodBody(String methodName, String[] args, StringBuffer sb) {
        sb.append("try ")
                .append(LINE)
                .append(RIGHT_CLOSE)
                .append(LINE)
                .append("super.h.invoke(this,")
                .append(methodName)
                .append(",")
                .append("new Object[]{");
        //這裡需要構造引數
        for (int i = 0; i < args.length; i++) {
            sb.append(args[i]);
            if (i != args.length - 1) {
                sb.append(",");
            }
        }
        sb.append("});")
                .append(LINE)
                .append("}catch(Throwable e){")
                .append(LINE)
                .append("e.printStackTrace();")
                .append(LINE)
                .append(LEFT_CLOSE)
                .append(LINE);
    }

    private static void createStaticBlock(Class ince,Map<String,String> classMethodMap,StringBuffer sb){
        sb.append("static ")
                .append(RIGHT_CLOSE)
                .append("try ")
                .append(RIGHT_CLOSE);
        Method[] ms = ince.getMethods();
        for (Method method:ms){
            Class[] ps = method.getParameterTypes();
            String pcs = "";
            for (int i = 0; i < ps.length; i++) {
                pcs += "Class.forName(\""+ps[i].getName() + "\")";
                if (i != ps.length-1){
                    pcs += ",";
                }
            }
            sb.append(classMethodMap.get(method.getName()))
                    .append("=")
                    .append("Class.forName(\"")
                    .append(ince.getName())
                    .append("\")")
                    .append(".getMethod(")
                    .append("\""+method.getName()+"\"")
                    .append(",")
                    .append("new Class[]{")
                    .append(pcs)
                    .append("});")
                    .append(LINE);
        }
        sb.append("}catch(Exception e)")
                .append(RIGHT_CLOSE)
                .append(LINE)
                .append("e.printStackTrace();")
                .append(LINE)
                .append(LEFT_CLOSE);
        sb.append(LEFT_CLOSE);
        sb.append(LINE);
    }
}

CompileJavaCode(負責編譯java檔案)

編譯工作是依賴javax.tools下的資源包

package com.yzz.study.proxy.custom;

import javax.tools.*;
import java.nio.charset.Charset;

/**
 * author:yzz
 * date:2018/12/1
 * E-mail:[email protected]
 * com.yzz.study.proxy.custom
 * 註釋:
 */

public class CompileJavaCode {

    public static void compile(String javaFileName){
        try {
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, Charset.defaultCharset());
            Iterable iterable = manager.getJavaFileObjects(javaFileName);
            JavaCompiler.CompilationTask compilationTask = compiler.getTask(null, manager, null, null, null, iterable);
            compilationTask.call();
            manager.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

ClassLoader(負責裝載指定目錄下的class檔案)

package com.yzz.study.proxy.custom;

import java.io.*;

/**
 * author:yzz
 * date:2018/12/1
 * E-mail:[email protected]
 * com.yzz.study.proxy.custom
 * 註釋: 載入class檔案
 */
public class MyClassLoader extends ClassLoader{

    private static String baseDir;

    public MyClassLoader(){
        baseDir = MyClassLoader.class.getResource("").getPath();
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String className = MyClassLoader.class.getPackage().getName()+"."+name;
        BufferedInputStream bufferedInputStream = null;
        ByteArrayOutputStream byteArrayOutputStream = null;
        try {
            File classFile = new File(baseDir,name.replaceAll("\\.","/") + ".class");
            bufferedInputStream = new BufferedInputStream(new FileInputStream(classFile));
            byteArrayOutputStream = new ByteArrayOutputStream();
            byte[] temp = new byte[1024];
            int len;
            while((len = bufferedInputStream.read(temp)) != -1){
                byteArrayOutputStream.write(temp,0,len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if (null != bufferedInputStream){
                try {
                    byteArrayOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != byteArrayOutputStream){
                try {
                    byteArrayOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        return defineClass(className,byteArrayOutputStream.toByteArray(),0,byteArrayOutputStream.size());
    }
}

interface

package com.yzz.study.proxy.custom;

/**
 * author:yzz
 * date:2018/12/1
 * E-mail:[email protected]
 * com.yzz.study.proxy.jdk
 * 註釋:
 */
public interface ILog {

    void printLog(String a,Object b);
    void printLog1(String a,Object b);
    void printLog2(String a,Object b);
}

測試

package com.yzz.study.proxy.custom;


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

/**
 * author:yzz
 * date:2018/12/2
 * E-mail:[email protected]
 * com.yzz.study.proxy.custom
 * 註釋:
 */
public class Test {

    public static void main(String[] args) {
        try {
            ILog log = Proxy.newInstance(new MyClassLoader(), ILog.class, new InvocationHandler() {
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("==================");
                    System.out.println(method.getName());
                    return null;
                }
            });
            System.out.println(log);
            log.printLog("1","2");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

展示

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.yzz.study.proxy.custom;

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

public class $proxy01 extends Proxy implements ILog {
    private static Method m1;
    private static Method m2;
    private static Method m3;

    public $proxy01(InvocationHandler var1) {
        super(var1);
    }

    public void printLog1(String var1, Object var2) {
        try {
            super.h.invoke(this, m1, new Object[]{var1, var2});
        } catch (Throwable var4) {
            var4.printStackTrace();
        }

    }

    public void printLog2(String var1, Object var2) {
        try {
            super.h.invoke(this, m2, new Object[]{var1, var2});
        } catch (Throwable var4) {
            var4.printStackTrace();
        }

    }

    public void printLog(String var1, Object var2) {
        try {
            super.h.invoke(this, m3, new Object[]{var1, var2});
        } catch (Throwable var4) {
            var4.printStackTrace();
        }

    }

    static {
        try {
            m1 = Class.forName("com.yzz.study.proxy.custom.ILog").getMethod("printLog1", new Class[]{Class.forName("java.lang.String"), Class.forName("java.lang.Object")});
            m2 = Class.forName("com.yzz.study.proxy.custom.ILog").getMethod("printLog2", new Class[]{Class.forName("java.lang.String"), Class.forName("java.lang.Object")});
            m3 = Class.forName("com.yzz.study.proxy.custom.ILog").getMethod("printLog", new Class[]{Class.forName("java.lang.String"), Class.forName("java.lang.Object")});
        } catch (Exception var1) {
            var1.printStackTrace();
        }

    }
}

總結

jdk的proxy嚴重依賴InvocationHandler介面,代理例項的方法都會回撥該介面的invoke()方法,在invoke()方法下,可以自定義代理邏輯。