mybatis源碼分析-資源加載-上篇

資源加載

mybatis資源加載的過程主要是處理xml里面的三種類型的標簽<configuration><mapper><select|insert|update|delete>這三種標簽分別對應三個處理類:XMLConfigurationBuilder急前;XMLMapperBuilder咏花;XMLStatementBuilder蹄衷。這三個類解析完數(shù)據(jù)之后會將所有的數(shù)據(jù)都放到configuration對象中塘幅。方便后面getMapper皿伺,和執(zhí)行sql使用雁芙。在解析過程中也會將各種標簽肥照,屬性創(chuàng)建合理的對象來與之對應起來鸠信。

  • Properties 在configuration對象中保存配置文件中<properties>的值。在propertiesElement這個方法中被解析
  • TypeHandlerRegistry 類型處理器注冊的類攒岛,用來處理查詢到的結果中jdbc類型和java類型不匹配進行映射赖临。默認情況下Mybatis已經(jīng)提供了許多類型匹配處理器
  • TypeAliasRegistry 別名處理注冊的類,用來收集別名與class對應的映射灾锯,默認Mybatis已經(jīng)提供了許多類型映射例如list表示java.util.List

處理configuration標簽

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

看一下SqlSessionFactoryBuilder類代碼兢榨,可以知道沒有構造方法直接采用默認的構造方法,然后使用build方法創(chuàng)建SqlSessionFactory對象顺饮。這個類采用了build模式吵聪,仿照了spring里面實例ClassPathXmlApplicationContext類的方式。多個方法最終都是調(diào)用一個方法類實例兼雄,如果沒有傳參吟逝,則采用默認方式。

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
  
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

先看創(chuàng)建XMLConfigBuilder對象的過程

  public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
      //XMLConfigBuilder對象持有XPathParser對象
      //XPathParser對象持有Document對象
      //Document對象持有inputStream對象解析出來的xml文檔的完整數(shù)據(jù)赦肋。解析方式采用JDK中的javax.xml.parsers
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }

繼續(xù)看一下實例化XPathParser對象

    //分析xml
  public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
    //這個創(chuàng)建對象澎办,比較簡單,就是屬性的賦值
      commonConstructor(validation, variables, entityResolver);

    this.document = createDocument(new InputSource(inputStream));
  }

以上實現(xiàn)方式采用的是java提供的xml解析方式金砍,在javax.xml.parser這個包里面局蚀。目前通過上面的操作xml文件已經(jīng)變成了Document對象∷〕恚可以通個getElementByName等等方法獲得Element元素琅绅。并且這個Document對象已經(jīng)間接的被XMLConfigBuilder持有。繼續(xù)調(diào)用XMLConfigBuilder.parse()方法鹅巍。

public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

先看上面的parser.evalNode("/configuration")我們在配置Mybatis的時候可以看到根節(jié)點就是configuration千扶。可以猜測應該是拿到configuration這個節(jié)點的所有數(shù)據(jù)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>

加載configuration細節(jié)

繼續(xù)看這句parseConfiguration(parser.evalNode("/configuration"));這個方法里面是要處理configuration節(jié)點骆捧。在parser.evalNode("/configuration")里面已經(jīng)拿到了<configuration>節(jié)點澎羞,這個節(jié)點被封裝成了XNode對象。XNode對象是將Node對象作為屬性(設計模式的原則:組合優(yōu)于繼承)在對象中使用敛苇,這個XNode就是獲得<configuration>節(jié)點中的各種數(shù)據(jù)妆绞。然后通過paserConfiguration方法處理這個節(jié)點。其實就是注冊各種信息枫攀±ㄈ模看源代碼

  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
        //獲得properties節(jié)點,保存到configuration.variables屬性里面
      propertiesElement(root.evalNode("properties"));

      //將別名添加到configuration.TypeAliasRegistry 這個里面已經(jīng)默認注冊了常用的數(shù)據(jù)類
        /**
         * 下面簡單列幾個mybatis提供的默認類型別名来涨。當然也可以自定義图焰。會解析下面標簽
         * <typeAlias alias="Author" type="org.apache.ibatis.domain.blog.Author"/>
         * registerAlias("byte", Byte.class);
         * registerAlias("byte[]", Byte[].class);
         * registerAlias("_byte", byte.class);
         * registerAlias("ResultSet", ResultSet.class);
         * registerAlias("list", List.class);
         */
      typeAliasesElement(root.evalNode("typeAliases"));
      //這個是解析插件,暫時跳過
      pluginElement(root.evalNode("plugins"));
      //這個也沒怎么用到蹦掐,先跳過
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectionFactoryElement(root.evalNode("reflectionFactory"));
      settingsElement(root.evalNode("settings"));
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //看一下這個類型處理器
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

加載properties節(jié)點

上面只標注了一些經(jīng)常使用的節(jié)點解析過程技羔,一些不常用的就沒有分析僵闯,例如objectFactory,objectWrapperFactory就不分析了藤滥。先來看一下properties這種鍵值對解析過程鳖粟。過程很簡單分為兩種情況:

  • 直接是鍵值對存在的直接生成Properties對象
  • 使用Resource進行引入文件的,先讀取文件變?yōu)閕nputStream流超陆,然后從中讀出鍵值對

最后解析完數(shù)據(jù)牺弹,將Properties對象保存到configuration對象中configuration.setVariables(defaults);

    //將配置文件里面的properties解析出來浦马,保存到configuration對象里面的variables里面
  private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
        //直接解析類似:<property name="a" value="a1"/>種的鍵值對
      Properties defaults = context.getChildrenAsProperties();
      //處理Resource這種資源:<properties resource="org/apache/ibatis/databases/blog/blog-derby.properties"/>
      String resource = context.getStringAttribute("resource");
      String url = context.getStringAttribute("url");
      if (resource != null && url != null) {
        throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
      }
      if (resource != null) {
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {
        defaults.putAll(Resources.getUrlAsProperties(url));
      }
      Properties vars = configuration.getVariables();
      if (vars != null) {
        defaults.putAll(vars);
      }
      parser.setVariables(defaults);
      configuration.setVariables(defaults);
    }
  }

加載typeAlias節(jié)點

繼續(xù)分析第二個方法typeAliasesElement(root.evalNode("typeAliases"));這個方法是處理別名时呀。當時用<typeAlias alias="Author" type="org.apache.ibatis.domain.blog.Author"/>的時候就是在時用別名。這些別名回注冊到configuration對象里面TypeAliasRegistry對象晶默。這個對象里面已經(jīng)默認創(chuàng)建了一些常用類型的別名谨娜,這就是為什么在寫sql的時候可以直接使用resultType="list"因為這個類型已經(jīng)默認注冊到TypeAliasRegistry而在執(zhí)行sql之后返回類型會先到configuration對象里面找到TypeAliasRegistry里面是否有這個別名的對象,如果沒有就只用反射Class創(chuàng)建對象磺陡。下面簡單看看TypeAliasRegistry趴梢。所有的類型最終都是放在map里面

public class TypeAliasRegistry {

  private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();

  public TypeAliasRegistry() {
    registerAlias("string", String.class);

    registerAlias("byte", Byte.class);
    registerAlias("long", Long.class);
    registerAlias("short", Short.class);
    //省略...
    }
}

加載typeHandler節(jié)點

注冊的過程比較簡單就是將name作為可以,class作為值放到map對象里面币他。細節(jié)就不介紹了坞靶。跳過不常用的屬性objectFactory,objectWrapperFactory蝴悉,reflectionFactory彰阴,可以簡單看一下setting。在使用mybatis的時候只要有許多默認的setting配置拍冠。這些配置就是從settingsElement(root.evalNode("settings"));里面注入進去的尿这。如果沒有設置那么統(tǒng)統(tǒng)都讀取默認配置,設置了就用你自己的庆杜。再來看一下類型處理器TypeHandler這個是指查詢sql之后返回的jdbs類型和java類型不匹配時可以使用你自定義的類型來處理返回數(shù)據(jù)射众。Mybatis里面已經(jīng)設置了好多默認的類型處理器。

    /**
     * 處理這種類型標簽晃财。分別將javaType,jdbcType,handler三個屬性注冊到configuration.typeHandlerRegistry里面
     * <typeHandler javaType="String" jdbcType="VARCHAR" handler="org.apache.ibatis.builder.ExampleTypeHandler"/>
     * @param parent
     * @throws Exception
     */
  private void typeHandlerElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String typeHandlerPackage = child.getStringAttribute("name");
          typeHandlerRegistry.register(typeHandlerPackage);
        } else {
          String javaTypeName = child.getStringAttribute("javaType");
          String jdbcTypeName = child.getStringAttribute("jdbcType");
          String handlerTypeName = child.getStringAttribute("handler");
          Class<?> javaTypeClass = resolveClass(javaTypeName);
          JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
          Class<?> typeHandlerClass = resolveClass(handlerTypeName);
          if (javaTypeClass != null) {
            if (jdbcType == null) {
              typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
            } else {
              typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
            }
          } else {
            typeHandlerRegistry.register(typeHandlerClass);
          }
        }
      }
    }
  }

上面的注冊過程很簡單叨橱,就是讀取node下面的jdbcType和javaType和處理類handler。然后注冊到typeHandlerRegistry里面断盛〕猓看一下默認的類型處理器

public TypeHandlerRegistry() {
    register(Boolean.class, new BooleanTypeHandler());
    register(boolean.class, new BooleanTypeHandler());
    register(JdbcType.BOOLEAN, new BooleanTypeHandler());
    register(JdbcType.BIT, new BooleanTypeHandler());
    //省略.....
}
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市郑临,隨后出現(xiàn)的幾起案子栖博,更是在濱河造成了極大的恐慌,老刑警劉巖厢洞,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仇让,死亡現(xiàn)場離奇詭異典奉,居然都是意外死亡,警方通過查閱死者的電腦和手機丧叽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門卫玖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人踊淳,你說我怎么就攤上這事假瞬。” “怎么了迂尝?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵脱茉,是天一觀的道長。 經(jīng)常有香客問我垄开,道長琴许,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任溉躲,我火速辦了婚禮榜田,結果婚禮上,老公的妹妹穿的比我還像新娘锻梳。我一直安慰自己箭券,他們只是感情好,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布疑枯。 她就那樣靜靜地躺著辩块,像睡著了一般。 火紅的嫁衣襯著肌膚如雪神汹。 梳的紋絲不亂的頭發(fā)上庆捺,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天,我揣著相機與錄音屁魏,去河邊找鬼滔以。 笑死,一個胖子當著我的面吹牛氓拼,可吹牛的內(nèi)容都是我干的你画。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼桃漾,長吁一口氣:“原來是場噩夢啊……” “哼坏匪!你這毒婦竟也來了?” 一聲冷哼從身側響起撬统,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤适滓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后恋追,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體凭迹,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡罚屋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了嗅绸。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脾猛。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖鱼鸠,靈堂內(nèi)的尸體忽然破棺而出猛拴,到底是詐尸還是另有隱情,我是刑警寧澤蚀狰,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布愉昆,位于F島的核電站,受9級特大地震影響造锅,放射性物質(zhì)發(fā)生泄漏撼唾。R本人自食惡果不足惜廉邑,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一哥蔚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蛛蒙,春花似錦糙箍、人聲如沸瞻颂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽厨埋。三九已至票堵,卻和暖如春抛姑,著一層夾襖步出監(jiān)牢的瞬間趁矾,已是汗流浹背瓮具。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工收奔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留掌呜,地道東北人。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓坪哄,卻偏偏與公主長得像质蕉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子翩肌,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容