mybatis源码分析(二)------------配置文件的解析

依然范特西╮ 提交于 2021-01-13 08:34:30

这篇文章中,我们将讲解配置文件中 properties,typeAliases,settings和environments这些节点的解析过程。

一 properties的解析

 private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
//解析properties的子节点,并将这些子节点内容转为属性对象Properties Properties defaults
= context.getChildrenAsProperties();
//获取properties节点中resource的属性值 String resource
= context.getStringAttribute("resource");
//获取properties节点中url的属性值 String url
= context.getStringAttribute("url");
//resource和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) {
// 从文件系统中加载并解析属性文件,此时会覆盖在xml中配置的properties子节点的同名key-value defaults.putAll(Resources.getResourceAsProperties(resource)); }
else if (url != null) {
// 从url中解析并加载属性文件 defaults.putAll(Resources.getUrlAsProperties(url)); }
// 获取configuration中已经定义的属性对象Properties Properties vars
= configuration.getVariables(); if (vars != null) {
// configuration中的key-value会覆盖上面两种情况中的key-value defaults.putAll(vars); }
// 将解析出的内容set到parser中 parser.setVariables(defaults);
// 将解析出的内容set到configuration中,configuration会装载所解析的配置文件中所有的节点内容,后面会使用到这个对象 configuration.setVariables(defaults); } }

 二 settings的解析

先看下settings的配置,下面只是settings配置中的一部分:

<!-- settings是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 -->
    <settings>
        <!-- 该配置影响的所有映射器中配置的缓存的全局开关。默认值true -->
        <setting name="cacheEnabled" value="true"/>
        <!--延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。默认值false  -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 是否允许单一语句返回多结果集(需要兼容驱动)。 默认值true -->
        <setting name="multipleResultSetsEnabled" value="true"/>
    </settings>

源码部分:

 private void settingsElement(XNode context) throws Exception {
    if (context != null) {
// 获取settings节点下所有的子节点信息,然后封装成Properties对象 Properties props
= context.getChildrenAsProperties(); // Check that all settings are known to the configuration class
// 获取Configuration的元信息对象
MetaClass metaConfig = MetaClass.forClass(Configuration.class); for (Object key : props.keySet()) {
// 检测Configuration中是否存在相关的属性,如果不存在,那么抛出异常
if (!metaConfig.hasSetter(String.valueOf(key))) { throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive)."); } }
//以下是把获取到的setting信息封装到Configuration中,所以才需要上面的检测 configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty(
"autoMappingBehavior", "PARTIAL"))); 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"), true)); 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.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"))); configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false)); configuration.setLogPrefix(props.getProperty("logPrefix")); configuration.setLogImpl(resolveClass(props.getProperty("logImpl"))); configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory"))); } }

 二 typeAliases的解析

先看下在配置文件中的写法:

    <typeAliases>
        <typeAlias alias="goods" type="com.yht.mybatisTest.entity.Goods"/>
    </typeAliases>

源码部分:

 private void typeAliasesElement(XNode parent) {
    if (parent != null) {
// 循环处理typeAliases下所有的子节点
for (XNode child : parent.getChildren()) {
// 这是针对子节点是package的处理
if ("package".equals(child.getName())) { String typeAliasPackage = child.getStringAttribute("name"); configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage); } else {
// 如果子节点是typeAlias String alias
= child.getStringAttribute("alias"); //获取alias的属性值 String type = child.getStringAttribute("type"); //获取type的属性值 try {
// 获取type对应的类型 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); } } } } }

进入registerAlias方法:

  public void registerAlias(Class<?> type) {
// 如果在配置文件中没有配置alias属性,这里会获取type的类名 String alias
= type.getSimpleName();
// 获取注解上的别名 Alias aliasAnnotation
= type.getAnnotation(Alias.class); if (aliasAnnotation != null) { alias = aliasAnnotation.value(); }
// 进入此方法 registerAlias(alias, type); }
  public void registerAlias(String alias, Class<?> value) {
    if (alias == null) throw new TypeException("The parameter alias cannot be null");
// 将别名转为小写 String key
= alias.toLowerCase(Locale.ENGLISH); // issue #748
// 如果TYPE_ALIASES已经存在该别名,并且对应的类型不为空,同时已经存在的类型不等于将要注册的类型value,那么抛出异常 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() + "'."); }
// 将别名和对应的全限定名放入到HashMap中,TYPE——ALIASES是一个HashMap TYPE_ALIASES.put(key, value); }
接着看下Mybatis内部常见别名注册:
 private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();

  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);
    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);
    registerAlias("_long", long.class);
    registerAlias("_short", short.class);
    registerAlias("_int", int.class);
    registerAlias("_integer", int.class);
    registerAlias("_double", double.class);
    registerAlias("_float", float.class);
    registerAlias("_boolean", boolean.class);

    registerAlias("_byte[]", byte[].class);
    registerAlias("_long[]", long[].class);
    registerAlias("_short[]", short[].class);
    registerAlias("_int[]", int[].class);
    registerAlias("_integer[]", int[].class);
    registerAlias("_double[]", double[].class);
    registerAlias("_float[]", float[].class);
    registerAlias("_boolean[]", boolean[].class);

    registerAlias("date", Date.class);
    registerAlias("decimal", BigDecimal.class);
    registerAlias("bigdecimal", BigDecimal.class);
    registerAlias("biginteger", BigInteger.class);
    registerAlias("object", Object.class);

    registerAlias("date[]", Date[].class);
    registerAlias("decimal[]", BigDecimal[].class);
    registerAlias("bigdecimal[]", BigDecimal[].class);
    registerAlias("biginteger[]", BigInteger[].class);
    registerAlias("object[]", Object[].class);

    registerAlias("map", Map.class);
    registerAlias("hashmap", HashMap.class);
    registerAlias("list", List.class);
    registerAlias("arraylist", ArrayList.class);
    registerAlias("collection", Collection.class);
    registerAlias("iterator", Iterator.class);

    registerAlias("ResultSet", ResultSet.class);
  }

四 environments的解析

在mybatis中事务管理器和数据源是在environments中配置的,配置如下:

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="${driver}" />
                <property name="url" value="${url}" />
                <property name="username" value="${username}" />
                <property name="password" value="${password}" />
            </dataSource>
        </environment>
    </environments>

对应的源码如下:

  private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
      if (environment == null) {
// 获取environments中default的属性值 environment
= context.getStringAttribute("default"); } for (XNode child : context.getChildren()) {
// 获取id的属性 String id
= child.getStringAttribute("id");
// 判断子节点id的属性和父节点environments的default属性是否相同,相同返回true,否则返回false
if (isSpecifiedEnvironment(id)) {
// 解析transactionManager节点 TransactionFactory txFactory
= transactionManagerElement(child.evalNode("transactionManager"));
// 解析DataSource节点 DataSourceFactory dsFactory
= dataSourceElement(child.evalNode("dataSource"));
// 获取DataSource对象 DataSource dataSource
= dsFactory.getDataSource(); Environment.Builder environmentBuilder = new Environment.Builder(id) .transactionFactory(txFactory) .dataSource(dataSource);
// 构建environment对象并设置到configuration中 configuration.setEnvironment(environmentBuilder.build()); } } } }

现在还有一个问题值得思考:对于<property name="driver" value="${driver}" /> 中,value值是如何被赋值的?接下里,我们跟踪源码进行分析:

进入XMLConfigBuilder类的environmentsElement方法,有这么一行代码: DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));是解析DataSource节点的,上述的过程就是在这个方法中发生的,进入该方法:

  private DataSourceFactory dataSourceElement(XNode context) throws Exception {
    if (context != null) {
// 获取DataSource的type属性 String type
= context.getStringAttribute("type");
// 解析DataSource所有的子节点信息并封装为属性对象Properties,进入该方法 Properties props
= context.getChildrenAsProperties(); DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance(); factory.setProperties(props); return factory; } throw new BuilderException("Environment declaration requires a DataSourceFactory."); }

进入context.getChildrenAsProperties();方法:

  public Properties getChildrenAsProperties() {
    Properties properties = new Properties();
//进入getChildren()方法
for (XNode child : getChildren()) { String name = child.getStringAttribute("name"); String value = child.getStringAttribute("value"); if (name != null && value != null) { properties.setProperty(name, value); } } return properties; } public List<XNode> getChildren() { List<XNode> children = new ArrayList<XNode>(); NodeList nodeList = node.getChildNodes(); if (nodeList != null) { for (int i = 0, n = nodeList.getLength(); i < n; i++) { Node node = nodeList.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) {
// 对DataSource中所有的子节点构建成XNode对象,并放到children集合中,进入这个构造方法 children.add(
new XNode(xpathParser, node, variables)); } } } return children; }
// 这个是DataSource的子节点的构造方法,具体的说就是把 <property name="driver" value="${driver}" /> 构造成一个XNode节点
public XNode(XPathParser xpathParser, Node node, Properties variables) { this.xpathParser = xpathParser; this.node = node; this.name = node.getNodeName(); this.variables = variables;
//value赋值过程在这个方法中
this.attributes = parseAttributes(node); this.body = parseBody(node); }
  private Properties parseAttributes(Node n) {
    Properties attributes = new Properties();
// 获取子节点的所有属性 如:[name="driver", value="${driver}"] NamedNodeMap attributeNodes
= n.getAttributes(); if (attributeNodes != null) { for (int i = 0; i < attributeNodes.getLength(); i++) { Node attribute = attributeNodes.item(i);
// 对${dirver}的解析赋值在parse方法中 String value
= PropertyParser.parse(attribute.getNodeValue(), variables); attributes.put(attribute.getNodeName(), value); } } return attributes; }
// 根据varibles中已经存储的值,对给定的${driver},在varibles中找到driver对应的真实值,并进行替换
public static String parse(String string, Properties variables) { VariableTokenHandler handler = new VariableTokenHandler(variables); GenericTokenParser parser = new GenericTokenParser("${", "}", handler); return parser.parse(string); }

 五 TypeHandler的解析

当向数据库中存储或者取出数据时,需要将数据库中的字段类型和java类型做一个转换,比如从数据库中取出的CHAR类型,转换为java中的String类型,这个功能就委托给类型处理器TypeHandler来处理。

mybatis已经提供了一些常见的类型处理器,如StringTypeHandler,ArrayTypeHandler,LongTypeHandler等,能够满足大多数的开发需求。对于某些特殊的需求,我们也可以自定义类型处理器,需要继承

BaseTypeHandler这个抽象类。下面我们自定义一个MyStringTypeHandler类型处理器,用于扩展StringTypeHandler的功能:

package com.yht.mybatisTest.typeHandlers;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

/**
 * @author chenyk
 * @date 2018年8月22日
 */

public class MyStringTypeHandler extends BaseTypeHandler<String>{

     @Override
      public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
          throws SQLException {
          System.out.println("新增逻辑");
          ps.setString(i, parameter);
      }

      @Override
      public String getNullableResult(ResultSet rs, String columnName)
          throws SQLException {
          System.out.println("新增逻辑");
          return rs.getString(columnName);
      }

      @Override
      public String getNullableResult(ResultSet rs, int columnIndex)
          throws SQLException {
          System.out.println("新增逻辑");
        return rs.getString(columnIndex);
      }

      @Override
      public String getNullableResult(CallableStatement cs, int columnIndex)
          throws SQLException {
          System.out.println("新增逻辑");
          return cs.getString(columnIndex);
      }

}

类型处理器在配置文件中有两种配置方法:

    <!-- 手动配置 -->
    <typeHandlers>
        <typeHandler jdbcType="CHAR" javaType="String" handler="com.yht.mybatisTest.typeHandlers.MyStringTypeHandler" />
    </typeHandlers>
    
    <!-- 自动扫描 -->
    <typeHandlers>
        <package name="com.yht.mybatisTest.typeHandlers"/>
    </typeHandlers>

看源码部分:

private void typeHandlerElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
// 扫描指定的包 然后注册TypeHandler String typeHandlerPackage
= child.getStringAttribute("name"); typeHandlerRegistry.register(typeHandlerPackage); } else {
// 获取javaType属性值,jdbcType属性值,handler属性值,然后注册TypeHandler 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); } } } } }

根据不同的情况,注册TypeHandler共有四种方法,这里我们选一种进行分析:typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);

private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
    if (javaType != null) {
      Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
      if (map == null) {
        map = new HashMap<JdbcType, TypeHandler<?>>();
// 存储JavaType到Map<JdbcType,TypeHandler>的映射 TYPE_HANDLER_MAP.put(javaType, map); } map.put(jdbcType, handler);
if (reversePrimitiveMap.containsKey(javaType)) { register(reversePrimitiveMap.get(javaType), jdbcType, handler); } }
//存储所有的TypeHandler ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler); }

mybatis内置的TypeHandler有哪些呢?贴出源码:

private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);
  private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new HashMap<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<?>>();

  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(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(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());

    // issue #273
    register(Character.class, new CharacterTypeHandler());
    register(char.class, new CharacterTypeHandler());
  }

到现在为止,对于配置文件中常见节点的解析过程做了分析,接下来在下一篇文章中我们继续对映射文件的解析过程进行讲解。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!