MyBatis启动之XMLConfigBuilder解析配置文件(二)

前言

XMLConfigBuilder是BaseBuilder(解析中会涉及到讲解)的其中一个子类,它的作用是把MyBatis的XML及相关配置解析出来,然后保存到Configuration中。本文就解析过程按照执行顺序进行分析,掌握常用配置的解析原理。
使用
调用XMLConfigBuilder进行解析,要进行两步操作,上篇文章中【MyBatis之启动分析(一)】有提到。
实例化XMLConfigBuilder对象。
    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;
      }
实例化Configuration
通过new Configuration()的方式实例化:

typeAliasRegistry是一个类型别名注册器,实现原理就是维护一份HashMap,别名作为key,类的全限定名作为value。这里将框架中使用的类注册到类型别名注册器中。
TypeAliasRegistry.registerAlias代码如下:
    public void registerAlias(String alias, Class<?> value) {
    if (alias == null) {
      throw new TypeException("The parameter alias cannot be null");
    }
    // issue #748
    //  在验证是否存在key和保存kv前,统一将key转换成小写
    String key = alias.toLowerCase(Locale.ENGLISH);
    if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
      // 当注册的类型已存在时,抛出异常
      throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
    }
    // TYPE_ALIASES 为定义的一个HashMap
    TYPE_ALIASES.put(key, value);
    }
在实例化Configuration类过程中,在该类里除了实例化了TypeAliasRegistry还实例化了另外一个下面用到的的类:
    // 类型处理器注册器
    protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
TypeHandlerRegistry和TypeAliasRegistry实例化逻辑相似,里面注册了一些常用类型和处理器,代码易懂。
TypeHandlerRegistry的属性
    // jdbc类型和TypeHandler的映射关系,key必须是JdbcType的枚举类型,读取结果集数据时,将jdbc类型转换成java类型
    private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);
    // Java类型与JdbcType类型的键值对,存在一对多的映射关系
    private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>();
    // 没有相应的类型处理器时,使用的处理器
    private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this);
    // 类型处理器类类型和类型处理器的映射关系
    private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();
    // 空处理器的值,用来做校验
    private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
    // 默认枚举类型处理器
    private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
TypeHandlerRegistry构造函数:
    public TypeHandlerRegistry() {
        register(Boolean.class, new BooleanTypeHandler());
        register(boolean.class, new BooleanTypeHandler());
        register(JdbcType.BOOLEAN, new BooleanTypeHandler());
        register(JdbcType.BIT, new BooleanTypeHandler());
        register(Byte.class, new ByteTypeHandler());
        register(byte.class, new ByteTypeHandler());
        register(JdbcType.TINYINT, new ByteTypeHandler());
        register(Short.class, new ShortTypeHandler());
        register(short.class, new ShortTypeHandler());
        register(JdbcType.SMALLINT, new ShortTypeHandler());
        register(Integer.class, new IntegerTypeHandler());
        register(int.class, new IntegerTypeHandler());
        register(JdbcType.INTEGER, new IntegerTypeHandler());
        register(Long.class, new LongTypeHandler());
        register(long.class, new LongTypeHandler());
        register(Float.class, new FloatTypeHandler());
        register(float.class, new FloatTypeHandler());
        register(JdbcType.FLOAT, new FloatTypeHandler());
        register(Double.class, new DoubleTypeHandler());
        register(double.class, new DoubleTypeHandler());
        register(JdbcType.DOUBLE, new DoubleTypeHandler());
        register(Reader.class, new ClobReaderTypeHandler());
        register(String.class, new StringTypeHandler());
        register(String.class, JdbcType.CHAR, new StringTypeHandler());
        register(String.class, JdbcType.CLOB, new ClobTypeHandler());
        register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
        register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler());
        register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
        register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
        register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
        register(JdbcType.CHAR, new StringTypeHandler());
        register(JdbcType.VARCHAR, new StringTypeHandler());
        register(JdbcType.CLOB, new ClobTypeHandler());
        register(JdbcType.LONGVARCHAR, new ClobTypeHandler());
        register(JdbcType.NVARCHAR, new NStringTypeHandler());
        register(JdbcType.NCHAR, new NStringTypeHandler());
        register(JdbcType.NCLOB, new NClobTypeHandler());
        register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
        register(JdbcType.ARRAY, new ArrayTypeHandler());
        register(BigInteger.class, new BigIntegerTypeHandler());
        register(JdbcType.BIGINT, new LongTypeHandler());
        register(BigDecimal.class, new BigDecimalTypeHandler());
        register(JdbcType.REAL, new BigDecimalTypeHandler());
        register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
        register(JdbcType.NUMERIC, new BigDecimalTypeHandler());
        register(InputStream.class, new BlobInputStreamTypeHandler());
        register(Byte[].class, new ByteObjectArrayTypeHandler());
        register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
        register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
        register(byte[].class, new ByteArrayTypeHandler());
        register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
        register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
        register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
        register(JdbcType.BLOB, new BlobTypeHandler());
        register(Object.class, UNKNOWN_TYPE_HANDLER);
        register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
        register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
        register(Date.class, new DateTypeHandler());
        register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
        register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
        register(JdbcType.TIMESTAMP, new DateTypeHandler());
        register(JdbcType.DATE, new DateOnlyTypeHandler());
        register(JdbcType.TIME, new TimeOnlyTypeHandler());
        register(java.sql.Date.class, new SqlDateTypeHandler());
        register(java.sql.Time.class, new SqlTimeTypeHandler());
        register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());
        // mybatis-typehandlers-jsr310
        // 是否包含日期,时间相关的Api,通过判断是否加载java.time.Clock作为依据
        if (Jdk.dateAndTimeApiExists) {
          this.register(Instant.class, InstantTypeHandler.class);
          this.register(LocalDateTime.class, LocalDateTimeTypeHandler.class);
          this.register(LocalDate.class, LocalDateTypeHandler.class);
          this.register(LocalTime.class, LocalTimeTypeHandler.class);
          this.register(OffsetDateTime.class, OffsetDateTimeTypeHandler.class);
          this.register(OffsetTime.class, OffsetTimeTypeHandler.class);
          this.register(ZonedDateTime.class, ZonedDateTimeTypeHandler.class);
          this.register(Month.class, MonthTypeHandler.class);
          this.register(Year.class, YearTypeHandler.class);
          this.register(YearMonth.class, YearMonthTypeHandler.class);
          this.register(JapaneseDate.class, JapaneseDateTypeHandler.class);
        }
        // issue #273
        register(Character.class, new CharacterTypeHandler());
        register(char.class, new CharacterTypeHandler());
    }
里面调用了两个register()重载方法, type + handler 参的TypeHandlerRegistry.register(Class<T> javaType, TypeHandler<? extends T> typeHandler)和 type + jdbc type + handler 参的TypeHandlerRegistry.register(Class<T> type, JdbcType jdbcType, TypeHandler<? extends T> handler)
    // java type + handler
    public <T> void register(Class<T> javaType, TypeHandler<? extends T> typeHandler) {
        register((Type) javaType, typeHandler);
    }
    private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
        MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
        if (mappedJdbcTypes != null) {
          for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
            register(javaType, handledJdbcType, typeHandler);
          }
          if (mappedJdbcTypes.includeNullJdbcType()) {
            register(javaType, null, typeHandler);
          }
        } else {
          register(javaType, null, typeHandler);
        }
    }
    // java type + jdbc type + handler
    public <T> void register(Class<T> type, JdbcType jdbcType, TypeHandler<? extends T> handler) {
        register((Type) type, jdbcType, handler);
    }
    // type + handler 和 type + jdbc type + handler 最终都调用此方法
    private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
        if (javaType != null) {
          // 当 javaType 不为空时, 获取 java 类型的的映射
          Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
          if (map == null || map == NULL_TYPE_HANDLER_MAP) {
            // 若映射为空,新建一个映射关系
            map = new HashMap<JdbcType, TypeHandler<?>>();
            // 保存至类型处理器映射关系中
            TYPE_HANDLER_MAP.put(javaType, map);
          }
          // 保存jdbcType和处理器关系,完成 java类型,jdbc类型,处理器三者之间的注册
          map.put(jdbcType, handler);
        }
        // 保存处理器信息中
        ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
    }
    // MappedJdbcTypes 注解
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface MappedJdbcTypes {
      JdbcType[] value();
      boolean includeNullJdbcType() default false;
    }
- type + handler方法:先获取处理器的- MappedJdbcTypes注解(自定义处理器注解),若注解的- value值不为空时,由于该值为- JdbcType[]类型,所以- for循环- javaType+jdbcType+TypeHandler注册,若- includeNullJdbcType(- jdbcType是否包含- null)为- true,默认值为- false,注册到相应映射中。若注解的- value为- null,直接调用注册操作,里面不会注册- type + jdbc type + handler关系。
- type + jdbc type + handler方法:该方法将java类强制转换为- java.lang.reflect.Type类型,然后调用最终注册的方法。
调用父类BaseBuilder的构造方法
BaseBuilder定义有三个属性
    protected final Configuration configuration;
    // 类型别名注册器
    protected final TypeAliasRegistry typeAliasRegistry;
    // 类型处理器注册器
    protected final TypeHandlerRegistry typeHandlerRegistry;
BaseBuilder构造方法
    public BaseBuilder(Configuration configuration) {
        this.configuration = configuration;
        this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
        this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
    }
这里属性,就是上面讲解到的。
调用 XMLConfigBuilder.parse() 作为解析入口。
parse()实现配置文件是否解析过
    public Configuration parse() {
        // 若parsed为true,配置文件解析过
        if (parsed) {
          throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        // 标志已解析过
        parsed = true;
        // 从根节点 configuration 开始解析
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }
解析/configuration里的配置
    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);
        }
    }
从上面源码中,不难看出这里是解析/configuration中的各个子节点。
properties 节点解析
properties配置方式
    <!-- 方法一 -->
    <properties>
        <property name="username" value="${jdbc.username}" />
    </properties>
    <!-- 方法二 -->
    <properties resource="xxxConfig.properties">
    </properties>
    <!-- 方法三 -->
    <properties url="file:///D:/xxxConfig.properties">
    </properties>
propertiesElement()方法
    private void propertiesElement(XNode context) throws Exception {
        if (context != null) {
          // 获取 propertie 节点,并保存 Properties 中
          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.");
          }
          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);
          }
          // 将解析的值保存到 XPathParser 中
          parser.setVariables(defaults);
          // 将解析的值保存到 Configuration 中
          configuration.setVariables(defaults);
        }
    }
从上面源码中,resource和url的配置形式不允许同时存在,否则抛出BuilderException异常。先解析propertie的配置值,再解析resource或url的值。
当propertie存在与resource或url相同的key时,propertie的配置会被覆盖,应为Properties实现的原理就是继承的Hashtable类来实现的。
settings 节点解析
settings配置方式
    <settings>
        <setting name="cacheEnabled" value="true" />
        ......
    </settings>
设置中各项的意图、默认值 图(引用来源:w3cschool)
| 设置参数 | 描述 | 有效值 | 默认值 | 
|---|---|---|---|
| cacheEnabled | 该配置影响的所有映射器中配置的缓存的全局开关。 | true,false | true | 
| lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。 | true,false | false | 
| aggressiveLazyLoading | 当启用时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载;反之,每种属性将会按需加载。 | true,false,true | |
| multipleResultSetsEnabled | 是否允许单一语句返回多结果集(需要兼容驱动)。 | true,false | true | 
| useColumnLabel | 使用列标签代替列名。不同的驱动在这方面会有不同的表现, 具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。 | true,false | true | 
| useGeneratedKeys | 允许 JDBC 支持自动生成主键,需要驱动兼容。 如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby)。 | true,false | False | 
| autoMappingBehavior | 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套)。 | NONE, PARTIAL, FULL | PARTIAL | 
| defaultExecutorType | 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。 | SIMPLE REUSE BATCH | SIMPLE | 
| defaultStatementTimeout | 设置超时时间,它决定驱动等待数据库响应的秒数。 | Any positive integer | Not Set (null) | 
| safeRowBoundsEnabled | 允许在嵌套语句中使用分页(RowBounds)。 | true,false | False | 
| mapUnderscoreToCamelCase | 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。 | true, false | False | 
| localCacheScope | MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。 | SESSION,STATEMENT | SESSION | 
| jdbcTypeForNull | 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。 | JdbcType enumeration. Most common are: NULL, VARCHAR and OTHER | OTHER | 
| lazyLoadTriggerMethods | 指定哪个对象的方法触发一次延迟加载。 | A method name list separated by commas | equals,clone,hashCode,toString | 
| defaultScriptingLanguage | 指定动态 SQL 生成的默认语言。 | A type alias or fully qualified class name. | org.apache.ibatis.scripting.xmltags.XMLDynamicLanguageDriver | 
| callSettersOnNulls | 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。注意基本类型(int、boolean等)是不能设置成 null 的。 | true,false | false | 
| logPrefix | 指定 MyBatis 增加到日志名称的前缀。 Any String | Not set | |
| logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J, LOG4J, LOG4J2, JDK_LOGGING, COMMONS_LOGGING, STDOUT_LOGGING, NO_LOGGING | Not set | 
| proxyFactory | 指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。 | CGLIB JAVASSIST | CGLIB | 
settingsAsProperties()方法
    private Properties settingsAsProperties(XNode context) {
        if (context == null) {
          return new Properties();
        }
        // 获取setting节点的name和value,并保存至Properties返回
        Properties props = context.getChildrenAsProperties();
        // Check that all settings are known to the configuration class
        // 创建Configuration的MetaClass
        MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
        // 校验Configuration中是否有setting设置的name值
        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;
    }
这里获取到setting的值,并返回Properties对象。然后做配置的name是否合法。
org.apache.ibatis.reflection.MetaClass类是保存着一个利用反射获取到的类信息,metaConfig.hasSetter(String.valueOf(key))是判断metaConfig对象中是否包含key属性。
vfsImpl()方法
    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);
            }
          }
        }
    }
该方法是解析虚拟文件系统配置,用来加载自定义虚拟文件系统的资源。类保存在Configuration.vfsImpl中。
settingsElement()方法
这个方法的作用就是将解析的settings设置到 configuration中
    private void settingsElement(Properties props) throws Exception {
        configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
        configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
        configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
        configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
        configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
        configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
        configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
        configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
        configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
        configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
        configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
        configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
        configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
        configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
        configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
        configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
        configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
        configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
        configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
        @SuppressWarnings("unchecked")
        Class<? extends TypeHandler> typeHandler = (Class<? extends TypeHandler>)resolveClass(props.getProperty("defaultEnumTypeHandler"));
        configuration.setDefaultEnumTypeHandler(typeHandler);
        configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
        configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
        configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
        configuration.setLogPrefix(props.getProperty("logPrefix"));
        @SuppressWarnings("unchecked")
        Class<? extends Log> logImpl = (Class<? extends Log>)resolveClass(props.getProperty("logImpl"));
        configuration.setLogImpl(logImpl);
        configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
    }
typeAliases 节点解析
typeAliases配置方式
    <typeAliases>
        <package name="com.ytao.main.model"/>
        // 或
        <typeAlias type="com.ytao.main.model.Student" alias="student"/>
        <typeAlias type="com.ytao.main.model.Person"/>
    </typeAliases>
该节点是配置类和别名的关系
- package节点是配置整个包下的类
- typeAlias节点是指定配置单个类,- type为必填值且为类全限定名,- alias为选填。
 配置后,是该类时,可直接使用别名。
typeAliasesElement()方法
    private void typeAliasesElement(XNode parent) {
        if (parent != null) {
          for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
              // 以 package 方式配置
              String typeAliasPackage = child.getStringAttribute("name");
              configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
            } else {
              // 以 alias 方式配置
              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);
              }
            }
          }
        }
    }
使用 package 配置
当扫描package时,获取到包名后TypeAliasRegistry.registerAliases(typeAliasPackage)
    public void registerAliases(String packageName){
        registerAliases(packageName, Object.class);
    }
    public void registerAliases(String packageName, Class<?> superType){
        ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
        // 获取 package 下所有已 .class 结尾的文件
        resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
        // 获取扫描出来的类
        Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
        for(Class<?> type : typeSet){
          // Ignore inner classes and interfaces (including package-info.java)
          // Skip also inner classes. See issue #6
          // 过滤类
          if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
            registerAlias(type);
          }
        }
    }
扫描到指定package下所有以.class结尾文件的类,并保存至Set集合中,然后遍历集合,过滤掉没有名称,接口,和底层特定类。
最后TypeAliasRegistry.registerAlias(Class<?> type)注册到别名注册器中。
    public void registerAlias(Class<?> type) {
        // 使用类的 simpleName 作为别名,也就是默认的别名命名规则
        String alias = type.getSimpleName();
        Alias aliasAnnotation = type.getAnnotation(Alias.class);
        if (aliasAnnotation != null) {
          alias = aliasAnnotation.value();
        }
        // 上面分析的最终注册的方法
        registerAlias(alias, type);
    }
通过类注册到注册器中时,如果该注册类有使用@Alias(org.apache.ibatis.type.Alias)注解,那么XML配置中配置的别名会被注解配置覆盖。
使用 typeAlias 配置
如果typeAlias的alias有设置值,使用自定名称方式注册,否则使用默认方式注册,即类的simpleName作为别名。
plugins 节点解析
plugins配置方式
    <plugins>
        // 配置自定义插件,可指定在某个点进行拦截
        <plugin interceptor="com.ytao.main.plugin.DemoInterceptor">
            // 当前插件属性
            <property name="name" value="100"/>
        </plugin>
    </plugins>
自定义插件需要实现org.apache.ibatis.plugin.Interceptor接口,同时在注解上指定拦截的方法。
pluginElement()方法
    private void pluginElement(XNode parent) throws Exception {
        if (parent != null) {
          for (XNode child : parent.getChildren()) {
            // 获取自定插件的类名
            String interceptor = child.getStringAttribute("interceptor");
            // 获取插件属性
            Properties properties = child.getChildrenAsProperties();
            // 实例化 Interceptor
            Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
            // 设置插件属性到插件中
            interceptorInstance.setProperties(properties);
            // 将插件保存在 configuration 中
            configuration.addInterceptor(interceptorInstance);
          }
        }
    }
这里取<plugin>节点的interceptor可以使用别名设置。从源码中resolveClass方法
    //
    protected Class<?> resolveClass(String alias) {
        if (alias == null) {
          return null;
        }
        try {
          return resolveAlias(alias);
        } catch (Exception e) {
          throw new BuilderException("Error resolving class. Cause: " + e, e);
        }
    }
    //
    protected Class<?> resolveAlias(String alias) {
        return typeAliasRegistry.resolveAlias(alias);
    }
    //
    public <T> Class<T> resolveAlias(String string) {
        try {
          if (string == null) {
            return null;
          }
          // issue #748
          // 将传入的 类 名称统一转换
          String key = string.toLowerCase(Locale.ENGLISH);
          Class<T> value;
          // 验证别名中是否有当前传入的key
          if (TYPE_ALIASES.containsKey(key)) {
            value = (Class<T>) TYPE_ALIASES.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);
        }
    }
以上源码为别名解析过程,其他别名的解析也是调用此方法,先去保存的别名中去找,是否有别名,如果没有就通过Resources.classForName生成实例。
objectFactory,objectWrapperFactory,reflectorFactory 节点解析
以上都是对实现类都是对MyBatis进行扩展。解析方法也类似,最后都是保存在configuration。
    // objectFactory 解析
    private void objectFactoryElement(XNode context) throws Exception {
        if (context != null) {
          String type = context.getStringAttribute("type");
          Properties properties = context.getChildrenAsProperties();
          ObjectFactory factory = (ObjectFactory) resolveClass(type).newInstance();
          factory.setProperties(properties);
          configuration.setObjectFactory(factory);
        }
    }
    // objectWrapperFactory 解析
    private void objectWrapperFactoryElement(XNode context) throws Exception {
        if (context != null) {
          String type = context.getStringAttribute("type");
          ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).newInstance();
          configuration.setObjectWrapperFactory(factory);
        }
    }
    // reflectorFactory 解析
    private void reflectorFactoryElement(XNode context) throws Exception {
        if (context != null) {
           String type = context.getStringAttribute("type");
           ReflectorFactory factory = (ReflectorFactory) resolveClass(type).newInstance();
           configuration.setReflectorFactory(factory);
        }
    }
以上为解析objectFactory,objectWrapperFactory,reflectorFactory源码,经过前面的分析后,这里比较容易看懂。
environments 节点解析
environments配置方式
    <environments default="development">
        <environment id="development">
            <!-- 事务管理 -->
            <transactionManager type="JDBC">
                <property name="prop" value="100"/>
            </transactionManager>
            <!-- 数据源 -->
            <dataSource type="UNPOOLED">
                <!-- JDBC 驱动 -->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <!-- 数据库的 url -->
                <property name="url" value="${jdbc.url}"/>
                <!-- 数据库登录名 -->
                <property name="username" value="${jdbc.username}"/>
                <!-- 数据库登录密码 -->
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
        <!-- 一个环境,对应一个environment -->
        ......
    </environments>
该节点可设置多个环境,针对不同的环境单独配置。environments的属性default是默认环境,该值对应一个environment的属性id的值。
- transactionManager为事务管理,属性- type为事务管理类型,上面的介绍的- new Configuration()有定义类型有:JDBC 和 MANAGED事务管理类型。
- dataSource是数据源,- type为数据源类型,与- transactionManager同理,可知内建的数据源类型有:JNDI,POOLED,UNPOOLED数据源类型。
environmentsElement()方法
    private void environmentsElement(XNode context) throws Exception {
        if (context != null) {
          if (environment == null) {
            environment = context.getStringAttribute("default");
          }
          for (XNode child : context.getChildren()) {
            String id = child.getStringAttribute("id");
            // 验证 id
            if (isSpecifiedEnvironment(id)) {
              // 解析 transactionManager, 并实例化 TransactionFactory
              TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
              // 解析 dataSource,并实例化 DataSourceFactory
              DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
              // 获取 dataSource
              DataSource dataSource = dsFactory.getDataSource();
              Environment.Builder environmentBuilder = new Environment.Builder(id)
                  .transactionFactory(txFactory)
                  .dataSource(dataSource);
              configuration.setEnvironment(environmentBuilder.build());
            }
          }
        }
    }
    private boolean isSpecifiedEnvironment(String id) {
        if (environment == null) {
          throw new BuilderException("No environment specified.");
        } else if (id == null) {
          throw new BuilderException("Environment requires an id attribute.");
        } else if (environment.equals(id)) {
          return true;
        }
        return false;
    }
若没有配置environment环境或环境没有给id属性,则会抛出异常,若当前id是要使用的就返回true,否则返回false。
TransactionFactory实例化过程比较简单,与创建DataSourceFactory类似。
数据源的获取
获取数据源,首先得创建DataSourceFactory,上面使用DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"))创建
    private DataSourceFactory dataSourceElement(XNode context) throws Exception {
        if (context != null) {
          String type = context.getStringAttribute("type");
          Properties props = context.getChildrenAsProperties();
          DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
          factory.setProperties(props);
          return factory;
        }
        throw new BuilderException("Environment declaration requires a DataSourceFactory.");
    }
这里就是获取到数据源得type后,利用上面所讲到得resolveClass()方法获取到DataSourceFactory。
以UNPOOLED为例,对应的DataSourceFactory实现类为UnpooledDataSourceFactory。实例化过程中就给该类的属性dataSource数据源赋值了
    /**
     * UnpooledDataSourceFactory 类
     */
    protected DataSource dataSource;
    public UnpooledDataSourceFactory() {
        this.dataSource = new UnpooledDataSource();
    }
    @Override
    public DataSource getDataSource() {
       return dataSource;
    }
UnpooledDataSource类里面有静态代码块所以数据源被加载
    /**
     * UnpooledDataSource 类
     */
    static {
        Enumeration<Driver> drivers = DriverManager.getDrivers();
        while (drivers.hasMoreElements()) {
          Driver driver = drivers.nextElement();
          registeredDrivers.put(driver.getClass().getName(), driver);
        }
    }
databaseIdProvider 节点解析
databaseIdProvider配置方式
    <databaseIdProvider type="DB_VENDOR">
        <property name="SQL Server" value="sqlserver"/>
        <property name="DB2" value="db2"/>
        <property name="Oracle" value="oracle" />
        <property name="MySQL" value="mysql"/>
    </databaseIdProvider>
    <select id="select" resultType="com.ytao.main.model.Student" databaseId="mysql">
        select
          *
        from student
    </select>
基于映射语句中的databaseId属性,可以根据不同数据库厂商执行不同的sql。
databaseIdProviderElement()方法
  private void databaseIdProviderElement(XNode context) throws Exception {
    DatabaseIdProvider databaseIdProvider = null;
    if (context != null) {
      String type = context.getStringAttribute("type");
      // 保持向后兼容
      if ("VENDOR".equals(type)) {
          type = "DB_VENDOR";
      }
      Properties properties = context.getChildrenAsProperties();
      databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
      databaseIdProvider.setProperties(properties);
    }
    Environment environment = configuration.getEnvironment();
    if (environment != null && databaseIdProvider != null) {
      String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
      configuration.setDatabaseId(databaseId);
    }
  }
根据匹配的数据库厂商类型匹配数据源databaseIdProvider.getDatabaseId(environment.getDataSource())
  @Override
  public String getDatabaseId(DataSource dataSource) {
    if (dataSource == null) {
      throw new NullPointerException("dataSource cannot be null");
    }
    try {
      return getDatabaseName(dataSource);
    } catch (Exception e) {
      log.error("Could not get a databaseId from dataSource", e);
    }
    return null;
  }
  private String getDatabaseName(DataSource dataSource) throws SQLException {
    // 根据数据源获取数据库产品名称
    String productName = getDatabaseProductName(dataSource);
    if (this.properties != null) {
      for (Map.Entry<Object, Object> property : properties.entrySet()) {
        // 判断是否包含,选择使用的数据库产品
        if (productName.contains((String) property.getKey())) {
          return (String) property.getValue();
        }
      }
      // no match, return null
      return null;
    }
    return productName;
  }
  private String getDatabaseProductName(DataSource dataSource) throws SQLException {
    Connection con = null;
    try {
      // 数据库连接
      con = dataSource.getConnection();
      // 获取连接元数据
      DatabaseMetaData metaData = con.getMetaData();
      // 获取数据库产品名称
      return metaData.getDatabaseProductName();
    } finally {
      if (con != null) {
        try {
          con.close();
        } catch (SQLException e) {
          // ignored
        }
      }
    }
  }
这里需要注意的是配置:比如使用mysql,我踩过这里的坑,这里Name为MySQL,我把y写成大写,结果匹配不上。
另外这里写个My也能匹配上,应为是使用的String.contains方法,只要包含就会符合,这里代码应该不够严谨。
typeHandlers 节点解析
typeHandlers配置方式
    <typeHandlers>
        <package name="com.ytao.main.handler"/>
        // 或
        <typeHandler javaType="java.util.Date"  jdbcType="TIMESTAMP" handler="com.ytao.main.handler.DemoDateHandler" />
    </typeHandlers>
扫描整个包或者指定类型之间的映射,javaType, jdbcType非必需,handler必填项
typeHandlerElement()方法
  private void typeHandlerElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          // 获取包名
          String typeHandlerPackage = child.getStringAttribute("name");
          // 注册包下所有的类型处理器
          typeHandlerRegistry.register(typeHandlerPackage);
        } else {
          String javaTypeName = child.getStringAttribute("javaType");
          String jdbcTypeName = child.getStringAttribute("jdbcType");
          String handlerTypeName = child.getStringAttribute("handler");
          Class<?> javaTypeClass = resolveClass(javaTypeName);
          JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
          Class<?> typeHandlerClass = resolveClass(handlerTypeName);
          if (javaTypeClass != null) {
            if (jdbcType == null) {
              typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
            } else {
              typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
            }
          } else {
            typeHandlerRegistry.register(typeHandlerClass);
          }
        }
      }
    }
  }
源码分析会根据包下所有处理器或者指定处理器进行解析,最后会根据上面分析到的type + handler和type + jdbc type + handler不同情况注册。
另外这里还有个TypeHandlerRegistry.register(Class<?> typeHandlerClass)注册类
  public void register(Class<?> typeHandlerClass) {
    // 标志是否从 MappedTypes 注解中获取 javaType 注册
    boolean mappedTypeFound = false;
    // 获取 MappedTypes 的值
    MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
    if (mappedTypes != null) {
      for (Class<?> javaTypeClass : mappedTypes.value()) {
        // 已 type + handler 的方式注册
        register(javaTypeClass, typeHandlerClass);
        // 标志已通过注解注册类型
        mappedTypeFound = true;
      }
    }
    if (!mappedTypeFound) {
      // 通过 TypeHandler 注册
      register(getInstance(null, typeHandlerClass));
    }
  }
  // 实例化
  public <T> TypeHandler<T> getInstance(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
    if (javaTypeClass != null) {
      try {
        // 获取有参构造函数
        Constructor<?> c = typeHandlerClass.getConstructor(Class.class);
        // 实例化对象
        return (TypeHandler<T>) c.newInstance(javaTypeClass);
      } catch (NoSuchMethodException ignored) {
        // ignored
      } catch (Exception e) {
        throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e);
      }
    }
    try {
      // 获取无参构造函数
      Constructor<?> c = typeHandlerClass.getConstructor();
      return (TypeHandler<T>) c.newInstance();
    } catch (Exception e) {
      throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e);
    }
  }  
  // 注册实例
  public <T> void register(TypeHandler<T> typeHandler) {
    boolean mappedTypeFound = false;
    MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
    if (mappedTypes != null) {
      for (Class<?> handledType : mappedTypes.value()) {
        register(handledType, typeHandler);
        mappedTypeFound = true;
      }
    }
    // @since 3.1.0 - try to auto-discover the mapped type
    if (!mappedTypeFound && typeHandler instanceof TypeReference) {
      try {
        TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
        register(typeReference.getRawType(), typeHandler);
        mappedTypeFound = true;
      } catch (Throwable t) {
        // maybe users define the TypeReference with a different type and are not assignable, so just ignore it
      }
    }
    if (!mappedTypeFound) {
      register((Class<T>) null, typeHandler);
    }
  }  
以上的register方法中,了解type + jdbc type + handler后,其他的register重载方法比较容易理解,其他的都是基于它上面的封装。
mappers 节点解析
mappers配置方式
    <mappers>
        <package name="com.ytao.main.mapper"/>
        // 或
        <mapper resource="mapper/studentMapper.xml"/>
        // 或
        <mapper url="file:///D:/mybatis-3-mybatis-3.4.6/src/main/resources/mapper/studentMapper.xml"/>
        // 或
        <mapper class="com.ytao.main.mapper.StudentMapper"/>
    </mappers>
可通过以上四种形式配置mappers节点,<package>和<mapper>为互斥节点。
mapperElement()方法
该方法是负责解析<mappers>节点
  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        // 如果配置 package 节点,则扫描
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          // 解析包下类Mapper接口,并注册到configuration的mapperRegistry中
          configuration.addMappers(mapperPackage);
        } else {
          // 获取mapper节点的resource,url,class属性
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          // 根据resource解析,并且url,class值必须为空,也就不能配置值。url,class同理,其它两个属性也不能配置值
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            // 通过resource获取流
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // 创建XMLMapperBuilder对象
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            // 解析映射配置文件
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            // 通过url获取流
            InputStream inputStream = Resources.getUrlAsStream(url);
            // 和resource解析方式一样,创建XMLMapperBuilder对象,然后解析映射配置文件
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            // 加载class属性的接口
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            // 将接口注册到configuration的mapperRegistry中
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }
<package>的包扫描到的类,然后单个单个注册到configuration的mapperRegistry中,这里和<mapper>使用class属性是一样逻辑。
解析package方式
  // Configuration 中定义了
  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  /**
   * 步骤一
   * 该函数于 Configuration 中
   */
  public void addMappers(String packageName) {
    // mapperRegistry定义在Configuration中的一个属性
    mapperRegistry.addMappers(packageName);
  }
  /**
   * 步骤二
   * 该函数于 MapperRegistry 中
   */
  public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
  }
  /**
   * 步骤三
   * 该函数于 MapperRegistry 中
   */
  public void addMappers(String packageName, Class<?> superType) {
    // 通过 ResolverUtil 获取包下的类
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
      // 遍历获取到的类,注册到 MapperRegistry
      addMapper(mapperClass);
    }
  }   
  /**
   * 步骤四
   * 该函数于 MapperRegistry 中
   */
  public <T> void addMapper(Class<T> type) {
    // mapper 类为 interface 接口
    if (type.isInterface()) {
      // 判断当前class是否已经注册过
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      // 校验是否加载完成
      boolean loadCompleted = false;
      try {
        // 保存 mapper 接口和 MapperProxyFactory 之间的映射
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        // 解析xml和注解
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        // 标志加载完成
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }   
解析mapper的class属性
  // 该函数于 Configuration 中
  public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }
  // ... 这里调用上面的【步骤四】
这两中方式是直接注册接口到mapperRegistry,另外两种是解析xml的方式就是获取映射文件的namespace,再注册进来,XMLMapperBuilder是负责解析映射配置文件的类,今后会单独详细分析这个类,这里不展开讲。
这里对XMLConfigBuilder解析配置文件到此分析完,本文对配置文件解析的流程大致了解流程和原理。相信遇到配置问题异常,大致能排查到根本原因。
个人博客: https://ytao.top
我的公众号 ytao

MyBatis启动之XMLConfigBuilder解析配置文件(二)的更多相关文章
- SpringBoot启动流程原理解析(二)
		在上一章我们分析了SpingBoot启动流程中实例化SpingApplication的过程. return new SpringApplication(primarySources).run(args ... 
- mybatis源码-解析配置文件(二)之解析的流程
		目录 1. 简介 2. 配置文件解析流程分析 2.1 调用 2.2 解析的目的 2.3 XML 解析流程 2.3.1 build(parser) 2.3.2 new XMLConfigBuilder( ... 
- mybatis源码-解析配置文件(四)之配置文件Mapper解析
		在 mybatis源码-解析配置文件(三)之配置文件Configuration解析 中, 讲解了 Configuration 是如何解析的. 其中, mappers作为configuration节点的 ... 
- mybatis源码-解析配置文件(三)之配置文件Configuration解析
		目录 1. 简介 1.1 系列内容 1.2 适合对象 1.3 本文内容 2. 配置文件 2.1 mysql.properties 2.2 mybatis-config.xml 3. Configura ... 
- Mybatis之Configuration初始化(配置文件.xml的解析)
		源码解读第一步我觉着应该从Mybatis如何解析配置文件开始. 1.先不看跟Spring集成如何解析,先看从SqlSessionFactoryBuilder如果解析的. String resouce ... 
- myBatis源码解析-配置文件解析(6)
		前言 本来打算此次写一篇关于SqlSession的解析,但发现SqlSession涉及的知识太多.所以先结合mybatis配置文件(我们项目中常写的如mybatisConfig.xml),来分析下my ... 
- Mybatis学习之自定义持久层框架(三) 自定义持久层框架:读取并解析配置文件
		前言 前两篇文章分别讲解了JDBC和Mybatis的基本知识,以及自定义持久层框架的设计思路,从这篇文章开始,我们正式来实现一个持久层框架. 新建一个项目 首先我们新建一个maven项目,将其命名为I ... 
- mybatis源码-解析配置文件(四-1)之配置文件Mapper解析(cache)
		目录 1. 简介 2. 解析 3 StrictMap 3.1 区别HashMap:键必须为String 3.2 区别HashMap:多了成员变量 name 3.3 区别HashMap:key 的处理多 ... 
- mybatis源码-解析配置文件(一)之XML的DOM解析方式
		目录 简介 Java 中 XML 文件解析 解析方式 DOM 解析 XML 新建 XML 文件 DOM 操作相关类 Java 读取 XML 文件 一起学 mybatis @ 简介 在之前的文章< ... 
随机推荐
- (转)linux内核虚拟文件系统浅析【转】
			转自:https://www.cnblogs.com/woainilsr/p/3590716.html 转自http://hi.baidu.com/_kouu/item/4e9db8758032824 ... 
- Java八大排序之插入排序
			插入排序 也可叫直接插入排序,该算法的思路是:初始可认为文件中的第1个记录已排好序,然后将第2个到第n个记录依次插入到已排序的记录组成的文件中. 步骤: 假设有一组数组为(数组下标0—n-1): ar ... 
- ProxyPool  代理
			ProxyPool:https://github.com/yucaifuyoyo/ProxyPool github上一个开源项目的proxypool添加一些免费代理IP网站 1.https://www ... 
- zz2017-2018年AI技术前沿进展与趋势
			2017年AI技术前沿进展与趋势 人工智能最近三年发展得如火如荼,学术界.工业界.投资界各方一起发力,硬件.算法与数据共同发展,不仅仅是大型互联网公司,包括大量创业公司以及传统行业的公司都开始涉足人工 ... 
- springboot配置spring security 静态资源不能访问
			在springboot整合spring security 过程中曾遇到下面问题:(spring boot 2.0以上版本 spring security 5.x (spring secur ... 
- 如何禁用Antimalware Service Executable
			有时发现风扇呼呼地转,查看任务管理器,发现其中antimalware service executable占用了大量的CPU和内存. 这是由于Windows Defender软件导致的,可以用如下方法 ... 
- openLayers绘制静态底图
			由于项目需要,需要是使用openlayers框架,于是开始安利一波openlayers,可以点击 https://openlayers.org/ 进入他的官网下载相关资源和案例 学习的过程总是慢慢 ... 
- Flask-SQLAlchemy相关与Flask-Migrate相关
			数据库按照一定规则保存应用数据,应用再发起查询,取回所需的数据.Web应用最常使用基于关系模型的数据库,这种数据库也称为SQL数据库,因为它们使用结构化查询语言SQL.不过近年来文档数据库和键 ... 
- Chrome操作技巧
			好用的插件: 如果你用 Chrome 浏览器,这8款插件一定要用! - 知乎 沙拉查词: 集合各大翻译,详细好用权威 Simple Allow Copy: 开启被网页屏蔽的自由复制功能 Qui ... 
- 论文阅读: Infrastructure-Based Calibration of a Multi-Camera Rig
			Abstract 在线标定很重要. 但是目前的方法都计算量都很高. 我们的方案不需要标定板之类的东西. 我们的方案不需要假设相机有重合的FOV,也不需要任何的初始猜测. 当相机模组行驶穿过之前建过地图 ... 
