jdk動態代理:由淺入深理解mybatis底層
什麼是代理
代理模式,目的就是為其他物件提供一個代理以控制對某個物件的訪問,代理類為被代理者處理過濾訊息,說白了就是對被代理者的方法進行增強。
看到這裡,有沒有感覺很熟悉?AOP,我們熟知的面向切面程式設計,不也是對方法增強,對切點進行處理過濾麼。
其實AOP這種設計思想,他的精髓便是,在預編譯和執行階段使用動態代理實現的。
初體驗
下面是我自己寫的小例子。
//代理的介面 /** * @created with IDEA * @author: yonyong * @version: 1.0.0 * @date: 2020/4/21 * @time: 21:13 **/ public interface person { void doSomething(); void fun1(); void fun2(); }
//被代理者/委託人 /** * @created with IDEA * @author: yonyong * @version: 1.0.0 * @date: 2020/4/21 * @time: 21:13 **/ public class Student implements person{ @Override public void doSomething() { System.out.println("dosomeThing"); } @Override public void fun1() { System.out.println("fun1"); } @Override public void fun2() { System.out.println("fun2"); } }
//實現InvocationHandler介面,加入切面邏輯 /** * @created with IDEA * @author: yonyong * @version: 1.0.0 * @date: 2020/4/21 * @time: 21:15 **/ public class StudentProxyHandler implements InvocationHandler { Student target; public StudentProxyHandler(Student target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("pre"); Object obj = method.invoke(target,args); System.out.println("aft"); return obj; } }
/**
* @created with IDEA
* @author: yonyong
* @version: 1.0.0
* @date: 2020/4/21
* @time: 21:19
**/
public class Main {
public static void main(String[] args) {
Student p=new Student();
person person = (person) Proxy.newProxyInstance(p.getClass().getClassLoader(), p.getClass().getInterfaces(), new StudentProxyHandler(p));
person.doSomething();
System.out.println("______________________");
person.fun1();
System.out.println("______________________");
person.fun2();
}
}
執行程式碼,我們可以得到:
pre
dosomeThing
after
______________________
pre
dosomeThing
after
______________________
pre
dosomeThing
after
我們來通過列印的結果來實實在在的體會jdk的優點及aop的特性:
如果有種業務場景,需要有多個方法有重複的程式碼塊,或者相同的實現規則,如果不用aop我們可能要每個方法寫一份規則,或者自定義個方法,每個方法呼叫來實現。首先這就已經使程式碼變得侵入性,也違反了java的封裝重構原則。
而使用jdk動態代理,無論這個委託人有多少方法,他都會執行切面邏輯裡的規則,這樣很便於後期的程式碼維護,即便是後續加入新的方法,也無須考慮其他的,因為在切面裡都已經做好了,這樣程式碼的侵入性便降了很多。其實這和AOP的原理是一樣的。
jdk動態代理與mybatis
看到這裡,我便覺得mybatis和jdk動態代理必定有著很緊密的聯絡。
但我們通過上面的例子,我們知道,想要用jdk動態代理,必須要有個介面的實現類,否則代理介面便沒什麼意義。而mybatis只是介面+xml的形式,他是怎麼被代理的呢?
還有一個疑問,我們知道Spring的註解如果放在service的介面層,而不是放在實現類,他會找不到這個bean,那為什麼mapper層可以加註解呢?
首先我們通過查詢資料知道,mybatis確實是有jdk動態代理實現的。那我們帶著這個疑問,去看mybatis的原始碼。
為了便於檢視原始碼,我使用sqlsession的方式獲取mapper。
LRoleMapper lRoleMapper = sqlSessionFactory.openSession().getMapper(LRoleMapper.class);
lRoleMapper.selectAll();
點進getMapper的實現類
SqlSessionManager
發現這裡有sql的各個方法
Connection getConnection(){}
void commit() {}
rollback(){}
int insert(String statement){}
update(String statement){}
...
那我們在往下尋找,我看到了這個方法
private class SqlSessionInterceptor implements InvocationHandler {
public SqlSessionInterceptor() {
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = (SqlSession)SqlSessionManager.this.localSqlSession.get();
if (sqlSession != null) {
try {
return method.invoke(sqlSession, args);
} catch (Throwable var12) {
throw ExceptionUtil.unwrapThrowable(var12);
}
} else {
SqlSession autoSqlSession = SqlSessionManager.this.openSession();
Object var7;
try {
Object result = method.invoke(autoSqlSession, args);
autoSqlSession.commit();
var7 = result;
} catch (Throwable var13) {
autoSqlSession.rollback();
throw ExceptionUtil.unwrapThrowable(var13);
} finally {
autoSqlSession.close();
}
return var7;
}
}
}
看到這裡,是不是眼前一亮,那我們再看這個類的構造方法
private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionManager.SqlSessionInterceptor());
}
那看到這裡我們明白了,這個類代理的是SqlSessionFactory這個類,而它的切面邏輯,就是執行方法前,如果當前sqlsession存在sqlsession,就正常執行這個方法,如果不存在,就建立一個session,建立失敗再回滾資料。
那這裡的opensession,我直接貼原始碼了,無非就是從配置檔案讀取jdbc,連線驗證後,獲取會話之類,大家感興趣可以一層一層往下扒。
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
我們繼續回到getMapper方法,我們一層一層往下扒,最後我們可以看到這個類
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
再繼續扒newInstance方法
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.apache.ibatis.binding;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
import org.apache.ibatis.reflection.ExceptionUtil;
import org.apache.ibatis.session.SqlSession;
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
} else {
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
this.methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
那到這裡我們可以得出,mybatis中使用了大量的jdk動態代理。
這個getMapper呢,到這裡也就結束了,可以看到,SqlSession這個物件,代理的介面就是我們的mapper層dao,而這裡的切面邏輯,如果當前宣告的類是Object,直接執行方法,如果不是,執行另外的excute。這裡我沒有細看,初步理解是區分註解sql與mapper.xml 的sql。因為獲取mapper的sql是通過反射得到的Class類。有興趣的同學可以繼續扒,我精力有限就先到這兒了。
到此為止,第一個問題迎刃而解,總結來說,其實我們注入的mapper,是jdk動態代理產生的物件
。
那麼為什麼Mapper層加註解,spring也能獲取到呢。
其實這個問題已經和jdk動態代理沒什麼關係了,在這裡大概解釋一下。
mybatis並不是spring的產品,而作為第三方的外掛,我們都知道spring被稱作膠水框架,而第三方就需要將自己的產品讓spring管理。
換句話說,他們和spring自身的bean生命週期並不是同步的。
spring--------------------
class->掃描->新建例項->交給容器
mybatis ---------------------spring---
class -> 掃描-> 新建物件 -> 交給spring
同理,mybatis那就需要自己建立物件,把他交給spring。而我們平時的那些註解Mapperscan之類的,其實只是mybatis在標誌這些介面,使用反射,獲取這些類,實現一個ImportBeanDefinitionRegistrar介面,把自己產生的物件交給Spring。