一:源碼分析流程圖
二:源碼分析開始
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