Mybaits 源碼解析 (三)—– Mapper介面底層原理(為什麼Mapper不用寫實現類就能訪問到資料庫?)

  • 2019 年 10 月 29 日
  • 筆記

上一篇我們講解到mapperElement方法用來解析mapper,我們這篇文章具體來看看mapper.xml的解析過程

mappers配置方式

mappers 標籤下有許多 mapper 標籤,每一個 mapper 標籤中配置的都是一個獨立的映射配置文件的路徑,配置方式有以下幾種。

介面資訊進行配置

<mappers>      <mapper class="org.mybatis.mappers.UserMapper"/>      <mapper class="org.mybatis.mappers.ProductMapper"/>      <mapper class="org.mybatis.mappers.ManagerMapper"/>  </mappers>

注意:這種方式必須保證介面名(例如UserMapper)和xml名(UserMapper.xml)相同,還必須在同一個包中。因為是通過獲取mapper中的class屬性,拼接上.xml來讀取UserMapper.xml,如果xml文件名不同或者不在同一個包中是無法讀取到xml的。

相對路徑進行配置

<mappers>      <mapper resource="org/mybatis/mappers/UserMapper.xml"/>      <mapper resource="org/mybatis/mappers/ProductMapper.xml"/>      <mapper resource="org/mybatis/mappers/ManagerMapper.xml"/>  </mappers>

注意:這種方式不用保證同介面同包同名。但是要保證xml中的namespase和對應的介面名相同。

絕對路徑進行配置

<mappers>      <mapper url="file:///var/mappers/UserMapper.xml"/>      <mapper url="file:///var/mappers/ProductMapper.xml"/>      <mapper url="file:///var/mappers/ManagerMapper.xml"/>  </mappers>

介面所在包進行配置

<mappers>      <package name="org.mybatis.mappers"/>  </mappers>

這種方式和第一種方式要求一致,保證介面名(例如UserMapper)和xml名(UserMapper.xml)相同,還必須在同一個包中。

注意:以上所有的配置都要保證xml中的namespase和對應的介面名相同。

我們以packae屬性為例詳細分析一下:

mappers解析入口方法

接上一篇文章最後部分,我們來看看mapperElement方法:

private void mapperElement(XNode parent) throws Exception {      if (parent != null) {          for (XNode child : parent.getChildren()) {              //包掃描的形式              if ("package".equals(child.getName())) {                  // 獲取 <package> 節點中的 name 屬性                  String mapperPackage = child.getStringAttribute("name");                  // 從指定包中查找 所有的 mapper 介面,並根據 mapper 介面解析映射配置                  configuration.addMappers(mapperPackage);              } else {                  // 獲取 resource/url/class 等屬性                  String resource = child.getStringAttribute("resource");                  String url = child.getStringAttribute("url");                  String mapperClass = child.getStringAttribute("class");                    // resource 不為空,且其他兩者為空,則從指定路徑中載入配置                  if (resource != null && url == null && mapperClass == null) {                      ErrorContext.instance().resource(resource);                      InputStream inputStream = Resources.getResourceAsStream(resource);                      XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());                      // 解析映射文件                      mapperParser.parse();                  // url 不為空,且其他兩者為空,則通過 url 載入配置                  } else if (resource == null && url != null && mapperClass == null) {                      ErrorContext.instance().resource(url);                      InputStream inputStream = Resources.getUrlAsStream(url);                      XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());                      // 解析映射文件                      mapperParser.parse();                  // mapperClass 不為空,且其他兩者為空,則通過 mapperClass 解析映射配置                  } else if (resource == null && url == null && mapperClass != null) {                      Class<?> mapperInterface = Resources.classForName(mapperClass);                      configuration.addMapper(mapperInterface);                  } else {                      throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");                  }              }          }      }  }

在 MyBatis 中,共有四種載入映射文件或資訊的方式。第一種是從文件系統中載入映射文件;第二種是通過 URL 的方式載入和解析映射文件;第三種是通過 mapper 介面載入映射資訊,映射資訊可以配置在註解中,也可以配置在映射文件中。最後一種是通過包掃描的方式獲取到某個包下的所有類,並使用第三種方式為每個類解析映射資訊。

我們先看下以packae掃描的形式,看下configuration.addMappers(mapperPackage)方法

public void addMappers(String packageName) {      mapperRegistry.addMappers(packageName);  }

我們看一下MapperRegistry的addMappers方法:

 1 public void addMappers(String packageName) {   2     //傳入包名和Object.class類型   3     this.addMappers(packageName, Object.class);   4 }   5   6 public void addMappers(String packageName, Class<?> superType) {   7     ResolverUtil<Class<?>> resolverUtil = new ResolverUtil();   8     /*   9      * 查找包下的父類為 Object.class 的類。  10      * 查找完成後,查找結果將會被快取到resolverUtil的內部集合中。上一篇文章我們已經看過這部分的源碼,不再累述了  11      */  12     resolverUtil.find(new IsA(superType), packageName);  13     // 獲取查找結果  14     Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();  15     Iterator i$ = mapperSet.iterator();  16  17     while(i$.hasNext()) {  18         Class<?> mapperClass = (Class)i$.next();  19         //我們具體看這個方法  20         this.addMapper(mapperClass);  21     }  22  23 }

其實就是通過 VFS(虛擬文件系統)獲取指定包下的所有文件的Class,也就是所有的Mapper介面,然後遍歷每個Mapper介面進行解析,接下來就和第一種配置方式(介面資訊進行配置)一樣的流程了,接下來我們來看看 基於 XML 的映射文件的解析過程,可以看到先創建一個XMLMapperBuilder,再調用其parse()方法:

 1 public void parse() {   2     // 檢測映射文件是否已經被解析過   3     if (!configuration.isResourceLoaded(resource)) {   4         // 解析 mapper 節點   5         configurationElement(parser.evalNode("/mapper"));   6         // 添加資源路徑到“已解析資源集合”中   7         configuration.addLoadedResource(resource);   8         // 通過命名空間綁定 Mapper 介面   9         bindMapperForNamespace();  10     }  11  12     parsePendingResultMaps();  13     parsePendingCacheRefs();  14     parsePendingStatements();  15 }

我們重點關注第5行和第9行的邏輯,也就是configurationElement和bindMapperForNamespace方法

解析映射文件

在 MyBatis 映射文件中,可以配置多種節點。比如 <cache>,<resultMap>,<sql> 以及 <select | insert | update | delete> 等。下面我們來看一個映射文件配置示例。

<?xml version="1.0" encoding="UTF-8"?>  <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">  <mapper namespace="mapper.EmployeeMapper">      <cache/>        <resultMap id="baseMap" type="entity.Employee">          <result property="id" column="id" jdbcType="INTEGER"></result>          <result property="name" column="name" jdbcType="VARCHAR"></result>      </resultMap>        <sql id="table">          employee      </sql>        <select id="getAll" resultMap="baseMap">          select * from  <include refid="table"/>  WHERE id = #{id}      </select>        <!-- <insert|update|delete/> -->  </mapper>

接著來看看configurationElement解析mapper.xml中的內容。

 1 private void configurationElement(XNode context) {   2     try {   3         // 獲取 mapper 命名空間,如 mapper.EmployeeMapper   4         String namespace = context.getStringAttribute("namespace");   5         if (namespace == null || namespace.equals("")) {   6             throw new BuilderException("Mapper's namespace cannot be empty");   7         }   8   9         // 設置命名空間到 builderAssistant 中  10         builderAssistant.setCurrentNamespace(namespace);  11  12         // 解析 <cache-ref> 節點  13         cacheRefElement(context.evalNode("cache-ref"));  14  15         // 解析 <cache> 節點  16         cacheElement(context.evalNode("cache"));  17  18         // 已廢棄配置,這裡不做分析  19         parameterMapElement(context.evalNodes("/mapper/parameterMap"));  20  21         // 解析 <resultMap> 節點  22         resultMapElements(context.evalNodes("/mapper/resultMap"));  23  24         // 解析 <sql> 節點  25         sqlElement(context.evalNodes("/mapper/sql"));  26  27         // 解析 <select>、<insert>、<update>、<delete> 節點  28         buildStatementFromContext(context.evalNodes("select|insert|update|delete"));  29     } catch (Exception e) {  30         throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);  31     }  32 }

接下來我們就對其中關鍵的方法進行詳細分析

解析 cache 節點

MyBatis 提供了一、二級快取,其中一級快取是 SqlSession 級別的,默認為開啟狀態。二級快取配置在映射文件中,使用者需要顯示配置才能開啟。如下:

<cache/>

也可以使用第三方快取

<cache type="org.mybatis.caches.redis.RedisCache"/>

其中有一些屬性可以選擇

<cache eviction="LRU"  flushInterval="60000"  size="512" readOnly="true"/>

  1. 根據數據的歷史訪問記錄來進行淘汰數據,其核心思想是“如果數據最近被訪問過,那麼將來被訪問的幾率也更高”
  2. 快取的容量為 512 個對象引用
  3. 快取每隔60秒刷新一次
  4. 快取返回的對象是寫安全的,即在外部修改對象不會影響到快取內部存儲對象

下面我們來分析一下快取配置的解析邏輯,如下:

private void cacheElement(XNode context) throws Exception {      if (context != null) {          // 獲取type屬性,如果type沒有指定就用默認的PERPETUAL(早已經註冊過的別名的PerpetualCache)          String type = context.getStringAttribute("type", "PERPETUAL");          // 根據type從早已經註冊的別名中獲取對應的Class,PERPETUAL對應的Class是PerpetualCache.class          // 如果我們寫了type屬性,如type="org.mybatis.caches.redis.RedisCache",這裡將會得到RedisCache.class          Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);          //獲取淘汰方式,默認為LRU(早已經註冊過的別名的LruCache),最近最少使用到的先淘汰          String eviction = context.getStringAttribute("eviction", "LRU");          Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);          Long flushInterval = context.getLongAttribute("flushInterval");          Integer size = context.getIntAttribute("size");          boolean readWrite = !context.getBooleanAttribute("readOnly", false);          boolean blocking = context.getBooleanAttribute("blocking", false);            // 獲取子節點配置          Properties props = context.getChildrenAsProperties();            // 構建快取對象          builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);      }  }    public <T> Class<T> resolveAlias(String string) {      try {          if (string == null) {              return null;          } else {              // 轉換成小寫              String key = string.toLowerCase(Locale.ENGLISH);              Class value;              // 如果沒有設置type屬性,則這裡傳過來的是PERPETUAL,能從別名快取中獲取到PerpetualCache.class              if (this.TYPE_ALIASES.containsKey(key)) {                  value = (Class)this.TYPE_ALIASES.get(key);              } else {                  //如果是設置了自定義的type,則在別名快取中是獲取不到的,直接通過類載入,載入自定義的type,如RedisCache.class                  value = Resources.classForName(string);              }                return value;          }      } catch (ClassNotFoundException var4) {          throw new TypeException("Could not resolve type alias '" + string + "'.  Cause: " + var4, var4);      }  }

快取的構建封裝在 BuilderAssistant 類的 useNewCache 方法中,我們來看看

public Cache useNewCache(Class<? extends Cache> typeClass,      Class<? extends Cache> evictionClass,Long flushInterval,      Integer size,boolean readWrite,boolean blocking,Properties props) {        // 使用建造模式構建快取實例      Cache cache = new CacheBuilder(currentNamespace)          .implementation(valueOrDefault(typeClass, PerpetualCache.class))          .addDecorator(valueOrDefault(evictionClass, LruCache.class))          .clearInterval(flushInterval)          .size(size)          .readWrite(readWrite)          .blocking(blocking)          .properties(props)          .build();        // 添加快取到 Configuration 對象中      configuration.addCache(cache);        // 設置 currentCache 屬性,即當前使用的快取      currentCache = cache;      return cache;  }

上面使用了建造模式構建 Cache 實例,我們跟下去看看。

public Cache build() {      // 設置默認的快取類型(PerpetualCache)和快取裝飾器(LruCache)      setDefaultImplementations();        // 通過反射創建快取      Cache cache = newBaseCacheInstance(implementation, id);      setCacheProperties(cache);      // 僅對內置快取 PerpetualCache 應用裝飾器      if (PerpetualCache.class.equals(cache.getClass())) {          // 遍歷裝飾器集合,應用裝飾器          for (Class<? extends Cache> decorator : decorators) {              // 通過反射創建裝飾器實例              cache = newCacheDecoratorInstance(decorator, cache);              // 設置屬性值到快取實例中              setCacheProperties(cache);          }          // 應用標準的裝飾器,比如 LoggingCache、SynchronizedCache          cache = setStandardDecorators(cache);      } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {          // 應用具有日誌功能的快取裝飾器          cache = new LoggingCache(cache);      }      return cache;  }    private void setDefaultImplementations() {      if (this.implementation == null) {          //設置默認快取類型為PerpetualCache          this.implementation = PerpetualCache.class;          if (this.decorators.isEmpty()) {              this.decorators.add(LruCache.class);          }      }  }    private Cache newBaseCacheInstance(Class<? extends Cache> cacheClass, String id) {      //獲取構造器      Constructor cacheConstructor = this.getBaseCacheConstructor(cacheClass);        try {          //通過構造器實例化Cache          return (Cache)cacheConstructor.newInstance(id);      } catch (Exception var5) {          throw new CacheException("Could not instantiate cache implementation (" + cacheClass + "). Cause: " + var5, var5);      }  }

如上就創建好了一個Cache的實例,然後把它添加到Configuration中,並且設置到currentCache屬性中,這個屬性後面還要使用,也就是Cache實例後面還要使用,我們後面再看。

解析 resultMap 節點

resultMap 主要用於映射結果。通過 resultMap 和自動映射,可以讓 MyBatis 幫助我們完成 ResultSet → Object 的映射。下面開始分析 resultMap 配置的解析過程。

private void resultMapElements(List<XNode> list) throws Exception {      // 遍歷 <resultMap> 節點列表      for (XNode resultMapNode : list) {          try {              // 解析 resultMap 節點              resultMapElement(resultMapNode);          } catch (IncompleteElementException e) {          }      }  }    private ResultMap resultMapElement(XNode resultMapNode) throws Exception {      return resultMapElement(resultMapNode, Collections.<ResultMapping>emptyList());  }    private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {      ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());        // 獲取 id 和 type 屬性      String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());      String type = resultMapNode.getStringAttribute("type",          resultMapNode.getStringAttribute("ofType",              resultMapNode.getStringAttribute("resultType",                  resultMapNode.getStringAttribute("javaType"))));      // 獲取 extends 和 autoMapping      String extend = resultMapNode.getStringAttribute("extends");      Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");        // 獲取 type 屬性對應的類型      Class<?> typeClass = resolveClass(type);      Discriminator discriminator = null;      //創建ResultMapping集合,對應resultMap子節點的id和result節點      List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();      resultMappings.addAll(additionalResultMappings);        // 獲取並遍歷 <resultMap> 的子節點列表      List<XNode> resultChildren = resultMapNode.getChildren();      for (XNode resultChild : resultChildren) {          if ("constructor".equals(resultChild.getName())) {              processConstructorElement(resultChild, typeClass, resultMappings);          } else if ("discriminator".equals(resultChild.getName())) {              discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);          } else {                List<ResultFlag> flags = new ArrayList<ResultFlag>();              if ("id".equals(resultChild.getName())) {                  // 添加 ID 到 flags 集合中                  flags.add(ResultFlag.ID);              }              // 解析 id 和 result 節點,將id或result節點生成相應的 ResultMapping,將ResultMapping添加到resultMappings集合中              resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));          }      }      //創建ResultMapResolver對象      ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend,          discriminator, resultMappings, autoMapping);      try {          // 根據前面獲取到的資訊構建 ResultMap 對象          return resultMapResolver.resolve();      } catch (IncompleteElementException e) {          configuration.addIncompleteResultMap(resultMapResolver);          throw e;      }  }

解析 id 和 result 節點

在 <resultMap> 節點中,子節點 <id> 和 <result> 都是常規配置,比較常見。我們來看看解析過程

private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {      String property;      // 根據節點類型獲取 name 或 property 屬性      if (flags.contains(ResultFlag.CONSTRUCTOR)) {          property = context.getStringAttribute("name");      } else {          property = context.getStringAttribute("property");      }        // 獲取其他各種屬性      String column = context.getStringAttribute("column");      String javaType = context.getStringAttribute("javaType");      String jdbcType = context.getStringAttribute("jdbcType");      String nestedSelect = context.getStringAttribute("select");        /*       * 解析 resultMap 屬性,該屬性出現在 <association> 和 <collection> 節點中。       * 若這兩個節點不包含 resultMap 屬性,則調用 processNestedResultMappings 方法,遞歸調用resultMapElement解析<association> 和 <collection>的嵌套節點,生成resultMap,並返回resultMap.getId();       * 如果包含resultMap屬性,則直接獲取其屬性值,這個屬性值對應一個resultMap節點       */      String nestedResultMap = context.getStringAttribute("resultMap", processNestedResultMappings(context, Collections.<ResultMapping>emptyList()));        String notNullColumn = context.getStringAttribute("notNullColumn");      String columnPrefix = context.getStringAttribute("columnPrefix");      String typeHandler = context.getStringAttribute("typeHandler");      String resultSet = context.getStringAttribute("resultSet");      String foreignColumn = context.getStringAttribute("foreignColumn");      boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));        Class<?> javaTypeClass = resolveClass(javaType);      Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);      JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);        // 構建 ResultMapping 對象      return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect,          nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);  }

看processNestedResultMappings解析<association> 和 <collection> 節點中的子節點,並返回ResultMap.id

private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings) throws Exception {      if (("association".equals(context.getName()) || "collection".equals(context.getName()) || "case".equals(context.getName())) && context.getStringAttribute("select") == null) {          ResultMap resultMap = this.resultMapElement(context, resultMappings);          return resultMap.getId();      } else {          return null;      }  }

下面以 <association> 節點為例,演示該節點的兩種配置方式,分別如下:

第一種配置方式是通過 resultMap 屬性引用其他的 <resultMap> 節點,配置如下:

<resultMap id="articleResult" type="Article">      <id property="id" column="id"/>      <result property="title" column="article_title"/>      <!-- 引用 authorResult -->      <association property="article_author" column="article_author_id" javaType="Author" resultMap="authorResult"/>  </resultMap>    <resultMap id="authorResult" type="Author">      <id property="id" column="author_id"/>      <result property="name" column="author_name"/>  </resultMap>

第二種配置方式是採取 resultMap 嵌套的方式進行配置,如下:

<resultMap id="articleResult" type="Article">      <id property="id" column="id"/>      <result property="title" column="article_title"/>      <!-- resultMap 嵌套 -->      <association property="article_author" javaType="Author">          <id property="id" column="author_id"/>          <result property="name" column="author_name"/>      </association>  </resultMap>

第二種配置,<association> 的子節點是一些結果映射配置,這些結果配置最終也會被解析成 ResultMap。

下面分析 ResultMapping 的構建過程。

public ResultMapping buildResultMapping(Class<?> resultType, String property, String column, Class<?> javaType,JdbcType jdbcType,      String nestedSelect, String nestedResultMap, String notNullColumn, String columnPrefix,Class<? extends TypeHandler<?>> typeHandler,      List<ResultFlag> flags, String resultSet, String foreignColumn, boolean lazy) {        // resultType:即 <resultMap type="xxx"/> 中的 type 屬性      // property:即 <result property="xxx"/> 中的 property 屬性      Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);        TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);        List<ResultMapping> composites = parseCompositeColumnName(column);        // 通過建造模式構建 ResultMapping      return new ResultMapping.Builder(configuration, property, column, javaTypeClass)          .jdbcType(jdbcType)          .nestedQueryId(applyCurrentNamespace(nestedSelect, true))          .nestedResultMapId(applyCurrentNamespace(nestedResultMap, true))          .resultSet(resultSet)          .typeHandler(typeHandlerInstance)          .flags(flags == null ? new ArrayList<ResultFlag>() : flags)          .composites(composites)          .notNullColumns(parseMultipleColumnNames(notNullColumn))          .columnPrefix(columnPrefix)          .foreignColumn(foreignColumn)          .lazy(lazy)          .build();  }    private Class<?> resolveResultJavaType(Class<?> resultType, String property, Class<?> javaType) {      if (javaType == null && property != null) {          try {              //獲取ResultMap中的type屬性的元類,如<resultMap id="user" type="java.model.User"/> 中User的元類              MetaClass metaResultType = MetaClass.forClass(resultType, this.configuration.getReflectorFactory());              //<result property="name" javaType="String"/>,如果result中沒有設置javaType,則獲取元類屬性對那個的類型              javaType = metaResultType.getSetterType(property);          } catch (Exception var5) {              ;          }      }        if (javaType == null) {          javaType = Object.class;      }        return javaType;  }    public ResultMapping build() {      resultMapping.flags = Collections.unmodifiableList(resultMapping.flags);      resultMapping.composites = Collections.unmodifiableList(resultMapping.composites);      resolveTypeHandler();      validate();      return resultMapping;  }

我們來看看ResultMapping類

public class ResultMapping {      private Configuration configuration;      private String property;      private String column;      private Class<?> javaType;      private JdbcType jdbcType;      private TypeHandler<?> typeHandler;      private String nestedResultMapId;      private String nestedQueryId;      private Set<String> notNullColumns;      private String columnPrefix;      private List<ResultFlag> flags;      private List<ResultMapping> composites;      private String resultSet;      private String foreignColumn;      private boolean lazy;        ResultMapping() {      }      //  }

ResultMapping就是和ResultMap中子節點id和result對應

<id column="wi_id" jdbcType="INTEGER"  property="id" />  <result column="warrant_no" jdbcType="String"  jdbcType="CHAR" property="warrantNo" />

ResultMap 對象構建

前面的分析我們知道了<id>,<result> 等節點最終都被解析成了 ResultMapping。並且封裝到了resultMappings集合中,緊接著要做的事情是構建 ResultMap,關鍵程式碼在resultMapResolver.resolve():

public ResultMap resolve() {      return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);  }    public ResultMap addResultMap(      String id, Class<?> type, String extend, Discriminator discriminator,      List<ResultMapping> resultMappings, Boolean autoMapping) {        // 為 ResultMap 的 id 和 extend 屬性值拼接命名空間      id = applyCurrentNamespace(id, false);      extend = applyCurrentNamespace(extend, true);        if (extend != null) {          if (!configuration.hasResultMap(extend)) {              throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");          }          ResultMap resultMap = configuration.getResultMap(extend);          List<ResultMapping> extendedResultMappings = new ArrayList<ResultMapping>(resultMap.getResultMappings());          extendedResultMappings.removeAll(resultMappings);            boolean declaresConstructor = false;          for (ResultMapping resultMapping : resultMappings) {              if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {                  declaresConstructor = true;                  break;              }          }            if (declaresConstructor) {              Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();              while (extendedResultMappingsIter.hasNext()) {                  if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {                      extendedResultMappingsIter.remove();                  }              }          }          resultMappings.addAll(extendedResultMappings);      }        // 構建 ResultMap      ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)          .discriminator(discriminator)          .build();      // 將創建好的ResultMap加入configuration中      configuration.addResultMap(resultMap);      return resultMap;  }

我們先看看ResultMap

public class ResultMap {      private String id;      private Class<?> type;      private List<ResultMapping> resultMappings;      //用於存儲 <id> 節點對應的 ResultMapping 對象      private List<ResultMapping> idResultMappings;      private List<ResultMapping> constructorResultMappings;      //用於存儲 <id> 和 <result> 節點對應的 ResultMapping 對象      private List<ResultMapping> propertyResultMappings;      //用於存儲 所有<id>、<result> 節點 column 屬性      private Set<String> mappedColumns;      private Discriminator discriminator;      private boolean hasNestedResultMaps;      private boolean hasNestedQueries;      private Boolean autoMapping;        private ResultMap() {      }      //  }

再來看看通過建造模式構建 ResultMap 實例

public ResultMap build() {      if (resultMap.id == null) {          throw new IllegalArgumentException("ResultMaps must have an id");      }      resultMap.mappedColumns = new HashSet<String>();      resultMap.mappedProperties = new HashSet<String>();      resultMap.idResultMappings = new ArrayList<ResultMapping>();      resultMap.constructorResultMappings = new ArrayList<ResultMapping>();      resultMap.propertyResultMappings = new ArrayList<ResultMapping>();      final List<String> constructorArgNames = new ArrayList<String>();        for (ResultMapping resultMapping : resultMap.resultMappings) {          /*           * 檢測 <association> 或 <collection> 節點           * 是否包含 select 和 resultMap 屬性           */          resultMap.hasNestedQueries = resultMap.hasNestedQueries || resultMapping.getNestedQueryId() != null;          resultMap.hasNestedResultMaps =              resultMap.hasNestedResultMaps || (resultMapping.getNestedResultMapId() != null && resultMapping.getResultSet() == null);            final String column = resultMapping.getColumn();          if (column != null) {              // 將 colum 轉換成大寫,並添加到 mappedColumns 集合中              resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH));          } else if (resultMapping.isCompositeResult()) {              for (ResultMapping compositeResultMapping : resultMapping.getComposites()) {                  final String compositeColumn = compositeResultMapping.getColumn();                  if (compositeColumn != null) {                      resultMap.mappedColumns.add(compositeColumn.toUpperCase(Locale.ENGLISH));                  }              }          }            // 添加屬性 property 到 mappedProperties 集合中          final String property = resultMapping.getProperty();          if (property != null) {              resultMap.mappedProperties.add(property);          }            if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {              resultMap.constructorResultMappings.add(resultMapping);              if (resultMapping.getProperty() != null) {                  constructorArgNames.add(resultMapping.getProperty());              }          } else {              // 添加 resultMapping 到 propertyResultMappings 中              resultMap.propertyResultMappings.add(resultMapping);          }            if (resultMapping.getFlags().contains(ResultFlag.ID)) {              // 添加 resultMapping 到 idResultMappings 中              resultMap.idResultMappings.add(resultMapping);          }      }      if (resultMap.idResultMappings.isEmpty()) {          resultMap.idResultMappings.addAll(resultMap.resultMappings);      }      if (!constructorArgNames.isEmpty()) {          final List<String> actualArgNames = argNamesOfMatchingConstructor(constructorArgNames);          if (actualArgNames == null) {              throw new BuilderException("Error in result map '" + resultMap.id                  + "'. Failed to find a constructor in '"                  + resultMap.getType().getName() + "' by arg names " + constructorArgNames                  + ". There might be more info in debug log.");          }          Collections.sort(resultMap.constructorResultMappings, new Comparator<ResultMapping>() {              @Override              public int compare(ResultMapping o1, ResultMapping o2) {                  int paramIdx1 = actualArgNames.indexOf(o1.getProperty());                  int paramIdx2 = actualArgNames.indexOf(o2.getProperty());                  return paramIdx1 - paramIdx2;              }          });      }        // 將以下這些集合變為不可修改集合      resultMap.resultMappings = Collections.unmodifiableList(resultMap.resultMappings);      resultMap.idResultMappings = Collections.unmodifiableList(resultMap.idResultMappings);      resultMap.constructorResultMappings = Collections.unmodifiableList(resultMap.constructorResultMappings);      resultMap.propertyResultMappings = Collections.unmodifiableList(resultMap.propertyResultMappings);      resultMap.mappedColumns = Collections.unmodifiableSet(resultMap.mappedColumns);      return resultMap;  }

主要做的事情就是將 ResultMapping 實例及屬性分別存儲到不同的集合中。

解析 sql 節點

<sql> 節點用來定義一些可重用的 SQL 語句片段,比如表名,或表的列名等。在映射文件中,我們可以通過 <include> 節點引用 <sql> 節點定義的內容。

<sql id="table">      user  </sql>    <select id="findOne" resultType="Article">      SELECT * FROM <include refid="table"/> WHERE id = #{id}  </select>

下面分析一下 sql 節點的解析過程,如下:

private void sqlElement(List<XNode> list) throws Exception {      if (configuration.getDatabaseId() != null) {          // 調用 sqlElement 解析 <sql> 節點          sqlElement(list, configuration.getDatabaseId());      }        // 再次調用 sqlElement,不同的是,這次調用,該方法的第二個參數為 null      sqlElement(list, null);  }    private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {      for (XNode context : list) {          // 獲取 id 和 databaseId 屬性          String databaseId = context.getStringAttribute("databaseId");          String id = context.getStringAttribute("id");            // id = currentNamespace + "." + id          id = builderAssistant.applyCurrentNamespace(id, false);            // 檢測當前 databaseId 和 requiredDatabaseId 是否一致          if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {              // 將 <id, XNode> 鍵值對快取到XMLMapperBuilder對象的 sqlFragments 屬性中,以供後面的sql語句使用              sqlFragments.put(id, context);          }      }  }

解析select|insert|update|delete節點

 <select>、<insert>、<update> 以及 <delete> 等節點統稱為 SQL 語句節點,其解析過程在buildStatementFromContext方法中:

private void buildStatementFromContext(List<XNode> list) {      if (configuration.getDatabaseId() != null) {          // 調用重載方法構建 Statement          buildStatementFromContext(list, configuration.getDatabaseId());      }      buildStatementFromContext(list, null);  }    private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {      for (XNode context : list) {          // 創建 XMLStatementBuilder 建造類          final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);          try {              /*               * 解析sql節點,將其封裝到 Statement 對象中,並將解析結果存儲到 configuration 的 mappedStatements 集合中               */              statementParser.parseStatementNode();          } catch (IncompleteElementException e) {              configuration.addIncompleteStatement(statementParser);          }      }  }

我們繼續看 statementParser.parseStatementNode();

public void parseStatementNode() {      // 獲取 id 和 databaseId 屬性      String id = context.getStringAttribute("id");      String databaseId = context.getStringAttribute("databaseId");        if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {          return;      }        // 獲取各種屬性      Integer fetchSize = context.getIntAttribute("fetchSize");      Integer timeout = context.getIntAttribute("timeout");      String parameterMap = context.getStringAttribute("parameterMap");      String parameterType = context.getStringAttribute("parameterType");      Class<?> parameterTypeClass = resolveClass(parameterType);      String resultMap = context.getStringAttribute("resultMap");      String resultType = context.getStringAttribute("resultType");      String lang = context.getStringAttribute("lang");      LanguageDriver langDriver = getLanguageDriver(lang);        // 通過別名解析 resultType 對應的類型      Class<?> resultTypeClass = resolveClass(resultType);      String resultSetType = context.getStringAttribute("resultSetType");        // 解析 Statement 類型,默認為 PREPARED      StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));        // 解析 ResultSetType      ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);        // 獲取節點的名稱,比如 <select> 節點名稱為 select      String nodeName = context.getNode().getNodeName();      // 根據節點名稱解析 SqlCommandType      SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;      boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);      boolean useCache = context.getBooleanAttribute("useCache", isSelect);      boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);        // 解析 <include> 節點      XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);      includeParser.applyIncludes(context.getNode());        processSelectKeyNodes(id, parameterTypeClass, langDriver);        // 解析 SQL 語句      SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);      String resultSets = context.getStringAttribute("resultSets");      String keyProperty = context.getStringAttribute("keyProperty");      String keyColumn = context.getStringAttribute("keyColumn");        KeyGenerator keyGenerator;      String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;      keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);      if (configuration.hasKeyGenerator(keyStatementId)) {          keyGenerator = configuration.getKeyGenerator(keyStatementId);      } else {          keyGenerator = context.getBooleanAttribute("useGeneratedKeys",              configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;      }        /*       * 構建 MappedStatement 對象,並將該對象存儲到 Configuration 的 mappedStatements 集合中       */      builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,          fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,          resultSetTypeEnum, flushCache, useCache, resultOrdered,          keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);  }

我們主要來分析下面幾個重要的方法:

  1. 解析 <include> 節點
  2. 解析 SQL,獲取 SqlSource
  3. 構建 MappedStatement 實例

解析 <include> 節點

先來看一個include的例子

<mapper namespace="java.mybaits.dao.UserMapper">      <sql id="table">          user      </sql>        <select id="findOne" resultType="User">          SELECT  * FROM  <include refid="table"/> WHERE id = #{id}      </select>  </mapper>

<include> 節點的解析邏輯封裝在 applyIncludes 中,該方法的程式碼如下:

public void applyIncludes(Node source) {      Properties variablesContext = new Properties();      Properties configurationVariables = configuration.getVariables();      if (configurationVariables != null) {          // 將 configurationVariables 中的數據添加到 variablesContext 中          variablesContext.putAll(configurationVariables);      }        // 調用重載方法處理 <include> 節點      applyIncludes(source, variablesContext, false);  }

繼續看 applyIncludes 方法

private void applyIncludes(Node source, final Properties variablesContext, boolean included) {        // 第一個條件分支      if (source.getNodeName().equals("include")) {            //獲取 <sql> 節點。          Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext);            Properties toIncludeContext = getVariablesContext(source, variablesContext);            applyIncludes(toInclude, toIncludeContext, true);            if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {              toInclude = source.getOwnerDocument().importNode(toInclude, true);          }          // 將 <select>節點中的 <include> 節點替換為 <sql> 節點          source.getParentNode().replaceChild(toInclude, source);          while (toInclude.hasChildNodes()) {              // 將 <sql> 中的內容插入到 <sql> 節點之前              toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);          }            /*           * 前面已經將 <sql> 節點的內容插入到 dom 中了,           * 現在不需要 <sql> 節點了,這裡將該節點從 dom 中移除           */          toInclude.getParentNode().removeChild(toInclude);        // 第二個條件分支      } else if (source.getNodeType() == Node.ELEMENT_NODE) {          if (included && !variablesContext.isEmpty()) {              NamedNodeMap attributes = source.getAttributes();              for (int i = 0; i < attributes.getLength(); i++) {                  Node attr = attributes.item(i);                  // 將 source 節點屬性中的佔位符 ${} 替換成具體的屬性值                  attr.setNodeValue(PropertyParser.parse(attr.getNodeValue(), variablesContext));              }          }            NodeList children = source.getChildNodes();          for (int i = 0; i < children.getLength(); i++) {              // 遞歸調用              applyIncludes(children.item(i), variablesContext, included);          }        // 第三個條件分支      } else if (included && source.getNodeType() == Node.TEXT_NODE && !variablesContext.isEmpty()) {          // 將文本(text)節點中的屬性佔位符 ${} 替換成具體的屬性值          source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));      }  }

我們先來看一下 applyIncludes 方法第一次被調用時的狀態,source為<select> 節點,節點類型:ELEMENT_NODE,此時會進入第二個分支,獲取到獲取 <select> 子節點列表,遍歷子節點列表,將子節點作為參數,進行遞歸調用applyIncludes ,此時可獲取到的子節點如下:

編號 子節點 類型 描述
1 SELECT * FROM TEXT_NODE 文本節點
2 <include refid=”table”/> ELEMENT_NODE 普通節點
3 WHERE id = #{id} TEXT_NODE 文本節點

接下來要做的事情是遍歷列表,然後將子節點作為參數進行遞歸調用。第一個子節點調用applyIncludes方法,source為 SELECT * FROM 節點,節點類型:TEXT_NODE,進入分支三,沒有${},不會替換,則節點一結束返回,什麼都沒有做。第二個節點調用applyIncludes方法,此時source為 <include refid=”table”/>節點,節點類型:ELEMENT_NODE,進入分支一,通過refid找到 sql 節點,也就是toInclude節點,然後執行source.getParentNode().replaceChild(toInclude, source);,直接將<include refid=”table”/>節點的父節點,也就是<select> 節點中的當前<include >節點替換成 <sql> 節點,然後調用toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);,將 <sql> 中的內容插入到 <sql> 節點之前,也就是將user插入到 <sql> 節點之前,現在不需要 <sql> 節點了,最後將該節點從 dom 中移除

創建SqlSource

創建SqlSource在createSqlSource方法中

public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {      XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);      return builder.parseScriptNode();  }    // -☆- XMLScriptBuilder  public SqlSource parseScriptNode() {      // 解析 SQL 語句節點      MixedSqlNode rootSqlNode = parseDynamicTags(context);      SqlSource sqlSource = null;      // 根據 isDynamic 狀態創建不同的 SqlSource      if (isDynamic) {          sqlSource = new DynamicSqlSource(configuration, rootSqlNode);      } else {          sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);      }      return sqlSource;  }

繼續跟進parseDynamicTags

/** 該方法用於初始化 nodeHandlerMap 集合,該集合後面會用到 */  private void initNodeHandlerMap() {      nodeHandlerMap.put("trim", new TrimHandler());      nodeHandlerMap.put("where", new WhereHandler());      nodeHandlerMap.put("set", new SetHandler());      nodeHandlerMap.put("foreach", new ForEachHandler());      nodeHandlerMap.put("if", new IfHandler());      nodeHandlerMap.put("choose", new ChooseHandler());      nodeHandlerMap.put("when", new IfHandler());      nodeHandlerMap.put("otherwise", new OtherwiseHandler());      nodeHandlerMap.put("bind", new BindHandler());  }    protected MixedSqlNode parseDynamicTags(XNode node) {      List<SqlNode> contents = new ArrayList<SqlNode>();      NodeList children = node.getNode().getChildNodes();      // 遍歷子節點      for (int i = 0; i < children.getLength(); i++) {          XNode child = node.newXNode(children.item(i));          //如果節點是TEXT_NODE類型          if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {              // 獲取文本內容              String data = child.getStringBody("");              TextSqlNode textSqlNode = new TextSqlNode(data);              // 若文本中包含 ${} 佔位符,會被認為是動態節點              if (textSqlNode.isDynamic()) {                  contents.add(textSqlNode);                  // 設置 isDynamic 為 true                  isDynamic = true;              } else {                  // 創建 StaticTextSqlNode                  contents.add(new StaticTextSqlNode(data));              }            // child 節點是 ELEMENT_NODE 類型,比如 <if>、<where> 等          } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) {              // 獲取節點名稱,比如 if、where、trim 等              String nodeName = child.getNode().getNodeName();              // 根據節點名稱獲取 NodeHandler,也就是上面註冊的nodeHandlerMap              NodeHandler handler = nodeHandlerMap.get(nodeName);              if (handler == null) {                  throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");              }              // 處理 child 節點,生成相應的 SqlNode              handler.handleNode(child, contents);                // 設置 isDynamic 為 true              isDynamic = true;          }      }      return new MixedSqlNode(contents);  }

對於if、trim、where等這些動態節點,是通過對應的handler來解析的,如下

handler.handleNode(child, contents);

該程式碼用於處理動態 SQL 節點,並生成相應的 SqlNode。下面來簡單分析一下 WhereHandler 的程式碼。

/** 定義在 XMLScriptBuilder 中 */  private class WhereHandler implements NodeHandler {        public WhereHandler() {      }        @Override      public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {          // 調用 parseDynamicTags 解析 <where> 節點          MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);          // 創建 WhereSqlNode          WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);          // 添加到 targetContents          targetContents.add(where);      }  }

我們已經將 XML 配置解析了 SqlSource,下面我們看看MappedStatement的構建。

構建MappedStatement

SQL 語句節點可以定義很多屬性,這些屬性和屬性值最終存儲在 MappedStatement 中。

public MappedStatement addMappedStatement(      String id, SqlSource sqlSource, StatementType statementType,      SqlCommandType sqlCommandType,Integer fetchSize, Integer timeout,      String parameterMap, Class<?> parameterType,String resultMap,      Class<?> resultType, ResultSetType resultSetType, boolean flushCache,      boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator,      String keyProperty,String keyColumn, String databaseId,      LanguageDriver lang, String resultSets) {        if (unresolvedCacheRef) {          throw new IncompleteElementException("Cache-ref not yet resolved");      }    // 拼接上命名空間,如 <select id="findOne" resultType="User">,則id=java.mybaits.dao.UserMapper.findOne      id = applyCurrentNamespace(id, false);      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;        // 創建建造器,設置各種屬性      MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)          .resource(resource).fetchSize(fetchSize).timeout(timeout)          .statementType(statementType).keyGenerator(keyGenerator)          .keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId)          .lang(lang).resultOrdered(resultOrdered).resultSets(resultSets)          .resultMaps(getStatementResultMaps(resultMap, resultType, id))          .flushCacheRequired(valueOrDefault(flushCache, !isSelect))          .resultSetType(resultSetType).useCache(valueOrDefault(useCache, isSelect))          .cache(currentCache);//這裡用到了前面解析<cache>節點時創建的Cache對象,設置到MappedStatement對象裡面的cache屬性中        // 獲取或創建 ParameterMap      ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);      if (statementParameterMap != null) {          statementBuilder.parameterMap(statementParameterMap);      }        // 構建 MappedStatement      MappedStatement statement = statementBuilder.build();      // 添加 MappedStatement 到 configuration 的 mappedStatements 集合中      // 通過UserMapper代理對象調用findOne方法時,就可以拼接UserMapper介面名java.mybaits.dao.UserMapper和findOne方法找到id=java.mybaits.dao.UserMapper的MappedStatement,然後執行對應的sql語句      configuration.addMappedStatement(statement);      return statement;  }

這裡我們要注意,MappedStatement對象中有一個cache屬性,將前面解析<cache>節點時創建的Cache對象,設置到MappedStatement對象裡面的cache屬性中,以備後面二級快取使用,我們後面專門來講這一塊。

Mapper 介面綁定

映射文件解析完成後,我們需要通過命名空間將綁定 mapper 介面,看看具體綁定的啥

private void bindMapperForNamespace() {      // 獲取映射文件的命名空間      String namespace = builderAssistant.getCurrentNamespace();      if (namespace != null) {          Class<?> boundType = null;          try {              // 根據命名空間解析 mapper 類型              boundType = Resources.classForName(namespace);          } catch (ClassNotFoundException e) {          }          if (boundType != null) {              // 檢測當前 mapper 類是否被綁定過              if (!configuration.hasMapper(boundType)) {                  configuration.addLoadedResource("namespace:" + namespace);                  // 綁定 mapper 類                  configuration.addMapper(boundType);              }          }      }  }    // Configuration  public <T> void addMapper(Class<T> type) {      // 通過 MapperRegistry 綁定 mapper 類      mapperRegistry.addMapper(type);  }    // MapperRegistry  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 {              /*               * 將 type 和 MapperProxyFactory 進行綁定,MapperProxyFactory 可為 mapper 介面生成代理類               */              knownMappers.put(type, new MapperProxyFactory<T>(type));                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);              // 解析註解中的資訊              parser.parse();              loadCompleted = true;          } finally {              if (!loadCompleted) {                  knownMappers.remove(type);              }          }      }  }

其實就是獲取當前映射文件的命名空間,並獲取其Class,也就是獲取每個Mapper介面,然後為每個Mapper介面創建一個代理類工廠,new MapperProxyFactory<T>(type),並放進 knownMappers 這個HashMap中,我們來看看這個MapperProxyFactory

public class MapperProxyFactory<T> {      //存放Mapper介面Class      private final Class<T> mapperInterface;      private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();        public MapperProxyFactory(Class<T> mapperInterface) {          this.mapperInterface = mapperInterface;      }        public Class<T> getMapperInterface() {          return this.mapperInterface;      }        public Map<Method, MapperMethod> getMethodCache() {          return this.methodCache;      }        protected T newInstance(MapperProxy<T> mapperProxy) {          //生成mapperInterface的代理類          return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);      }        public T newInstance(SqlSession sqlSession) {          MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);          return this.newInstance(mapperProxy);      }  }

這一塊我們後面文章再來看是如何調用的。