Mybatis的Mapper接口的動態代理機制

Mybatis源碼中Mapper的動態代理實現原理

現在工作中用的最多的就是Mybatis這款半自動ORM框架,用的久卻對其瞭解不是很深,現在準備對其進行一些深入的學習,順便對知識進行查漏補缺.本篇是對Mapper動態代理原理的詳解.


代理模式定義

爲另一個對象提供一個替身或者佔位符以控制對這個對象的訪問.也就是說目的是控制對象行駛他的職責.當然也可以增強其職責,比如Spring AOP.

代理模式類圖

由下圖分析,代理模式所需要的角色爲:

  1. 對外的行爲接口Subject,對於調用方Client可見

  2. RealSubject真實的Subject,其包含具體的接口行爲,對於Client不可見

  3. 代理類Proxy,其是RealSubject的替身,也可以當成對RealSubject的一層包裝,對於Client不可見. 
    這裏寫圖片描述

JDK動態代理實例

案例採取Java的動態代理形式開發,按照上述類圖定義角色 
Subject接口 
public interface Subject { 
/** 
* 反轉輸入的input字符串 
* @param input 要反轉的串 
* @return 反轉後的串 
*/ 
String reversalInput(String input); 

RealSubject對接口的實現 
public class RealSubject implements Subject { 
public String reversalInput(String input) { 
System.out.println(“我是RealSubject: “+input); 
return new StringBuilder(input).reverse().toString(); 


SubjectProxy代理方法類 
該類實現了InvocationHandler,實際上是對調用的攔截,攔截後轉向真實對象的調用,從而拿到正確的結果.是不是很像裝飾者模式?其實也可以這樣理解,設計模式之前本身就有很多關聯性,不需要認定某一個行爲就是單一的某個模式,從產生效果來看這裏的SubjectProxy實際上就是對RealSubject的裝飾,只不過這個裝飾並沒有添加新功能.

public class SubjectProxy implements InvocationHandler { 
private Object target; 
public SubjectProxy(Object target) { 
this.target = target; 

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
System.out.println(“proxy subject is “+proxy.getClass()); 
System.out.println(“real subject : “+ToStringBuilder.reflectionToString(target)); 
System.out.println(“method: “+method); 
System.out.println(“args: “+ ToStringBuilder.reflectionToString(args)); 
return method.invoke(target, args); 


Client調用

public class Client { 
public static void main(String[] args) { 
RealSubject subject = new RealSubject(); 
Subject proxyInstance = (Subject) Proxy.newProxyInstance( 
Subject.class.getClassLoader(), 
new Class[]{Subject.class}, 
new SubjectProxy(subject)); 
System.out.println(proxyInstance.reversalInput(“hello world”)); 


輸出

proxy subject is com.sun.proxy.$Proxy0 
real subject: cn.mrdear.proxy.RealSubject@51016012[] 
method: public abstract java.lang.String cn.mrdear.proxy.Subject.reversalInput(java.lang.String) 
args: [Ljava.lang.Object;@29444d75[{hello world}] 
我是RealSubject: hello world 
dlrow olleh

分析 
1.動態代理哪裏體現了動態?

對於常規Java類變量創建要求有.java文件,然後編譯成.class文件,然後虛擬機加載該.class文件,最後才能生成對象.但是對於Subject proxyInstance該代理類其是不存在.java文件的,也就是該對象的.class文件是動態生成的,然後虛擬機加載該class文件,創建對象.在Proxy.java中有如下代碼動態生成class文件,這裏不多深入(因爲我也不懂~~~).

/* 
* Generate the specified proxy class. 
*/ 
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);

2.JDK動態代理的要求

JDK動態代理只能針對接口,如果要針對普通類則可以考慮CGLib的實現,這裏不多分析.其次動態代理的要求有接口類Subject,InvocationHandler代理方法類存在,才能創建出代理對象,代理對象的執行方法都被InvocationHandler接口所攔截,轉向真實類的執行或者你想要的操作.

Mybatis的動態代理Mapper

由上面內容可以看出JDK動態代理需要接口,真實實現類,Clinet調用方,在常規的Mybatis的Mapper代理中接口就是Mapper(Dao中的接口方法),Client是service,那麼真實的實現類是什麼?顯而易見這裏就是Mapper代理的關鍵點.

MapperProxyFactory

顧名思義該類是產生Mapper接口的工廠類,其內部有如下方法,由此可以看出MapperProxy是方法攔截的地方,那麼到此動態代理所需要的必須角色都以湊齊,那麼接下來分析最重要的MapperProxy方法攔截.

protected T newInstance(MapperProxy<T> mapperProxy) { 
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); 
}

MapperProxy

該類是Mapper接口的Proxy角色,繼承了InvocationHandler,所以具有方法攔截功能,看代碼註釋.

@Override 
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
try { 
if (Object.class.equals(method.getDeclaringClass())) { ////判斷是否爲object,因爲其不是接口 
return method.invoke(this, args); 
} else if (isDefaultMethod(method)) { //判斷是否爲接口總的默認方法,jdk8允許接口中聲明默認方法. 
return invokeDefaultMethod(proxy, method, args); 

} catch (Throwable t) { 
throw ExceptionUtil.unwrapThrowable(t); 

//對正常Mapper請求的處理 
final MapperMethod mapperMethod = cachedMapperMethod(method); 
return mapperMethod.execute(sqlSession, args); 

對於正常的Mapper接口中的方法調用,mybatis都會轉向到MapperMethod的execute方法中執行,拿到結果返回給調用方Client,整個代理過程結束.對於正常調用是有緩存的,並且該代理類是項目啓動時就生成好的,對於性能影響並不是很大實用性還是很高的.

這裏要注意下對於默認接口方法的處理invokeDefaultMethod(proxy, method, args),該方法中每次都直接生成代理類,對性能是一種損耗應該不小,所以不建議在Mapper接口中寫默認方法. 
@UsesJava7 
private Object invokeDefaultMethod(Object proxy, Method method, Object[] args) 
throws Throwable { 
final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class 
.getDeclaredConstructor(Class.class, int.class); 
if (!constructor.isAccessible()) { 
constructor.setAccessible(true); 

final Class<?> declaringClass = method.getDeclaringClass(); 
return constructor 
.newInstance(declaringClass, 
MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED 
| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC) 
.unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args); 
}

總結

從上面來看動態代理的最大的好處就是接口與其實現類的解耦,原本接口和動態類之間是強關聯狀態,接口不能實例化,實現類必須實現接口的所有方法,有了動態代理之後,接口與實現類的關係並不是很大,甚至不需要實現類就可以完成調用,比如Mybatis這種形式,其並沒有創建該接口的實現類,而是用一個方法攔截器轉向到自己的通用處理邏輯. 
另外就是Spring AOP的動態代理,解耦後自然可以實現對原有方法增強的同時又對其代碼的零侵入性. 
    Mybatis的Mapper動態代理實現原理還是很清晰的,很多東西還需要不斷的積累才能更明白。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章