Mybatis整體框架和運行原理圖
JDK動態代理:
代理是一種模式,提供了對目標對象的間接訪問方式,即通過代理訪問目標對象。如此便於在目標實現的基礎上增加額外的功能操作,前攔截,後攔截等,以滿足自身的業務需求,同時代理模式便於擴展目標對象功能的特點也爲多人所用。
JDK動態代理
定義接口
public interface Subject {
int add(int x, int y);
}
實現類
public class RealSubject implements Subject {
@Override
public int add(int x, int y) {
System.out.println("計算兩個數的和,結果爲:" + (x + y));
return x + y;
}
}
定義代理類,實現動態代理接口
public class MyProxy implements InvocationHandler{
private Object targer;
public MyProxy(Object targer) {
this.targer = targer;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before method do something");
Object result = method.invoke(targer, args);
System.out.println("after method do something");
return result;
}
}
獲取代理類
public class Client {
public static void main(String[] args) {
Subject target = new RealSubject();
MyProxy h = new MyProxy(target);
Subject proxy = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(),
new Class[]{Subject.class}, h);
int result = proxy.add(1,2);
}
}
簡單三步即可獲取到代理對象,調用代理對象的任何方法都會執行 invoke 方法。
Mybatis動態代理源碼分析:
從以上代碼可以看出,JDK 動態代理只能代理接口,而且還需要定義實現類,那 mybatis 是如何做到不需要實現類就輕鬆獲取到代理對象的呢?(Mybatis 之所以不用我們自己實現接口的實例,根本原因就在於它採用了反射機制以及動態代理來代理我們的接口。)
mybatis中的getMapper方法的實現方式理解爲動態代理方法的實現方式理解爲動態代理
mybatis中的getMapper的調用爲:sqlSession.getMapper(OrdersMapper1.class);
mapper是如何添加進去的呢?
DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(OrdersMapper1.class);// 添加Mapper接口
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
等價於
String resource = "mybatis-config.xml"; // xml內容就不貼了
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
我們後去sqlSessionFactory採用以上方式
分析上面代碼生成sqlSessionFactory過程有這麼一行:
sqlSession類:
configuration.addMapper(OrdersMapper1.class);// 添加Mapper接口
addMapper 方法的代碼實現
Configuration類:
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
mapper 實際上被添加到 mapperRegissry 中。繼續跟進代碼:
MapperRegistry類:
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {// 只添加接口
if (hasMapper(type)) {// 不允許重複添加
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(type));// 注意這裏
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
所執行的configuration.addMapper(BlogMapper.class); 其實最終被放到了HashMap中,其名爲knownMappers ,knowMappers是MapperRegistry 類的一個私有屬性,它是一個HashMap 。其Key 爲當前Class對象,value 爲一個MapperProxyFactory 實例。
總結一下: 諸如OrdersMapper1之類的Mapper接口被添加到了MapperRegistry 中的一個HashMap中。並以 Mapper 接口的 Class 對象作爲 Key , 以一個攜帶Mapper接口作爲屬性的MapperProxyFactory 實例作爲value 。MapperProxyFacory從名字來看,好像是一個工廠,用來創建Mapper Proxy的工廠。
從源碼上我們可以看到getMapper方法會去調用Configuration類的getMapper方法。
DefaultSqlSession類:
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
Configuration類裏面存放了所有關於XML文件的配置信息。從參數上我們可以看到他要我們傳入一個Class類型。這已經可以看到後面一定要用到反射機制和動態生成相應的類實例
Configuration類:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
代碼所示,這裏的 getMapper 調用了 configuration.getMapper , 這一步操作其實最終是調用了MapperRegistry,而此前我們已經知道,MapperRegistry是存放了一個HashMap的
MapperRegistry類:
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession); //重點看這裏
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
我們調用的session.getMapper(OrdersMapper1.class);最終會到達上面這個方法,這個方法根據OrdersMapper1的class對象,以它爲key在knowMappers 中找到了對應的value —— MapperProxyFactory(BlogMapper) 對象,然後調用這個對象的newInstance()方法。根據這個名字,我們就能猜到這個方法是創建了一個對象
public class MapperProxyFactory<T> { //映射器代理工廠
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
//刪除不必要代碼
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
//使用了JDK自帶的動態代理生成映射器代理類的對象
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
最終是通過Proxy.newProxyInstance產生了一個OrdersMapper1的代理對象。Mybatis 爲了完成 Mapper 接口的實現,運用了代理模式。具體是使用了JDK動態代理,這個Proxy.newProxyInstance方法生成代理類的三個要素是:
ClassLoader —— 指定當前接口的加載器即可
OrdersMapper1 —— 這當前被代理的接口是什麼
MapperProxy——代理類是什麼
代理模式中,代理類(MapperProxy)中才真正的完成了方法調用的邏輯。我們貼出MapperProxy的代碼
public class MapperProxy<T> implements InvocationHandler, Serializable {
//實現了InvocationHandler接口
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
//代理以後,所有Mapper的方法調用時,都會調用這個invoke方法
try {
return method.invoke(this, args);// 注意1
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method); // 使用了緩存
//執行CURD
return mapperMethod.execute(sqlSession, args);// 注意2
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
Blog blog =mapper.getOrdersById(3); 實際上最後是會調用這個MapperProxy的invoke方法。這段代碼中,if 語句先判斷,我們想要調用的方法是否來自Object類,這裏的意思就是,如果我們調用toString()方法,那麼是不需要做代理增強的,直接還調用原來的method.invoke()就行了。只有調用getOrdersById之類的方法的時候,才執行增強的調用——即mapperMethod.execute(sqlSession, args);
而mapperMethod.execute(sqlSession, args);這句最終就會執行增刪改查了
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
}
// 刪除部分代碼
}
// 刪除部分代碼
return result;
}
我們分析了 Mapper接口是如何註冊的,Mapper接口是如何產生動態代理對象的,Maper接口方法最終是如何執行的。總結起來主要就是這幾個點:
- Mapper 接口在初始SqlSessionFactory 註冊的。
- Mapper 接口註冊在了名爲 MapperRegistry 類的 HashMap中, key = Mapper class value = 創建當前Mapper的工廠。
- Mapper 註冊之後,可以從SqlSession中get
- SqlSession.getMapper 運用了 JDK動態代理,產生了目標Mapper接口的代理對象。
- 動態代理的 代理類是 MapperProxy ,這裏邊最終完成了增刪改查方法的調用。