代理模式是常見的設計模式之一,顧名思義,代理模式就是代理對象具備真實對象的功能,并代替真實對象完成相應操作,并能夠在操作執行的前后,對操作進行增強處理。(為真實對象提供代理,然后供其他對象通過代理訪問真實對象)
分為
-
靜態代理
-
動態代理
-
jdk動態代理
-
cglib動態代理
-
靜態代理
真實類和代理類要實現同一個接口,在代理類中實現真實類的方法同時可以進行真實類方法的增強處理,在一個代理類中就可以完成對多個真實對象的注入工作。
public interface IRentHouse {void rentHouse();
}
public class RentHouse implements IRentHouse {@Overridepublic void rentHouse() {System.out.println("實現租房");}
}
public class IntermediaryProxy implements IRentHouse {private IRentHouse iRent;public IntermediaryProxy(IRentHouse iRentHouse) {iRent=iRentHouse;}@Overridepublic void rentHouse() {System.out.println("交中介費");iRent.rentHouse();System.out.println("中介負責維修管理");}
}
//client測試類
public class TestStaticProxy {public static void main(String[] args) {//定義租房IRentHouse iRentHouse = new RentHouse();//定義中介IRentHouse intermediaryProxy = new IntermediaryProxy(iRentHouse);//中介租房intermediaryProxy.rentHouse();}
}
動態代理
從靜態代理的代碼中可以發現,靜態代理的缺點顯而易見,那就是當真實類的方法越來越多的時候,這樣構建的代理類的代碼量是非常大的,所以就引進動態代理.
動態代理允許使用一種方法的單個類(代理類)為具有任意數量方法的任意類(真實類)的多個方法調用提供服務,
JAVA 反射機制是在運行狀態中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱為 java 語言的反射機制。
jdk動態代理(接口代理)
Jdk代理涉及到java.lang.reflect包中的InvocationHandler接口和Proxy類,核心方法是
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
jdk動態代理過程中實際上代理的是接口,是因為在創建代理實例的時候,依賴的是java.lang.reflect包中Proxy類的newProxyInstance方法,該方法的生效就恰恰需要這個參數;
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException{……
}
下面以案例來說明jdk動態代理的完整過程:
//接口
public interface Person {void wakeup();void sleep();
}
//實現類1
public class Student implements Person{private String name;public Student() {}public Student(String name) {this.name = name;}@Overridepublic void wakeup() {System.out.println("學生"+name+"早晨醒來啦");}@Overridepublic void sleep() {System.out.println("學生"+name+"晚上睡覺啦");}
}
//代理類
public class JDKDynamicProxy implements InvocationHandler {private Object bean;public JDKDynamicProxy(Object bean) {this.bean=bean;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {String methodname=method.getName();if (methodname.equals("wakeup")){System.out.println("早安~~~");}else if(methodname.equals("sleep")){System.out.println("晚安~~~");}return method.invoke(bean,args);}
}
//測試類
public class TestJDKDynamicProxy {public static void main(String[] args) {JDKDynamicProxy proxy = new JDKDynamicProxy(new Student("張三"));//創建代理實例Person student = (Person) Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[]{Person.class}, proxy);student.wakeup();student.sleep();}
}
輸出結果為
早安~~
學生張三早晨醒來啦
晚安~~
學生張三晚上睡覺啦
接口中的方法,以及代理類中重寫的invoke方法,但是invoke()方法并不是顯式調用的,是在創建代理實例的過程中生成的接口虛擬代理類中調用了invoke方法。(把Sproxy0的實例強制轉換成對應接口類型的引用,然后執行接口方法,進而執行代理類中invoke ())
總結對比:
1.靜態代理中,代理類和真實類實現的是同一個接口,重寫同樣的方法;jdk動態代理中,代理類和真實類關系不大,代理類實現無侵入式的代碼擴展。
2.靜態代理中當接口中方法增加的時候,在代理類代碼量也會增加,顯然是不妥的;jdk動態代理解決了這個問題,當業務增加,代理類的代碼不會增加。
3.jdk動態代理實現的是jdk自帶InvocationHandler接口,實現了這個接口的類也叫攔截器類,也叫代理類。
cglib動態代理
從上面可以看出,jdk動態代理的前提條件是,要有接口存在,那還有許多場景是沒有接口的,這個時候就需要cglib動態代理了,CGLIB(Code Generation Library)是一個基于ASM的字節碼生成庫,它允許我們在運行時對字節碼進行修改和動態生成。CGLIB通過繼承方式實現代理。cglib動態代理過程中生成的是實現類的子類,cglib是如何憑空創造的實現類的子類的,下面是測試代碼
//所需的代理類
public class CglibProxy implements MethodInterceptor {private Enhancer enhancer=new Enhancer();private Object bean;public CglibProxy(Object bean) {this.bean = bean;}public Object getProxy(){//設置需要創建子類的類enhancer.setSuperclass(bean.getClass());enhancer.setCallback(this);return enhancer.create();}@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {String methodName = method.getName();if (methodName.equals("wakeup")){System.out.println("早安~~~");}else if(methodName.equals("sleep")){System.out.println("晚安~~~");}return method.invoke(bean,objects);}
}
//測試類
public class TestCglibProxy {public static void main(String[] args) {//生成虛擬代理類的代碼,本來虛擬代理子類是看不見的,//下面這句話的作用就是把執行過程中cglib增強后的class字節碼文件System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\aop");CglibProxy proxy = new CglibProxy(new Cat("咪咪"));Cat cat = (Cat) proxy.getProxy();cat.wakeup();cat.sleep();}
}
總結:
cglib動態代理和jdk動態代理的區別顯而易見,但是實現邏輯差不多,cglib代理類是通過實現MethodInterceptor,重寫intercept方法,通過生成被代理類的子類來達到代理增強代碼的目的;而Jdk代理是通過實現InvocationHandler,重寫invoke方法,通過生成接口的代理類來達到代碼增強的目的,所以jdk動態代理的實現需要接口,cglib則不需要,spring5.0以上以及springboot2.0以上默認采用cglib動態來實現AOP。