zoukankan      html  css  js  c++  java
  • Mybatis3源码笔记(三)Configuration

    1. XMLConfigBuilder

    上一篇大致介绍了SqlSession的生成。在DefaultSqlSessionFactory的构造函数中就提到了Configuration这个对象。现在我们来看看Configuration的生成流程。

      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.
          }
        }
      }
    

    代码比较简单,就是根据XMLConfigBuilder根据配置文件XML来parse生成的。其实不用看代码,我们脑海中应该也有一个大致的Configuration的构成细节,肯定是根据mybatis-config.xml具体生成对应的组成属性。一般的mybatis-config.xml文件如下:

    <configuration>
    
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC">
                    <property name="" value="" />
                </transactionManager>
                <dataSource type="UNPOOLED">
                    <property name="driver" value="org.hsqldb.jdbcDriver" />
                    <property name="url" value="jdbc:hsqldb:mem:stringlist" />
                    <property name="username" value="sa" />
                </dataSource>
            </environment>
        </environments>
    
        <mappers>
            <mapper resource="org/apache/ibatis/submitted/stringlist/Mapper.xml" />
        </mappers>
    
    </configuration>
    

    但是我们都知道XML的解析都有DTD文件来约束和验证,那我们常用的mybatis-config.xml是DTD肯定也是有的。在哪呢?秘密就在XMLConfigBuilder构造函数中的XMLMapperEntityResolver

      public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
        this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
      }
    
    public class XMLMapperEntityResolver implements EntityResolver {
    
      private static final String IBATIS_CONFIG_SYSTEM = "ibatis-3-config.dtd";
      private static final String IBATIS_MAPPER_SYSTEM = "ibatis-3-mapper.dtd";
      private static final String MYBATIS_CONFIG_SYSTEM = "mybatis-3-config.dtd";
      private static final String MYBATIS_MAPPER_SYSTEM = "mybatis-3-mapper.dtd";
    
      private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd";
      private static final String MYBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd";
      
      ...
    
    

    上面很明显就用到mybatis-3-config.dtdmybatis-3-mapper.dtd,同时为了兼容旧版本的ibatis,还用到了ibatis-3-config.dtdibatis-3-mapper.dtd

    之前说过mybatis-3-mapper.dtd,那mybatis-3-mapper.dtd看名字,我们也能猜到是专门用来解析mapper的xml文件的,一般的样例如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="org.apache.ibatis.submitted.stringlist.Mapper">
    
        <select id="getUsersAndGroups" resultMap="results">
            select * from users where id = #{id}
        </select>
    
        <resultMap type="org.apache.ibatis.submitted.stringlist.User" id="results">
            <id column="id" property="id"/>
            <collection property="groups" ofType="string">
                <result column="group_id"/>
            </collection>
            <collection property="roles" ofType="string">
                <result column="rol_id"/>
            </collection>
        </resultMap>
    
        <select id="getUsersAndGroupsMap" resultMap="mapResults">
            select * from users where id = #{id}
        </select>
    
        <resultMap type="map" id="mapResults">
            <id column="id" property="id" />
            <collection property="groups" ofType="string" javaType="list">
                <result column="group_id" />
            </collection>
            <collection property="roles" ofType="string" javaType="list">
                <result column="rol_id"/>
            </collection>
      </resultMap>
    
    </mapper>
    
    

    具体的dtd文件就不贴出来了,免的有凑字数的嫌疑。继续看代码:

    public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
        commonConstructor(validation, variables, entityResolver);
       //最终把XML文件生成document对象用来后面的解析工作
        this.document = createDocument(new InputSource(inputStream));
      }  
      private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        super(new Configuration());
        //本地异常日志记录(ThreadLocal)单例模式来记录每一次的执行过程,用来详细定位异常信息。(为了防止内存泄露,在finally里有reset的操作)
        ErrorContext.instance().resource("SQL Mapper Configuration");
        //记录入参props
        this.configuration.setVariables(props);
        this.parsed = false;
        //记录入参environment 
        this.environment = environment;
        this.parser = parser;
      }
    
      public Configuration parse() {
        //相同的XMLConfigBuilder对象只允许parse一次
        if (parsed) {
          throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        //开始解析configuration根节点
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
      }
    

    下面就到了重头戏Configuration的详细解析过程。其实前几项的解析相对来说比较简单,就是最后的mapper的解析比较复杂。

    private void parseConfiguration(XNode root) {
        try {
          //解析properties节点(variables)
          propertiesElement(root.evalNode("properties"));
          //解析settings节点
          Properties settings = settingsAsProperties(root.evalNode("settings"));
          //根据settings内容解析VFS(虚拟文件系统vfsImpl)
          loadCustomVfs(settings);
          //根据settings内容解析log日志具体实现类(logImpl)
          loadCustomLogImpl(settings);
          //解析typeAliases节点(TypeAliasRegistry里注册对应的别名)
          typeAliasesElement(root.evalNode("typeAliases"));
          //解析plugins节点(注册interceptorChain里记录对应的拦截器)
          pluginElement(root.evalNode("plugins"));
          //解析objectFactory节点(自定义objectFactory)
          objectFactoryElement(root.evalNode("objectFactory"));
          //解析objectWrapperFactory节点(自定义objectWrapperFactory)
          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
          //解析reflectorFactory节点(自定义reflectorFactory)
          reflectorFactoryElement(root.evalNode("reflectorFactory"));
          //设置其它的setting参数
          settingsElement(settings);
          //解析environments节点(environment)解析放到objectFactory and objectWrapperFactory解析之后,具体原因参见issue117
          environmentsElement(root.evalNode("environments"));
          //解析databaseIdProvider节点(databaseId)
          databaseIdProviderElement(root.evalNode("databaseIdProvider"));
          //解析typeHandlers节点,注册类型转换器(typeHandlerRegistry)
          typeHandlerElement(root.evalNode("typeHandlers"));
          //解析mappers节点(重中之重)
          mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
      }
    
    下面逐一解析下各个节点
    1. propertiesElement(root.evalNode("properties"))
      private void propertiesElement(XNode context) throws Exception {
        if (context != null) {
          //解析所有子节点property
          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.");
          }
          //根据url或者resource生成对应的property集合
          if (resource != null) {
            defaults.putAll(Resources.getResourceAsProperties(resource));
          } else if (url != null) {
            defaults.putAll(Resources.getUrlAsProperties(url));
          }
          //入参中的variables如果也存在的话,一并放入defaults
          Properties vars = configuration.getVariables();
          if (vars != null) {
            defaults.putAll(vars);
          }
          parser.setVariables(defaults);
          //设置variables
          configuration.setVariables(defaults);
        }
      }
    
      <properties resource="org/apache/ibatis/builder/jdbc.properties">
        <property name="prop1" value="aaaa"/>
        <property name="jdbcTypeForNull" value="NULL" />
      </properties>
    
      <properties url="file:./src/test/java/org/apache/ibatis/builder/jdbc.properties">
        <property name="prop1" value="bbbb"/>
      </properties>
    
    2. Properties settings = settingsAsProperties(root.evalNode("settings"))
      private Properties settingsAsProperties(XNode context) {
        if (context == null) {
          return new Properties();
        }
        Properties props = context.getChildrenAsProperties();
        // 利用MetaClass来检查Configuration
        MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
        //检查下对应的setting的key值在configuration里存不存在
        for (Object key : props.keySet()) {
          if (!metaConfig.hasSetter(String.valueOf(key))) {
            throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
          }
        }
        return props;
      }
    
      <settings>
        <setting name="autoMappingBehavior" value="NONE"/>
        <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
        <setting name="cacheEnabled" value="false"/>
        <setting name="proxyFactory" value="CGLIB"/>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="true"/>
        <setting name="multipleResultSetsEnabled" value="false"/>
        <setting name="useColumnLabel" value="false"/>
        <setting name="useGeneratedKeys" value="true"/>
        <setting name="defaultExecutorType" value="BATCH"/>
        <setting name="defaultStatementTimeout" value="10"/>
        <setting name="defaultFetchSize" value="100"/>
        <setting name="defaultResultSetType" value="SCROLL_INSENSITIVE"/>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <setting name="safeRowBoundsEnabled" value="true"/>
        <setting name="localCacheScope" value="STATEMENT"/>
        <setting name="jdbcTypeForNull" value="${jdbcTypeForNull}"/>
        <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString,xxx"/>
        <setting name="safeResultHandlerEnabled" value="false"/>
        <setting name="defaultScriptingLanguage" value="org.apache.ibatis.scripting.defaults.RawLanguageDriver"/>
        <setting name="callSettersOnNulls" value="true"/>
        <setting name="logPrefix" value="mybatis_"/>
        <setting name="logImpl" value="SLF4J"/>
        <setting name="vfsImpl" value="org.apache.ibatis.io.JBoss6VFS"/>
        <setting name="configurationFactory" value="java.lang.String"/>
        <setting name="defaultEnumTypeHandler" value="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
        <setting name="shrinkWhitespacesInSql" value="true"/>
        <setting name="defaultSqlProviderType" value="org.apache.ibatis.builder.XmlConfigBuilderTest$MySqlProvider"/>
      </settings>
    
    3. loadCustomVfs(settings)根据setting解析Vfs(代码比较简单,就不注释了)
      private void loadCustomVfs(Properties props) throws ClassNotFoundException {
        String value = props.getProperty("vfsImpl");
        if (value != null) {
          String[] clazzes = value.split(",");
          for (String clazz : clazzes) {
            if (!clazz.isEmpty()) {
              @SuppressWarnings("unchecked")
              Class<? extends VFS> vfsImpl = (Class<? extends VFS>) Resources.classForName(clazz);
              configuration.setVfsImpl(vfsImpl);
            }
          }
        }
      }
    
    4. loadCustomLogImpl(settings)根据setting解析log实现
    private void loadCustomLogImpl(Properties props) {
      Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
      configuration.setLogImpl(logImpl);
    }
    

    这个resolveClass方法会经常用到,我们跟一下看看。

    public <T> Class<T> resolveAlias(String string) {
      try {
      if (string == null) {
      return null;
      }
      // issue #748
      String key = string.toLowerCase(Locale.ENGLISH);
      Class<T> value;
      //根据typeAliases先去捞一波class,如果没有的话用Resources走classpath生成class
      if (typeAliases.containsKey(key)) {
      value = (Class<T>) typeAliases.get(key);
      } else {
      value = (Class<T>) Resources.classForName(string);
      }
      return value;
      } catch (ClassNotFoundException e) {
      throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e);
      }
    }
    
    public class TypeAliasRegistry {
    
    private final Map<String, Class<?>> typeAliases = new HashMap<>();
    //内置了一系列的类型别名
    public TypeAliasRegistry() {
    registerAlias("string", String.class);
    registerAlias("byte", Byte.class);
    registerAlias("long", Long.class);
    registerAlias("short", Short.class);
    registerAlias("int", Integer.class);
    registerAlias("integer", Integer.class);
    registerAlias("double", Double.class);
    registerAlias("float", Float.class);
    registerAlias("boolean", Boolean.class);
    registerAlias("byte[]", Byte[].class);
    ...
    
    5. typeAliasesElement(root.evalNode("typeAliases"))解析typeAliases节点(TypeAliasRegistry里注册对应的别名),正好跟上前的resolveClass联动起来。
      private void typeAliasesElement(XNode parent) {
        if (parent != null) {
          for (XNode child : parent.getChildren()) {
            //注册package包下面所有的Class,key:getSimpleName(),value:Class(不包括接口,内部类,匿名类)
            if ("package".equals(child.getName())) {
              String typeAliasPackage = child.getStringAttribute("name");
              configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
            } else {
             //普通加载
              String alias = child.getStringAttribute("alias");
              String type = child.getStringAttribute("type");
              try {
                Class<?> clazz = Resources.classForName(type);
                if (alias == null) {
                  typeAliasRegistry.registerAlias(clazz);
                } else {
                  typeAliasRegistry.registerAlias(alias, clazz);
                }
              } catch (ClassNotFoundException e) {
                throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
              }
            }
          }
        }
      }
    
      <typeAliases>
        <typeAlias alias="BlogAuthor" type="org.apache.ibatis.domain.blog.Author"/>
        <typeAlias type="org.apache.ibatis.domain.blog.Blog"/>
        <typeAlias type="org.apache.ibatis.domain.blog.Post"/>
        <package name="org.apache.ibatis.domain.jpetstore"/>
      </typeAliases>
    
  • 相关阅读:
    Help document of CAD Build
    NSTableView 中 NSTextField无法输入
    NSScrollView 内容显示不正常问题
    Mac 中 NSTrackingArea 鼠标移动事件捕获
    java定时任务调度框架
    uboot: initramfs image to roofs, and rootfs to initramfs image
    jfrog CLI(command line interface)
    git submodule list/clone/update
    python run javascript in ubuntu
    git blame
  • 原文地址:https://www.cnblogs.com/zhou-yuan/p/14566417.html
Copyright © 2011-2022 走看看