代理:無論哪種代理,都存在代理對象和目標對象兩個模型,所以目標對象就是我們要生成的代理對象代理的那個對象。
一、包裝的動態(tài)代理
接口:
public interface Animal {
void eat(String food);
String type();
}
public interface Primate {
void think();
}
實現(xiàn)類:
public class Monkey implements Animal,Primate{
public void eat(String food) {
System.out.println("the food is "+food+" !");
}
public String type() {
String type="哺乳動物";
System.out.println(type);
return type;
}
public void think() {
System.out.println("思考");
}
}
包裝類的代理:
public class AnimalWrapper implements Animal{
private Animal animal;
public AnimalWrapper(Animal animal){
this.animal=animal;
}
public void eat(String food) {
System.out.println("+++Wrapped Before!+++");
animal.eat(food);
System.out.println("+++Wrapped After!+++");
}
public String type() {
System.out.println("+++Wrapped Before!+++");
String type=animal.type();
System.out.println("+++Wrapped After!+++");
return type;
}
}
可以看到,AnimalWrapper完成了對Animal所有子類的代理,在代理方法中,可以加入一些自己的額外邏輯,Spring的前置、后置、環(huán)繞方法通知,都可以通過這種方式有限的模擬出來。
缺點就是,當Animal接口新增了新的方法,包裝類也必須新增方法,而且一個包裝類實際上只能對應一個接口。
二、繼承的代理
public class ExtendProxyTest extends Monkey{
public static void main(String args[]){
}
public void eat(String food){
System.out.println("+++warpped brfore+++");
super.eat(food);
System.out.println("+++warpped after+++");
}
public String type(){
System.out.println("+++warpped brfore+++");
String type=super.type();
System.out.println("+++warpped after+++");
return type;
}
}
這種方式最簡單,不過不能實現(xiàn)對Animal所有子類的代理,與包裝的模式相比,大大縮小了代理范圍。
三、動態(tài)代理
/**
* 基于reflect.Proxy、reflect.InvocationHandler兩個類來完成的,使用java反射機制
*1.
Onbject proxy=Proxy.newProxyInstance(定義代理對象的類加載器,
要代理的目標對象的歸屬接口數(shù)組, 回調接口InvacationHandler);
*
*2. Onbject proxy=Proxy.newProxyInstance(定義代理對象的類加載器,
要代理的目標對象的歸屬接口數(shù)組);
proxy=proxClass.getConstructor(
new Class[]{InvocationHandler.class}).
newInstance(new 回調接口InvocationHandler);
缺點:不能對類進行代理,只能對接口進行代理。因為生成的代理類$Proxy這個類繼承了Proxy,Java的
繼承不允許出現(xiàn)多個父類*
*/
public class DynamicProxy {
public static void main(String args[]) throws {
/*
* 使用第一種方法來創(chuàng)建代理對象
*/
Object proxy=Proxy.newProxyInstance(
Monkey.class.getClassLoader(),
Monkey.class.getInterfaces(),
new AnimalInvocationHandler(new Monkey()));
/*
* 代理對象調用的時候,會調用回調的invoke方法,Method.args是調用invoke的時候傳入的
*/
Animal animal=(Animal)proxy;
animal.eat("橡膠");
animal.type();
* 第2中方式 */
Class<?> proxClass=Proxy.getProxyClass(
Monkey.class.getClassLoader(),
Monkey.class.getInterfaces());
proxy=proxClass.getConstructor(new
Class[]{InvocationHandler.class}).
newInstance(new AnimalInvocationHandler(
new Monkey()));
animal=(Animal)proxy;
animal.eat("香蕉");
animal.type();
Primate pre=(Primate)proxy;
pre.think();
}}
回調類:
/**
* 回調類。
*
* InvocationHandler接口只有一個invoke需要實現(xiàn),這個方法會在目標對象的方法調用的時候被激活
* 你可以在這里控制目標對象的方法的調用,在調用前后插入一些其他從操作
* 比如:鑒權、日志,事務管理等。
*
* 后兩個參數(shù),一個是調用的方法的Method對象,另一個是方法的參數(shù),第一個參數(shù),,就是我們使用Proxy
* 的靜態(tài)方法創(chuàng)建的動態(tài)代理的對象,也就是$Proxy實例,由于$Proxy在JDK中不是靜態(tài)存在的,所以不可以把
* Object proxy強制轉換成$Proxy類型,因為你根本就無法從Classpath中道途$Proxy。那么我們可以把proxy
* 轉換為目標接口對象嗎?可以。因為$Proxy是實現(xiàn)了目標對象的,但是這樣的實際意義不大,因為轉換為
* 目標對象的接口之后,你調用接口中的任何方法,都會導致invoke的調用陷入死循環(huán)而導致棧溢出。
* 這是因為目標對象的大部分方法都被代理了,在invoke通過代理對象轉換之后的接口,調用目標對象的方法
* 依然走的是代理對象
*
* 所以第一個參數(shù),一般情況下都用不到,除非想獲得代理對象$Proxy的類描述信息,它的getClass方法不會陷入死循環(huán).
* 不過還是要注意,調用$proxy的從object上繼承的方法,比如hashCode等也會導致陷入死循環(huán),因為getClass()
* 方法是final的,不可以被覆蓋,所以也就不會被Proxy代理。當然,因為Proxy代理的是Monkey接口,不是Monkey本身,
* 所以即使Monkey在實現(xiàn)Animal、Primate接口的時候,把方法都變?yōu)?/span>final,也不會影響到proxy的動態(tài)代理。 *
* *
*/
public class AnimalInvocationHandler implements InvocationHandler{
private Object obj;
public AnimalInvocationHandler(Animal animal){
this.obj=animal;
}
/**
* proxy是實際的代理對象
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("invoke method hefore");
Object returnObj=method.invoke(obj, args);
System.out.println("invoke method hefore");
return returnObj;
}
}
CGLIB
該類沒有實現(xiàn)接口,并且有一個final方法
public class Monkey{
public void eat(String food) {
System.out.println("the food is "+food+" !");
}
public final String type() {
String type="哺乳動物";
System.out.println(type);
return type;
}
public void think() {
System.out.println("思考");
}
}
/**
* CGLIB進行動態(tài)代理的編程于Proxy沒有多少不同,Enhancer是CGLIB的入口,通過它
* 創(chuàng)建代理對象,同時為代理對象分配一個Callbacl回調接口,用于執(zhí)行回調。
*
* 常用的是MethodInterceptor,他繼承Callback,用于執(zhí)行方法攔截。另外還有一些內置的回調處理:
* 1.FixedValue,為提高性能,FixedValue回調對槍支某一特別方法返回固定值是有用的
* 2.NoOp,該回調把方法調用直接委派到這個方法在父類中的實現(xiàn),相當于不處理
* 3.LazyLoader,當時機的對象需要延遲加載時,可以使用LazyLoader回調。一旦實際對象被裝載,
* 它將被每一個調用代理對象的方法使用
*
* CGLIB被Hibernate、Spring等很多開源框架內部使用,用于完成對類的動態(tài)代理,Spring中的很多XML配置屬性的proxy-target-class
* 默認都為false,含義就是默認不啟用對目標類的動態(tài)代理,而是對接口動態(tài)代理。某些情況下,如果相對Struts2的Action或者Spring
* MVC的Controller進行動態(tài)代理,會發(fā)現(xiàn)默認Spring會報告找不到$Proxy的XXX方法,這是因為一般我們都不會給控制層寫一個接口,
* 而是直接在實現(xiàn)類中寫請求方法,這樣JDK自帶的Proxy是找不到這些方法的,應為他們不在借口中,此時就要設置proxy-target-class="true"
* 引入CGLIB/ASM等庫。
*/
public class CglibMethodInterceptor implements MethodInterceptor{
/**
* 1.obj 代理對象
* 2.被調用的方法
* 3.方法的參數(shù)
* 4.CGLIB提供的方法代理對象
*
* 一般使用最后一個參數(shù),而不是第2個,java的反射,因為CGLIB試用ASM的
* 字節(jié)碼操作,代理對象的執(zhí)行效率比反射機制更高.而且,試用method會造成死循環(huán)
*/
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("=============================");
Object o=proxy.invokeSuper(obj, args);
//Object o1=method.invoke(obj, args);
System.out.println("++++++++++++++++++++++++++++++");
return o;
}
public static void main(String args[])
{
/**
* create方法用來創(chuàng)建代理對象
*/
Monkey mon=(Monkey)Enhancer.create(Monkey.class,
new CglibMethodInterceptor());
mon.think();
/*
* 發(fā)現(xiàn)type方法沒有被代理,沒有輸出裝飾的信息
* 這是因為CGLIB代理的原理是試用ASM動態(tài)生成目標對象的子類,final方法不能
* 被子類覆蓋,所以也就不能被動態(tài)代理,這也是CGLIB的一個缺點
*/
mon.type();
mon.eat("xiangjiao");
System.out.println("hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh");
/*
* 很多情況下,我們不是使用靜態(tài)方法,而不是Enhancer的實例去完成動態(tài)代理對象的創(chuàng)建
* 因為試用實例,可以獲得更多的功能,比如是否使用緩存,生成策略等等.
*
* ,默認情況,子類會繼承父類無參的構造方法進行實例化,如果想
* 調用其他構造器,那么用.create(argumentTypes, arguments),參數(shù)分別是構造方法的
* 參數(shù)類型、傳遞參數(shù)
*/
Enhancer enhancer=new Enhancer();
enhancer.setSuperclass(Monkey.class);
/*
* 可以使用setCallbacks(Callback[] callbacks)方法為代理對象設置一組回調器
* 可以配合CallbackFilter為不同方法使用不同的回調器。CallbackFilter的accept方法
* 返回的是回調器的索引值
*/
enhancer.setCallbacks(new Callback[]{new CglibMethodInterceptor(),NoOp.INSTANCE});
enhancer.setCallbackFilter(new CallbackFilter() {
public int accept(Method method) {
// 方法think使用回調數(shù)組中的第二個回調器
if(method.getName().equals("think"))
return 1;
else return 0;
}
});
Monkey monk=(Monkey)enhancer.create();
monk.think();
monk.type();
monk.eat("xiangjiao");
}
}