Mybatis源码分析之(二)根据配置文件创建SqlSessionFactory(Configuration的创建过程)
2022/1/11 2:03:57
本文主要是介绍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);来获取SqlSessionFactory SqlSessionFactory 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 类这个地方。
也就是下面这两句话
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); parser.parse();
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类的实际值
//接下来就要解析并给configuration赋值拉。因为configuration在新建XMLConfigBuilder的时候就已经创建好了,解析的过程其实就是赋值的过程(我们在xml文件里配置的各个节点都在下面这个函数里解析) private void parseConfiguration(XNode root) { try { //issue #117 read properties first propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(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); } }
这是xml文件中要的标签,和上面代码一对比,你就会清楚的明白,上面每一个方法其实就是把每一个标签转换成configuration。
private void propertiesElement(XNode context) throws Exception { if (context != null) { //从子属性直接获取属性的键值对 Properties defaults = context.getChildrenAsProperties(); //从外部引入属性文件(其实就是获取resource属性的值) String resource = context.getStringAttribute("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."); } //从resource 或者 url的属性文件中加载键值对,若和上面的defaults,键相同则替换defaults中的 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的Variables parser.setVariables(defaults); //设置configuration的Variables configuration.setVariables(defaults); } }
其他的解析其实和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的创建过程)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-15在使用平台私钥进行解密时提示 "私钥解密失败" 错误信息是什么原因?-icode9专业技术文章分享
- 2024-11-15Layui框架有哪些方式引入?-icode9专业技术文章分享
- 2024-11-15Layui框架中有哪些减少对全局环境的污染方法?-icode9专业技术文章分享
- 2024-11-15laydate怎么关闭自动的日期格式校验功能?-icode9专业技术文章分享
- 2024-11-15laydate怎么取消初始日期校验?-icode9专业技术文章分享
- 2024-11-15SendGrid 的邮件发送时,怎么设置回复邮箱?-icode9专业技术文章分享
- 2024-11-15使用 SendGrid API 发送邮件后获取到唯一的请求 ID?-icode9专业技术文章分享
- 2024-11-15mailgun 发送邮件 tags标签最多有多少个?-icode9专业技术文章分享
- 2024-11-15mailgun 发送邮件 怎么批量发送给多个人?-icode9专业技术文章分享
- 2024-11-15如何搭建web开发环境并实现 web项目在浏览器中访问?-icode9专业技术文章分享