Mybatis源码分析之(二)根据配置文件创建SqlSessionFactory(Configuration的创建过程)
SqlSessionFactoryBuilder.build創建SqlSessionFactory(粗略走一步流程)
看完上篇文章后,你對mybatis應該有個大概的了解了,那么我們知道new SqlSessionFactoryBuilder().build是框架的入口,我們到SqlSessionFactoryBuilder類里看到里面其實都是build函數的。
全都是build重載函數。上面幾個重載其實最終都是調用了build(Reader reader, String environment, Properties properties)這個方法。下面的幾個則是調用了build(InputStream inputStream, String environment, Properties properties) 。
最終都會調用最后一個build(Configuration config)方法,把創建好的Configuration賦給DefaultSqlSessionFactory里面的Configuration字段,所以這里的邏輯其實非常簡單,就是解析傳入配置文件的Reader或者inputStream,然后生成Configuration,賦值給新建出來的DefaultSqlSessionFactor中的configuration字段,然后返回DefaultSqlSessionFactor。
public class SqlSessionFactoryBuilder {public SqlSessionFactory build(Reader reader) {return build(reader, null, null);}public SqlSessionFactory build(Reader reader, String environment) {return build(reader, environment, null);}public SqlSessionFactory build(Reader reader, Properties properties) {return build(reader, null, properties);}public SqlSessionFactory build(Reader reader, String environment, Properties properties) {try {XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);return build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {reader.close();} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}}public SqlSessionFactory build(InputStream inputStream) {return build(inputStream, null, null);}public SqlSessionFactory build(InputStream inputStream, String environment) {return build(inputStream, environment, null);}public SqlSessionFactory build(InputStream inputStream, Properties properties) {return build(inputStream, null, properties);}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);}}下面,我們來完整的走一個流程,就以上一篇的demo為例
//第一步public static SqlSession getSqlsession(){//獲取mybatis,config的xml文件的輸入流InputStream config = MybatisUtil.class.getClassLoader().getResourceAsStream("mybatis.cfg.xml");//使用SqlSessionFactory build(InputStream inputStream);來獲取SqlSessionFactorySqlSessionFactory build = new SqlSessionFactoryBuilder().build(config);return build.openSession();}//第二步調用SqlSessionFactoryBuilder中的build(InputStream inputStream)方法public SqlSessionFactory build(InputStream inputStream) {return build(inputStream, null, null);}//第三部調用SqlSessionFactoryBuilder中的build(InputStream inputStream, String environment, Properties properties)方法public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {//把輸入流解析成可以用做分析的document,以及準備其他解析需要的東西XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);//parser.parse()生成出Configuration類,最后調用build(Configuration config)方法來生成DefaultSqlSessionFactory,并返回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.}}}//第四步調用SqlSessionFactoryBuilder中的 build(Configuration config) 方法public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);}這是最上面的一層,邏輯還是非常簡單的,一句話就能概括,就是從給定的配置文件中解析出Configuration,然后生成DefaultSqlSessionFactory。(如果要指定環境,和特定的屬性的話用另外2個build 的重載方法)
我們先進入最后一個build看一下,new DefaultSqlSessionFactory(config);究竟做了什么。
public class DefaultSqlSessionFactory implements SqlSessionFactory {private final Configuration configuration;public DefaultSqlSessionFactory(Configuration configuration) {//非常簡單,就是生成了一個DefaultSqlSessionFactory 類,然后把里面的configuration字段,賦值為傳入的configuration。this.configuration = configuration;}//后面的函數就不列出來了}build是如何通過xml文件來生成Configuration的(比較詳細的分析流程)
所以build的重點在解析xml成Configuration 類這個地方。
也就是下面這兩句話
ok,第一句話是生成了一個XMLConfigBuilder類,我們來看看XMLConfigBuilder到底是什么樣的。只列出字段和關鍵方法
public class XMLConfigBuilder extends BaseBuilder {private boolean parsed;private XPathParser parser;private String environment;private ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();//上層調用到這個方法,然后他調用到了下面的重載方法public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {//這里只有new XPathParser(inputStream, true, props, new XMLMapperEntityResolver())需要看一下this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);}//其實就是給關鍵的幾個字段賦值,這里的第一句調用了父類的構造函數,傳入了一個新的Configuration,也就是說到這步的時候,我們用來返回的Configuration對象已經有了,但是Configuration的值還沒有構造好。private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {super(new Configuration());ErrorContext.instance().resource("SQL Mapper Configuration");this.configuration.setVariables(props);this.parsed = false;this.environment = environment;this.parser = parser;}}public abstract class BaseBuilder {protected final Configuration configuration;protected final TypeAliasRegistry typeAliasRegistry;protected final TypeHandlerRegistry typeHandlerRegistry;}接下來我們來看看XPathParser類
public class XPathParser {private Document document;private boolean validation;private EntityResolver entityResolver;private Properties variables;private XPath xpath;public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {//為類的其他字段賦值commonConstructor(validation, variables, entityResolver);//根據輸入流創建一個Document ,并賦值給 document字段(之后的解析就靠這里面的信息了)this.document = createDocument(new InputSource(inputStream));}}xml解析成Document的具體過程LZ就不帶大家深入研究了,有興趣的朋友可以自己跟下去看看。
接下來我們回到SqlSessionFactoryBuilder類。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {//現在我們已經拿到parser了XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);//接下來要去看parse()是怎么創建出Configuration的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 Configuration parse() {if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");}parsed = true;//解析靠這步,首先拿到configuration節點parseConfiguration(parser.evalNode("/configuration"));return configuration;}public XNode evalNode(String expression) {return evalNode(document, expression);}public XNode evalNode(Object root, String expression) {//拿到configuration節點Node node = (Node) evaluate(expression, root, XPathConstants.NODE);if (node == null) {return null;}//返回封裝成XNode類return new XNode(this, node, variables);} public class XNode {private Node node;private String name;private String body;private Properties attributes;private Properties variables;private XPathParser xpathParser;public XNode(XPathParser xpathParser, Node node, Properties variables) {this.xpathParser = xpathParser;this.node = node;this.name = node.getNodeName();this.variables = variables;this.attributes = parseAttributes(node);this.body = parseBody(node);} }XPathParser類的實際值
封裝好的Xnode的實際值
這是xml文件中要的標簽,和上面代碼一對比,你就會清楚的明白,上面每一個方法其實就是把每一個標簽轉換成configuration。
其他的解析其實和properties的解析差不多。根據配置文件節點的信息設置configuration。
因為mybatis非常重要的一個點就是在mapper上,所以樓主之后會有一篇專門寫mapper是什么解析和使用的。所以這里大家可以簡單過一下mapper的解析過程。
private void mapperElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {if ("package".equals(child.getName())) {String mapperPackage = child.getStringAttribute("name");configuration.addMappers(mapperPackage);} else {String resource = child.getStringAttribute("resource");String url = child.getStringAttribute("url");String mapperClass = child.getStringAttribute("class");if (resource != null && url == null && mapperClass == null) { //根據resource解析ErrorContext.instance().resource(resource);InputStream inputStream = Resources.getResourceAsStream(resource);XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());mapperParser.parse();} else if (resource == null && url != null && mapperClass == null) {//根據url 解析ErrorContext.instance().resource(url);InputStream inputStream = Resources.getUrlAsStream(url);XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());mapperParser.parse();} else if (resource == null && url == null && mapperClass != null) {//根據mapperClass解析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的mapper的實現方法還是要細細討論的,讓我們在之后的文章中在細致的學習以下。
小結
看了加載過程的源碼,我們在這至少了解到了mybatis是如何解析xml文件的,還有properties,假如我們即在xml文件中直接配置的屬性和又設置了resource(或者url)的話,resource(或者url)中的屬性會覆蓋xml文件中直接配的(key相同的情況下才會覆蓋,否則是累加的情況),若詳細看過mapper的創建過程的話,還會知道在xml中配的mapper的語句會被注解中配置的語句覆蓋。為什么會有這些特性?因為源碼的讀取步驟以及策略如此,所以要善于利用
總結
以上是生活随笔為你收集整理的Mybatis源码分析之(二)根据配置文件创建SqlSessionFactory(Configuration的创建过程)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 校招 | 第四范式 Plan1956 招
- 下一篇: 喜报!第四范式助力宁波银行荣获2020