1. 程式人生 > >Java中的代理--proxy

Java中的代理--proxy

char tproxy 字節 內部類 creat 16px 結果 方法 width

  講到代理,好像在之前的springMVC,還是spring中或者是hibernate中學習過,並沒有特別在意,這次好好理解一下。(原來是在spring中的AOP,面向切面 Aspect Oriented Program,無語了,這都忘了)

一、代理的概念和作用

1、程序中的代理

要為已存在的多個具有相同接口目標類的各個方法增加一些系統功能,例如:異常處理、日誌、計算方法的運行時間、事務管理等等,

 1 class x{
 2   void sayHello(){System.out.print("Hello world")}  
 3 }
 4 
 5 // 作為x的代理類除了打印Hello World 還要計算程序執行的時間
6 class XProxy{ 7 void sayHello(){ 8 startTime; 9 System.out.print("Hello World") 10 endTime; 11 } 12 }

註意:當調用目標方法的時候,直接調用代理類,既能完成目標方法,還能做一些額外的事情,這就是代理類的作用吧,也就是代理類和目標類具有相同的方法,但是在調用方法時,會加上系統功能的其他代碼,下面的圖應該更好理解一點:(具有相同的方法,但是調用的時候加上了系統代碼)

技術分享圖片

2、采用工廠模式和配置文件的方式進行管理,則不需要修改客戶端的程序,在配置文件中是使用目標類,還是代理類,這樣,以後很用以切換

3、代理是實現AOP 技術的核心和關鍵技術

4、動態代理技術

(1)JVM可以在運行期間動態生成出類的字節碼,這種動態生成的類往往被用作代理類,即動態代理類

(2)JVM動態生成的類必須實現一個或多個接口,所以,JVM生成的動態類只能用作具有相同接口的目標類的代理

(3)CGLIB庫可以動態生成一個類的子類,一個類的子類也可以用作該類的代理,所以,如果要為一個沒有實現接口的類生成動態代理類,那麽可以使用CGLIB庫

(4)代理類的各個方法中通常除了要調用目標的相應方法和對外返回目標方法的結果外,還可以在代碼中的如下位置加上系統代碼:

A:調用目標方法之前

B:調用目標方法之後

C:在調用目標方法前後

D:在處理目標方法異常的catch塊中

二、分析JVM動態生成的類

1、創建實現了Collection接口的動態類和查看其名稱,分析Proxy.getProxyClass()方法的各個參數

2、編碼列出動態類中的所有構造方法和參數名稱

3、編碼列出動態類中所有方法和參數名稱

 1         Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
 2         System.out.println(clazzProxy1.getName());
 3 
 4         System.out.println("------begin constructors list-------------");
 5         Constructor[] constructors = clazzProxy1.getConstructors();
 6         for (Constructor constructor : constructors) {
 7             String name = constructor.getName();
 8             StringBuilder sBuilder = new StringBuilder(name);
 9             sBuilder.append("(");
10             Class[] clazzParams = constructor.getParameterTypes();
11             for (Class clazzParam : clazzParams) {
12                 sBuilder.append(clazzParam.getName()).append(",");
13             }
14             if (clazzParams != null && clazzParams.length > 0) {
15                 sBuilder.deleteCharAt(sBuilder.length() - 1);
16             }
17             sBuilder.append(")");
18             System.out.println(sBuilder.toString());
19         }
20         System.out.println("------end constructors list-------------");
21 
22         System.out.println("------begin methods list-------------");
23         Method[] methods = clazzProxy1.getMethods();
24         for (Method method : methods) {
25             String name = method.getName();
26             StringBuilder sBuilder = new StringBuilder(name);
27             sBuilder.append("(");
28             Class[] clazzParams = method.getParameterTypes();
29             for (Class clazzParam : clazzParams) {
30                 sBuilder.append(clazzParam.getName()).append(",");
31             }
32             if (clazzParams != null && clazzParams.length > 0) {
33                 sBuilder.deleteCharAt(sBuilder.length() - 1);
34             }
35             sBuilder.append(")");
36             System.out.println(sBuilder.toString());
37         }
38         System.out.println("------end methods list-------------");

4、創建動態類的實例對象

A:用反射獲得構造方法

B:編寫一個簡單的InvocationHandler類

C:調用構造方法創建動態類的實例對象,並將編寫的InvocationHandler類的實例對象傳進去

D:打印創建的對象和調用對象的沒有返回值的方法和getClass()方法,演示調用其他有返回值的方法出現了異常

E:將創建動態類的實例對象的代理改成匿名內部類的形式編寫

5、讓JVM創建動態類及實例對象,需要給它提供哪些信息?

A:生成的類中有哪些方法,通過讓其實現哪些接口的方式告知

B:產生的類字節碼必須有一個關聯的類加載器對象

C:生成的類中方法的代碼是怎麽樣的,也得由我們提供,把我們的代碼寫在一個約定好了接口對象的方法中,把對象傳給他,即是相當於插入了我的代碼,提供執行代碼的對象就是那個InvocationHandler對象,他是在創建動態類的實例對象的構造方法的時候傳遞進去的,在上面的InvocationHandler類中的Invoke方法中加入代碼,就可以看到這些代碼被調用執行了

        Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
        System.out.println(clazzProxy1.getName());

        System.out.println("------begin creat instance list-------------");
        Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);

        // 普通的方法進行實例化
        class myInvokeHandler1 implements InvocationHandler {
            @Override
            public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable {
                return null;
            }
        }

        Collection proxy1 = (Collection) constructor.newInstance(new myInvokeHandler1());
        System.out.println(proxy1);
        proxy1.clear();
        //proxy1.size();

        // 用內部類的方式進行實例化
        Collection proxy2 = (Collection) constructor.newInstance(new InvocationHandler() {
            @Override
            public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable {
                return null;
            }
        });

        // 直接一步到位 其實本質還是一樣的,只不過寫法不一樣
        Collection proxy3 = (Collection) Proxy.newProxyInstance(
                        Collection.class.getClassLoader(), 
                        new Class[]{Collection.class}, 
                        new InvocationHandler() {
                            
                            List target = new ArrayList();
                            @Override
                            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                                
                                long beginTime = System.currentTimeMillis();
                                // 這裏就是Collection類或者是子類中的方法執行 也就是調用了add()方法 同時我還做了一些其他事情。計算程序運行時間
                                Object retVal = method.invoke(target, args);
                                long endTime = System.currentTimeMillis();
                                System.out.println(method.getName() + "running time:" + (endTime - beginTime));
                                return retVal;
                            }
                        });

        System.out.println("------end creat instance list-------------");

        proxy3.add("aaa");
        proxy3.add("bbb");
        proxy3.add("ccc");
        System.out.println(proxy3.size());

三、動態生成類的運行原理分析

1、先分析一下InvocationHandler對象中的invoke方法的三個參數的意義:

A:當前代理對象

B:代理對象執行哪個方法

C:方法中需要哪些參數

2、動態代理的工作原理圖

技術分享圖片

在InvocationHandler類中執行invoke()方法的時候,如何來編寫可配置的代碼,讓程序在運行的時候,將代碼以參數的形式進行傳遞,這種解決辦法,在java中是不常用的,一般的做法是將對象傳遞給InvocationHandler的invoke()方法中去執行傳遞的對象中的方法,這樣的話,我們就可以,也算是動態的去改變執行的代碼了,這就是最關鍵的部分,最巧妙的部分,值得去學習:這就是面向切面編程,就是把代碼封裝到一個對象中,將這個對象傳遞給需要執行的方法,讓方法去執行傳遞中的對象中的方法,傳遞進來的對象也就是那個切面,去執行切面中的方法,改造之前的代碼:

 1 // 這個就是需要傳入的參數對象的抽象接口,同時傳入了目標方法
 2 public interface Advice {
 3 
 4     void beforeMethod(Method method);
 5     void afterMethod(Method method);
 6     
 7 }
 8 
 9 // 參數接口的實例化對象
10 public class MyAdvice implements Advice {
11 
12     long beginTime = 0;
13     long endTime = 0;
14     
15     @Override
16     public void beforeMethod(Method method) {
17         System.out.println("Advice中的方法開始執行了。。。");
18         beginTime = System.currentTimeMillis();
19     }
20 
21     @Override
22     public void afterMethod(Method method) {
23         endTime = System.currentTimeMillis();
24         System.out.println(method.getName() + "running time:" + (endTime - beginTime));
25         System.out.println("Advice中的方法結束執行了。。。");
26     }
27 
28 }
 1 // 改造之後的方法
 2 private static Object getProxy(final Object target, final Advice advice) {
 3         Object proxy3 = Proxy.newProxyInstance(target.getClass().getClassLoader(),
 4                 target.getClass().getInterfaces(), new InvocationHandler() {
 5 
 6                     @Override
 7                     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 8 
 9                         /*long beginTime = System.currentTimeMillis();
10                         // 這裏就是Collection類或者是子類中的方法執行 也就是調用了add()方法 同時我還做了一些其他事情。計算程序運行時間
11                         Object retVal = method.invoke(target, args);
12                         long endTime = System.currentTimeMillis();
13                         System.out.println(method.getName() + "running time:" + (endTime - beginTime));
14                         return retVal;*/
15                         
16                         // 這裏就是Collection類或者是子類中的方法執行 也就是調用了add()方法 同時我還做了一些其他事情。計算程序運行時間
17                         advice.beforeMethod(method);
18                         Object retVal = method.invoke(target, args);
19                         advice.afterMethod(method);
20                         
21                         return retVal;
22                         
23                     }
24                 });
25         return proxy3;
26 }
27 // 實際調用,驗證正確性
28 public static void main(String[] args){
29         final List target = new ArrayList();
30         // 直接一步到位 其實本質還是一樣的,只不過寫法不一樣
31         Collection proxy3 = (Collection) getProxy(target, new MyAdvice());
32 
33         proxy3.add("aaa");
34         proxy3.add("bbb");
35         proxy3.add("ccc");
36         System.out.println(proxy3.size());
37 }

總結:這就是傳說中的spring框架的雛形,真的是這樣子的嗎?傳說中的spring就是這樣的啊!

Java中的代理--proxy