爲什麼需要代理
假設有一個計算器類,類中定義了加,減,乘,除操作,然後我希望在每個方法執行前後打印一些提示信息,比如 “XX方法開始執行。。”,“XX方法結束執行。。”,要做到這一點,普通的解決方式是在每個方法前後加上 System.out.println("XX方法正在執行。。"),System.out.println("XX方法結束執行。。"),這樣寫起來十分麻煩,如果我們以後要擴充這個類,要加上乘方,開方等方法,還要再加上提示信息,代碼及其冗餘,也不便於維護。
代理模式可以解決這種囧境,利用這種模式,可以將一些通用的操作抽取出來,比如記錄日誌,權限管理等等,這些操作在很多地方都能用到,將這些操作單獨拿出來,運行時動態加進去,這時我們希望的。
代理分爲靜態代理:主要通過繼承和組合的方式實現,以及下面主要介紹的動態代理。
此外代理模式是Spring框架中AOP的基礎,所以理解代理模式非常重要。
使用jdk自帶的庫來實現動態代理
被代理的類需要實現特定的接口,我們先定義這個接口:
package com.hunan.proxy;
public interface Moveable {
//move方法,參數爲移動速度,返回移動的總路程
public double move(double speed);
}
接下來我們寫一個Handler爲所有實現這個接口的類做代理,上面的接口中聲明瞭move方法,我們希望再執行move方法前後記錄日誌,利用代理來將日誌處理模塊提取出來:
package com.hunan.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class LogHandler implements InvocationHandler {
private Object target;
public LogHandler(Object target) {
super();
this.target = target;
}
/*
* 參數說明:
* proxy 被代理的對象
* method 被代理對象的方法
* args 方法的參數
*
* 返回值:
* Object 被代理的對象的方法的返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 本例僅爲演示動態代理,具體的日誌處理過程就不具體實現了,僅打印一條提示信息。
System.out.println("開始記錄日誌。。。");
Object obj = method.invoke(target, args);
System.out.println("日誌記錄完畢。。。");
return obj;
}
}
注意:代理是代理某個特定接口的實現類,而不是代理某個特定類,所以儘量用Object引用指向對象,使代碼有更好的通用性。
第三步實現接口,用線程休眠的方式模擬汽車行駛:
package com.hunan.proxy;
import java.util.Random;
public class Car implements Moveable {
@Override
public double move(double speed) {
System.out.println("汽車行駛中。。。");
int moveTime = new Random().nextInt(3000);
try {
Thread.sleep(moveTime);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return speed*moveTime;
}
}
最後寫個main方法測試一下:
package com.hunan.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
Car car = new Car();
InvocationHandler h = new LogHandler(car);
Class<?> cls = car.getClass();
/*
* loader:類加載器
* interface:實現的接口
* h:InvocationHandler實例
*/
Moveable m = (Moveable) Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), h);
double distance = m.move(50);
System.out.println("move: " + distance + " meters....");
}
}
運行結果如下:
基於cglib的動態代理
使用cglib來實現動態代理需要引入cglib的依賴包,這個網上很容易下載到。cglib的代理實現方式實際上是基於繼承來實現的,它會創建一個被代理類的子類,在子類中添加日誌處理等模塊並調用父類的方法。這些子類由cglib幫我們管理。
仍然是代理前面的Moveable接口的實現類:
首先要實現MethodInterceptor接口:
package com.hunan.cglibproxy;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class<?> clazz) {
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
/*
* 攔截所有目標類方法的調用
* obj:目標類的實例
* m: 目標方法的反射對象
* args:方法的參數
* proxy:代理類的實例
* @see net.sf.cglib.proxy.MethodInterceptor#intercept(java.lang.Object, java.lang.reflect.Method, java.lang.Object[], net.sf.cglib.proxy.MethodProxy)
*/
@Override
public Object intercept(Object obj, Method m, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("日誌開始。。。");
//代理類調用父類方法
Object distance= proxy.invokeSuper(obj, args);
System.out.println("日誌結束。。。");
return distance;
}
}
這個enhancer就是用於實現代理的子類了,不過cglib幫我們包裝了一下。
接下來寫一個Train類實現Moveable接口:
package com.hunan.cglibproxy;
import java.util.Random;
import com.hunan.proxy.Moveable;
public class Train implements Moveable {
@Override
public double move(double speed) {
System.out.println("火車行駛中。。。");
int moveTime = new Random().nextInt(3000);
try {
Thread.sleep(moveTime);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return speed*moveTime;
}
}
寫一個main方法測試一下:
package com.hunan.cglibproxy;
public class Client {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
Train t = (Train)proxy.getProxy(Train.class);
double distance = t.move(100);
System.out.println("火車行駛了: " + distance + "路程。。");
}
}
打印輸出如下: