-------android教育訓練、java教育訓練、期待與您交流! ----------
内容源自 張孝祥老師的Java高新技術. 以下是我看視訊的筆記: --------------------------------------------------------------------------- 這些筆記對應張孝祥老師的Java高新技術的如下視訊:
49.分析代理類的作用與原理及AOP概念
50.建立動态類及檢視其方法清單資訊
51.建立動态類的執行個體對象及調用其方法
52.完成InvocationHandler對象的内部功能
53.分析InvocationHandler對象的運作原理
54.總結分析動态代理類的設計原理與結構
55.編寫可生成代理和插入通告的通用方法
56.實作類似spring的可配置的AOP架構
-------------------------------------------------------
49.分析代理類的作用與原理及AOP概念
比如:
要為已存在的多個具有相同接口的目标類的各個方法
增加一些系統功能:
如: 異常處理, 日志, 計算方法的運作時間, 事務管理, 等等.
如何處理這種要求呢?
使用代理機制來處理這種問題.
如下
class X{
void sayHello(){syso:helloworld;}
}
代理類:
class XProxy{
void sayHello(){
starttime
X.sayHello() //這裡調用X的方法
endtime
}
}
即,
做一個X的代理XProxy.
即, 其中的方法和X一樣.
在X代理XProxy的方法裡面, 調用X的方法.
即, 在調用原來的方法的前後可以加上一些功能.
然後, 用戶端, 調用的是代理.
代理和目标類 有着相同的 接口.
即, 我們會把是先把目标類中的要用上代理的方法做成一個接口.
然後, 代理隻需要去實作這個接口就可以了.
然後, 在調用時, 用戶端實際上是就是調用代理的這個接口的方法.
我們采用工廠模式 和 配置檔案 的方式進行管理,
則不需要修改用戶端程式.
在配置檔案中配置 是使用普通類, 還是使用代理類.
這樣以後很容易切換.
如:
想要日志功能時, 就配置代理類,
否則, 就配置為目标類.
這樣, 增加系統功能很容易.
在運作一段時間後, 想去掉系統功能也很容易.
AOP(aspect oriented program), 面向方面的程式設計.
系統中存在交叉業務
如多個類, 各處理各的事,
但不管處理什麼事, 都要有 安全, 事務, 日志 等功能.
即, 安全, 事務, 日志 等功能, 要貫穿在好多子產品裡面.
即, 安全, 事務, 日志就是這些類的就交叉業務.
安全 事務 日志
StudentService -----|------|------|-------
CourseService -----|------|------|-------
MiscService -----|------|------|-------
用具體的程式代碼描述交叉業務:
method1 method2 method3
{ { {
------------------------------------切面
... ... ...
------------------------------------切面
} } }
AOP的目标就是要使交叉業務子產品化,
可以采用将切面代碼移動到原始方法的周圍,
這與直接在方法中編寫切面代碼的運作效果是一樣的.
如下所示:
------------------------------------切面
func1 func2 func3
{ { {
... ... ...
} } }
------------------------------------切面
使用代理技術正好可以解決這種問題,
代理是實作AOP功能的核心和關鍵技術.
隻要涉及AOP, 就會涉及到代理技術.
要為系統中的各種接口的類, 增加代理功能,
那将需要太多的代理類,
全部采用靜态代理方式, 将是一件非常麻煩的事情.
JVM可以在運作期動态生成出類的位元組碼,
這種動态生成的類 往往被用作代理類, 即動态代理類.
注意:
動态生成的類 不一定是代理類.
隻是多用來生成代理類而已.
JVM生成的動态類, 必須實作 一個/多個接口.
是以, JVM生成的動态類
隻能用作 具有相同接口 的目标類 的代理.
如果目标類沒有接口的話, 怎麼辦?
有一個第三方的開源類庫:CGLIB
其可以動态生成一個類的子類,
一個類的子類也可以用作該類的代理
----這個就是項妙的地方了.
---- 在子類中重寫父類的方法,
并在重寫的方法中調用父類的方法.
代理類中的方法, 所做的功能可以添加的位置有:
1 在調用目标方法 之前
2 在調用目标方法 之後
3 在調用目标方法 之前 和 之後 都加
4 在調用目标方法 異常的catch塊中
-------------------------------------------------------
50.建立動态類及檢視其方法清單資訊
我們現在, 用JVM的接口來生成動态代理類
----要求目标類要用接口.
Proxy
全稱: java.lang.reflect.Proxy
----即, 其用的是反射的機制.
裡面全是 static方法
----即, 這是一個工具類.
其有一個方法:
Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces)
即, 動态得到一個代理類.
ClassLoader loader: 指定類加載器來加載這個所生成的代理類.
Class<?>... interfaces: 指定這個代理類所實作的接口的Class對象.
如:
我們要生成一個動态代理類來實作Collection接口,
其所用的類加載器, 通常是Collection類的加載器.
-----即, 我們一般是這樣做的.
應用:
public static void main(String[] args) {
// 生成實作了Collection接口的動态代理類
// 并列印其構造方法
// 列印其所有方法
//1 生成一個動态代理類
Class<?> clazzProxy =
Proxy.getProxyClass(Collection.class.getClassLoader(),
Collection.class);
//2 擷取這個動态代理類的名稱: com.sun.proxy.$Proxy0
System.out.println(clazzProxy.getName());
//3 擷取這個動态代理類的構造方法, 并列印出來
//com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
Constructor[] constructors = clazzProxy.getConstructors();
System.out.println("---------------構造方法-------------");
for (Constructor constructor : constructors){
StringBuilder str = new StringBuilder();
str.append(constructor.getName());
str.append("(");
Class[] clazzParas = constructor.getParameterTypes();
for (Class clazzPara : clazzParas){
str.append(clazzPara.getName() + ",");
}
//注意, 這一步是必須的, 因為我們不能保證構造方法有參數
//注意, 下面這兩個條件, 我隻是為了保險起見才這麼做的.
if (clazzParas!=null && clazzParas.length!=0){
str.deleteCharAt(str.length()-1);
}
str.append(")");
System.out.println(str);
}
//4 擷取這個動态代理類的方法, 并列印出來
Method[] methods = clazzProxy.getMethods();
System.out.println("---------------一般方法-------------");
for (Method method : methods){
StringBuilder str = new StringBuilder();
str.append(method.getName());
str.append("(");
Class[] clazzParas = method.getParameterTypes();
for (Class clazzPara : clazzParas){
str.append(clazzPara.getName() + ",");
}
//注意, 這一步是必須的, 因為我們不能保證構造方法有參數
//注意, 下面這兩個條件, 我隻是為了保險起見才這麼做的.
if (clazzParas!=null && clazzParas.length!=0){
str.deleteCharAt(str.length()-1);
}
str.append(")");
str.append(":" + method.getReturnType().getName());
System.out.println(str);
}
}
得到結果:
com.sun.proxy.$Proxy0 -----動态代理類放在com.sun.proxy包中---名稱帶$表示複合類, 0表示序号
---------------構造方法-------------
com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
---------------一般方法-------------
add(java.lang.Object):boolean
remove(java.lang.Object):boolean
equals(java.lang.Object):boolean ------- Collection裡已有
toString():java.lang.String
hashCode():int ------- Collection裡已有
clear():void
contains(java.lang.Object):boolean
isEmpty():boolean
iterator():java.util.Iterator
size():int
toArray([Ljava.lang.Object;):[Ljava.lang.Object;
toArray():[Ljava.lang.Object;
spliterator():java.util.Spliterator
addAll(java.util.Collection):boolean
stream():java.util.stream.Stream
forEach(java.util.function.Consumer):void
containsAll(java.util.Collection):boolean
removeAll(java.util.Collection):boolean
removeIf(java.util.function.Predicate):boolean
retainAll(java.util.Collection):boolean
parallelStream():java.util.stream.Stream ------- 代理類自身的
isProxyClass(java.lang.Class):boolean ------- 代理類自身的
getInvocationHandler(java.lang.Object):java.lang.reflect.InvocationHandler ------- 代理類自身的
newProxyInstance(java.lang.ClassLoader,Ljava.lang.Class;,java.lang.reflect.InvocationHandler):java.lang.Object ------- 代理類自身的
getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;):java.lang.Class ------- 代理類自身的
wait():void
wait(long,int):void
wait(long):void
getClass():java.lang.Class
notify():void
notifyAll():void
一個是:
我們看到生成的動态代理類的名稱為:
com.sun.proxy.$Proxy0 ---而Proxy是放在java.lang.reflect包中的.
-----動态代理類放在com.sun.proxy包中
-----名稱帶$表示複合類,
-----Proxy 表示這是一個代理類
-----0表示序号
一個是:
我們在命名一個位元組碼變量時, 常常用 clazz來命名.
這是一個習慣.
一個是:
其構造方法:
com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
隻有一個.
參數為: InvocationHandler類型.
後面會說到.
一個是:
這個類中的方法, 不僅僅有Collection的方法,
還有一些代理類自己的方法:
parallelStream():java.util.stream.Stream
isProxyClass(Class):boolean
getInvocationHandler(Object):InvocationHandler
newProxyInstance(ClassLoader,[Ljava.lang.Class;,InvocationHandler):Object
getProxyClass(ClassLoader,[Ljava.lang.Class;):Class
其中 [Ljava.lang.Class 相當于 Class[].
一個是:
InvocationHandler 是一個接口, 其隻有一個方法:
public Object invoke(Object proxy, //動态代理類對象
//invoke内部可能會用到動态代理類對象
Method method, //所調用的方法的Method對象
Object[] args) //方法的參數
-----在這個方法裡, 我們可 實作這些系統的功能.
具體的内容下面就要講到了.
-------------------------------------------------------
51.建立動态類的執行個體對象及調用其方法
通過上面, 我們僅僅得到了動态代理類的Class對象.
現在,
我們利用 動态代理類 來建立其執行個體對象.
如何來建立 動态代理類的對象呢?
當然 隻有兩個方法:
一種: Class::newInstance()
---用無參構造方法
---但我們知道, 動态代理類沒有無參構造方法.
---是以不能采用這種方法.
一種: $Proxy0 的構造方法.
然後調用它來建立對象.
---下面我們說的就是這一種.
後面還會介紹一種:
Proxy.newProxyInstance(ClassLoader, Class<?>[], InvocationHandler):Object ---後面基本隻用這種
這是一個 static 方法.
此方法是綜合以下兩個步驟:
Proxy.getProxyClass(ClassLoader, Class<?>... interfaces):Class<?>
和
$Proxy0(InvocationHandler): Object
動态代理類的構造方法隻有一種:
$Proxy0(InvocationHandler)
即, 要傳向一個 InvocationHandler 對象.
但這個是一個 接口, 不能 new InvocationHandler()!
是以, 我們常常是用匿名類的方法來實作:
Collection<?> col = (Collection<?>) constructor.newInstance(new InvocationHandler(){
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return null;
}
});
完整代碼如下:
//建立 動态代理類的 執行個體對象
try {
//1 獲得構造方法
// 這個clazzProxy就是剛才我們上面:
// 用Proxy.getProxyClass(_,_)得到的動态代理類Class對象
Constructor constructor = clazzProxy.getConstructor(InvocationHandler.class);
//2 調用這個構造方法
// 一個是: 獲得的對象類型, 我們要用那個接口Collection來接收.
// 一個是: 這裡有類型強制轉換
// 一個是: 現在暫時将invoke方法傳回null, 即沒有實作其中功能.
Collection<?> col = (Collection<?>) constructor.newInstance(new InvocationHandler(){
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return null;
}
});
//3 使用這個 動态代理類對象col
System.out.println(col); //null
System.out.println(col.toString()); //null
System.out.println(col.size()); //異常 java.lang.NullPointerException
} catch (Exception e){
e.printStackTrace();
}
注意:
System.out.println(col); //null
得到null, 并不代表着col就為null, 有可能其toString為null.
System.out.println(col.toString()); //null
我們看到, 确實是toString為"null".
如果是 col為null, 其會指空指針異常
System.out.println(col.size()); //異常 java.lang.NullPointerException
這是因為, 這是一個接口啊, 這個Collection沒有指向任何實際集合啊.
是以, 不能得到集合的大小, 是以報異常.
-------------------------------------------------------
52.完成InvocationHandler對象的内部功能
總結下:
我們上面要建立一個動态代理類, 需要三個資訊:
一個是: 該代理類的加載器 ----我們要指定
一個是: 該代理類實作的接口 ----我們要指定, 如Collection接口
一個是: 用該代理類建立執行個體對象---我們要指定InvocationHandler對象
---在這裡, 我們可以做一些系統的功能.
上面, 将這三個資訊, 分成幾個部分來完成.
而實際上,
Proxy中有一個static方法newProxyInstance可以一步到位:
Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
其參數就是我們上面所說的那三個:
ClassLoader loader: 該代理類的加載器 ----我們要指定
Class<?>[] interfaces: 該代理類實作的接口 ----我們要指定, 如Collection接口
InvocationHandler h: 用該代理類建立執行個體對象---我們要指定InvocationHandler對象
---在這裡, 我們可以做一些系統的功能.
因為實作的接口可能不止一個, 是以是一個數組.
因為最後要還傳入一個InvocationHandler對象,
是以, 那麼得用數組, 而不能用可變參數.
現在, 我們用這個方式來寫一個:
public static void main(String[] args) {
//注意, 這裡将類型改為final
final ArrayList<String> list = new ArrayList<String>();
//建立 動态代理類的執行個體----其已經和這個list對象挂鈎了
Collection<String> proxy =
(Collection<String>) Proxy.newProxyInstance(Collection.class.getClassLoader(),
new Class[]{Collection.class},
new InvocationHandler(){
//目錄标對象記錄到這裡來
private Object target = list; //要求這個list是final的.
//這個功能, 是用于計算目标類方法的運作時間
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.currentTimeMillis();
Object retVal = method.invoke(target, args);
long endTime = System.currentTimeMillis();
System.out.println("方法"+method.getName()+"運作時間為:"+(endTime-startTime));
return retVal;
}
}
);
//調用動态代理類對象方法 來 調用list的方法
proxy.add("hello");
proxy.add("world");
proxy.add("nihao");
System.out.println(proxy.size());;
}
這裡幾點注意處:
一個是:
InvocationHandler對象, 同樣也是用匿名内部類實作的, 如下:
new InvocationHandler(){
//目錄标對象記錄到這裡來
private Object target = list; //要求這個list是final的.
//這個功能, 是用于計算目标類方法的運作時間
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.currentTimeMillis();
Object retVal = method.invoke(target, args);
long endTime = System.currentTimeMillis();
System.out.println("方法"+method.getName()+"運作時間為:"+(endTime-startTime));
return retVal;
}
}
其中, 我們得設一個域: private Object target = list; //要求這個list是final的.
用于記錄 這個動态代理類對象 所要 代理的 那個目标對象 是哪一個.
因為, 我們設立 代理的目标就是, 對目标類進行AOP程式設計的.
代理類對象, 要和 目标類對象 一一對應.
注意, 這裡, 我們是直接将list傳給這個target資料域的.
因為匿名内部類不能有構造方法.
而且, 剛好在匿名内部類内部是可以直接通路其外的方法的局部變量的.
是以就這麼做了.
一個是:
見下面講解 動态代理類 的原理部分吧.
-------------------------------------------------------
53.分析InvocationHandler對象的運作原理
JVM所自動生成的動态代理類的内部實質是:
class $Proxy0{
//一個資料域
private InvocationHandler handler;
//構造方法
public $Proxy0(InvocationHandler handler)
{
this.handler = handler;
}
//一般方法:
int size(){
return handler.invoke(this, //動态代理類對象
this.getClass().getMethods("size"), //size這個Method對象
null); //參數
}
...
}
注意:
一個是:
内部一個資料域:
private InvocationHandler handler;
用于記錄, 這個代理類對象對應的 InvocationHandler 是哪一個.
這個 資料域 , 我們得在構造方法中對它進行指派.
一個是:
我們調用:
proxy.add("haha");
轉為三個要素:
一個是 col---動态代理類的對象
一個是 add ---所調用的方法
一個是 "haha" ---參數.
是以,
InvocationHandler的invoke方法的參數, 也對應這三個.
handler.invoke(proxy, //動态代理類對象
this.getClass().getMethods("add"), //size這個Method對象
"haha"); //參數
還把這個結果傳回.
proxy.size()的機制也一樣.
一個是:
如果 我們在invoke内部這麼調用:
return method.invoke(proxy, args);
結果呢, 會是一個死循環.
要注意:
動态代理類 和 目标類, 都實作了 共同的接口.
是以, 如果 method.invoke(proxy, args)
那麼, 就是代理類對象不斷調用自身了, 是以會死循環.
一個是:
在invoke方法中,
我們可以加之前, 之後, 前後, 異常處理,
我們也可以對這個參數args進行修改.
我們 在invoke中, 可以用到代理類對象,
為什麼 要傳入這個代理類對象呢?
因為 我們在invoke中, 會進行一些操作,
可能會用到動态代理類對象
是以, 我們invoke的參數中, 保留了Object proxy, 以備用.
而, 其内部, 必然會 用到 目标類對象.
而目标類對象, 我們通常是設一個資料域來處理.
一個是:
在我們最上面的例子中:
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return null;
}
invoke直接傳回null.
也正是這個原因, 導緻當時調用 col.size()會抛出異常.
因為 size應傳回int型的,
而它, 卻傳回了null了.
是以 抛異常.
如果col有指向具體的集合的話, size會傳回0,1,等對應的包裝器對象,
是以不會傳回null的, 是以不會異常的的.
一個是:
我們調用 動态代理類對象的 proxy.getClass(),
我們會傳回$Proxy0啊,
而不是 目标的 Class對象: java.util.Arraylist.
為什麼呢?
因為 動态代理類 隻對 其所實作的接口的那些方法去調用invoke方法
而對于Object的幾個方法,
隻有 hashCode, equals, toString才派發給handler去invoke目标類方法去處理,
其它的 方法, 如getClass, wait之類, 保留動态代理類自身的内容的.
即, 不會去調用invoke實用目标的方法的.
比如, 上面我們調用col.toString, 列印的null ,
即, 對toString, 其會去目标那裡去實作.
-----注意一點: Object 不是 接口.
那為什麼會對 toString去調用invoke呢?----我已查證, Collection中沒有toString.
總結下, 動态代理類的機制:
像GUI中的注冊監聽器一樣,
當外界有 事件發生時, 兩件事:
控制内部會得到一個事件對象,
觸發注冊到控制内部的事件監聽器.
然後, 事件監聽器調用自己内部的方法來完成對事件的影應.
----不過, 這個說法勉強.
因為GUI的事件監聽器, 其中不需要我們直接傳一個target進去的.
而是, 而是通過它的響應方法的參數來傳進去的.
----而這個代理類的 InvoketionHandler的target對象, 需要我們手動傳進去. 麻煩.
動态代理類是這樣的:
proxy調用接口方法時,
實際上是 注冊到自身的handler上的invoke來響應.
在handler中, 其包含的内容有:
一個是: 目标對象要用---否則怎麼調得動方法呢
一個是: invoke方法要有.
總之:
最終是要調用 InvocationHandler對象的invoke方法的.
是以 invoke要知道幾個要素:
一個是 target, 即, 要知道其調用的是哪一個目标類
一個是 proxy, 即, 要知道其代理類對象是哪一個.
invoke内部可能會用到動态代理類對象--如取一些資訊之類
一個是 method, 得知道自己要調接口的哪一個方法
一個是 agrs, 得知道要往這個方法傳哪些參數.
下面三個元素, 以invoke的參數形式傳進去,
而第一個元素, 通常是以 InvocationHandler的資料成員形式存在.
-------------------------------------------------------
54.總結分析動态代理類的設計原理與結構
55.編寫可生成代理和插入通告的通用方法
其工作原理圖為:
中間那個圈 要注意.
我們上面是将這個圈的代碼直接寫死了.---這叫寫死.
這樣的話, 這個代理類隻能處理這種 代碼.
不靈活.
是以, 我們可以把這個圈裡的東西封裝成一個方法.
----提高了代碼的複用性.
即, 下面, 我們就要 讓這種系統功能的代碼 以參數的形式傳給它.
通常, 我們是傳一個對象給 InvocationHandler中的一個資料域.
然後 invoke方法中, 去調用 這個資料域的方法就可以了.
通常, 我們會把這個東西設為一個接口/抽象類.
名稱稱為Advice
在這個接口中, 設幾個方法:
前面執行的操作,
後面執行的操作,
異常進行中執行的操作.
其實有多個方法的話, 那麼改為抽象類更好一些.
如下:
public abstract class Advice { //視訊是将其做interface處理.
//這個會不會有影響? ---至少, 在我這裡, 代碼是可以運作的.
//這個是不是抽象類沒關系, 這隻是AOP所執行的方法而已.
//而代理類那些則必須是接口!
public void front(Object proxy, Method method, Object[] args){}
public void behind(Object proxy, Method method, Object[] args){}
public void exceptionProcess(Object proxy, Method method, Object[] args, Throwable exception){}
}
為什麼這裡的方法參數要這些?
因為, 在我們要添加的系統功能的這些方法, 可能會用到這些資訊.---當然, 可以不用, 需要時再寫.
因為, 我們這裡要做一個架構, 即要有廣泛的适應性, 是以建議把invoke方法的參數都寫上去.
即, 要在 InvocationHandler中設定兩個資料域.
一個是 Object target; 域, 以放目标對象.
一個是 Advice advice; 域, 以放一些處理的方法.
而invoke應當寫成這樣:
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object retValue = null;
try{
advice.front(proxy, method, args);
retValue = method.invoke(target, args);
advice.behind(proxy, method, args);
} catch (Exception e){
advice.exceptionProcess(proxy, method, args, e);
}
return retValue;
}
即, 也要用一些異常處理的操作.---這個純粹是我寫的.
好了, 看下我的代碼:
先寫一個MyAdvice:
----即, 我們使用代理的最關鍵部分---AOP的核心.
public class MyAdvice extends Advice{
private long startTime;
private long endTime;
@Override
public void front(Object proxy, Method method, Object[] args) {
startTime = System.currentTimeMillis();
System.out.println("開始執行"+method.getName());
}
@Override
public void behind(Object proxy, Method method, Object[] args) {
endTime = System.currentTimeMillis();
System.out.println("執行完成"+method.getName()+", 執行時間:"+(endTime-startTime)+"毫秒.");
}
@Override
public void exceptionProcess(Object proxy, Method method, Object[] args, Throwable exception) {
//還沒有想好要怎麼處理它
}
}
--------當然, 我們也可以用 匿名内部類的形式來寫這一個類.
呵呵.
然後是寫一個 獲得動态代理對象的方法:
------這是我們這一單元的核心部分了.
------這個代碼是可以重用的.
------我把很對類型都改為Object類型了.---這樣, 可以目标對象的類型可以随意.
我們可以看到:
參數兩個:
一個是 目标;----要在誰的方法這前做
一個是 建議;----即要做什麼事
public static Object getProxy(final Object obj, final Advice adv) {
return Proxy.newProxyInstance(
obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(), //擷取其所有接口.
new InvocationHandler(){
//目錄标對象記錄到這裡來
private Object target = obj;
//記錄這個Advice對象
private Advice advice = adv;
//實作invoke方法
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object retVal = null;
try{
advice.beforeMethod(method);
retVal = method.invoke(target, args);
advice.afterMethod(method);
} catch (Throwable t){
advice.exceptionMethod(method, t);
}
return retVal;
}
}
);
}
然後是, 進行一個調用:
public static void main(String[] args) {
//注意, 這裡将類型改為final
final ArrayList<String> list = new ArrayList<String>();
//建立 動态代理類的執行個體----其已經和這個list對象挂鈎了
Collection<String> proxy =
(Collection<String>) getProxy(list, new MyAdvice());
//調用動态代理類對象方法 來 調用list的方法
proxy.add("hello");
proxy.add("world");
proxy.add("nihao");
System.out.println(proxy.size());;
}
以後寫Spring, 其實就是寫MyAdvice.
即, 我們傳入目标, 還有這個Advice對象 就行了.
這就是一個小小的架構.
即, 給你目标, 給你系統功能, 你得給我一個代理.
-------------------------------------------------------
56.實作類似spring的可配置的AOP架構
如果, 我們要做一個類似于Spring框的架的AOP,
其大概的實作思路是怎麼樣的?
Object obj = BeanFacktory.getBean("XXX");
"XXX"---是指Bean的名字, 如java.util.Array
這個"XXX"是由一個配置檔案來提供的.
這個BeanFacktory.getBean(__)内部怎麼寫?
兩種可能:
一種是 如果是 cn.itcast.ProxyFactoryBean這個特殊的類的話,
那麼 調用其方法來建立代理.
一種是: 不是這種特殊的類.
則用 BeanFacktory.getBean(__)來建立Bean對象.
比如配置檔案如下:
#xxx=java.util.ArrayList
xxx=cn.itcast.ProxyFactoryBean
xxx.target=java.util.ArrayList
xxx.advic=cn.itcast.MyAdvice
如果是 ArrayList的話,
BeanFacktory.getBean("xxx")傳回ArrayList的javabean對象
如果是 ProxyFactoryBean的話,
那麼做調用 ProxyFactoryBean
則 還會取下其target和advice是誰,
然後生成 一個其對應的動态代理類對象 傳回.
那麼:
obj就是建立出的javaBean對象.
或者是 一個代理.
BeanFactory構造方法 接受一個配置檔案.
即有一個資料域: Properties型的資料域.
對javabean而言, 一個重要的特性, 其必須有一個不帶參數的構造方法.
public class BeanFactory {
//因為BeanFactory是根據配置檔案來産生javabean的,
//是以要有一個域來保留這個配置檔案的資訊
private Properties properties;
//構造方法
public BeanFactory(InputStream inputStream){
properties = new Properties();
try {
properties.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//非靜态方法---因為每一個配置檔案對應一個對象
public Object getBean(){
String clazzName = properties.getProperty("className");
Class<?> clazz = null;
Object obj = null;
try {
clazz = Class.forName(clazzName);
obj = clazz.newInstance();
if (obj instanceof ProxyBeanFactory){
Advice advice = (Advice)(Class.forName(properties.getProperty("advice")).newInstance());
Object target = Class.forName(properties.getProperty("target")).newInstance();
ProxyBeanFactory proxyFactory = (ProxyBeanFactory)obj;
proxyFactory.setAdvice(advice);
proxyFactory.setTarget(target);
obj = proxyFactory.getProxy();
return obj;
}
return obj;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
}
public static void main(String[] args) throws Exception{
InputStream config =
BeanFactory.class.getResourceAsStream("config.properties");
BeanFactory beanFactory = new BeanFactory(config);
config.close(); //用完, 就要關掉.
Object obj = beanFactory.getBean();
System.out.println(obj.getClass());
System.out.println(obj);
}
}
public class ProxyBeanFactory {
private Advice advice;
private Object target;
public Advice getAdvice() {
return advice;
}
public void setAdvice(Advice advice) {
this.advice = advice;
}
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
//與之前寫的那個getProxy不一樣了
//這裡沒有參數, 而是把advice和target做為資料成員處理.
public Object getProxy() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler(){
//目錄标對象記錄到這裡來
private Object target = ProxyBeanFactory.this.target;
//記錄這個Advice對象
private Advice advice = ProxyBeanFactory.this.advice;
//實作invoke方法
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object retVal = null;
try{
advice.beforeMethod(method);
retVal = method.invoke(target, args);
advice.afterMethod(method);
} catch (Throwable t){
advice.exceptionMethod(method, t);
}
return retVal;
}
}
);
}
}
這個就是 Spring的BeanFactory的原理了.
這上面講了 Spring 的 兩個架構:
一個是 getProxy 方法
一個是 BeanFactory和ProxyBeanFactory兩個架構了.
道理其實就是這麼簡單.