第一 代理的概念及作用
一、概念:
1、生活中的代理:也就是所謂的代理商,從生產商和消費者之間搭建起一到橋樑,方便了消費者。
2、程序中的代理:給目標類的方法增加一些系統功能,如異常處理、日誌、計算方法的運行時間、事物管理等等,增加公共後的類就是代理。
3、代理類的優點:
如果採用工廠模式和配置文件的方式進行管理,則不需要修改客戶端程序,在配置文件中配置是使用目標類還是代理類。這樣以後很容易切換,如果想要日誌功能時,就配置代理類,否則配置目標類,這樣,增加系統功能很容易,以後運行一段時間後,又想換掉系統功能也很容易。
二、AOP
1、簡述:AOP(Aspect Oriented Program)即面向方面的編程。
2、示意圖:
系統中存在着交叉業務,一個交叉業務就是要切入到系統中的一個方面,如圖:
3、用具體的程序代碼描述交叉業務:
4、交叉業務的編程問題即爲面向方面的編程(Aspect Oriented Program,簡稱爲AOP),AOP的目標就是要使交叉業務模塊化。可以採用將切面代碼移動到原始方法的周圍,與之直接在方法中編寫切面代碼的運行效果是一致的,如下圖:
因此使用代理技術正好可以解決這種問題,代理是實現AOP功能的核心和關鍵技術。
實例:
/*
* 代理
*/
public class ProxyDemo {
public static void main(String[] args) throws Exception {
// 類加載器一般和接口屬於同一個
Class clazzProxy = Proxy.getProxyClass(
Collection.class.getClassLoader(), Collection.class);
System.out.println(clazzProxy.getName());
// 獲取加載的類的構造函數
Constructor[] constructors = clazzProxy.getConstructors();
for (Constructor constructor : constructors) {
String name = constructor.getName();
StringBuilder strB = new StringBuilder();
strB.append(name);
strB.append('(');
Class[] clazzParams = constructor.getParameterTypes();//得到構造函數的參數列表
for (Class clazzParam : clazzParams) {
strB.append(clazzParam.getName()).append(',');
}
if (clazzParams != null && clazzParams.length != 0) {
strB.deleteCharAt(strB.length() - 1);
}
strB.append(')');
System.out.println(strB);
}
// 獲取加載的類的方法
Method[] methods = clazzProxy.getMethods();
for (Method method : methods) {
String name = method.getName();
StringBuilder strB = new StringBuilder(name);
strB.append('(');
Class[] clazzParams = method.getParameterTypes();
for (Class clazzParam : clazzParams) {
strB.append(clazzParam.getName()).append(',');
}
if (clazzParams != null && clazzParams.length != 0) {
strB.deleteCharAt(strB.length() - 1);
}
strB.append(')');
System.out.println(strB);
}
}
第二 動態代理
一、概述:
1、要爲系統中的各種接口的類增加代理功能,那將需要太多的代理類,這時就不能採用靜態代理方式,需用動態代理技術。
2、動態代理類:JVM可在運行時,動態生成類的字節碼,這種動態(不是代理,只是拿出來作爲代理類)生成的類往往被用作代理類,即動態代理類。
注:JVM生成的動態類必須實現一或多個接口,所以JVM生成的動態代理類只能用作具有相同接口的目標類代理。
3、CGLIB庫可以動態生成一個類的子類,一個類的子類也可以作爲該類的代理,所以,如果要爲一個沒有實現接口的類生成動態代理,那麼可以使用CGLIB庫。
4、代理類各個方法通常除了調用目標相應方法和對外返回目標返回的結果外,還可以在代理方法中的如下位置上加上系統功能代碼:
1)在調用目標方法之前
2)在調用目標方法之後
3)在調用目標方法前後
4)在處理目標方法異常的catch塊中。
二、分析JVM動態生成的類
1、創建動態類的實例對象:
1)用反射獲得構造方法
2)編寫一個最簡單的InvocationHandler的類
3)調用構造方法創建動態類的實例對象,並將編寫的InvocationHandler類的實例對象傳進去。
public class ProxyTest {
public static void main(String[] args) throws Exception{
//創建動態代理類的三種方式
//方式一:通過接口的子類創建對象
Collection proxy1 = (Collection)
constructor.newInstance(new MyInvocationHandler());
System.out.println(proxy1);//null
System.out.println(proxy1.toString());//null
proxy1.clear();//無異常
//proxy1.size();//異常
//方式二:匿名內部類
Collection proxy2 = (Collection)
constructor.newInstance(new InvocationHandler(){
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
return null;
}
});
//方式三:
//通過代理類的newProxyInstance方法直接創建對象
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();
//調用目標方法,將其從return抽出來,加入代理所需的代碼
Object retVal = method.invoke(target, args);
long endTime = System.currentTimeMillis();
//測試
System.out.println(method.getName() +
" run time of " +
(endTime - beginTime));
return retVal;
}
}
);
//通過代理類調用目標方法,每調用一個目標的方法就會執行代理類的方法
//當調用一次add方法時,就會找一次InvocationHandler這個參數的invoke方法
proxy3.add("123");
proxy3.add("abc");
proxy3.add("tvb");
System.out.println(proxy3.size());
System.out.println(proxy3.getClass().getName());
}
}
2、讓JVM創建動態類需要提供的信息:
1)生成類中的哪些方法,通過讓其實現哪些接口的方式進行告知。
2)產生的類字節碼必須有一個關聯的類加載器對象
3)生成的類中的方法的代碼是怎麼樣的,也得由我們自己提供,把我們的代碼寫在一個約定好的子接口對象的方法中,把對象傳給它,它調用我們的方法,即相當於插入了我們自己的代碼。提供執行代碼的對象就是InvocationHandler對象,其實流程就是代理將參數傳遞給InvocationHandler的invoke方法,也就是代理在執行的時候就需要調用InvocationHandler的invoke方法。
三、分析動態生成的類的內部代碼
1、構造方法接受一個InvocationHandler對象,接受此對象的用處:接受一個handler參數是爲了記錄它,以便在之後的程序中運用它。
2、調用代理涉及到三個因素:代理對象,代理對象的哪個方法,以及此方法接受的參數。要執行目標對象,只需要將代理對象作爲目標對象即可。
3、對於上面代碼中的proxy3.add()的分析:
1)代理對象調用add方法,傳遞了參數。
2)add方法內部會找到InvocationHandler中的invoke方法,將代理對象proxy傳進去,把add方法傳入,將參數傳入代理對象中的handler參數,返回了一個結果,就是給了add方法,add方法繼續向外返回給調用的對象proxy3,即最終結果。
其中的handler的invoke方法返回又來自於目標target返回值,從而將此返回值返給Object invoke()方法,即作爲handler參數位置上的值返回給add方法。add方法作爲最後的結果返回。
四、動態代理類原理和結構:
1、動態代理的工作原理:
1)Client(客戶端)調用代理,代理的構造方法接受一個InvocationHandler,client調用代理的各個方法,代理的各個方法請求轉發給剛纔通過構造方法傳入的handler對象,又把各請求分發給目標的相應的方法。就是將handler封裝起來,其中this引用了當前的放(發來什麼請求就接受哪個方法)。
2、把系統功能代理模塊化,即切面代碼也改爲通過參數形式提供,怎麼把要執行的系統功能代碼以參數的形式提供:把要執行的代碼裝到一個對象的某個方法中,然後把此對象作爲參數傳遞,接收者只要調用這個對象的方法,即等於執行了外接提供的代碼。
示例:
/*
* 通訊的契約
* 一般說來應該有四個方法,前面、後面、前後、處理目標方法異常的catch塊中,而且一般會有target、args、method參數
* 但是這個例子中就之定義兩個了
*/
public interface Advice {
void beforeMethod(Method method);
void afterMethod(Method method);
}
public class MyAdvice implements Advice {
long startTime;
@Override
public void beforeMethod(Method method) {
System.out.println("開始計時");
startTime = System.currentTimeMillis();
}
@Override
public void afterMethod(Method method) {
long endTime = System.currentTimeMillis();
System.out.println(method.getName() + "run time"
+ (endTime - startTime));
System.out.println("結束計時");
}
}
public class ProxyDemo {
public static void main(String[] args) throws Exception {
// 類加載器一般和接口屬於同一個
Class clazzProxy = Proxy.getProxyClass(
Collection.class.getClassLoader(), Collection.class);
System.out.println(clazzProxy.getName());
// 獲取加載的類的構造函數
Constructor[] constructors = clazzProxy.getConstructors();
for (Constructor constructor : constructors) {
String name = constructor.getName();
StringBuilder strB = new StringBuilder();
strB.append(name);
strB.append('(');
Class[] clazzParams = constructor.getParameterTypes();
for (Class clazzParam : clazzParams) {
strB.append(clazzParam.getName()).append(',');
}
if (clazzParams != null && clazzParams.length != 0) {
strB.deleteCharAt(strB.length() - 1);
}
strB.append(')');
System.out.println(strB);
}
// 獲取加載的類的方法
Method[] methods = clazzProxy.getMethods();
for (Method method : methods) {
String name = method.getName();
StringBuilder strB = new StringBuilder(name);
strB.append('(');
Class[] clazzParams = method.getParameterTypes();
for (Class clazzParam : clazzParams) {
strB.append(clazzParam.getName()).append(',');
}
if (clazzParams != null && clazzParams.length != 0) {
strB.deleteCharAt(strB.length() - 1);
}
strB.append(')');
System.out.println(strB);
}
// 創建實例對象,通過構造方法的方式
Constructor constructor = clazzProxy
.getConstructor(InvocationHandler.class);
Collection proxy = (Collection) constructor
.newInstance(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
return null;
}
});
System.out.println(proxy);// 打印紙是null,toString方法返回null
proxy.clear();
// System.out.println(proxy.size());//這裏調用有返回值的方法就會報錯,因爲InvocationHandler中的invoke方法返回是null
final ArrayList target = new ArrayList();
// 另外一種創建對象的方法
Collection proxy1 = (Collection) getProxy(target, new MyAdvice());
proxy1.add("abc");
proxy1.add("bcd");
proxy1.add("jjc");
System.out.println(proxy1.size());
// 只把Object中的hashCode、equals 或 toString 交給代理,其他的都有自己的實現方式
System.out.println(proxy1.getClass().getName());
}
/*
* AOP面向面的編程,就是在在調用目標方法前面、後面、前後、處理目標方法異常的catch塊中加上系統功能代碼
* 需要把添加的系統功能代碼抽取成對象,然而在代理中必須調用系統功能對象,但是不知道其對象的名字,就沒法調用
* 所以用接口進行約定,系統功能對象必須實現一個調用者知道的接口,這就叫通訊的契約,一般定義爲Advice,也就是說
* 建議使用的系統功能,即將插入進來的這麼一種建議
*/
private static Object getProxy(final Object target, final Advice advice) {
//調用Proxy類的靜態方法newProxyInstance直接創建對象
Object proxy1 = Proxy.newProxyInstance(
// Collection.class.getClassLoader(),
target.getClass().getClassLoader(), target.getClass()
.getInterfaces(), new InvocationHandler() {
// 客戶端程序調用代理的時候涉及三個要素:當前的代理對象、調用的方法、傳遞的參數
// 方法內部就把三要素傳遞給了InvocationHandler的invoke方法
/*
* @Override public Object invoke(Object proxy, Method
* method, Object[] args) throws Throwable { //
* 這個方法有返回值,InvocationHandler就會把這個返回值返回給代理,也就是代理調用了那個方法就返回給誰
* long startTime = System.currentTimeMillis(); Object
* objValue = method.invoke(target, args); long endTime =
* System.currentTimeMillis();
* System.out.println(method.getName() + "run time" +
* (endTime - startTime)); return objValue; }
*/
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
// 這個方法有返回值,InvocationHandler就會把這個返回值返回給代理,也就是代理調用了那個方法就返回給誰
advice.beforeMethod(method);
Object objValue = method.invoke(target, args);
advice.afterMethod(method);
return objValue;
}
});
return proxy1;
}
}
第三 AOP類似spring小框架
一、工廠類BeanFactory:
1、工廠類BeanFactory負責創建目標類或代理類的實例對象,並通過配置文件實現切換。
2、getBean方法根據參數字符串返回一個相應的實例對象,如果參數字符串在配置文件中對應的類名不是ProxyFactoryBean,則直接返回該類的實例對象,否則返回該類示例對象的getProxy方法返回的對象。
3、BeanFactory的構造方法接收代表配置文件的輸入流對象的配置文件格式如下:
xxx=java.util.ArrayList#xxx=enhance.test.proxy.aopframework.ProxyFactoryBean
xxx.advice=enhance.test.proxy.MyAdvice
xxx.target=java.util.ArrayList
注意:其中的#代表註釋當前行。
4、ProxyFactoryBean充當封裝成動態的工廠,需爲工廠提供的配置參數信息包括:
目標(target)
建議(advice)
5、BeanFactory和ProxyFactoryBean:
1)BeanFactory是一個純粹的bean工程,就是創建bean即相應的對象的工廠。
2)ProxyfactoryBean是BeanFactory中的一個特殊的Bean,是創建代理的工廠。
看實例,首先是配置文件config.properties,這裏配置文件需要放在相對路徑上,也就是同一個包中,因爲在代碼引用的是相對路徑。
#config.properties
xxx=java.util.ArrayList
#xxx=enhance.test.proxy.aopframework.ProxyFactoryBean
xxx.advice=enhance.test.proxy.MyAdvice
xxx.target=java.util.ArrayList
下面就是BeanFactory:
public class BeanFactory {
private Properties props = new Properties();
BeanFactory(InputStream in){
try {
props.load(in);
} catch (IOException e) {
e.printStackTrace();
}
}
public Object getBean(String name){
String className = props.getProperty(name);
Object bean = null;
try {
Class clazz = Class.forName(className);
bean = clazz.newInstance();//Bean中必須要有一個無參的構造函數
if(bean instanceof ProxyFactoryBean){
ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean)bean;
Advice advice = (Advice) Class.forName(props.getProperty(name + ".advice")).newInstance();
Object target = Class.forName(props.getProperty(name + ".target")).newInstance();
proxyFactoryBean.setAdvice(advice);
proxyFactoryBean.setTarget(target);
Object proxy = proxyFactoryBean.getProxy();
return proxy;
}
} catch (Exception e) {
e.printStackTrace();
}
return bean;
}
}
接下來就該是ProxyfactoryBean了:
public class ProxyFactoryBean {
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;
}
public Object getProxy() {
Object proxy = Proxy.newProxyInstance(target.getClass()
.getClassLoader(), target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
// 這個方法有返回值,InvocationHandler就會把這個返回值返回給代理,也就是代理調用了那個方法就返回給誰
advice.beforeMethod(method);
Object objValue = method.invoke(target, args);
advice.afterMethod(method);
return objValue;
}
});
return proxy;
}
}
好了上面我們的mini小框架就完事了,下面就下一個測試測試一下吧:
public class AopFrameworkTest {
public static void main(String[] args) {
InputStream ips = AopFrameworkTest.class
.getResourceAsStream("config.properties");
Object bean = new BeanFactory(ips).getBean("xxx");
System.out.println(bean.getClass().getName());
}
}
說明,當需要運行代理類就把配置文件中的xxx=java.util.ArrayList用#註釋掉,如果想要運行原類就把xxx=enhance.test.proxy.aopframework.ProxyFactoryBean註釋掉就行了。