zoukankan      html  css  js  c++  java
  • mybatis源码配置文件解析之二:解析settings标签

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

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

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

    调用settingsAsProperties方法,从方法名中可以看出要把settings标签中的内容解析到Proerties对象中,因为settings标签中是name-value的配置,刚好解析到Properties中以键值对的形式存储。下面是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
        MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
        //如果获取的配置的<setting name="" value="">信息,name不在metaConfig中,则会抛出异常
        //这里metaConfig中的信息是从Configuration类中解析出来的,包含set方法的属性
        //所以在配置<setting>标签的时候,其name值可以参考configuration类中的属性,配置为小写
        for (Object key : props.keySet()) {
            //从metaConfig的relector中的setMethods中判断是否存在该属性,setMethods中存储的是可写的属性,
            //所以这里要到setMethods中进行判断
          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;
      }

    1、解析子标签

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

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

    调用了getChildrenAsProperties方法,

     public Properties getChildrenAsProperties() {
        Properties properties = new Properties();
        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;
      }

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

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

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

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

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

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

    // Check that all settings are known to the configuration class
        MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);

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

    private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();

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

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

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

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

    private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
        this.reflectorFactory = reflectorFactory;
        this.reflector = reflectorFactory.findForClass(type);
      }

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

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

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

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

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

    addDefaultConstructor(clazz)如下,

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

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

    addGetMethods(clazz)如下,

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

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

            resolveGetterConflicts(conflictingGetters);

      }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    private void addSetField(Field field) {
        if (isValidPropertyName(field.getName())) {
          setMethods.put(field.getName(), new SetFieldInvoker(field));
          Type fieldType = TypeParameterResolver.resolveFieldType(field, type);
          setTypes.put(field.getName(), typeToClass(fieldType));
        }
      }

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

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

    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);

    把Configuration中的构造方法、get方法、set方法、field放入了metaConfig中的reflector对象中的下列属性

    private final String[] readablePropertyNames;
      private final String[] writeablePropertyNames;
      private final Map<String, Invoker> setMethods = new HashMap<String, Invoker>();
      private final Map<String, Invoker> getMethods = new HashMap<String, Invoker>();
      private final Map<String, Class<?>> setTypes = new HashMap<String, Class<?>>();
      private final Map<String, Class<?>> getTypes = new HashMap<String, Class<?>>();
      private Constructor<?> defaultConstructor;

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

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

    for (Object key : props.keySet()) {
            //从metaConfig的relector中的setMethods中判断是否存在该属性,setMethods中存储的是可写的属性,
            //所以这里要到setMethods中进行判断
          if (!metaConfig.hasSetter(String.valueOf(key))) {
            throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
          }
        }

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

    public boolean hasSetter(String name) {
        PropertyTokenizer prop = new PropertyTokenizer(name);
        if (prop.hasNext()) {
          if (reflector.hasSetter(prop.getName())) {
            MetaClass metaProp = metaClassForProperty(prop.getName());
            return metaProp.hasSetter(prop.getChildren());
          } else {
            return false;
          }
        } else {
          return reflector.hasSetter(prop.getName());
        }
      }

    看hasSetter的定义

    public boolean hasSetter(String propertyName) {
        return setMethods.keySet().contains(propertyName);
      }

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

    三、总结

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

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

  • 相关阅读:
    迅雷亲历面试经过,笔试+上机+面试(完整)
    Flash Player安全高级攻略
    EBS查看Report程式所挂在的. 报表名. 组. 责任
    ORACLE常用后台表查询
    Ap_Aging_Report SQL月底结账使用
    2012年最新会计科目表
    如何用sql实现AP_payments中应付余额与GL_balance对应科目余额相同
    SQL应收帐款帐龄报表(AR_Aging_Reporting)
    Navigator: Disable Multiform
    GL: 访问权限集
  • 原文地址:https://www.cnblogs.com/teach/p/12753756.html
Copyright © 2011-2022 走看看