美文网首页
Mybatis源码分析(一)解析配置文件保存到Configura

Mybatis源码分析(一)解析配置文件保存到Configura

作者: 小尾巴1024 | 来源:发表于2022-09-01 01:15 被阅读0次

    一、导读与猜想

    在开始分析Mybatis的源码之前,我们不妨来猜想一下,Mybatis是如设计的?
    使用过Mybatis框架都知道,使用Mybatis的过程大致经历如下步骤:

    • 创建一张表t_people
    • 创建一个实体People
    • 创建PeopleMapper接口
    • 创建PeopleMapper.xml文件
    • 创建mybatis-config.xml配置文件,里面配置数据库连接信息(dbUrl、user、password等),mappers、mapper标签等;也可以不使用xml,使用yml配置数据源+注解方式配置数据源+mapper。

    猜想Mybatis的设计与使用流程:

     1. 读取并装载mybatis-config.xml,输入流InputStream。
     2 .解析输入流并把mybatis-config.xml配置文件中相关配置项解析,校验,保存起来。
     3.创建sqlSessionFactory对象,在我们的印象里,session就是一次会话,所以我们可以理解sqlSessionFactory就是个工厂类,就专门创建sqlSession对象,并且这个sqlSessionFactory工厂类是唯一不变的(单例)。
     4.创建sqlSession,SqlSession中保存了配置文件内容信息和执行数据库相关的操作。
     5.获取PeopleMapper对象,但是PeopleMapper是接口,并且没有实现类。怎么就可以调用其方法呢?这里猜想可能用到了动态代理。
     6.PeopleMapper接口中的方法是如何关联到SQL的,这个猜想可能是有个专门映射的类,另外,肯定使用到了接口全路径名+方法名称,这个才能确保方法和SQL关联(主要是使用的时候,都是方法名必须和SQL中statementId一致,由此猜想的)。
     7.最后底层使用JDBC去操作数据库。
     8.作为一个持久化框架,很有可能会使用到缓存,用来存储每次查询数据。

    // 我们平时使用的大致流程如下,当然使用springboot就不是这样,不过大同小异。
    //读取mybatis-config.xml
    InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    //解析mybatis-config.xml配置文件,创建sqlSessionFactory
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //创建sqlSession
    sqlSession = sqlSessionFactory.openSession();
    //创建PeopleMapper对象(PeopleMapper并没有实现类)
    PerpleMapper peopleMapper= sqlSession.getMapper(PeopleMapper.class);
    //调用PeopleMapper对象的方法
    People people = peopleMapper.selectById(1);
    

    第二个猜想,Mybatis是如果设计,并把配置信息保存到Configuration中的?

    第一步是读取mybatis-config.xml配置文件,获得输入流InputStream。

    // 我们先从build()方法开始
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    

    SqlSessionFactoryBuilder中有好几个build方法,这在java中叫方法重载,如下图:

    image.png
    从上图可以看到SqlSessionFactory中提供了三种读取配置信息的方法后:字节流、字符流和Configuration配置类。
    build方法里面主要创建XMLConfigBuilder对象,这个类是BaseBuilder的子类,BaseBuilder类图。
    image.png
    看到这些子类基本上都是以Builder结尾,所以这里使用的是建造者设计模式
    这个类名可以猜出给类就是解析xml配置文件的。然后我们继续进入
    image.png
    从上图看到new XPathParser(...),这个类位于org.apache.ibatis.parsing包下,主要用于解析Mybatis中的mybatis-config.xml、xxxMapper.xml等xml文件。
    继续分析源码
    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;
    }
    

    构造一个XMLConfigBuilder对象,给属性设置相应值。
    然后我们再回到SqlSessionFactoryBuilder中的build方法里:

    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    build(parser.parse());
    

    对应的parse()方法源码如下:

    public Configuration parse() {
      if (parsed) {
       throw new BuilderException("Each XMLConfigBuilder can only be used once.");
      }
      parsed = true;
      //mybatis-config.xml的一级标签
      parseConfiguration(parser.evalNode("/configuration"));
      return configuration;
    }
    

    再继续看parseConfiguration()方法

    private void parseConfiguration(XNode root) {
        try {
          //issue #117 read properties first
          propertiesElement(root.evalNode("properties"));
          Properties settings = settingsAsProperties(root.evalNode("settings"));
          loadCustomVfs(settings);
          loadCustomLogImpl(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);
        }
      }
    

    再结合 mybatis-config.xml配置文件和解析方法,如果回头去看mybatis-config.xml中的所有标签,包括一级标签、二级标签、三级标签等。parseConfiguration()方法是解析xml中的标签,并将标签内容封装在Configuration对象中。
    如果我们想知道有哪些标签可以定义,可以看org.apache.ibatis.builder.xml下的mybatis-3-config.dtd,这里已经定义了

    <!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>
    

    与之对应的具体标签定义可查看mybatis-config.xsd,如下:

     <xs:element name="configuration">
        <xs:complexType>
          <xs:sequence>
            <xs:element minOccurs="0" ref="properties"/>
            <xs:element minOccurs="0" ref="settings"/>
            <xs:element minOccurs="0" ref="typeAliases"/>
            <xs:element minOccurs="0" ref="typeHandlers"/>
            <xs:element minOccurs="0" ref="objectFactory"/>
            <xs:element minOccurs="0" ref="objectWrapperFactory"/>
            <xs:element minOccurs="0" ref="reflectorFactory"/>
            <xs:element minOccurs="0" ref="plugins"/>
            <xs:element minOccurs="0" ref="environments"/>
            <xs:element minOccurs="0" ref="databaseIdProvider"/>
            <xs:element minOccurs="0" ref="mappers"/>
          </xs:sequence>
        </xs:complexType>
     </xs:element>
    

    我们平时用得最多的是xxxMapper.xml文件,所以更关心这个xml文件里面有哪些标签,这个在mybatis-3-mapper.dtd也定义了,与之对应的mybatis-mapper.xsd可查看到具体的标签,如下:

    <xs:element name="mapper">
        <xs:complexType>
          <xs:choice maxOccurs="unbounded">
            <xs:element ref="cache-ref"/>
            <xs:element ref="cache"/>
            <xs:element minOccurs="0" maxOccurs="unbounded" ref="resultMap"/>
            <xs:element minOccurs="0" maxOccurs="unbounded" ref="parameterMap"/>
            <xs:element minOccurs="0" maxOccurs="unbounded" ref="sql"/>
            <xs:element minOccurs="0" maxOccurs="unbounded" ref="insert"/>
            <xs:element minOccurs="0" maxOccurs="unbounded" ref="update"/>
            <xs:element minOccurs="0" maxOccurs="unbounded" ref="delete"/>
            <xs:element minOccurs="0" maxOccurs="unbounded" ref="select"/>
          </xs:choice>
          <xs:attribute name="namespace"/>
        </xs:complexType>
      </xs:element>
    
    <xs:element name="parameterMap">
        <xs:complexType>
          <xs:sequence>
            <xs:element minOccurs="0" maxOccurs="unbounded" ref="parameter"/>
          </xs:sequence>
          <xs:attribute name="id" use="required"/>
          <xs:attribute name="type" use="required"/>
        </xs:complexType>
      </xs:element>
    

    还有很多select、update、delete等我们平时常用的标签,都在mybatis-mapper.xsd中可查看到。
    挑些重点:我们来看看这些标签内容是如何存入configuration对象中?
    我们主要查看org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration中的几个重要方法:

    1. this.propertiesElement(root.evalNode("properties"));
    private void propertiesElement(XNode context) throws Exception {
            if (context != null) {
                Properties defaults = context.getChildrenAsProperties();
                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 = this.configuration.getVariables();
                if (vars != null) {
                    defaults.putAll(vars);
                }
                this.parser.setVariables(defaults);
                this.configuration.setVariables(defaults);
            }
        }
    
    1. this.typeAliasesElement(root.evalNode("typeAliases"));
    private void typeAliasesElement(XNode parent) {
            if (parent != null) {
                Iterator var2 = parent.getChildren().iterator();
                while(var2.hasNext()) {
                    XNode child = (XNode)var2.next();
                    String alias;
                    if ("package".equals(child.getName())) {
                        alias = child.getStringAttribute("name");
                        this.configuration.getTypeAliasRegistry().registerAliases(alias);
                    } else {
                        alias = child.getStringAttribute("alias");
                        String type = child.getStringAttribute("type");
                        try {
                            Class<?> clazz = Resources.classForName(type);
                            if (alias == null) {
                                this.typeAliasRegistry.registerAlias(clazz);
                            } else {
                                this.typeAliasRegistry.registerAlias(alias, clazz);
                            }
                        } catch (ClassNotFoundException var7) {
                            throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + var7, var7);
                        }
                    }
                }
            }
        }
    
    1. this.pluginElement(root.evalNode("plugins"));
    private void pluginElement(XNode parent) throws Exception {
            if (parent != null) {
                Iterator var2 = parent.getChildren().iterator();
                while(var2.hasNext()) {
                    XNode child = (XNode)var2.next();
                    //获取interceptor标签
                    String interceptor = child.getStringAttribute("interceptor");
                    Properties properties = child.getChildrenAsProperties();
                    Interceptor interceptorInstance = (Interceptor)this.resolveClass(interceptor).newInstance();
                    interceptorInstance.setProperties(properties);
                    this.configuration.addInterceptor(interceptorInstance);
                }
            }
        }
    

    Configuration中interceptorChain用来存储所有定义的插件。

     public void addInterceptor(Interceptor interceptor) {
          // 将插件存入interceptorChain中
            this.interceptorChain.addInterceptor(interceptor);
        }
    

    InterceptorChain插件链(连接链),责任链模式。

    public class InterceptorChain {
        private final List<Interceptor> interceptors = new ArrayList();
        public InterceptorChain() {
        }
        public Object pluginAll(Object target) {
            Interceptor interceptor;
            for(Iterator var2 = this.interceptors.iterator(); var2.hasNext(); target = interceptor.plugin(target)) {
                interceptor = (Interceptor)var2.next();
            }
            return target;
        }
        public void addInterceptor(Interceptor interceptor) {
            this.interceptors.add(interceptor);
        }
    }
    
    1. 最后看看mapper是怎样解析放到configuration中的,主要的解析方法是在org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement方法中
     private void mapperElement(XNode parent) throws Exception {
            if (parent != null) {
                Iterator var2 = parent.getChildren().iterator();
                while(true) {
                    while(var2.hasNext()) {
                        XNode child = (XNode)var2.next();
                        String resource;
                        //自动扫描包下所有映射器
                        if ("package".equals(child.getName())) {
                            resource = child.getStringAttribute("name");
                             //放到配置对象configuration中  
                            this.configuration.addMappers(resource);
                        } else {
                            resource = child.getStringAttribute("resource");
                            String url = child.getStringAttribute("url");
                            String mapperClass = child.getStringAttribute("class");
                            XMLMapperBuilder mapperParser;
                            InputStream inputStream;
                            if (resource != null && url == null && mapperClass == null) {
                                ErrorContext.instance().resource(resource);
                                 //根据文件存放目录,读取XxxMapper.xml
                                inputStream = Resources.getResourceAsStream(resource);
                                mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
                                mapperParser.parse();
                            } else if (resource == null && url != null && mapperClass == null) {
                                ErrorContext.instance().resource(url);
                                inputStream = Resources.getUrlAsStream(url);
                                mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
                                mapperParser.parse();
                            } else {
                                if (resource != null || url != null || mapperClass == null) {
                                    throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                                }
                                Class<?> mapperInterface = Resources.classForName(mapperClass);
                                // 重点在这里,将xxxMapper接口放入configuration中
                                this.configuration.addMapper(mapperInterface);
                            }
                        }
                    }
                    return;
                }
            }
        }
    

    至此,配置文件mybatis-config.xml和我们定义映射文件XxxMapper.xml就全部解析完成。
    总结: 从上面的代码和流程可以看出,关于其他配置项,解析方式类似,最终都保存到了一个Configuration大对象中。Configuration对象类似于单例模式,就是整个Mybatis中只有一个Configuration对象。

    相关文章

      网友评论

          本文标题:Mybatis源码分析(一)解析配置文件保存到Configura

          本文链接:https://www.haomeiwen.com/subject/dfgwgrtx.html