Mybatis源碼解析 - mapper代理對象的生成,你有想過嗎
前言
開心一刻
本人幼教老師,冬天戴帽子進教室,被小朋友看到,這時候,有個小家夥對我說:老師你的帽子太醜,趕緊摘了吧。我逗他:那你好好學習,以後給老師買個漂亮的?這孩子想都沒想立刻回答:等我賺錢了,帶你去韓國整形
簡單示例
我們先來看一個純粹的mybatis示例(不集成spring等其他框架),代碼很簡單,結構如下
完整代碼地址:mybatis;mapper層和我們平時說的dao層指的是同一個內容,都是數據庫操作的封裝,但是在沒有集成mybatis時,dao層的接口都是需要我們手動去寫其實現類,可在上圖中我們卻發現:我們並沒有手動去實現PersonMapper接口,但工程卻能實實在在的查詢數據庫,獲取我們需要的數據,如下圖所示
從上圖我們發現,PersonMapper實例是一個代理對象,我們操作的其實是PersonMapper的代理實現;也就是說不用我們手動去實現PersonMapper接口,mybatis會動態生成PersonMapper的代理實例,然後由代理實例完成數據庫的操作
那麽問題來了,mybatis是何時、何地、如何生成mapper代理實例的呢?我們接著往下看
源碼分析
針對上述問題,我們來跟下mybatis源碼
SqlSessionFactory的創建
XMLConfigBuilder解析Mybatis配置文件(mybatis-config.xml),將配置文件中各個屬性解析到Configuration實例中,然後以Configuration實例構建SqlSessionFactory(實際是DefaultSqlSessionFactory);其中parseConfiguration方法是解析的具體過程,有興趣的可以更深一步的去探究
/** * root是以configuration標簽開始的文檔樹 * 解析配置文件中的各個標簽,並存放到Configuration實例對應的屬性中 * 解析完成之後,配置文件中的內容全部解析到了Configuration實例中 * @param root */ private void parseConfiguration(XNode root) { try { //issue #117 read properties first propertiesElement(root.evalNode("properties")); //View Code解析配置文件中的properties標簽 Properties settings = settingsAsProperties(root.evalNode("settings")); // 解析配置文件中的settings標簽 loadCustomVfs(settings); typeAliasesElement(root.evalNode("typeAliases")); // 解析配置文件中的typeAliases標簽 pluginElement(root.evalNode("plugins")); // 解析配置文件中的plugins標簽 objectFactoryElement(root.evalNode("objectFactory")); // 解析配置文件中的objectFactory標簽 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); // 解析配置文件中的objectWrapperFactory標簽 reflectorFactoryElement(root.evalNode("reflectorFactory")); // 解析配置文件中的reflectorFactory標簽 settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 environmentsElement(root.evalNode("environments")); // 解析配置文件中的environments標簽 databaseIdProviderElement(root.evalNode("databaseIdProvider")); // 解析配置文件中的databaseIdProvider標簽 typeHandlerElement(root.evalNode("typeHandlers")); // 解析配置文件中的typeHandlers標簽 mapperElement(root.evalNode("mappers")); // 解析配置文件中的mappers標簽 } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
上述代碼中的mapperElement(root.evalNode("mappers"));是不是很誘人?與我們的mapper有關系,是不是在這裏就生成了mapper的代理實例,還是只是讀取了mapper配置文件的內容?暫時還不敢肯定,那麽我們跟進去看看
其中有兩個方法值得重點關註下,具體如下,裏面的註釋可以重點看下,有興趣的可以更進一步的跟進去
public void parse() { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper")); // 解析映射文件Person.xml configuration.addLoadedResource(resource); bindMapperForNamespace(); // 將mapper與namespace綁定起來; 將PersonMapper接口與MapperProxyFactory關聯起來 } parsePendingResultMaps(); // 解析Configuration的incompleteResultMaps到Configuration的resultMaps parsePendingCacheRefs(); // 解析Configuration的incompleteCacheRefs到Configuration的cacheRefMap parsePendingStatements(); // 解析Configuration的incompleteStatements到Configuration的mappedStatements } /** * context是映射文件:Person.xml的文檔樹,以mapper標簽開始 * 解析映射文件中的各個標簽,並存放到MapperBuilderAssistant實例對應的屬性中 */ private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace"); // 解析mapper標簽的namespace屬性 if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper‘s namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); // namespace屬性值解析到Configuration的mapperRegistry中 cacheRefElement(context.evalNode("cache-ref")); // 解析cache-ref標簽到Configuration的cacheRefMap中 cacheElement(context.evalNode("cache")); // 解析cache標簽到Configuration的caches中 parameterMapElement(context.evalNodes("/mapper/parameterMap")); // 解析parameterMap標簽到Configuration的parameterMaps中 resultMapElements(context.evalNodes("/mapper/resultMap")); // 解析resultMap標簽到Configuration的resultMaps中 sqlElement(context.evalNodes("/mapper/sql")); // 解析sql標簽到XMLMapperBuilder的sqlFragments中 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); // 解析select|insert|update|delete標簽到Configuration的mappedStatements中 } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. The XML location is ‘" + resource + "‘. Cause: " + e, e); } }View Code
此時SqlSessionFactory已經創建,但PersonMapper的代理實例還沒有創建;期間準備了很多東西,包括讀取配置文件和映射文件的內容,並將其放置到Configuration實例的對應屬性中
SqlSession的創建
實例化了Transaction(JdbcTransaction)、Executor(SimpleExecutor)和SqlSession(DefaultSqlSession),此時mapper代理實例仍未被創建
Mapper代理對象的創建
可以看到,最終還是利用了JDK的動態代理
protected T newInstance(MapperProxy<T> mapperProxy) { // 利用JDK的動態代理生成mapper的代理實例 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }
生成了mapper的代理實例,後續就可以利用此代理實例進行數據庫的操作了
總結
1、我們用mytabis操作數據庫,有一個固定流程:先創建SqlSessionFactory,然後創建SqlSession,然後再創建獲取mapper代理對象,最後利用mapper代理對象完成數據庫的操作;一次數據庫操作完成後需要關閉SqlSession;
2、創建SqlSessionFactory實例的過程中,解析mybatis配置文件和映射文件,將內容都存放到Configuration實例的對應屬性中;創建SqlSession的過程中,有創建事務Transaction、執行器Executor,以及DefaultSqlSession;Mapper代理對象的創建,利用的是JDK的動態代理,InvocationHandler是MapperProxy,後續Mapper代理對象方法的執行都會先經過MapperProxy的invoke方法;
3、很多細節沒有講到,但大體流程就是這樣;另外提下,實際應用中,mybatis往往不會單獨使用,絕大多數都是集成在spring中;關於在spring的集成下,mapper代理對象的創建過程請期待我的下篇博文
Mybatis源碼解析 - mapper代理對象的生成,你有想過嗎