1. 程式人生 > >mybatis原始碼配置檔案解析之二:解析settings標籤

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中進行配置,及其他操作。

 

原創不易,有不正之處歡迎指正。