mybatis源码配置文件解析之二:解析settings标签 mybatis源码配置文件解析之一:解析properties标签

前端之家收集整理的这篇文章主要介绍了mybatis源码配置文件解析之二:解析settings标签 mybatis源码配置文件解析之一:解析properties标签前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

在前边的博客中分析了mybatis解析properties标签,《mybatis源码配置文件解析之一:解析properties标签》。下面来看解析settings标签的过程。

一、概述

在mybatis的核心配置文件(mybatis-config.xml)文件中,有关于settings标签的配置,如下

<settings>
        <!-- 设置日志输出为LOG4J -->
        <setting name="logImpl" value="STDOUT_LOGGING" />
        <!--将以下画线方式命名的数据库列映射到 Java 对象的驼峰式命名属性中-->
        <setting name= "mapUnderscoreToCamelCase" value="true" />
    </settings>

上面只简单的给出settings标签的配置,settings标签配置在<configuration>标签中,是<configuration>标签的子标签。在settings标签中可以配置setting子标签,上面是我的一个配置,是以name-value键值对的放式进行配置。这里有个问题setting标签中的name怎么配置,共有多少配置?

二、详述

上面,看到了settings标签的配置方式,下面看其解析过程,在XMLConfigBuilder类中的parseConfiguration方法中有关于该标签的解析,

private@H_301_19@ void@H_301_19@ parseConfiguration(XNode root) {
    @H_301_19@try@H_301_19@ {
      @H_301_19@//@H_301_19@issue #117 read properties first
      @H_301_19@解析properties标签    @H_301_19@
      propertiesElement(root.evalNode("properties"));
      @H_301_19@解析settings标签,1、把<setting>标签解析为Properties对象@H_301_19@
      Properties settings = settingsAsProperties(root.evalNode("settings"/*@H_301_19@2、对<settings>标签中的<setting>标签中的内容进行解析,这里解析的是<setting name="vfsImpl" value=",">
      * VFS是mybatis中用来表示虚拟文件系统的一个抽象类,用来查找指定路径下的资源。上面的key为vfsImpl的value可以是VFS的具体实现,必须
      * 是权限类名,多个使用逗号隔开,如果存在则设置到configuration中的vfsImpl属性中,如果存在多个,则设置到configuration中的仅是最后一个
      * @H_301_19@*/@H_301_19@
      loadCustomVfs(settings);
      @H_301_19@解析别名标签,例<typeAlias alias="user" type="cn.com.bean.User"/>@H_301_19@
      typeAliasesElement(root.evalNode("typeAliases"解析插件标签@H_301_19@
      pluginElement(root.evalNode("plugins"解析objectFactory标签,此标签的作用是mybatis每次创建结果对象的新实例时都会使用ObjectFactory,如果不设置
      @H_301_19@则默认使用DefaultObjectFactory来创建,设置之后使用设置的@H_301_19@
      objectFactoryElement(root.evalNode("objectFactory"解析objectWrapperFactory标签@H_301_19@
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"解析reflectorFactory标签@H_301_19@
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      @H_301_19@ read it after objectFactory and objectWrapperFactory issue #631
      @H_301_19@解析environments标签@H_301_19@
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode(@H_301_19@"databaseIdProvider"));
      typeHandlerElement(root.evalNode(@H_301_19@"typeHandlers"解析<mappers>标签@H_301_19@
      mapperElement(root.evalNode("mappers"));
    } @H_301_19@catch@H_301_19@ (Exception e) {
      @H_301_19@throw@H_301_19@ new@H_301_19@ BuilderException("Error parsing sql Mapper Configuration. Cause: " + e,e);
    }
  }@H_301_19@

上面便是parseConfiguration方法,在此方法中下面的方法对settings进行了解析,

解析settings标签,1、把<setting>标签解析为Properties对象@H_301_19@
      Properties settings = settingsAsProperties(root.evalNode("settings"));

调用settingsAsProperties方法,从方法名中可以看出要把settings标签中的内容解析到Proerties对象中,因为settings标签中是name-value的配置,刚好解析到Properties中以键值对的形式存储。下面是settingsAsProperties方法

private@H_301_19@ Properties settingsAsProperties(XNode context) {
    @H_301_19@if@H_301_19@ (context == null@H_301_19@) {
      @H_301_19@return@H_301_19@ new@H_301_19@ Properties();
    }
    @H_301_19@把<setting name="" value="">标签解析为Properties对象@H_301_19@
    Properties props = context.getChildrenAsProperties();
    @H_301_19@ Check that all settings are known to the configuration class@H_301_19@
    MetaClass MetaConfig = MetaClass.forClass(Configuration.class@H_301_19@,localReflectorFactory);
    @H_301_19@如果获取的配置的<setting name="" value="">信息,name不在MetaConfig中,则会抛出异常
    @H_301_19@这里MetaConfig中的信息是从Configuration类中解析出来的,包含set方法属性
    @H_301_19@所以在配置<setting>标签的时候,其name值可以参考configuration类中的属性,配置为小写@H_301_19@
    for@H_301_19@ (Object key : props.keySet()) {
        @H_301_19@MetaConfig的relector中的setMethods中判断是否存在该属性,setMethods中存储的是可写的属性,
        @H_301_19@所以这里要到setMethods中进行判断@H_301_19@
      if@H_301_19@ (!MetaConfig.hasSetter(String.valueOf(key))) {
        @H_301_19@new@H_301_19@ BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
      }
    }
    @H_301_19@return@H_301_19@ props;
  }@H_301_19@

1、解析子标签

解析子标签也就是settings标签中的setting标签,使用下面的方法进行解析,

把<setting name="" value="">标签解析为Properties对象@H_301_19@
    Properties props = context.getChildrenAsProperties();

调用了getChildrenAsProperties方法

 public@H_301_19@ Properties getChildrenAsProperties() {
    Properties properties @H_301_19@=  Properties();
    @H_301_19@ (XNode child : getChildren()) {
      String name @H_301_19@= child.getStringAttribute("name");
      String value @H_301_19@= child.getStringAttribute("value");
      @H_301_19@if@H_301_19@ (name != null@H_301_19@ && value != ) {
        properties.setProperty(name,value);
      }
    }
    @H_301_19@ properties;
  }@H_301_19@

方法就是解析<settings></settings>标签中的<setting></setting>标签,取出标签中的name和value属性,存储到Properties对象中且返回。

我们再看上面的settingsAsProperties方法调用上述getChildrenAsProperties方法获得Properties对象后又进行了其他操作。

2、校验setting标签中的name值是否存在

2.1、获得setting标签中的所有name值

在本文开篇提到一个问题,setting标签中的name值怎么配置,答案是可以参考mybatis的官方文档,在官方文档中有详细的解释,再有就是分析源码,继续往下看。

在settingsAsProperties方法中看下面一行代码

class@H_301_19@,localReflectorFactory);

上面这行代码就解析了setting标签中的name可以配置的所有值。再看代码上的注释,是不是豁然开朗。该方法有两个参数,一个是Configuration.class,一个是localReflectorFactory,看localReflectorFactory,

final@H_301_19@ ReflectorFactory localReflectorFactory = new@H_301_19@ DefaultReflectorFactory();

使用了DefaultReflectorFactory,看其默认构造方法

默认构造方法仅初始化了classCacheEnabled和relectorMap两个属性。后过来继续看MetaClass.forClass方法

public@H_301_19@ static@H_301_19@ MetaClass forClass(Class<?> type,ReflectorFactory reflectorFactory) {
    @H_301_19@ MetaClass(type,reflectorFactory);
  }@H_301_19@

方法返回的是一个MetaClass的对象,

private@H_301_19@ MetaClass(Class<?>this@H_301_19@.reflectorFactory = reflectorFactory;
    @H_301_19@this@H_301_19@.reflector = reflectorFactory.findForClass(type);
  }@H_301_19@

重点看reflectorFactory.findForClass方法,这里reflectorFactory是DefaultReflectorFactory的一个实例。下面是DefaultReflectorFactory的findForClass方法

@Override
  @H_301_19@public@H_301_19@ Reflector findForClass(Class<?> type) {
    @H_301_19@if@H_301_19@ (classCacheEnabled) {
            @H_301_19@ synchronized (type) removed see issue #461@H_301_19@
      Reflector cached = reflectorMap.get(type);
      @H_301_19@if@H_301_19@ (cached == ) {
        cached @H_301_19@=  Reflector(type);
        reflectorMap.put(type,cached);
      }
      @H_301_19@ cached;
    } @H_301_19@else@H_301_19@ Reflector(type);
    }
  }@H_301_19@

上面方法中,重点看new Reflector(type)这句方法

public@H_301_19@ Reflector(Class<?> clazz) {
    type @H_301_19@= clazz;
    @H_301_19@解析默认的构造方法,及无参构造方法@H_301_19@
    addDefaultConstructor(clazz);
    @H_301_19@解析clazz中的get方法,这里的clazz指的是Configuration.class@H_301_19@
    addGetMethods(clazz);
    @H_301_19@解析clazz中的set方法,这里的clazz指的是Configuration.class@H_301_19@
    addSetMethods(clazz);
    addFields(clazz);
    readablePropertyNames @H_301_19@= getMethods.keySet().toArray( String[getMethods.keySet().size()]);
    writeablePropertyNames @H_301_19@= setMethods.keySet().toArray( String[setMethods.keySet().size()]);
    @H_301_19@ (String propName : readablePropertyNames) {
      caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH),propName);
    }
    @H_301_19@ (String propName : writeablePropertyNames) {
      caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH),propName);
    }
  }@H_301_19@

方法完成的功能是解析clazz(包含其父类)的构造方法、getXX方法、setXX方法、字段,通过一个类的Class对象获取

addDefaultConstructor(clazz)如下,

void@H_301_19@ addDefaultConstructor(Class<?> clazz) {
      @H_301_19@获得该类的声明的构造方法@H_301_19@
    Constructor<?>[] consts = clazz.getDeclaredConstructors();
    @H_301_19@对构造方法进行循环@H_301_19@
    for@H_301_19@ (Constructor<?> constructor : consts) {
        @H_301_19@判断构造方法的参数是否为0,为0代表为默认的无参构造方法@H_301_19@
      if@H_301_19@ (constructor.getParameterTypes().length == 0) {
          @H_301_19@如果是私有的(修饰符为private),这里需要设置可见。@H_301_19@
         (canAccessPrivateMethods()) {
          @H_301_19@ {
            constructor.setAccessible(@H_301_19@true@H_301_19@);
          } @H_301_19@ (Exception e) {
            @H_301_19@ Ignored. This is only a final precaution,nothing we can do.@H_301_19@
          }
        }
        @H_301_19@ (constructor.isAccessible()) {
          @H_301_19@this@H_301_19@.defaultConstructor = constructor;
        }
      }
    }
  }@H_301_19@

上面方法获得传入的Class对象所以构造方法,把默认的无参构造方法赋给defaultConstructor。

addGetMethods(clazz)如下,

void@H_301_19@ addGetMethods(Class<?> cls) {
    Map@H_301_19@<String,List<Method>> conflictingGetters = new@H_301_19@ HashMap<String,List<Method>>();
    @H_301_19@使用反射的放上获得cls的所有方法@H_301_19@
    Method[] methods = getClassMethods(cls);
    @H_301_19@把所有的方法放入conflictingGetters中,key为属性名,value为List<Method>@H_301_19@
     (Method method : methods) {
        @H_301_19@方法的参数大于0,则结束本次循环,因为这里解析的是get方法,get方法默认不应该有参数@H_301_19@
      if@H_301_19@ (method.getParameterTypes().length > 0) {
        @H_301_19@continue@H_301_19@;
      }
      String name @H_301_19@= method.getName();
      @H_301_19@如果以get或is开头,且方法名称分别大于3和2,则说明是get方法@H_301_19@
      if@H_301_19@ ((name.startsWith("get") && name.length() > 3)
          @H_301_19@|| (name.startsWith("is") && name.length() > 2)) {
          @H_301_19@通过方法名转化为属性名,如,getUserName--userName@H_301_19@
        name = PropertyNamer.methodToProperty(name);
        
        addMethodConflict(conflictingGetters,name,method);
      }
    }
@H_301_19@

        /**处理一个属性多个get方法的情况,即conflictingGetter方法中一个key对应的value的长度大于1的情况,如下
         *key propertyName
         *value list<Method> 其长度大于1
         */ 

        resolveGetterConflicts(conflictingGetters);@H_301_19@

  }@H_301_19@

获取所有以get和is开头的方法调用addMethodConflict方法,这里的方法名直译过来是添加冲突的方法,这里冲突怎么理解,我们看addMethodConflict方法

void@H_301_19@ addMethodConflict(Map<String,1)"> conflictingMethods,String name,Method method) {
      @H_301_19@根据字段名取方法@H_301_19@
    List<Method> list = conflictingMethods.get(name);
    @H_301_19@if@H_301_19@ (list == ) {
      list @H_301_19@= new@H_301_19@ ArrayList<Method>();
      conflictingMethods.put(name,list);
    }
    list.add(method);
  }@H_301_19@

这里是根据get和is开头的方法获取属性名作为键值,并且使用list作为value进行存储,为什么使用list那,我们看下面的方法

 getUser(){}
@H_301_19@ User getuser(){}
@H_301_19@public@H_301_19@ List<User> getUser(){}
public void getUser(String id){}

上面三个方法都会以user为键进行存储,但是其方法名是一样的,所以这里要存储为list,即存储多个Method对象。

我们知道一个字段的属性的get或set方法,不可能出现上面的情况,所以针对上面的情况需要做处理,这里调用resolveGetterConflicts(conflicttingGetters),

void@H_301_19@ resolveGetterConflicts(Map<String,1)"> conflictingGetters) {
      @H_301_19@遍历conflictingGetters@H_301_19@
    for@H_301_19@ (Entry<String,1)"> entry : conflictingGetters.entrySet()) {
      Method winner @H_301_19@= ;
      String propName @H_301_19@= entry.getKey();
      @H_301_19@循环value这里value是一个List<Method>类型@H_301_19@
       (Method candidate : entry.getValue()) {
        @H_301_19@if@H_301_19@ (winner == ) {
          winner @H_301_19@= candidate;
          @H_301_19@;
        }
        @H_301_19@获得get方法的返回值类型@H_301_19@
        Class<?> winnerType = winner.getReturnType();
        Class@H_301_19@<?> candidateType = candidate.getReturnType();
        @H_301_19@如果winnerType和candidateType相等,@H_301_19@
         (candidateType.equals(winnerType)) {
          @H_301_19@boolean@H_301_19@..equals(candidateType)) {
            @H_301_19@ ReflectionException(
                @H_301_19@"Illegal overloaded getter method with ambiguous type for property "
                    + propName + " in class " + winner.getDeclaringClass()
                    @H_301_19@+ ". This breaks the JavaBeans specification and can cause unpredictable results."else@H_301_19@ if@H_301_19@ (candidate.getName().startsWith("is")) {
            winner @H_301_19@= candidate;
          }
        } @H_301_19@ (candidateType.isAssignableFrom(winnerType)) {
          @H_301_19@ OK getter type is descendant@H_301_19@
        }  (winnerType.isAssignableFrom(candidateType)) {
          winner @H_301_19@= candidate;
        } @H_301_19@ {
          @H_301_19@ ReflectionException(
              @H_301_19@"Illegal overloaded getter method with ambiguous type for property "
                  + propName + " in class " + winner.getDeclaringClass()
                  @H_301_19@+ ". This breaks the JavaBeans specification and can cause unpredictable results.");
        }
      }
      addGetMethod(propName,winner);
    }
  }@H_301_19@

上面的方法处理了上面提到的一个属性存在多个get方法的情况,最后调用addGetMethod方法

 addGetMethod(String name,Method method) {
    @H_301_19@ (isValidPropertyName(name)) {
      getMethods.put(name,@H_301_19@ MethodInvoker(method));
      Type returnType @H_301_19@= TypeParameterResolver.resolveReturnType(method,type);
      getTypes.put(name,typeToClass(returnType));
    }
  }@H_301_19@

上面的方法把信息放到了getMethods和getTyps中,分别存储了get方法和返回值。

 

上面分析了Reflector中的addGetMethods方法,addSetMethods方法和其处理过程类似,最终把set方法和返回值放到了setMethods和setTypes中。

addFileds(clazz)方法即是处理clazz中的属性

void@H_301_19@ addFields(Class<?> clazz) {
    Field[] fields @H_301_19@= clazz.getDeclaredFields();
    @H_301_19@ (Field field : fields) {
      @H_301_19@ (canAccessPrivateMethods()) {
        @H_301_19@ {
          field.setAccessible(@H_301_19@);
        } @H_301_19@ (Exception e) {
          @H_301_19@        }
      }
      @H_301_19@ (field.isAccessible()) {
          @H_301_19@检查是否存在set方法,如果不存在添加该field@H_301_19@
        setMethods.containsKey(field.getName())) {
          @H_301_19@ issue #379 - removed the check for final because JDK 1.5 allows
          @H_301_19@ modification of final fields through reflection (JSR-133). (JGB)
          @H_301_19@ pr #16 - final static can only be set by the classloader@H_301_19@
          int@H_301_19@ modifiers = field.getModifiers();
          @H_301_19@if@H_301_19@ (!(Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers))) {
            addSetField(field);
          }
        }
        @H_301_19@检查是否存在get方法,如果不存在添加该field@H_301_19@
        getMethods.containsKey(field.getName())) {
          addGetField(field);
        }
      }
    }
    @H_301_19@添加父类的field@H_301_19@
    if@H_301_19@ (clazz.getSuperclass() != ) {
      addFields(clazz.getSuperclass());
    }
  }@H_301_19@

获得field之后,判断是否在getMethods和setMethods中,如果不在则进行添加,只看addSetField方法

 addSetField(Field field) {
    @H_301_19@ (isValidPropertyName(field.getName())) {
      setMethods.put(field.getName(),1)"> SetFieldInvoker(field));
      Type fieldType @H_301_19@= TypeParameterResolver.resolveFieldType(field,type);
      setTypes.put(field.getName(),typeToClass(fieldType));
    }
  }@H_301_19@

从上面看到如果一个field不存在set方法,则生成一个SetFieldInvoker把该对象放入setMethods,从这里可以看出一个setting配置的name值在configuration中可以没有set方法。同理也可以没有get方法

 

上面分析完了settingsAsProperties方法中的下面这行代码

MetaClass MetaConfig = MetaClass.forClass(Configuration.方法、get方法、set方法、field放入了MetaConfig中的reflector对象中的下列属性

final@H_301_19@ String[] readablePropertyNames;
  @H_301_19@ String[] writeablePropertyNames;
  @H_301_19@final@H_301_19@ Map<String,Invoker> setMethods = ();
  @H_301_19@private@H_301_19@ Constructor<?> defaultConstructor;

2.2、校验配置的setting标签中的name是否存在

上面分析完了MetaClass.forClass方法,下面看如何对setting标签配置的name进行校验

);
      }
    }@H_301_19@

遍历从setting标签解析出来的Properties对象,调用MetaConfig.hasSetter方法

boolean@H_301_19@ hasSetter(String name) {
    PropertyTokenizer prop @H_301_19@=  PropertyTokenizer(name);
    @H_301_19@ (prop.hasNext()) {
      @H_301_19@ (reflector.hasSetter(prop.getName())) {
        MetaClass MetaProp @H_301_19@= MetaClassForProperty(prop.getName());
        @H_301_19@ MetaProp.hasSetter(prop.getChildren());
      } @H_301_19@ {
        @H_301_19@false@H_301_19@;
      }
    } @H_301_19@ reflector.hasSetter(prop.getName());
    }
  }@H_301_19@

看hasSetter的定义

 hasSetter(String propertyName) {
    @H_301_19@ setMethods.keySet().contains(propertyName);
  }@H_301_19@

可以看到是判断setMethods是否存在该key,也就是已set方法为表标准,只要在setMethods中,便可以在<setting>标签的name中配置,具体配置值还需要看其类型。

三、总结

上面分析了mybatis的核心配置文件中<settings>标签的解析及子标签中name属性的配置值是怎么取的。如果要扩展核心文件配置中的setting标签的name属性值,需要在configuration中进行配置,及其他操作。

 

原创不易,有不正之处欢迎指正。

猜你在找的Mybatis相关文章