淺談下代理模式
一直沒太弄明白的代理模式,這次花時間弄明白下,如果有錯誤,希望有人看到可以糾正交流下,我理解的 代理模式就是給A對象提供一個代理B對象,B對象可以控制調用A對象的方法。
代理模式分爲兩種,一種是靜態代理,一種是動態代理。下面我們先看下靜態代理。
靜態代理
這裏面有三個角色,百度百科就可以查到。
抽象角色: 通過接口或抽象類聲明真實角色實現的業務方法。
代理角色: 實現抽象角色,是真實角色的代理,通過真實角色的業務邏輯方法來實現抽象方法,並可以附加自己的操作。
真實角色: 實現抽象角色,定義真實角色所要實現的業務邏輯,供代理角色調用。
下面看下靜態代理的代碼實現
第一步,先定義一個抽象角色,看到抽象了,當然是接口:
package proxy;
public interface User {
void buyHosue();
}
第二步,再定義一個真實角色,來實現這個抽象角色:
package proxy;
public class UserImpl implements User {
@Override
public void buyHosue() {
System.out.println("Buy a big house.");
}
}
第三步,定義一個代理角色,代理角色的構造或者實例方法我們需要將抽象角色的實現真實角色當作參數傳入,參數類型需要保證是抽象角色
package proxy;
public class UserProxy implements User {
public User user;
public UserProxy(User user) {
this.user = user;
}
@Override
public void buyHosue() {
System.out.println("Prepare money to buy a house");
user.buyHosue();
System.out.println("Prepare money to decorate the house");
}
}
第四步,測試靜態代理。
package proxy;
public class ProxyTest {
public static void main(String[] args) {
User user = new UserImpl();
UserProxy proxyA = new UserProxy(user);
proxyA.buyHosue();
User userB = new UserBImpl();
UserProxy proxyB = new UserProxy(userB);
proxyB.buyHosue();
}
}
看下輸出:
Prepare money to buy a house
Buy a big house.
Prepare money to decorate the house
Prepare money to buy a house
B Buy a big house.
Prepare money to decorate the house
可以看出通過抽象對象的不同實現,我們可以代理不同的真實角色。代理類可以爲委託類預處理消息、把消息轉發給委託類和事後處理消息等
總結,靜態代理是由程序員創建或特定工具自動生成源代碼,在對其編譯。在程序員運行之前,代理類.class文件就已經被創建了。動態代理是在程序運行時通過反射機制動態創建的。
優點: 符合開閉原則,對目標對象進行了擴展。
缺點: 通常只代理一個類,事先知道要代理的是什麼,接口一旦發生改變,代理類也得相應修改。
動態代理:
與靜態代理類對照的是動態代理類,動態代理類的字節碼在程序運行時由Java反射機制動態生成,無需程序員手工編寫它的源代碼。動態代理類不僅簡化了編程工作,而且提高了軟件系統的可擴展性,因爲Java反射機制可以生成任意類型的動態代理類。java.lang.reflect 包中的Proxy類和InvocationHandler接口提供了生成動態代理類的能力,這種代理模式是 JDK動態代理模式 ,實現了JDK裏的InvocationHandler接口的invoke方法,但注意的是代理的是接口,也就是你的真實對象必須要實現抽象對象,通過Proxy裏的newProxyInstance得到代理對象,我們在網上看到的先用Proxy.getProxyClass生成代理類,再獲得代理類的構造函數,最後新建一個實例是Proxy.newProxyInstance的內部實現,直接用newProxyInstance這種方式最簡單;另外一種是 Cglib動態代理模式 ,這種模式是藉助利用asm開源包,對代理對象類的class文件加載進來,通過修改其字節碼生成子類來處理。
JDK動態代理模式
對象類型還是三個,抽象對象,真實對象,代理對象
貼下我的練習代碼,還是用上面的抽象對象User
第一步,真實對象
package proxy;
import proxy.annotation.AfterExecute;
import proxy.annotation.BeforeExecute;
public class UserImpl implements User {
@BeforeExecute
public void before() {
System.out.println("Prepare money to buy a house");
}
@Override
public void buyHosue() {
System.out.println("Buy a big house.");
}
@AfterExecute
public void after() {
System.out.println("Prepare money to decorate the house");
}
}
新增了兩個方法before和after用來實現一點小邏輯,自定義了兩個註解,一個BeforeExecute,另一個是AfterExecute。
第二步,代理對象
package proxy;
import proxy.annotation.AfterExecute;
import proxy.annotation.BeforeExecute;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxyHandler<T> implements InvocationHandler {
private T realObject;
private Method beforeMethod;
private Method afterMethod;
public Object newProxy(T object) {
this.realObject = object;
//獲取真實對象中的所有方法
Method[] declaredMethods = object.getClass()
.getDeclaredMethods();
for (Method method : declaredMethods) {
if (!method.isAccessible())
//setAccessible設置成true取消了Java的權限控制檢查,
//是用來訪問private的方法
method.setAccessible(true);
//如果註解是BeforeExecute
if (method.isAnnotationPresent(BeforeExecute.class)) {
beforeMethod = method;
} else if (method.isAnnotationPresent(AfterExecute.class)) {
afterMethod = method;
}
}
//用newProxyInstance獲取代理對象的實例
//第一個參數是真實對象的類加載器
//第二個參數是一個數組,我們都知道一個類可以實現多個接口,所以這裏是數組,在主程序使用的時候,我們可以轉換成要用的接口類型就行
//第三個參數也是需要將DynamicProxyHandler傳入,這裏也是對invoke的回調
//可以直接在這裏new一個InvocationHandler,就不用DynamicProxyHandler繼承實現InvocationHandler了,具體看下cglib代理。
return Proxy.newProxyInstance(realObject .getClass().getClassLoader(), object.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (null != beforeMethod)
beforeMethod.invoke(realObject);
} catch (Exception e) {
e.printStackTrace();
}
Object result = method.invoke(realObject, args);
try {
if (null != afterMethod)
afterMethod.invoke(realObject);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}
這裏加了一個應用,對AOP的使用,如果不需要,完全可以只需要method調用invoke方法就行了。
第三步,測試動態代理
package proxy;
public class DynamicProxyTest {
public static void main(String[] args) {
DynamicProxyHandler dynamicProxyHandler = new DynamicProxyHandler();
User user = (User) dynamicProxyHandler.newProxy(new UserImpl());
user.buyHosue();
}
}
輸出:
Prepare money to buy a house
Buy a big house.
Prepare money to decorate the house
總結,可以看出JDK動態代理的代理對象完全不需要知道它代理的是什麼鬼,在運行時,人家給啥他就代理啥,這個極大的增強了代碼的擴展性,減少了代碼的改動,這就是所謂開閉原則。
Cglib動態代理模式
如果我們代理的對象不是一個接口,而是一個單純的對象,這時候JDK代理模式就歇菜了,所以這時候就出現了Cglib這個大救星,它的主要邏輯是藉助Enhancer這個工具類爲所代理的類生成一個子類,覆蓋其中的方法,因爲是繼承,所以裏面的類不能用final修飾。所以在這種代理模式中是沒有必要有抽象對象的,當然有也沒有關係,我們使用的是真實對象realObject,直接看代碼。
第一步,真實對象
package proxy;
//注意:這裏沒有實現抽象對象(接口)
public class CglibUser {
public void buyHosue() {
System.out.println("Buy a big house.");
}
}
第二步,通過Enhancer爲代理的對象創建一個子類
package proxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxy implements MethodInterceptor {
private Object realObject;
public Object getInstance(Object target) {
//真實對象
this.realObject = target;
//new一個工具類,後面的操作靠它了,可以把他理解成工具類
Enhancer enhancer = new Enhancer();
//當然是給真實對象創建一個子類,所以設置它爲父類
enhancer.setSuperclass(this.realObject.getClass());
//callback回調的方法就是這個方法本身,這裏回調的方法就是invoke方法
//我們要調用的代理的方法就在這個invoke方法裏了
enhancer.setCallback(this);
//創建一個子類,返回給主程序使用,在主程序做你想做的事
return enhancer.create();
}
@Override
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("Prepare money to buy a house");
Object result = methodProxy.invoke(realObject, args);
System.out.println("Prepare money to decorate the house");
return result;
}
}
這個不是很明顯,再看下下面這個,兩個邏輯一樣,回調顯示的更加明顯
package proxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* Created by user on 2018/12/20.
*/
public class CglibProxy2<T> {
private Object realObject;
public Object getInstance(Object target) {
//真實對象
this.realObject = target;
//new一個工具類,後面的操作靠它了,可以把他理解成工具類
Enhancer enhancer = new Enhancer();
//當然是給真實對象創建一個子類,所以設置它爲父類
enhancer.setSuperclass(this.realObject.getClass());
//callback的時候我們實現了一個MethodInterceptor
//我們要調用的代理的方法就在這個invoke方法裏了
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("Prepare money to buy a house");
Object result = methodProxy.invoke(realObject, args);
System.out.println("Prepare money to decorate the house");
return result;
}
});
return enhancer.create();
}
}
第三步,測試Cglib代理
package proxy;
public class CglibProxyTest {
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
CglibUser cglibUser = (CglibUser) cglibProxy.getInstance(new CglibUser());
cglibUser.buyHosue();
CglibProxy2 cglibProxy2 = new CglibProxy2();
CglibUser cglibUser2 = (CglibUser) cglibProxy2.getInstance(new CglibUser());
cglibUser2.buyHosue();
}
}
看下輸出結果:
Prepare money to buy a house
Buy a big house.
Prepare money to decorate the house
Prepare money to buy a house
Buy a big house.
Prepare money to decorate the house
總結,Cglib代理模式的優點就是看可以給不是接口實現類做代理,這極大的方便了spring對象的管理。
JDK代理和Cglib代理,簡單來看就是先生成新的class文件,然後加載到jvm中,然後使用反射,先用class取得他的構造方法,然後使用構造方法反射得到他的一個實例。唯一的區別在於生成新的class文件方式和結果不一樣。在生成代理對象的時候兩者都使用了緩存,JDK代理使用了WeakReference引用,這個可以跟下源碼,JDK中是在獲取代理類的時候使用的,而cglib使用的直接是 WeakHashMap,基本也類似。
一些疑問
1.spring使用的是哪種代理呢?
如果一個類有頂層接口,則默認使用JDK的動態代理來代理,如果直接是一個類,不是實現類,則使用cglib動態代理。 其次,如果沒有需要代理的方法,如所有方法都沒有@Transactional @Service @Controller @Repository等註解,則不會被代理。Tips,springboot好像默認使用的cglib代理,改動參數配置改不過來。
2.Mybatis的Mapper使用的是哪種代理?
JDK動態代理,我們處理數據庫的數據的時候都是藉助Mybatis的SqlSession這個強大的接口去實現對數據庫語句的CRUD,代理的時候我們只需要將所用到的SqlSession實現類也就是真實對象傳輸給JDK,讓JDK去生成代理對象。
看下源碼MapperProxyFactory的newInstance方法就一目瞭然了:
3.Spring的Aop使用的是哪種代理?
- 如果所要代理的對象實現了接口,默認情況下會採用JDK的動態代理實現AOP;
- 如果所要代理的對象實現了接口,可以強制使用Cglib代理,因爲他不關注是否實現接口,在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>
- 如果目標對象沒有實現接口,則會使用Cglib代理
- 所以spring的代理模式是在兩種模式之間切換,但是springboot2.0之後默認使用了Cglib代理,修改強制不使用也不起作用,這個我還在驗證。