一、代理的概念與作用
1.生活中的代理:
武漢衆武漢的代理商手中買聯想電腦和直接跑到北京來找聯想總部買電腦 你覺得最終的主體業務目標有什麼區別嗎?基本上一樣吧,
都解決了核心問題,但是,一點區別都沒有嗎?從代理商那裏買真的一點好處都沒有嗎?
2.程序中的代理:
要爲已存在的多個具有相同接口的目標類的各個方法增加一些系統功能 。例如:
異常處理,日誌,計算方法的運行時間 ,事務管理,等等
你準備如何做?
編寫一個與目標類具有相同接口的代理類,代理類的每個方法調用目標類的相同方法,並在調用方法時加上系統功能的代碼
如果採用工廠模式和配置文件的方式進行管理,則不需要修改客戶端程序,在配置文件中配置是使用目標類,還是代理類,這樣以後很容易切換
譬如,想要日誌功能時就配置代理類,否則配置目標類,這樣,增加系統功能很容易,以後運行一段時間後,又想去掉系統功能也很容易。
二、AOP
1、系統中存在交叉業務,一個交叉業務就是要切入到系統中的一個方面,如下所示:
安全 事務 日誌
Student Service ----------|-----------|----------|----------
CourseService ----------|-----------|----------|----------
MiscService ------------|-----------|----------|----------
2、用具體的程序代碼描述交叉業務:
method1 method2 method3
{ { {
------------------------------------------------切面
------ -------- ------
------------------------------------------------切面
} } }
3、交叉業務的編程問題即爲面向方面的編程(Aspect oiented program,簡稱AOP),AOP的目標就是要使交叉業務模塊化。可以採用將切面代碼移動到原始方法的周圍,這與直接在方法中編寫切面代碼的運行效果是一樣的。如下所示:
--------------------------------------------------切面
func1 func2 func3
{ { {
--- --- ---
} } }
-------------------------------------------------切面
4、使用代理技術正好可以解決這種問題,代理是實現AOP功能的核心和關鍵技術。
三、動態代理技術:
1.要爲系統中的各種接口的類增加代理功能,那將需要太多的代理類,全部採用靜態代理方式,將是一件非常麻煩的事情.
2.JVM可以在運行期動態生成出類的字節碼,這種動態生成的類往往被用作代理類,即動態代理類
3.JVM生成的動態類必須實現一個或多個接口,所以JVM生成的動態類只能用作具有相同接口的目標類的代理
4.CGLIB庫可以動態生成一個類的子類,一個類的子類也可以用作該類的代理,所以,如果要爲一個沒有實現接口的類生成動態代理類,那麼可以使用CGLIB庫
5.代理類的各個方法中通常除了要調用目標的相應方法和對外返回目標返回的結果 ,還可以在代理方法中的如下四個位置加上系統功能代碼 :
(1)在調用目標方法之前
(2)在調用目標方法之後
(3)在調用目標方法前後
(4)在處理目標方法異常的catch塊中
一、分析JVM動態生成的類
1、創建實現類Collection接口的動態類和查看其名稱,分析Proxy.getProxyClass方法的各個參數。
2、編碼列出動態類中的所有構造方法和參數簽名
3、編碼列出動態類中的所有方法和參數簽名
4、創建動態類的實例對象
(1)、用反射獲得構造方法
(2)、編寫一個最簡單的InvocationHandler類
(3)、調用構造方法創建動態類的實例對象,並將編寫的InvocationHandler類的實例對象傳遞進去
(4)、打印創建的對象和調用對象的沒有返回值的方法和getClass方法,演示調用其他有返回值的方法報告了異常。
(5)、將創建動態類的實例對象的代理改成匿名內部類的形式編寫,鍛鍊大家習慣匿名內部類。
5、總結思考:讓jvm創建動態類及其實例對象,需要給它提供哪些信息?
三個方面:
1、生成的類中有哪些方法,通過讓其他實現哪些接口的方式進行告知;
2、產生的類字節碼必須有一個關聯的類加載器對象;
3、生成的類中的方法的代碼是怎樣的,也得由我們提供,把我們的代碼寫在一個約定好了接口對象的方法中,把對象傳遞給它,它調 用我的方法,即相當於插入了我們的代碼。提供執行代碼的對象就是那個InvocationHandler對象的invoke方法中加一點代碼,就可 以看到這些代碼被調用運行了。
6、用Proxy.newInstance方法直接一步就創建出代理對象。
InvocationHandler對象的運行原理
一、猜想分析動態生成的類的內部代碼
1、動態生成的類實現了Collection接口(可以實現若干接口),生成的類有Collection接口中的所有方法和一個如下接受InvocationHandler參數的構造方法。
2、構造方法接受一個InvocationHandler對象,接受對象了要幹什麼用呢?該方法內部的代碼會是怎樣的呢?
Class $Proxy0{
InvocationHandler handler;
public $Proxy0(InvocationHandler handler)
{
this.handler = handler;
}
}
3、實現的Collection接口中的各個方法的代碼又是怎樣的呢?InvocationHandler接口中定義的invoke方法接受的三個參數又是什麼意思?
Client程序調用objProxy.add("abc")方法時,涉及三要素:objProxy對象、add方法、"abc"參數
Class Proxy${
add(Object object){
return handler.invoke(Object proxy,Method method,Object[] args);
}
}
4、分析先前但因動態類的實例對象時,結果爲什麼會是null呢?調用有基本類型
在調用代理對象的方法的時候,會將方法的參數傳遞給handler中的invoke方法,在invoke方法中我們會找到對應的目標來調用目標對象的invoke方法的並得到目標對象方法的返回值,依次會返回給handler中的invoke方法->最後返回給代理對象的方法。
5、注意:
在代理實例上的 java.lang.Object 中聲明的 hashCode、equals 或 toString 方法的調用將按照與編碼和指派接口方法調用相同的方式進行編碼,並被指派到調用處理程序的 invoke 方法,如上所述。傳遞到 invoke 的 Method 對象的聲明類是 java.lang.Object。代理類不重寫從 java.lang.Object 繼承的代理實例的其他公共方法,所以這些方法的調用行爲與其對 java.lang.Object 實例的操作一樣。
package com.itcast.day2;
import java.lang.reflect.*;
import java.util.*;
public class ProxyTest {
public static void main(String[] args) throws Exception{
Class classProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
System.out.println("-----------begin constructors list---------------");
System.out.println(classProxy1.getName());
Constructor[] constructors = classProxy1.getConstructors();
for(Constructor constructor : constructors){
String name = constructor.getName();
StringBuilder sBuilder = new StringBuilder();
sBuilder.append("(");
Class[] clazzParams = constructor.getParameterTypes();
for(Class clazzParam : clazzParams){
sBuilder.append(clazzParam.getName()).append(",");
}
if(clazzParams!=null&&clazzParams.length!=0){
sBuilder.deleteCharAt(sBuilder.length()-1);
}
sBuilder.append(")");
System.out.println(sBuilder.toString());
}
System.out.println("-----------begin methods list---------------");
Method[] methods = classProxy1.getMethods();
for(Method method : methods){
String name = method.getName();
StringBuilder sBuilder = new StringBuilder(name);
sBuilder.append("(");
Class[] clazzParams = method.getParameterTypes();
for(Class clazzParam : clazzParams){
sBuilder.append(clazzParam.getName()).append(",");
}
if(clazzParams!=null&&clazzParams.length!=0){
sBuilder.deleteCharAt(sBuilder.length()-1);
}
sBuilder.append(")");
System.out.println(sBuilder.toString());
}
System.out.println("-----------begin create instance---------------");
Constructor constructor = classProxy1.getConstructor(InvocationHandler.class);
//一般使用匿名內部類
class MyInvocationHander1 implements InvocationHandler{
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
return null;
}
}
//第一種
Collection proxy1 = (Collection)constructor.newInstance(new MyInvocationHander1());
//第二種
Collection proxy2 = (Collection)constructor.newInstance(new InvocationHandler(){
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}});
//第三種
Collection proxy3 = (Collection)Proxy.newProxyInstance(
Collection.class.getClassLoader(),
new Class[]{Collection.class},
new InvocationHandler(){
ArrayList target = new ArrayList();
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long beginTime = System.currentTimeMillis();
System.out.println(args);
Object retVal = method.invoke(target, args);
long endTime = System.currentTimeMillis();
System.out.println(method.getName()+" running time of "+(endTime-beginTime));
//return method.invoke(target, args);
return retVal;
}}
);
System.out.println(proxy3.toString());
System.out.println(proxy3.size());
proxy3.add("abc");
System.out.println(proxy3.size());
proxy3.clear();
System.out.println(proxy3.getClass().getName());//返回的是$Proxy0,不是ArrayList,只有hashCode equals toString 才委託給handler
}
}