MyBatis源碼解讀(2)——MapperProxy

來源:互聯網
上載者:User

標籤:映射   param   繼承   參數傳遞   addm   調用   erp   配置   handle   

SqlSession可以說是整個MyBatis的重中之重,在SqlSession中涉及到前一篇四大對象:Executor、StatementHandler、ParameterHandler、ResultHandler,所以在此先只對SqlSession有一個大概的瞭解。

在代碼中我們可以看到當我們構造出一個SqlSession執行個體過後,可以通過SqlSession構造出Mappper映射器。UserMapper是一個介面,那麼我們可以肯定的是,它一定是用了Java的動態代理產生了一個代理類。

SqlSession sqlSession = SessionFactory.getSqlSession(resource); UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

接著上一篇講到通過DefaultSqlSessionFactory可以得到一個SqlSession的執行個體DefaultSqlSession。

通過原始碼可以發現,SqlSessionManger又實現了SqlSession,在上一節中可知SqlSessionManager同樣也繼承了SqlSessionFactory介面。我們把它們結合起來看看。

 

看來這個SqlSessionManager有點神奇,同時繼承SqlSession和SqlSessionFactory。從名字上來看好像是管理SqlSession的,這裡不討論,隨著原始碼的閱讀相信我們能逐步清晰,包括整個包的結構。

回到DefaultSqlSession中來。在SqlSession介面中提供了很多方法,用於我們的增刪改查,這在舊版的MyBatis或者iBatis中常常所使用的,我們現在大多直接使用xml設定檔以達到更加靈活的效果。所以我們將注意力放在getMapper方法上。

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

注意UserMapper僅僅是一個介面,這裡會涉及到Java的動態代理,所以得要有一定的基礎才能讀懂。

通過打斷點調試我們可以發現確實產生了一個叫MapperProxy的代理類。

下面是DefaultSqlSession的getMapper方法:

//org.apache.ibatis.session.default.DefaultSqlSession
public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this);}

看起來文法有點奇怪,這是一個泛型方法。看來是調用了Configuration的getMapper方法,還不是DefaultSqlSession實現了getMapper。接著再看Configuration的getMapper方法:

//org.apache.ibatis.session.Configuration
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession);}

 Configuration.getMapper一共兩個參數,一個是Class類型,一個是SqlSession,在DefaultSqlSession.getMapper調用Configuration.getMapper時,將傳遞進來的Class型別參數和其本身傳遞給了Configuration.getMapper。此時還不是在Configuration中實現了getMapper,看來還是一個叫做mapperRegistry的變數。

//org.apache.ibatis.session.Configuration
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

看著名字好像是註冊Mapper映射器的地方,想來也是,既然要得到Mapper的映射,那麼所有的Mapper都要一個地方去註冊(在我們的mybytis-config.xml裡),註冊好過後需要的時候再去尋找是否已經註冊,那麼就是MapperRegistry,所以取一個好的變數名是非常重要的。

 1 //org.apache.ibatis.binding.MapperRegistry 2 public <T> T getMapper(Class<T> type, SqlSession sqlSession) { 3   final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); 4   if (mapperProxyFactory == null) { 5     throw new BindingException("Type " + type + " is not known to the MapperRegistry."); 6   } 7   try { 8     return mapperProxyFactory.newInstance(sqlSession); 9   } catch (Exception e) {10     throw new BindingException("Error getting mapper instance. Cause: " + e, e);11   }12 }

在第3行代碼中試圖從一個叫knownMappers的變數取出MapperProxyFactory。這個knownMapper的定義:

private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

既然能用get方法取,那說明就有add方法咯?果不其然我們在MapperRegistry類中發現了public <T> void addMapper(Class<T> type)方法,那麼是在哪裡調用的這個方法呢?

我們來重新理一理。

使用MyBatis的第一步是配置mybatis-config.xml,配置好過後,mybatis-config跑起來的第一步也一定是首先解析xml設定檔,將解析好的設定檔各個配置參數放入Configuration對象中,包括Mapper的配置,所以應該是在解析xml檔案的某個類中解析過來後調用Configuration的方法將mapper放置到MapperRegister中。事實也的確如此,有興趣可以跟蹤下代碼看看。回到MapperRegistry.getMapper的方法中。

當我們一切正確時,我們就能擷取到一個MapperProxyFactory執行個體。想必MapperProxy代理類的產生正是通過MapperProxyFactory工廠類構建的,即第8行代碼。進入MapperProxyFactory類。

//org.apache.ibatis.binding.MapperProxyFactorypublic T newInstance(SqlSession sqlSession) {    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);    return newInstance(mapperProxy);}

在這裡終於看到了MapperProxy代理類,是通過sqlSession、mapperInterface、mechodCache三個參數構造的。

newInstance有一個重載方法:

//org.apache.ibatis.binding.MapperProxyFactoryprotected T newInstance(MapperProxy<T> mapperProxy) {    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);}

終於是走到頭了,這裡就是返回的一個代理類執行個體。最後來看看MapperProxy。

MapperProxy是一個重要的類,所以我們將其代碼全部貼出:

 1 //org.apache.ibatis.binding.MapperProxy 2 public class MapperProxy<T> implements InvocationHandler, Serializable { 3  4   private static final long serialVersionUID = -6424540398559729838L; 5   private final SqlSession sqlSession; 6   private final Class<T> mapperInterface; 7   private final Map<Method, MapperMethod> methodCache; 8  9   public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {10     this.sqlSession = sqlSession;11     this.mapperInterface = mapperInterface;12     this.methodCache = methodCache;13   }14 15   @Override16   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {17     if (Object.class.equals(method.getDeclaringClass())) {18       try {19         return method.invoke(this, args);20       } catch (Throwable t) {21         throw ExceptionUtil.unwrapThrowable(t);22       }23     }24     final MapperMethod mapperMethod = cachedMapperMethod(method);25     return mapperMethod.execute(sqlSession, args);26   }27 28   private MapperMethod cachedMapperMethod(Method method) {29     MapperMethod mapperMethod = methodCache.get(method);30     if (mapperMethod == null) {31       mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());32       methodCache.put(method, mapperMethod);33     }34     return mapperMethod;35   }36 37 }

要使用Java的動態代理就必須得實現InvocationHandler介面,在第17行代碼中首先判斷代理對象是一個介面還是一個類,顯然我們沒有對mapper介面進行任何實現,那麼它將產生一個MapperMethod對象,接著調用其execute方法,把sqlSession和參數傳遞進去。

 

MyBatis源碼解讀(2)——MapperProxy

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.