深度Mybatis源碼分析——SqlSessionFactoryBuilder(建造者模式),Mapper接口綁定原理(代理模式)

一:源碼分析流程圖

二:源碼分析開始

public class TestMyBatis {
   
    public static void main(String[] args) {
        try {
            // 基本mybatis環境
            // 1.定義mybatis_config文件地址
            String resources = "mybatis_config.xml";
            // 2.獲取InputStreamReaderIo流
            Reader reader = Resources.getResourceAsReader(resources);
            // 3.獲取SqlSessionFactory
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
            // 4.獲取Session
            SqlSession sqlSession = sqlSessionFactory.openSession();
            // 5.操作Mapper接口
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            UserEntity user = mapper.getUser(2);
            System.out.println(user.getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

1.首先分析目標有兩個

1.mybatis SqlSessionFactoryBuilder源碼分析 (建造者模式)

2.MybatisMapper接口綁定原理(代理設計模式)

目標一:SqlSessionFactoryBuilder源碼分析 (建造者模式)

1.讀取resources獲取對應的Reader對象,進入getResourceAsReader(resources)源碼片段

Reader reader = Resources.getResourceAsReader(resources);

/*
 *讀取resources獲取對應的Reader對象
 */
public static Reader getResourceAsReader(String resource) throws IOException {
    Reader reader;
    //判斷編碼
    if (charset == null) {
      //調用javaioAPI  讀取resources配置文件,獲取InputStreamReader
      reader = new InputStreamReader(getResourceAsStream(resource));
    } else {
      reader = new InputStreamReader(getResourceAsStream(resource), charset);
    }
    return reader;
}

2.進入SqlSessionFactoryBuilder()去看看無參構造函數做了什麼事情,我們發現無參構造函數沒有做什麼事情,那麼我們就點到build(reader)去看這個方法具體如何實現的。

我們發現SqlSessionFactoryBuilder , 通過XMLConfigBuilder解析mybatis配置文件內容。下面的代碼都是對配置文件的解析過程。

SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);

/*
 *創建 SqlSessionFactory
 */
public class SqlSessionFactoryBuilder {

  //第一步進入這個方法,Reader讀取mybatis配置文件,傳入構造方法
  public SqlSessionFactory build(Reader reader) {
    //調用重載的方法,我們點進去
    return build(reader, null, null);
  }
....省略部分源碼
  //第二步進入這個重載方法
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      //通過XMLConfigBuilder解析mybatis配置文件,源碼分析
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      //源碼分析
      return build(parser.parse());
...省略部分源碼
  }

下面就接着看看 XMLConfigBuilder 源碼片段:具體如何解析配置文件的,是通過xml解析器去解析配置文件

/**
 * 使用xml解析器去解析mybatis 配置文件信息
 */
public class XMLConfigBuilder extends BaseBuilder {

  private boolean parsed;
  //xml解析器
  private XPathParser parser;
....
  //第一步進入到這個帶參數的構造方法中
  public XMLConfigBuilder(Reader reader, String environment, Properties props) {
    //調用具體的執行邏輯方法,點進去,進入重載的方法
    this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
  }
....
  //第二步執行具體邏輯
  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    //在構造函數設置了parsed 爲fasle
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

xml解析器通過return build(parser.parse())這個方法去解析配置文件內容,我們去看看parse()方法源碼

//外部調用此方法對mybatis配置文件進行解析  
public Configuration parse() {
    //因爲在構造函數設置了parsed 爲fasle,xml解析器只解析一次
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    //只解析一次,Configuration配置文件是全局的,只能被解析一次
    parsed = true;
    //源碼分析,從根節點configuration
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

在上面這段代碼調用了:parseConfiguration(parser.evalNode("/configuration")),我們點進源碼看看具體怎麼做的

//1.此方法就是解析configuration節點下的子節點
//2.由此也可看出,我們在configuration下面能配置的節點爲以下10個節點
//3.首先要看的就是properties節點和environments節點
private void parseConfiguration(XNode root) {
    try {
      //1.解析properties元素,源碼分析
      propertiesElement(root.evalNode("properties"));
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectionFactoryElement(root.evalNode("reflectionFactory"));
      settingsElement(root.evalNode("settings"));
      //2.解析environments元素,源碼分析
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      //3.這裏源碼分析
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

下面我門來看看解析properties的具體方法, propertiesElement(root.evalNode("properties")),這裏點進去看源碼如何寫的

/*
* 解析properties具體方法
*/  
private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
      //將子節點的 name 以及value屬性set進properties對象
      //這兒可以注意一下順序,xml配置優先, 外部指定properties配置其次
      Properties defaults = context.getChildrenAsProperties();
      //獲取properties節點上 resource屬性的值
      String resource = context.getStringAttribute("resource");
      //獲取properties節點上 url屬性的值, resource和url不能同時配置
      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.");
      }
      //把解析出的properties文件set進Properties對象
      if (resource != null) {
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {
        defaults.putAll(Resources.getUrlAsProperties(url));
      }
      //將configuration對象中已配置的Properties屬性與剛剛解析的融合
      //configuration這個對象會裝載所解析mybatis配置文件的所有節點元素,以後也會頻頻提到這個對象
      //既然configuration對象用有一系列的get/set方法, 那是否就標誌着我們可以使用java代碼直接配置?
      //答案是肯定的, 不過使用配置文件進行配置,優勢不言而喻
      Properties vars = configuration.getVariables();
      if (vars != null) {
        defaults.putAll(vars);
      }
      //把裝有解析配置propertis對象set進解析器, 因爲後面可能會用到
      parser.setVariables(defaults);
      //set進configuration對象
      configuration.setVariables(defaults);
    }
  }

下面來看看解析envioments元素節點的方法,environmentsElement(root.evalNode("environments")),這裏點進去看源碼如何實現的

/*
* 解析envioments元素節點的方法
*/
private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
      if (environment == null) {
        //解析environments節點的default屬性的值
        //例如: <environments default="development">
        environment = context.getStringAttribute("default");
      }
      //遞歸解析environments子節點
      for (XNode child : context.getChildren()) {
        //<environment id="development">, 只有enviroment節點有id屬性,那麼這個屬性有何作用?
        //environments 節點下可以擁有多個 environment子節點
        // //類似於這樣: <environments default="development"><environment id="development">... 
        //</environment><environment id="test">...</environments>
        //意思就是我們可以對應多個環境,比如開發環境,測試環境等, 由environments的default屬性去選擇對應的 
        //enviroment
        String id = child.getStringAttribute("id");
        //isSpecial就是根據由environments的default屬性去選擇對應的enviroment
        if (isSpecifiedEnvironment(id)) {
          //事務, mybatis有兩種:JDBC 和 MANAGED, 配置爲JDBC則直接使用JDBC的事務,配置爲MANAGED則是將事務託管 
          //給容器
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          //enviroment節點下面就是dataSource節點了,解析dataSource節點(下面會貼出解析dataSource的具體方法)
          //這裏源碼分析
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          DataSource dataSource = dsFactory.getDataSource();
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          //老規矩,會將dataSource設置進configuration對象
          configuration.setEnvironment(environmentBuilder.build());
        }
      }
    }
  }

下面來看看解析datasource元素節點的方法,dataSourceElement(child.evalNode("dataSource")),這裏看源碼怎麼實現datasource解析

/*
* 解析datasource元素節點的方法
*/
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
    if (context != null) {
      //dataSource的連接池
      String type = context.getStringAttribute("type");
      //子節點 name, value屬性set進一個properties對象
      Properties props = context.getChildrenAsProperties();
      //創建dataSourceFactory
      DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
      factory.setProperties(props);
      return factory;
    }
    throw new BuilderException("Environment declaration requires a DataSourceFactory.");
  }

3.SqlSessionFactoryBuilder源碼分析 (建造者模式)通過以上源碼,我們就能看出,在mybatis的配置文件中:

1. configuration節點爲根節點。

2. 在configuration節點之下,我們可以配置10個子節點, 分別爲:properties、typeAliases、plugins、objectFactory、objectWrapperFactory、settings、environments、databaseIdProvider、typeHandlers、mappers。

3.解析配置文件完成了之後,都會裝配到configuration

4.Configuration作用:mybatis核心的配置文件內容 ,使用xml轉換bean
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章