小時候很喜歡看小故事大道理,用通俗易懂的小故事去解釋抽象難懂的大道理,並受到啓發,瞭解真諦。今天我們就來試着講一講 代理。
故事前情提要:老師佈置了一道作業,要求同學們做完了交給他。
靜態代理
首先,我們先創建一個學生接口,同學們都需要寫完交作業
public interface Student {
void handWork();
}
實現類表示作業寫完了要交作業了
public class StudentImpl implements Student {
private String name;
public StudentImpl(String name){
this.name = name;
}
public void handWork() {
System.out.println(name + "交作業");
}
}
有個同學叫小王,寫完了正常交作業
public static void main(String[] args) {
StudentImpl student = new StudentImpl("小王");
student.handWork();
}
平時也是這麼做的,最終打印出來“小王交作業”。
可是有一天,小王忽然有事請假了,他的作業做完了,誰去幫他交呢。沒辦法,只能委託玩的最好的小夥伴,小李來交作業了,並告訴他,我把交作業的事情全權交於你處理了。你一定不要辜負我的信任吶。
於是乎,小李成了小王的代理
public class StudentProxy implements Student {
Student student;
public StudentProxy(StudentImpl impl){
this.student = impl;
}
public void handWork() {
student.handWork();
}
}
作業寫完了,小王不在,小李被小王委以重任,他就幫小王交了作業
public class StaticProxyTest {
public static void main(String[] args) {
StudentImpl student = new StudentImpl("小王");
StudentProxy studentProxy = new StudentProxy(student);
studentProxy.handWork();
}
}
這就是靜態代理,所謂靜態也就是在程序運行前就已經存在代理類的字節碼文件,代理類和委託類的關係在運行前就確定了。
小王已經告知了小李需要做什麼(交作業),他們都是做作業交作業,倘若有一天小王被老是罰了只有他自己需要交作業,或者小王想喝奶茶,那如果按照上邊的邏輯,小李也要受罰,也要被迫喝奶茶了。
JDK動態代理
這次作業是老師罰你自己寫的,我纔不寫呢
public class StudentInvocationHandler implements InvocationHandler {
Object target;
public StudentInvocationHandler(Object target){
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理執行" + method.getName() + "方法");
Object invoke = method.invoke(target, args);
return invoke;
}
}
invoke方法可以實現代理邏輯,invoke方法的三個參數含義:
- proxy:代理對象,就是bind方法生成的對象。
- method:當前調度的方法
- args:調度方法的參數
當我們使用了代理對象調度方法後,它就會進入到invoke方法裏面。
小李拿到小王的授權,表示什麼都能幫你做,只可惜小王只有一個交作業的事情,其實什麼喝奶茶,吃辣條我也可以代理
public class ProxyTest {
public static void main(String[] args) {
StudentImpl student = new StudentImpl("小王");
StudentInvocationHandler studentInvocationHandler = new StudentInvocationHandler(student);
Student studentProxy = (Student)Proxy.newProxyInstance(Student.class.getClassLoader(), new Class[]{Student.class}, studentInvocationHandler);
studentProxy.handWork();
//趁機喝小王一杯奶茶,也是可以的
// studentProxy.drink();
}
}
建立代理對象和真實對象的關係
Student studentProxy = (Student)Proxy.newProxyInstance(Student.class.getClassLoader(), new Class[]{Student.class}, studentInvocationHandler);
其中newProxyInstance方法包含了3個參數。
-
ClassLoader
:類加載器,我們採用了target本身的類加載器。 -
Class<?>[]
:是把生成的動態代理對象下掛在哪些接口下,這個寫法是放在target實現的接口下。 -
InvocationHandler
:是定義實現方法邏輯的代理類,它必須實現InvocationHandler接口的invoke方法,它就是代理邏輯方法的現實方法。
最終打印結果:代理執行handWork方法
小王交作業
動態代理類的源碼是在程序運行期間由JVM根據反射等機制動態的生成,所以不存在代理類的字節碼文件。代理類和委託類的關係是在程序運行時確定。需要實現java.lang.reflect.InvocationHandler
的invoke方法。也需要提供接口方可用JDK動態代理
CGLIB動態代理
如果沒有接口,那麼JDK動態代理就會拋錯JdkProxyExample
需要寫一個接口Student,再寫一個StudentImpl實現類,小王也很無奈,我沒有接口,就一個類,但是我還要你代理我,幫我交作業
public class StudentCglib {
public void handWork(String name) {
System.out.println(name + "交作業");
}
}
小李也恭敬不如從命了,畢竟小王說,他請假回來要和我開黑
public class CglibProxy implements MethodInterceptor {
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("調用真實對象前");
//CGLIB反射調用真實對象方法
Object result = methodProxy.invokeSuper(proxy, args);
System.out.println("調用真實對象後");
return result;
}
}
這裏採用了CGLIB的加強者Enhander
,通過設置超類的方法(setSuperclass
),然後通過setCallBack
方法設置哪個類爲它的代理類。其中,參數爲this就意味着是當前對象,那就要求用this這個對象實現接口MethodInterceptor
的方法——intercept
,然後返回代理對象。
那麼此時當前類的intercept
方法就是其代理邏輯方法,包含四個參數
proxy
代理對象method
方法args
方法參數methodProxy
方法代理
沒有接口,我也可以代理
public class CglibTest {
public static void main(String[] args) {
//CGLIB enhancer增強類對象
Enhancer enhancer = new Enhancer();
//設置增強類型
enhancer.setSuperclass(StudentCglib.class);
//定義代理邏輯對象爲當前對象,要求當前對象實現MethodInterceptor方法
enhancer.setCallback(new CglibProxy());
//生成並返回代理對象
StudentCglib proxy = (StudentCglib)enhancer.create();
proxy.handWork("小王");
}
}
最終結果: 調用真實對象前
小王交作業
調用真實對象後
最終,小王小李幸福的生活在了一起
JDK動態代理和CGLIB字節碼生成的區別?
- JDK動態代理只能對實現了接口的類生成代理,而不能針對類
- CGLIB是針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的方法,因爲是繼承,所以該類或方法最好不要聲明成final