Mybatis源碼中Mapper的動態代理實現原理
現在工作中用的最多的就是Mybatis這款半自動ORM框架,用的久卻對其瞭解不是很深,現在準備對其進行一些深入的學習,順便對知識進行查漏補缺.本篇是對Mapper動態代理原理的詳解.
代理模式定義
爲另一個對象提供一個替身或者佔位符以控制對這個對象的訪問.也就是說目的是控制對象行駛他的職責.當然也可以增強其職責,比如Spring AOP.
代理模式類圖
由下圖分析,代理模式所需要的角色爲:
對外的行爲接口Subject,對於調用方Client可見
RealSubject真實的Subject,其包含具體的接口行爲,對於Client不可見
代理類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動態代理實現原理還是很清晰的,很多東西還需要不斷的積累才能更明白。