Java學習之代理

一、概述

1、概念:

1、生活中的代理:
就是常說的代理商,從廠商將商品賣給消費者,消費者不用很麻煩的到廠商在購買了。

2、程序中的代理:
要為已經(jīng)存在的多個具有相同接口的目標類的各個方法增加一些系統(tǒng)功能,如異常處理、日志、計算方法的運行時間、事物管理等等。

3、簡單示例:
編寫一個與目標類具有相同接口的代理類,代理類的每個方法調(diào)用目標類的相同方法,并在調(diào)用方法時加上系統(tǒng)功能的代碼
如:

目標類:

class X{
  void sayHello(){
    syso:Hello;
  }                                          
}                                    

代理類:

Xproxy{
  void sayHello(){
    X. sayHello();
    endTime;
  }
}

一般用接口來引用其子類,如:

Collectioncoll = new ArrayList();

4、代理類的優(yōu)點:
如果采用工廠模式和配置文件的方式進行管理,則不需要修改客戶端程序,在配置文件中配置是使用目標類還是代理類。這樣以后很容易切換,如果想要日志功能時,就配置代理類,否則配置目標類,這樣,增加系統(tǒng)功能很容易,以后運行一段時間后,又想換掉系統(tǒng)功能也很容易。

示例:

import java.lang.reflect.*;  
import java.util.*;  
public class ProxyTest {  
    public static void main(String[] args) throws Exception{  
        //獲取代理類Proxy的Class對象,傳入的是類加載器和相應的字節(jié)碼對象  
        Class clazzProxy1 = Proxy.getProxyClass(  
                Collection.class.getClassLoader(), Collection.class);  
        System.out.println(clazzProxy1.getName());//$Proxy0  
          
        System.out.println("---begin constructor list------");  
        //獲取代理類的構造方法,可能含有多個,得到數(shù)組  
        Constructor[] constructors = clazzProxy1.getConstructors();  
        //遍歷數(shù)組,獲取每個構造方法  
        for(Constructor constructor : constructors){  
            //先得到構造方法的名字,并裝入字符串容器中  
            String name = constructor.getName();  
            StringBuilder sBul = new StringBuilder(name);  
            sBul.append('(');  
            //獲取構造方法中的參數(shù)類型,并遍歷  
            Class[] clazzParams = constructor.getParameterTypes();  
            for(Class clazzParam : clazzParams){  
                sBul.append(clazzParam.getName()).append(',');  
            }  
            //將最后一個逗號去除  
            if(clazzParams != null && clazzParams.length!=0)  
                sBul.deleteCharAt(sBul.length()-1);  
            sBul.append(')');  
            System.out.println(sBul.toString());  
        }  
          
        System.out.println("---begin method list------");  
        //獲取代理類的方法,存入數(shù)組  
        Method[] methods = clazzProxy1.getMethods();  
        //遍歷數(shù)組,獲取每個方法  
        for(Method method : methods){  
            //先得到方法的名字,并裝入字符串容器中  
            String name = method.getName();  
            StringBuilder sBul = new StringBuilder(name);  
            sBul.append('(');  
            //獲取方法中的參數(shù)類型,并遍歷  
            Class[] clazzParams = method.getParameterTypes();  
            for(Class clazzParam : clazzParams){  
                sBul.append(clazzParam.getName()).append(',');  
            }  
            //將最后一個逗號去除  
            if(clazzParams!=null && clazzParams.length!=0)  
                sBul.deleteCharAt(sBul.length()-1);  
            sBul.append(')');  
            System.out.println(sBul.toString());  
        }  
}

2、AOP

1、簡述:AOP(Aspect Oriented Program)即面向方面的編程。

2、示意圖:
系統(tǒng)中存在著交叉業(yè)務,一個交叉業(yè)務就是要切入到系統(tǒng)中的一個方面,如圖:

安全、事務、日志等功能要貫穿于好多個模塊中,所以他們就是交叉業(yè)務。

3、用具體的程序代碼描述交叉業(yè)務:
1)代碼實現(xiàn)


2)交叉業(yè)務的編程問題即面向方面的編程(AOP),AOP的目標就是使交叉業(yè)務模塊化,可以采用將切面代理移動到原始方法的周圍,這與直接在方法中編寫切面代理的過程效果是一樣的,如圖:

因此使用代理技術正好可以解決這種問題,代理是實現(xiàn)AOP功能的核心和關鍵技術。

二、動態(tài)代理技術

1、概述:

1、要為系統(tǒng)中的各種接口的類增加代理功能,那將需要太多的代理類,這時就不能采用靜態(tài)代理方式,需用動態(tài)代理技術。

2、動態(tài)代理類:
JVM可在運行時,動態(tài)生成類的字節(jié)碼,這種動態(tài)(不是代理,只是拿出來作為代理類)生成的類往往被用作代理類,即動態(tài)代理類。
注:
JVM生成的動態(tài)類必須實現(xiàn)一或多個接口,所以JVM生成的動態(tài)代理類只能用作具有相同接口的目標類代理。

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

4、代理類各個方法通常除了調(diào)用目標相應方法和對外返回目標返回的結果外,還可以再代理方法中的如下位置上加上系統(tǒng)功能代碼:

1)在調(diào)用目標方法之前

2)在調(diào)用目標方法之后

3)在調(diào)用目標方法前后

4)在處理目標方法異常的catch塊中。

2、分析JVM動態(tài)生成的類

1、創(chuàng)建動態(tài)類的實例對象:

1)用反射獲得構造方法

2)編寫一個最簡單的InvocationHandler的類

3)調(diào)用構造方法創(chuàng)建動態(tài)類的實例對象,并將編寫的InvocationHandler類的實例對象傳進去。

示例:
第一、打印創(chuàng)建的對象和調(diào)用對象的無返回值的方法和getClass方法,演示調(diào)用其他沒有返回值的方法報告的異常
第二、將創(chuàng)建的動態(tài)類的實例對象的代理改寫成為匿名內(nèi)部類的形式編寫。

package cn.itcast.test3;  
import java.lang.reflect.*;  
import java.util.*;  
public class ProxyTest {  
    public static void main(String[] args) throws Exception{  
//創(chuàng)建動態(tài)代理類的三種方式  
        //方式一:通過接口的子類創(chuàng)建對象  
        Collection proxy1 = (Collection)  
                constructor.newInstance(new MyInvocationHandler());  
        System.out.println(proxy1);//null  
        System.out.println(proxy1.toString());//null  
        proxy1.clear();//無異常  
        //proxy1.size();//異常          
        //方式二:匿名內(nèi)部類  
        Collection proxy2 = (Collection)  
                constructor.newInstance(new InvocationHandler(){  
                    public Object invoke(Object proxy, Method method,  
                            Object[] args) throws Throwable {  
                        // TODO Auto-generated method stub  
                        return null;  
                    }  
                });  
          
        //方式三:  
        //通過代理類的newProxyInstance方法直接創(chuàng)建對象  
        Collection proxy3 = (Collection)Proxy.newProxyInstance(  
            //定義代理類的類加載器  
            Collection.class.getClassLoader(),  
            //代理類要實現(xiàn)的接口列表  
            new Class[]{Collection.class},  
            //指派方法調(diào)用的調(diào)用處理程序  
            new InvocationHandler() {  
                //創(chuàng)建集合,制定一個目標  
                ArrayList target = new ArrayList();  
                public Object invoke(Object proxy, Method method, Object[] args)  
                        throws Throwable {  
                    //測試程序運行時間  
                    long beginTime = System.currentTimeMillis();  
                    //調(diào)用目標方法,將其從return抽出來,加入代理所需的代碼  
                    Object retVal = method.invoke(target, args);  
                    long endTime = System.currentTimeMillis();  
                    //測試  
                    System.out.println(method.getName() +   
                            " run time of " +   
                            (endTime - beginTime));  
                    return retVal;  
                }  
            }  
            );  
        //通過代理類調(diào)用目標方法,每調(diào)用一個目標的方法就會執(zhí)行代理類的方法  
        //當調(diào)用一次add方法時,就會找一次InvocationHandler這個參數(shù)的invoke方法  
        proxy3.add("sdfd");  
        proxy3.add("shrt");  
        proxy3.add("rtbv");  
        System.out.println(proxy3.size());  
        System.out.println(proxy3.getClass().getName());  
    }  
}

2、讓JVM創(chuàng)建動態(tài)類需要提供的信息:
1)生成類中的哪些方法,通過讓其實現(xiàn)哪些接口的方式進行告知。

2)產(chǎn)生的類字節(jié)碼必須有一個關聯(lián)的類加載器對象

3)生成的類中的方法的代碼是怎么樣的,也得由我們自己提供,把我們的代碼寫在一個約定好的子接口對象的方法中,把對象傳給它,它調(diào)用我們的方法,即相當于插入了我們自己的代碼。提供執(zhí)行代碼的對象就是InvocationHandler對象,它是在創(chuàng)建動態(tài)類的實例對象的構造方法時傳遞進去的,在上面的InvocationHandler對象的invoke方法中,加一點代碼就可以看到這些代碼被調(diào)用運行了。

3、分析動態(tài)生成的類的內(nèi)部代碼

1、構造方法接受一個InvocationHandler對象,接受此對象的用處:
接受一個handler參數(shù)是為了記錄它,以便在之后的程序中運用它。

2、實現(xiàn)Collection接口的動態(tài)類中的各個方法的代碼的解析:
1)InvocationHandler接口中定義的invoke方法接受三個參數(shù)的含義:
第一、Client(客戶端)程序調(diào)用objProxy.add(“avc”)方法時,涉及到了三個參數(shù),分別為:
objProxy對象,
add方法,
”avc”參數(shù)。

代碼如下:

class Proxy${
  add(Object obj){
      return handler.invoke(Object proxy, Method method, Object[] args);
   }
}

第二、其中的Objectproxy 即為objProxy對象,Method method對應add方法,Object[] args就是”avc”參數(shù)。在使用newProxyInstance的方式創(chuàng)建代理對象實現(xiàn)時,當前正在調(diào)用代理對象(Object proxy),調(diào)用當前對象的哪個方法(Method method),調(diào)用此對象方法時傳入的參數(shù)(Object[] args)。

3、調(diào)用代理涉及到三個因素:代理對象,代理對象的哪個方法,以及此方法接受的參數(shù)。要執(zhí)行目標對象,只需要將代理對象作為目標對象即可。

4、對于上面代碼中的Object retVal = method.invoke(target,args)的分析:
目標對象target執(zhí)行完返回一個值為retVal,接著將值作為結果return回去,則代理方法就會收到一個返回值。其中還可以定義一個過濾器,對參數(shù)args(即當前對象的方法傳入的參數(shù))進行過濾(修改)。

5、對于上面代碼中的proxy3.add(“sdfd”)的分析:
1)代理對象調(diào)用add方法,傳遞了sdfd參數(shù)。

2)add方法內(nèi)部會找到InvocationHandler中的invoke方法,將代理對象proxy傳進去,把add方法傳入,將“sdfd”參數(shù)傳入代理對象中的handler參數(shù),返回了一個結果,就是給了add方法,add方法繼續(xù)向外返回給調(diào)用的對象proxy3,即最終結果。
其中的handler的invoke方法返回又來自于目標target返回值,從而將此返回值返給Object invoke()方法,即作為handler參數(shù)位置上的值返回給add方法。add方法作為最后的結果返回。

4、問題:

1、在上面的方式一的代碼中,調(diào)用無返回值的方法返回的是null,而調(diào)用有返回值方法的時候會報NullPointException異常的原因:
在proxy1.size()方法中,size()會調(diào)用Object invoke()方法,而對其覆寫時的返回值是null,而size()本身會返回int類型,兩者返回的類型不相等,所以就會報錯。
而對于proxy3.size()不報錯,是因為size()返回值與代理對象中handler參數(shù)返回值是一致的,當前目標返回什么,代理就返回什么,所以不會報錯。

注意:目標返回值和代理返回值必須是同一類型。

2、為何動態(tài)類的實例對象的getClass()方法返回了正確結果,而沒調(diào)用invoke方法:
因為代理類從Object上繼承了許多方法,其中只對三個方法(hashCode、equals和toString)進行開發(fā),委托給handler去自行處理,對于它身上其他方法不會交給代理類去實現(xiàn),所以對于getClass()方法,還是由Object本身實現(xiàn)的。即proxy3.getClass(),該是什么結果還是什么結果,并不會交給invoke方法處理。

五、總結分析動態(tài)代理類的統(tǒng)計原理和結構:
1、怎樣將目標傳進去:
1)直接在InvocationHandler實現(xiàn)類中創(chuàng)建目標類的實例對象,可看運行效果和加入日志代碼,但是毫無意義。

2)為InvocationHandler實現(xiàn)類注入目標的實例對象,不能采用匿名內(nèi)部類的形式了。

3)讓匿名內(nèi)部類的InvocationHandler實現(xiàn)類訪問外面的方法中的目標類實例對象的final類型的引用變量。

2、動態(tài)代理的工作原理:
1)Client(客戶端)調(diào)用代理,代理的構造方法接受一個InvocationHandler,client調(diào)用代理的各個方法,代理的各個方法請求轉發(fā)給剛才通過構造方法傳入的handler對象,又把各請求分發(fā)給目標的相應的方法。就是將handler封裝起來,其中this引用了當前的放(發(fā)來什么請求就接受哪個方法)。

示意圖

2)將創(chuàng)建代理的過程改為一種更優(yōu)雅的方式,eclipse重構出一個getProxy方法綁定接受目標,同時返回代理對象,讓調(diào)用者更懶惰,更方便,調(diào)用者甚至不用接觸任何代理的API。

在這里將InvocationHandler加入到Proxy的構造方法中,因此,在創(chuàng)建出來的對象,就會存有構造方法中InvocationHandler的一些功能和信息,因為我們把想要運行的代碼封裝在InvocationHandler對象,把它傳入到構造函數(shù)中,那么就實現(xiàn)了代理對象每次調(diào)用目標方法(因為實現(xiàn)了同一接口)時,都會調(diào)用我們加入到InvocationHandler對象中的代碼。這就保證了每次調(diào)用代理時,可以在目標上加入我們自己加入的功能。

3、把系統(tǒng)功能代理模塊化,即切面代碼也改為通過參數(shù)形式提供,怎么把要執(zhí)行的系統(tǒng)功能代碼以參數(shù)的形式提供:
1)把要執(zhí)行的代碼裝到一個對象的某個方法中,然后把此對象作為參數(shù)傳遞,接收者只要調(diào)用這個對象的方法,即等于執(zhí)行了外接提供的代碼。
2)為bind方法增加一個Advice參數(shù)。

示例:

//封裝getProxy方法  
package cn.itcast.test3;  
import java.lang.reflect.*;  
import java.util.*;  
public class MyProxy {  
    public static void main(String[] args)throws Exception {  
        //創(chuàng)建目標對象,并進行操作測試  
        final ArrayList target = new ArrayList();  
        Collection proxy = (Collection)getProxy(target,new MyAdvice());  
        proxy.add("sdf");  
        proxy.add("wgcd");  
        proxy.add("hgwe");  
        System.out.println(proxy.size());  
          
    }  
    //作為一個通用的方法,就使用Object  
    //傳入一個目標,并傳入一個接口,此接口作為通信的契約,才能調(diào)用額外的方法  
    private static Object getProxy(final Object target,final Advice advice) {  
        Object proxy = Proxy.newProxyInstance(  
                target.getClass().getClassLoader(),  
                //這里的接口要和target實現(xiàn)相同的接口  
                target.getClass().getInterfaces(),  
                new InvocationHandler() {  
                    public Object invoke(Object proxy, Method method, Object[] args)  
                            throws Throwable {  
                        //通過契約,使用其方法--before和after方法  
                        advice.beforeMethod(method);  
                        Object value = method.invoke(target, args);  
                        advice.afterMethod(method);  
                        return value;  
                    }  
                }  
                );  
        return proxy;  
    }  
}  
//創(chuàng)建實現(xiàn)Advice接口的子類  
package cn.itcast.test3;  
import java.lang.reflect.Method;  
//實現(xiàn)Advice接口中方法的具體內(nèi)容  
public class MyAdvice implements Advice {  
  
    long beginTime = 0;  
    public void beforeMethod(Method method) {  
        // TODO Auto-generated method stub  
        System.out.println("從這里開始");  
        beginTime = System.currentTimeMillis();   
    }  
    public void afterMethod(Method method) {  
        // TODO Auto-generated method stub  
        long endTime = System.currentTimeMillis();  
        System.out.println("從這里結束");  
        System.out.println(method.getName() + " run time of " + (endTime-beginTime));  
    }  
}  
//創(chuàng)建接口Advice  
import java.lang.reflect.Method;  
/*接口中需要實現(xiàn)四個方法 
 * 調(diào)用目標方法之前 
 * 調(diào)用目標方法之后 
 * 調(diào)用目標方法前后 
 * 在處理目標方法異常的catch塊中 
 */  
//這里只列出兩個作為示例  
public interface Advice {  
    void beforeMethod(Method method);  
    void afterMethod(Method method);  
}

三、實現(xiàn)類似spring的可配置的AOP框架

1、工廠類BeanFactory:

1、工廠類BeanFactory負責創(chuàng)建目標類或代理類的實例對象,并通過配置文件實現(xiàn)切換。

2、getBean方法根據(jù)參數(shù)字符串返回一個相應的實例對象,如果參數(shù)字符串在配置文件中對應的類名不是ProxyFactoryBean,則直接返回該類的實例對象,否則返回該類示例對象的getProxy方法返回的對象。

3、BeanFactory的構造方法接收代表配置文件的輸入流對象的配置文件格式如下:

#xxx=java.util.ArrayList
xxx=cn.itcast.test3.aopframework.ProxyFactoryBean
xxx.advice=cn.itcast.test3.MyAdvice
xxx.target=java.util. ArrayList

注意:其中的#代表注釋當前行。

4、ProxyFactoryBean充當封裝成動態(tài)的工廠,需為工廠提供的配置參數(shù)信息包括:
目標(target)
通告(advice)

5、BeanFactory和ProxyFactoryBean:
1)BeanFactory是一個純粹的bean工程,就是創(chuàng)建bean即相應的對象的工廠。
2)ProxyfactoryBean是BeanFactory中的一個特殊的Bean,是創(chuàng)建代理的工廠。

2、實現(xiàn)類似spring的可配置的AOP框架的思路:

1、創(chuàng)建BeanFactory類:
1)構造方法:接受一個配置文件,通過Properties對象加載InputStream流對象獲得。
2)創(chuàng)建getBean(String name)方法,接收Bean的名字,從上面加載后的對象獲得。
3)通過其字節(jié)碼對象創(chuàng)建實例對象bean。
4)判斷bean是否是特殊的Bean即ProxyFactoryBean,如果是,就要創(chuàng)建代理類,并設置目標和通告,分別得到各自的實例對象,并返回代理類實例對象。如果不是在返回普通類的實例對象。

2、創(chuàng)建ProxyFactoryBean(接口),此處用類做測試,其中有一個getProxy方法,用于獲得代理類對象。

3、對配置文件進行配置,如上面配置一樣。

4、作一個測試類:AopFrameworkTest進行測試。

示例:

//創(chuàng)建BeanFactory類  
package cn.itcast.test3.aopframework;  
import java.io.*;  
import java.util.Properties;  
import cn.itcast.test3.Advice;  
public class BeanFactory {  
    Properties prop = new Properties();  
    //創(chuàng)建對象時需要傳入一個配置文件中的數(shù)據(jù),所以需要在構造方法中接受一個參數(shù)  
    public BeanFactory(InputStream ips) {  
        try {  
            //將配置文件加載進來  
            prop.load(ips);  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
    //創(chuàng)建getBean方法,通過配置文件中的名字獲取bean對象  
    public Object getBean(String name){  
        //從配置文件中讀取類名  
        String className = prop.getProperty(name);  
        Object bean = null;  
        try {  
            //由類的字節(jié)碼獲取對象  
            Class clazz = Class.forName(className);  
            bean = clazz.newInstance();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }   
        //判斷bean是特殊的bean即ProxyFactoryBean還是普通的bean  
        if(bean instanceof ProxyFactoryBean){  
            Object proxy = null;  
            try {  
                //是ProxyFactoryBean的話,強轉,并獲取目標和通告  
                ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean)bean;  
                //獲取advice和target  
                Advice advice = (Advice)Class.forName(prop.getProperty(name + ".advice")).newInstance();  
                Object target = Class.forName(prop.getProperty(name + ".target")).newInstance();  
                //設置目標和通告  
                proxyFactoryBean.setAdvice(advice);  
                proxyFactoryBean.setTarget(target);  
                //通過類ProxyFactoryBean(開發(fā)中是作為接口存在)中獲得proxy對象  
                proxy = proxyFactoryBean.getProxy();  
            } catch (Exception e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }   
            //是ProxyFactoryBean的話,返回proxy對象  
            return proxy;  
        }  
        //否則返回普通bean對象  
        return bean;  
    }  
}  
  
//創(chuàng)建ProxyFactoryBean類  
package cn.itcast.test3.aopframework;  
import java.lang.reflect.*;  
import cn.itcast.test3.Advice;  
public class ProxyFactoryBean {  
    private Object target;  
    private Advice advice;  
    public Object getTarget() {  
        return target;  
    }  
    public void setTarget(Object target) {  
        this.target = target;  
    }  
    public Advice getAdvice() {  
        return advice;  
    }  
    public void setAdvice(Advice advice) {  
        this.advice = advice;  
    }  
    public Object getProxy() {  
        Object proxy = Proxy.newProxyInstance(  
                target.getClass().getClassLoader(),  
                //這里的接口要和target實現(xiàn)相同的接口  
                target.getClass().getInterfaces(),  
                new InvocationHandler() {  
                    public Object invoke(Object proxy, Method method, Object[] args)  
                            throws Throwable {  
                        //通過契約,使用其方法--before和after方法  
                        advice.beforeMethod(method);  
                        Object value = method.invoke(target, args);  
                        advice.afterMethod(method);  
                        return value;  
                    }  
                }  
                );  
        return proxy;  
    }  
}  
//創(chuàng)建測試類AopFrameworkTest  
package cn.itcast.test3.aopframework;  
import java.io.InputStream;  
public class AopFramewrorkTest {  
    public static void main(String[] args)throws Exception {  
        //讀取配置文件的數(shù)據(jù)  
        InputStream ips =   
                AopFramewrorkTest.class.getResourceAsStream("config.property");  
        //獲取bean對象  
        Object bean = new BeanFactory(ips).getBean("xxx");  
        System.out.println(bean.getClass().getName());  
    }  
}

此時,如果傳入的是java.util.ArrayList的話,返回的結果就是這個對象的名字
否則,如果是特殊的bean對象,那么返回的就是$Proxy這個名字,是一個ProxyFactoryBean對象。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容