Table of Contents
1.3:創建InvocationHandler方法執行器 和生成代理類
1.5:或者使用內部類來標識InvocationHandler處理器;
目前java動態代理的實現分爲兩種
1.基於JDK的動態代理
2.基於CGILB的動態代理
在業務中使用動態代理,一般是爲了給需要實現的方法添加預處理或者添加後續操作,但是不干預實現類的正常業務,把一些基本業務和主要的業務邏輯分離。我們一般所熟知的Spring的AOP原理就是基於動態代理實現的。
1:基於JDK的動態代理
基於JDK的動態代理就需要知道兩個類:1.InvocationHandler(接口)、2.Proxy(類)
還要知道JDK是基於接口的動態代理,也就是說我們動態代理的被代理類,必須實現接口
1.1:創建接口
public interface UserInter {
void add();
}
1.2:創建實現類
public class UserImpl implements UserInter {
@Override
public void add() {
System.out.println("add.....");
}
}
1.3:創建InvocationHandler方法執行器 和生成代理類
package com.wkl.impl.Proxy;
import com.wkl.impl.UserImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* Description:方法執行器,幫我們執行方法
* Date: 2020/6/26 - 下午 10:16
* author: wangkanglu
* version: V1.0
*/
public class MyInvocationHandler implements InvocationHandler {
private UserImpl userImpl;
public void setUserImpl(UserImpl userImpl) {
this.userImpl = userImpl;
}
/**
* Object proxy:代理對象;給jdk使用,任何時候都不要動這個對象
* Method method:當前將要執行的目標對象的方法
* Object[] args:這個方法調用時外界傳入的參數值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//參數爲被代理的類的實例,和傳入的參數
System.out.println("準備執行方法:"+method.getName());
Object invoke = method.invoke(userImpl, args);
System.out.println("執行方法:"+method.getName()+"完畢!");
return invoke;
}
}
1.4:編寫代理類實際的調用。
UserInter userInter = new UserImpl();
//獲取處理器實例
MyInvocationHandler userProxy = new MyInvocationHandler();
userProxy.setUserInter(userInter);
UserInter o = (UserInter) Proxy.newProxyInstance(userInter.getClass().getClassLoader(), userInter.getClass().getInterfaces(), userProxy);
o.add();
準備執行方法:add
add.....
執行方法:add完畢!
1.5:或者使用內部類來標識InvocationHandler處理器;
package com.wkl.impl.Proxy;
import com.wkl.impl.UserImpl;
import com.wkl.inter.UserInter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* Description:
* Date: 2020/6/26 - 下午 10:15
* author: wangkanglu
* version: V1.0
*/
public class UserProxy {
public UserInter getProxy(UserInter userInter){
InvocationHandler i = new InvocationHandler() {
/**
* Object proxy:代理對象;給jdk使用,任何時候都不要動這個對象
* Method method:當前將要執行的目標對象的方法
* Object[] args:這個方法調用時外界傳入的參數值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("準備執行方法:"+method.getName());
Object invoke = method.invoke(userInter, args);
System.out.println("執行方法:"+method.getName()+"完畢!");
return invoke;
}
};
UserInter o = (UserInter) Proxy.newProxyInstance(userInter.getClass().getClassLoader(), userInter.getClass().getInterfaces(), i);
return o;
}
public static void main(String[] args) {
UserInter userInter = new UserImpl();
//獲取處理器實例
UserProxy userProxy = new UserProxy();
UserInter proxy = userProxy.getProxy(userInter);
proxy.add();
}
}
注:處理器接口傳入的是接口的實現;
2:基於CGLIB的動態代理
本文主要講的是CGLIB的動態代理,因爲基於JDK的動態代理一定要繼承一個接口,而絕大部分情況是基於POJO類的動態代理,那麼CGLIB就是一個很好的選擇,在Hibernate框架中PO的字節碼生產工作就是靠CGLIB來完成的。還是先看代碼。
2.1:引入jar包
<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
2.2:被代理類
public class UserImpl {
public void add() {
System.out.println("add.....");
}
}
2.3:代理了實現MethodInterceptor接口
package com.wkl.impl.Proxy;
import com.wkl.impl.UserImpl;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* Description:
* Date: 2020/6/26 - 下午 10:55
* author: wangkanglu
* version: V1.0
*/
public class UserCGLIB implements MethodInterceptor {
private UserImpl userimpl;
public void setUserimpl(UserImpl userimpl) {
this.userimpl = userimpl;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//參數爲被代理的類的實例,和傳入的參數
System.out.println("準備執行方法:"+method.getName());
用。
////method無法得到父類的方法,所以使用methoproxy
Object invoke = methodProxy.invoke(userimpl, objects);
System.out.println("執行方法:"+method.getName()+"完畢!");
return invoke;
}
}
2.4:測試:
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserImpl.class);
UserCGLIB userCGLIB = new UserCGLIB();
UserImpl user = new UserImpl();
userCGLIB.setUserimpl(user);
enhancer.setCallback(userCGLIB);
UserImpl o = (UserImpl) enhancer.create();
o.add();
}
準備執行方法:add
add.....
執行方法:add完畢!
2.5:CGLIB原理
從代碼可以看出,它和jdk動態代理有所不同,對外表現上看CreatProxyedObj,它只需要一個類型clazz就可以產生一個代理對象, 所以說是“類的代理”,且創造的對象通過打印類型發現也是一個新的類型。不同於jdk動態代理,jdk動態代理要求對象必須實現接口(三個參數的第二個參數),cglib對此沒有要求。
cglib的原理是這樣,它生成一個繼承B的類型C(代理類),這個代理類持有一個MethodInterceptor,我們setCallback時傳入的。 C重寫所有B中的方法(方法名一致),然後在C中,構建名叫“CGLIB”+“$父類方法名$”的方法(下面叫cglib方法,所有非private的方法都會被構建),方法體裏只有一句話super.方法名(),可以簡單的認爲保持了對父類方法的一個引用,方便調用。
這樣的話,C中就有了重寫方法、cglib方法、父類方法(不可見),還有一個統一的攔截方法(增強方法intercept)。其中重寫方法和cglib方法肯定是有映射關係的。
C的重寫方法是外界調用的入口(LSP原則),它調用MethodInterceptor的intercept方法,調用時會傳遞四個參數,第一個參數傳遞的是this,代表代理類本身,第二個參數標示攔截的方法,第三個參數是入參,第四個參數是cglib方法,intercept方法完成增強後,我們調用cglib方法間接調用父類方法完成整個方法鏈的調用。
爲什麼我們使用methodproxy,不適用method
因爲如果我們通過反射 arg1.invoke(arg0, ...)這種方式是無法調用到父類的方法的,子類有方法重寫,隱藏了父類的方法,父類的方法已經不可見,如果硬調arg1.invoke(arg0, ...)很明顯會死循環。
所以調用的是cglib開頭的方法,但是,我們使用arg3也不是簡單的invoke,而是用的invokeSuper方法,這是因爲cglib採用了fastclass機制,不僅巧妙的避開了調不到父類方法的問題,還加速了方法的調用。
fastclass基本原理是,給每個方法編號,通過編號找到方法執行避免了通過反射調