一、概述
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對象。