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

一、导读与猜想

在开始分析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,这里已经定义了


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

 
    
      
        
        
        
        
        
        
        
        
        
        
        
      
    
 

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


    
      
        
        
        
        
        
        
        
        
        
      
      
    
  


    
      
        
      
      
      
    
  

还有很多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 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源码分析(一)解析配置文件保存到Configuration类中)