成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Java動態(tài)代理實現(xiàn)原理(模擬實現(xiàn))

K_B_Z / 2766人閱讀

摘要:很多框架底層都使用了的動態(tài)代理技術(shù)來實現(xiàn)的,比如大名鼎鼎的這篇文章將帶你一步一步揭開動態(tài)代理技術(shù)的神秘面紗。接下來客戶端就可以這樣使用了毫秒到目前為止,我們實現(xiàn)的類可以為任何接口生成代理類了,是不是很神奇。

? 動態(tài)代理是java語言中常用的設(shè)計模式,java在1.3版本以后也提供了動態(tài)代理技術(shù),允許開發(fā)者在運(yùn)行期間創(chuàng)建接口的代理對象。 很多框架底層都使用了java的動態(tài)代理技術(shù)來實現(xiàn)的,比如大名鼎鼎的springAOP;這篇文章將帶你一步一步揭開JDK動態(tài)代理技術(shù)的神秘面紗。

? 我們先來定義一個接口:

package com.yanghui.study.proxy;
public interface IFlyable {
    int fly(int x,int y);
}

再來一個實現(xiàn)類:

package com.yanghui.study.proxy;
public class Plane implements IFlyable{
    @Override
    public int fly(int x, int y) {
        int result = x * x + y * y;
        try {
            Thread.sleep(new Random().nextInt(700));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return result;
    }
}

如果我們要統(tǒng)計一下這個fly方法的運(yùn)行時間,該怎么做呢?很簡單,可以修改源碼在方法fly方法里面加上兩句代碼①、②,這樣就打印出方法的運(yùn)行時間了,如下:

//省略不必要代碼......
public int fly(int x, int y) {
    long start = System.currentTimeMillis();//①記錄開始時間
    int result = x * x + y * y;
    try {
        Thread.sleep(new Random().nextInt(700));
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("time:" + (System.currentTimeMillis() - start) + "毫秒");//②結(jié)束時間減去開始時間
    return result;
}

但是如果我們沒有這個方法的源碼,這個類是別人寫好打好jar包提供給我們用的,這時如果你還想統(tǒng)計下這個方法運(yùn)行時間,又該怎么辦呢?至少有兩種方式可以來實現(xiàn):

1、使用繼承,寫一個類繼承Plane,重寫fly方法,在調(diào)用父類的fly方法前后加上①②處的代碼,這樣就可以統(tǒng)計fly方法的執(zhí)行時間了。

package com.yanghui.study.proxy;
public class PlaneTimerProxy1 extends Plane{
    @Override
    public int fly(int x, int y) {
        long start = System.currentTimeMillis();//①
        int result = super.fly(x, y);
        System.out.println("time:" + (System.currentTimeMillis() - start) + "毫秒");//②
        return result;
    }
}

2、使用聚合的方式,寫一個類PlaneTimerProxy2實現(xiàn)跟Plane一樣的接口,并且持有IFlyable的引用,當(dāng)調(diào)用fly方法時,實際調(diào)用的是IFlyable的fly方法,這樣就可以在方法調(diào)用前后加上①②處的代碼統(tǒng)計fly方法的執(zhí)行的時間。

public class PlaneTimerProxy2 implements IFlyable{
    private IFlyable flyable;
    public PlaneTimerProxy2(IFlyable flyable) {
        this.flyable = flyable;
    }
    @Override
    public int fly(int x, int y) {
        long start = System.currentTimeMillis();//①
        int result = this.flyable.fly(x, y);
        System.out.println("time:" + (System.currentTimeMillis() - start) + "毫秒");//②
        return result;
    }
}

這兩種方式都可以實現(xiàn),那么哪種方式更好呢?答案是聚合的方式更好,為什么呢?想象一下,如果我還想實現(xiàn)更多的功能,比如給fly方法執(zhí)行前后加上日志,事務(wù)控制,權(quán)限控制,這時用繼承的方式你會需要新建更多的類來實現(xiàn),可能你會想,聚合的實現(xiàn)方式不也是要新建更多的類來實現(xiàn)嗎?是的,但是如果我要你先記錄日志再記錄時間,有如果我要你先記錄時間再記錄日志,需要實現(xiàn)這樣隨意的組合的功能,繼承就顯得很麻煩了,而聚合的方式就會很靈活了。在思考下,如果想給不同類的100個方法記錄下時間和日志,那么你想想看是不是要產(chǎn)生100個代理類呢?類的數(shù)量又在不停的膨脹了。如果我們能夠為實現(xiàn)了某個接口的類動態(tài)生成代理類就好了?想法很好,先來新建一個類Proxy,提供一個方法newProxyInstance,這個方法可以為一個實現(xiàn)了IFlyable接口的類產(chǎn)生代理類,那么客戶端調(diào)用就可以這樣做:

package com.yanghui.study.proxy.custom;
public class Client {
    public static void main(String[] args) {
        IFlyable flyable = (IFlyable)Proxy.newProxyInstance();
        flyable.fly(1, 2);
    }
}

那么我們?nèi)绾卧?b>newProxyInstance方法里面動態(tài)的生成一個代理類呢?為了模擬JDK的實現(xiàn),先定義一個接口InvocationHandler:

package com.yanghui.study.proxy.custom;
import java.lang.reflect.Method;
public interface InvocationHandler {
    Object invoke(Object proxy,Method method,Object[] args)throws Throwable;
}

下面來個完整代碼:

public class Proxy {
    private static final Map bytesMap = new HashMap<>();
    private static final AtomicInteger count = new AtomicInteger();
    public static Object newProxyInstance(Class intaface,InvocationHandler handler) {
        //代碼①處
        String rn = "
";
        String className = "Proxy" + count.getAndIncrement();
        String str = "package com.yanghui.study.proxy.custom;" + rn +
                     "public class " + className + " implements " + intaface.getName() + "{" + rn +
                     "    private InvocationHandler handler;" + rn +
                     "    public " + className + "(InvocationHandler handler){" + rn + 
                     "        this.handler=handler;" + rn + 
                     "    }" + rn;
        
        String methodStr = "";
        for(Method m : intaface.getMethods()) {
            methodStr = methodStr + "    @Override" + rn +
             "    public " + m.getReturnType().getName() + " " + m.getName() + "(";
            String parameterStr = "";
            String psType = "";
            String pname = "";
            for(Parameter p : m.getParameters()) {
                parameterStr = parameterStr + p + ",";
                psType = psType + p.getType().getName() + ".class,";
                pname = pname + p.getName() + ",";
            }
            if(!parameterStr.equals("")) {
                parameterStr = parameterStr.substring(0, parameterStr.length() - 1);
            }
            parameterStr = parameterStr + "){" + rn + 
                     "        try{" + rn +
                     "            " + Method.class.getName() + " method = " + intaface.getName() + ".class.getDeclaredMethod("" + m.getName() + """;
            if(!psType.equals("")) {
                psType = psType.substring(0, psType.length() - 1);
                parameterStr = parameterStr + "," + psType + ");" + rn;
            }else {
                parameterStr = parameterStr + ");" + rn;
            }
            if(pname.length() > 0) {
                pname = pname.substring(0, pname.length() - 1);
            }
            String returnStr = "";
            if(!"void".equals(m.getReturnType().getName())) {
                returnStr = returnStr + "            return (" + m.getReturnType().getName() + ")";
            }
            parameterStr = parameterStr + 
                    returnStr + "this.handler.invoke(this,method," + (pname.length() == 0 ? "null" : "new Object[]{" + pname + "}") + ");" + rn +
             "        } catch (Throwable e) {" + rn +
             "        throw new RuntimeException(e);" + rn +
             "    }" + rn +
             "    }" + rn;
            methodStr = methodStr + parameterStr;
        }
        String endStr = "}";
        str = str + methodStr + endStr;
        String path = Thread.currentThread().getContextClassLoader().getResource("").getPath() + "com/yanghui/study/proxy/custom/";
        String fileStr = path + className + ".java";
        //代碼②處
        //寫入文件
        writeToFile(fileStr, str);
        //代碼③處
        //動態(tài)編譯
        String className1 = "com.yanghui.study.proxy.custom." + className;
        return compileToFileAndLoadclass(className1, fileStr, handler);
    }
    
    /**
     * 從源文件到字節(jié)碼文件的編譯方式
     * @param className
     * @param fileStr
     * @param handler
     * @return
     */
    private static Object compileToFileAndLoadclass(String className,String fileStr,InvocationHandler handler) {
        //獲取系統(tǒng)Java編譯器
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        //獲取Java文件管理器
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        //定義要編譯的源文件
        File file = new File(fileStr);
        //通過源文件獲取到要編譯的Java類源碼迭代器,包括所有內(nèi)部類,其中每個類都是一個 JavaFileObject,也被稱為一個匯編單元
        Iterable compilationUnits = fileManager.getJavaFileObjects(file);
        //生成編譯任務(wù)
        JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, compilationUnits);
        //執(zhí)行編譯任務(wù)
        task.call();
        try {
            fileManager.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            Class c = Thread.currentThread().getContextClassLoader().loadClass(className);
            Constructor ct = c.getConstructor(InvocationHandler.class);
            Object object = ct.newInstance(handler);
            return object;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    private static void writeToFile(String file,String context) {
        FileWriter fw = null;
        try {
            fw = new FileWriter(new File(file));
            fw.write(context);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(fw != null) {
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

我來解釋下上面代碼的意思:

1、代碼①處,根據(jù)傳入的接口動態(tài)生成java代碼的字符串,類名取名為Proxy+序號,該類實現(xiàn)了傳入的接口,真正的方法調(diào)用將委托傳入InvocationHandler的實現(xiàn)類來實現(xiàn)。

2、代碼②處,將生成的java代碼的字符串寫入文件

3、代碼③處,真正的核心,動態(tài)編譯2步生成的java文件,再通過classLoader把編譯生成的class文件加載進(jìn)內(nèi)存,然后反射創(chuàng)建實例。

接下來客戶端就可以這樣使用了:

public class Client {
    public static void main(String[] args) {
        Plane plane = new Plane();
        IFlyable flyable = (IFlyable)Proxy.newProxyInstance(IFlyable.class,new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                long start = System.currentTimeMillis();
                Object result = method.invoke(plane, args);
                System.out.println("time:" + (System.currentTimeMillis() - start) + "毫秒");
                return result;
            }
        });
        System.out.println(flyable.fly(1, 2));
    }
}

到目前為止,我們實現(xiàn)的Proxy類可以為任何接口生成代理類了,是不是很神奇。當(dāng)然我們這里只是模擬實現(xiàn)了JDk的動態(tài)代理,還有很多細(xì)節(jié)是沒有考慮的,有興趣的同學(xué)可以自己閱讀JDK源碼,相信您理解了其背后的原理后,看起來也不會太費(fèi)力了。

擴(kuò)展

在上面我們實現(xiàn)了動態(tài)生成java文件,動態(tài)編譯java文件,需要把文件寫入磁盤,也會在java源文件的目錄生成編譯后的.class文件,那么可以不可以只在內(nèi)存中編譯加載呢?答案是可以的,代碼如下(方法是Proxy類下的方法):

/**
     * 從內(nèi)存到內(nèi)存的編譯方式
     * @param className
     * @param code
     * @param handler
     * @return
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    private static Object compileMemoryToMemoryAndLoadClass(String className,String code,InvocationHandler handler) {
        if(bytesMap.get(className) != null) {
            return loadClass(className, bytesMap.get(className), handler);
        }
        //獲取系統(tǒng)Java編譯器
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        //獲取Java文件管理器
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        ForwardingJavaFileManager fjf = new ForwardingJavaFileManager(fileManager) {
            @Override
            public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind,
                    FileObject sibling) throws IOException {
                if(kind == JavaFileObject.Kind.CLASS) {
                    return new SimpleJavaFileObject(URI.create(""), JavaFileObject.Kind.CLASS) {
                        public OutputStream openOutputStream() {
                            return new FilterOutputStream(new ByteArrayOutputStream()) {
                                public void close() throws IOException{
                                    out.close();
                                    ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
                                    bytesMap.put(className, bos.toByteArray());
                                }
                            };
                        }
                    };
                }else{
                    return super.getJavaFileForOutput(location, className, kind, sibling);
                }
            }
        };
        
        SimpleJavaFileObject sourceJavaFileObject = new SimpleJavaFileObject(URI.create(className.replace(".", "/") + Kind.SOURCE.extension),JavaFileObject.Kind.SOURCE){
            @Override
            public CharBuffer getCharContent(boolean b) {
                return CharBuffer.wrap(code);
            }
        };
        //生成編譯任務(wù)
        JavaCompiler.CompilationTask task = compiler.getTask(null, fjf, null, null, null, Arrays.asList(new JavaFileObject[] {sourceJavaFileObject}));
        //執(zhí)行編譯任務(wù)
        task.call();
        try {
            fileManager.close();
            fjf.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return loadClass(className, bytesMap.get(className), handler);
    }
    
    private static Object loadClass(String className,byte[] bytes,InvocationHandler handler) {
        try {
            Class c = new MyClassLoader(bytes).loadClass(className);
            Constructor ct = c.getConstructor(InvocationHandler.class);
            Object object = ct.newInstance(handler);
            return object;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

首先通過自己定義sourceJavaFileObject類來加載java格式的字符串,通過ForwardingJavaFileManager類來重新定義編譯文件的輸出行為,這里我直接寫入內(nèi)存,用一個map(bytesMap)來保存,key就是類名,value就是編譯好的.class的二進(jìn)制文件。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/71942.html

相關(guān)文章

  • Spring AOP的實現(xiàn)原理

    摘要:使用與的靜態(tài)代理不同,使用的動態(tài)代理,所謂的動態(tài)代理就是說框架不會去修改字節(jié)碼,而是在內(nèi)存中臨時為方法生成一個對象,這個對象包含了目標(biāo)對象的全部方法,并且在特定的切點(diǎn)做了增強(qiáng)處理,并回調(diào)原對象的方法。 AOP(Aspect Orient Programming),我們一般稱為面向方面(切面)編程,作為面向?qū)ο蟮囊环N補(bǔ)充,用于處理系統(tǒng)中分布于各個模塊的橫切關(guān)注點(diǎn),比如事務(wù)管理、日志、緩存...

    ephererid 評論0 收藏0
  • 動態(tài)代理模式實現(xiàn)原理

    摘要:代理模式概念代理模式分為兩種,一種是靜態(tài)代理模式,一種是動態(tài)代理模式。面向切面的編程也是使用動態(tài)代理模式來實現(xiàn)的。 1.代理模式概念 代理模式分為兩種,一種是靜態(tài)代理模式,一種是動態(tài)代理模式。 靜態(tài)代理模式:在程序運(yùn)行之前需要寫好代理類 動態(tài)代理模式:在程序運(yùn)行期間動態(tài)生成代理類 2.動態(tài)代理的實現(xiàn) 動態(tài)代理實現(xiàn)的步驟: (1)寫一個代理類SubjectHandler實現(xiàn)Invoca...

    songjz 評論0 收藏0
  • Java反射-動態(tài)代理

    摘要:動態(tài)代理有多種不同的用途,例如,數(shù)據(jù)庫連接和事務(wù)管理用于單元測試的動態(tài)模擬對象其他類似的方法攔截。調(diào)用序列和下面的流程類似單元測試動態(tài)對象模擬利用動態(tài)代理實現(xiàn)單元測試的動態(tài)存根代理和代理??蚣馨寻b成動態(tài)代理。 使用反射可以在運(yùn)行時動態(tài)實現(xiàn)接口。這可以使用類java.lang.reflect.Proxy。這個類的名稱是我將這些動態(tài)接口實現(xiàn)稱之為動態(tài)代理的原因。動態(tài)代理有多種不同的用途,...

    Acceml 評論0 收藏0
  • Java動態(tài)追蹤技術(shù)探究

    摘要:對于人類來說,字節(jié)碼文件的可讀性遠(yuǎn)遠(yuǎn)沒有代碼高。盡管如此,還是有一些杰出的程序員們創(chuàng)造出了可以用來直接編輯字節(jié)碼的框架,提供接口可以讓我們方便地操作字節(jié)碼文件,進(jìn)行注入修改類的方法,動態(tài)創(chuàng)造一個新的類等等操作。 引子 在遙遠(yuǎn)的希艾斯星球爪哇國塞沃城中,兩名年輕的程序員正在為一件事情苦惱,程序出問題了,一時看不出問題出在哪里,于是有了以下對話: Debug一下吧。 線上機(jī)器,沒開Debu...

    BlackFlagBin 評論0 收藏0

發(fā)表評論

0條評論

K_B_Z

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<