MyBatis 學習筆記(四)---源碼分析篇---配置文件的解析過程(一)
概述
前幾篇我們介紹了MyBatis的一些基本特性,對MyBatis有了個初步了解。接下來,我們將著手來分析一下MyBatis的源碼,從源碼層面復盤MyBatis的執(zhí)行流程。
思維導圖概括
配置文件解析過程分析
有了上述思維導圖,我們對配置文件文件的解析過程就有了一個大概的認識,下面我們就來具體分析下解析過程。
配置文件解析入口
首先,我們來看看調用MyBatis的示例代碼
String resource = "chapter1/mybatis-cfg.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
從上述示例代碼中我們可以很清晰的看出,初始化過程是首先通過Resources 解析配置文件得到文件流。然后,將文件流傳給SqlSessionFactoryBuilder的build方法,并最終得到sqlSessionFactory。
那么我們MyBatis的初始化入口就是SqlSessionFactoryBuilder的build 方法。
//* SqlSessionFactoryBuilder類
//以下3個方法都是調用下面第8種方法
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
//第8種方法和第4種方法差不多,Reader換成了InputStream
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
//最后一個build方法使用了一個Configuration作為參數(shù),并返回DefaultSqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
從上述源碼,我們可以知道build 構建SqlSessionFactory 分為兩步,首先 實例化一個XMLConfigBuilder,然后,調用XMLConfigBuilder的parse方法得到Configuration對象,最后將Configuration對象作為參數(shù)實例化一個DefaultSqlSessionFactory 即SqlSessionFactory對象。
接著往下看,下面我們來看看XMLConfigBuilder類。首先是實例化XMLConfigBuilder的過程。
//* XMLConfigBuilder
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
//上面6個構造函數(shù)最后都合流到這個函數(shù),傳入XPathParser
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
//首先調用父類初始化Configuration
super(new Configuration());
//錯誤上下文設置成SQL Mapper Configuration(XML文件配置),以便后面出錯了報錯用吧
ErrorContext.instance().resource("SQL Mapper Configuration");
//將Properties全部設置到Configuration里面去
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
//* XPathParser
public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(reader));
}
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
//這個是DOM解析方式
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(validation);
//名稱空間
factory.setNamespaceAware(false);
//忽略注釋
factory.setIgnoringComments(true);
//忽略空白
factory.setIgnoringElementContentWhitespace(false);
//把 CDATA 節(jié)點轉換為 Text 節(jié)點
factory.setCoalescing(false);
//擴展實體引用
factory.setExpandEntityReferences(true);
DocumentBuilder builder = factory.newDocumentBuilder();
//需要注意的就是定義了EntityResolver(XMLMapperEntityResolver),這樣不用聯(lián)網去獲取DTD,
//將DTD放在org\apache\ibatis\builder\xml\mybatis-3-config.dtd,來達到驗證xml合法性的目的
builder.setEntityResolver(entityResolver);
builder.setErrorHandler(new ErrorHandler() {
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void warning(SAXParseException exception) throws SAXException {
}
});
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
從上述源碼中,我們可以看出在XMLConfigBuilder的實例化過程包括兩個過程,1. 創(chuàng)建XPathParser的實例并初始化;2.創(chuàng)建Configuration的實例對象,然后將XPathParser的實例設置到XMLConfigBuilder中,而XPathParser 初始化主要做了兩件事,初始化DocumentBuilder對象,并通過調用DocumentBuilder對象的parse方法得到Document對象,我們配置文件的配置就全部都轉移到了Document對象中。我們下面通過調試看看Document 對象中的內容,測試用例是MyBatis 自身的單元測試XPathParserTest
測試的xml
<!--
nodelet_test.xml
-->
<employee id="${id_var}">
<blah something="that"/>
<first_name>Jim</first_name>
<last_name>Smith</last_name>
<birth_date>
<year>1970</year>
<month>6</month>
<day>15</day>
</birth_date>
<height units="ft">5.8</height>
<weight units="lbs">200</weight>
<active>true</active>
</employee>
測試用例:
//* XPathParserTest
@Test
public void shouldTestXPathParserMethods() throws Exception {
String resource = "resources/nodelet_test.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
XPathParser parser = new XPathParser(inputStream, false, null, null);
assertEquals((Long)1970l, parser.evalLong("/employee/birth_date/year"));
assertEquals((short) 6, (short) parser.evalShort("/employee/birth_date/month"));
assertEquals((Integer) 15, parser.evalInteger("/employee/birth_date/day"));
assertEquals((Float) 5.8f, parser.evalFloat("/employee/height"));
assertEquals((Double) 5.8d, parser.evalDouble("/employee/height"));
assertEquals("${id_var}", parser.evalString("/employee/@id"));
assertEquals(Boolean.TRUE, parser.evalBoolean("/employee/active"));
assertEquals("<id>${id_var}</id>", parser.evalNode("/employee/@id").toString().trim());
assertEquals(7, parser.evalNodes("/employee/*").size());
XNode node = parser.evalNode("/employee/height");
assertEquals("employee/height", node.getPath());
assertEquals("employee[${id_var}]_height", node.getValueBasedIdentifier());
}
調試結果:
介紹完XMLConfigBuilder的初始化過程之后,接著我們來看看XMLConfigBuilder中的parse()方法,由前面其初始化過程我們可以得知我們的配置信息已經保存到了XMLConfigBuilder的XPathParser對象的Document中了。解析來其實就是將XPathParser中的信息轉移到Configuration對象中,不多說了,看看源碼。
//* XMLConfigBuilder
//解析配置
public Configuration parse() {
//如果已經解析過了,報錯
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//根節(jié)點是configuration
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
//解析配置
private void parseConfiguration(XNode root) {
try {
//分步驟解析
//issue #117 read properties first
//1.properties
propertiesElement(root.evalNode("properties"));
//2.類型別名
typeAliasesElement(root.evalNode("typeAliases"));
//3.插件
pluginElement(root.evalNode("plugins"));
//4.對象工廠
objectFactoryElement(root.evalNode("objectFactory"));
//5.對象包裝工廠
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//6.設置
settingsElement(root.evalNode("settings"));
// read it after objectFactory and objectWrapperFactory issue #631
//7.環(huán)境
environmentsElement(root.evalNode("environments"));
//8.databaseIdProvider
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//9.類型處理器
typeHandlerElement(root.evalNode("typeHandlers"));
//10.映射器
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
至此,一個MyBatis的解析過程就出來了,每個配置的解析邏輯封裝在相應的方法中,接下來將重點介紹一些常用的配置,例如properties,settings,environments,typeAliases, typeHandler, mappers。閑話少敘,接下來我們首先來分析下properties的解析過程
解析properties配置
首先我們來看看一個普通的properties配置。
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
</properties>
//* XMLConfigBuilder
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
//如果在這些地方,屬性多于一個的話,MyBatis 按照如下的順序加載它們:
//1.在 properties 元素體內指定的屬性首先被讀取。
//2.從類路徑下資源或 properties 元素的 url 屬性中加載的屬性第二被讀取,它會覆蓋已經存在的完全一樣的屬性。
//3.作為方法參數(shù)傳遞的屬性最后被讀取, 它也會覆蓋任一已經存在的完全一樣的屬性,這些屬性可能是從 properties 元素體內和資源/url 屬性中加載的。
//傳入方式是調用構造函數(shù)時傳入,public XMLConfigBuilder(Reader reader, String environment, Properties props)
//1.XNode.getChildrenAsProperties函數(shù)方便得到孩子所有Properties
Properties defaults = context.getChildrenAsProperties();
//2.然后查找resource或者url,加入前面的Properties
String resource = context.getStringAttribute("resource");
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) {
//從文件系統(tǒng)中加載并解析屬性文件
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
//通過url加載并解析屬性文件
defaults.putAll(Resources.getUrlAsProperties(url));
}
//3.Variables也全部加入Properties
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
//4. 將屬性值設置到configuration中
configuration.setVariables(defaults);
}
}
/**
* //得到孩子,返回Properties,孩子的格式肯定都有name,value屬性
* @return
*/
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;
}
代碼中注釋比較詳實,代碼結構不太復雜,讀者們看下就會明白。不過需要特別說明:properties元素的解析順序是:
1. 在Properties 元素體內指定的屬性首先被讀取。
2. 在類路徑下資源或properties元素的url 屬性中加載的屬性第二個被讀取,它會覆蓋完全一樣的屬性
3. 作為方法參數(shù)傳遞的屬性最后被讀取,它也會覆蓋任一已存在的完全一樣的屬性,這些屬性可能是從properties 元素體內和資源 /url 屬性中加載的。
//傳入方式是調用構造函數(shù)時傳入,public XMLConfigBuilder(Reader reader, String environment, Properties props)
解析settings配置
settings 節(jié)點的解析過程
settings相關配置是MyBatis中非常重要的配置,這些配置用戶調整MyBatis運行時的行為。settings配置繁多,在對這些配置不熟悉的情況下,保持默認的配置即可。詳細的配置說明可以參考MyBatis官方文檔setting
我們先看看一個settings 的簡單配置
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
</settings>
setting的解析源碼
接下來我們來看看setting的解析源碼。
//*XMLConfigBuilder
private void settingsElement(XNode context) throws Exception {
if (context != null) {
// 獲取settings子節(jié)點中的內容
Properties props = context.getChildrenAsProperties();
// 創(chuàng)建Configuration 類的"元信息"對象
MetaClass metaConfig = MetaClass.forClass(Configuration.class);
for (Object key : props.keySet()) {
// Check that all settings are known to the configuration class
//檢查下是否在Configuration類里都有相應的setter方法(沒有拼寫錯誤)
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
從上述源碼中我們可以總結出setting 的解析主要分為如下幾個步驟:
- 獲取settings 子節(jié)點中的內容,這段代碼在之前已經解釋過,再次不在贅述。
- 然后就是創(chuàng)建Configuration類的“元信息”對象,在這一部分中出現(xiàn)了一個陌生的類MetaClass,我們一會在分析。
- 接著檢查是否在Configuration類里都有相應的setter方法,不存在則拋出異常。
- 若通過MetaClass的檢測,則將Properties中的信息設置到configuration對象中,邏輯結束
上述代碼看似簡單,實際上在第二步創(chuàng)建元信息對象還是蠻復雜的。接下來我們就來看看MetaClass類
MetaClass類源碼解析
//*MetaClass
public class MetaClass {
//有一個反射器
//可以看到方法基本都是再次委派給這個Reflector
private Reflector reflector;
private MetaClass(Class<?> type) {
// 根據類型創(chuàng)建Reflector
this.reflector = Reflector.forClass(type);
}
public static MetaClass forClass(Class<?> type) {
// 調用構造器方法
return new MetaClass(type);
}
/**
* 檢查指定的屬性是否有setter方法。
* @param name
* @return
*/
public boolean hasSetter(String name) {
// 屬性分詞器,用于解析屬性名
PropertyTokenizer prop = new PropertyTokenizer(name);
// hasNext返回true,則表明是一個復合屬性
if (prop.hasNext()) {
// 調用reflector的hasSetter方法
if (reflector.hasSetter(prop.getName())) {
// 為屬性創(chuàng)建MetaClass
MetaClass metaProp = metaClassForProperty(prop.getName());
// 再次調用hasSetter
return metaProp.hasSetter(prop.getChildren());
} else {
return false;
}
} else {
// 非復合屬性則直接調用hasSetter一次即可
return reflector.hasSetter(prop.getName());
}
}
public MetaClass metaClassForProperty(String name) {
Class<?> propType = reflector.getGetterType(name);
return MetaClass.forClass(propType);
}
從源碼我們可以看出MetaClass 的forClass 方法最終委托給了這個Reflector的forClass方法。而hasSetter 方法中又調用了reflector的hasSetter方法,那么Reflector類內部實現(xiàn)如何呢?同時我們還注意到出現(xiàn)了一個新的類PropertyTokenizer,那么這個類內部實現(xiàn)如何呢?我們待會再來分析下。首先我們簡單介紹下這幾個類。
Reflector -----> 反射器,用于解析和存儲目標類的元信息
PropertyTokenizer -----> 屬性分詞器,用于解析屬性名。
接下來,我們來看看Reflector的相關實現(xiàn)。
Reflector類源碼解析
Reflector 類的源碼較多,在此處我們不做一一分析。我主要從以下三個方面:
- Reflector的構造方法和成員變量分析
- getter 方法解析過程分析
- setter 方法解析過程分析
//* Reflector
private static boolean classCacheEnabled = true;
private static final String[] EMPTY_STRING_ARRAY = new String[0];
//這里用ConcurrentHashMap,多線程支持,作為一個緩存
private static final Map<Class<?>, Reflector> REFLECTOR_MAP = new ConcurrentHashMap<Class<?>, Reflector>();
private Class<?> type;
//getter的屬性列表
private String[] readablePropertyNames = EMPTY_STRING_ARRAY;
//setter的屬性列表
private String[] writeablePropertyNames = EMPTY_STRING_ARRAY;
//setter的方法列表
private Map<String, Invoker> setMethods = new HashMap<String, Invoker>();
//getter的方法列表
private Map<String, Invoker> getMethods = new HashMap<String, Invoker>();
//setter的類型列表
private Map<String, Class<?>> setTypes = new HashMap<String, Class<?>>();
//getter的類型列表
private Map<String, Class<?>> getTypes = new HashMap<String, Class<?>>();
//構造函數(shù)
private Constructor<?> defaultConstructor;
private Map<String, String> caseInsensitivePropertyMap = new HashMap<String, String>();
/**
* 得到某個類的反射器,是靜態(tài)方法,而且要緩存,
* 又要多線程,所以REFLECTOR_MAP是一個ConcurrentHashMap
*/
public static Reflector forClass(Class<?> clazz) {
if (classCacheEnabled) {
// synchronized (clazz) removed see issue #461
//對于每個類來說,我們假設它是不會變的,這樣可以考慮將這個類的信息
// (構造函數(shù),getter,setter,字段)加入緩存,以提高速度
Reflector cached = REFLECTOR_MAP.get(clazz);
if (cached == null) {
cached = new Reflector(clazz);
REFLECTOR_MAP.put(clazz, cached);
}
return cached;
} else {
return new Reflector(clazz);
}
}
private Reflector(Class<?> clazz) {
type = clazz;
//解析目標類的默認構造方法,并賦值給defaultConstructor變量
addDefaultConstructor(clazz);
//解析getter,并將解析結果放入getMethods中
addGetMethods(clazz);
//解析setter方法,并將解析結果放入setMethods中
addSetMethods(clazz);
//解析屬性字段,并將解析結果添加到setMethods或getMethods中
addFields(clazz);
// 從getMethods映射中獲取可讀屬性名數(shù)組
readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
// 從setMethods 映射中獲取可寫屬性名數(shù)組
writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);
//將所有屬性名的大寫形式作為鍵,屬性名作為值,存入到caseInsensitivePropertyMap中
for (String propName : readablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
for (String propName : writeablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
}
//省略其他方法
如上,Reflector 定義了一個ConcurrentHashMap 用于緩存每個類的反射器,以提高速度。我們知道ConcurrentHashMap是一個線程安全類,所以不存在線程安全問題。同時,其他的集合用于存儲getter,setter 方法的相關信息。構造器里會講元信息里里的構造方法,屬性字段,setter方法,getter方法設置到相應的集合中。
接下來,我們來分析下getter方法。
getter方法解析過程分析
//* Reflector
private void addGetMethods(Class<?> cls) {
Map<String, List<Method>> conflictingGetters = new HashMap<String, List<Method>>();
// 獲取當前類,接口,以及父類中的方法。該方法邏輯不是很復雜
Method[] methods = getClassMethods(cls);
for (Method method : methods) {
// getter方法不應該有參數(shù),若存在參數(shù),則忽略當前方法
if (method.getParameterTypes().length > 0) {
continue;
}
String name = method.getName();
// 過濾出以get或is開頭的方法
if (name.startsWith("get") && name.length() > 3) {
if (method.getParameterTypes().length == 0) {
// 將getXXX方法名轉成相應的屬性,比如 getName -> name
name = PropertyNamer.methodToProperty(name);
/* 將沖突的方法添加到conflictingGetters中,考慮這樣一種情況
getTitle和isTitle兩個方法經過methodToProperty處理,
均得到 name=title,這會導致沖突
對于沖突的方法,這里想統(tǒng)一存起來,后續(xù)在解決沖突
*/
addMethodConflict(conflictingGetters, name, method);
}
} else if (name.startsWith("is") && name.length() > 2) {
if (method.getParameterTypes().length == 0) {
name = PropertyNamer.methodToProperty(name);
addMethodConflict(conflictingGetters, name, method);
}
}
}
// 處理getter沖突
resolveGetterConflicts(conflictingGetters);
}
如上, addGetMethods 方法的的執(zhí)行流程如下:
- 獲取當前類,接口,以及父類中的方法
- 遍歷上一步獲取的方法數(shù)組,并過濾出以get和is開頭方法
- 根據方法名截取出屬性名
- 將沖突的屬性名和方法對象添加到沖突集合中
- 處理getter沖突,篩選出合適的方法。
我們知道getter截取屬性沖突主要是由于 getXXX() 和isXXX() 兩種類型的方法,截取屬性后會沖突。
比較核心的知識點就是處理getter 沖突,接下來,我們就來看看相應的源碼
//* Reflector
/**
* // 添加屬性名和方法對象到沖突集合中
* @param conflictingMethods
* @param name
* @param method
*/
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);
}
/**
* 解決沖突
* @param conflictingGetters
*/
private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) {
for (String propName : conflictingGetters.keySet()) {
List<Method> getters = conflictingGetters.get(propName);
Iterator<Method> iterator = getters.iterator();
Method firstMethod = iterator.next();
if (getters.size() == 1) {
addGetMethod(propName, firstMethod);
} else {
Method getter = firstMethod;
// 獲取返回值類型
Class<?> getterType = firstMethod.getReturnType();
while (iterator.hasNext()) {
Method method = iterator.next();
Class<?> methodType = method.getReturnType();
/**
* 兩個方法的返回值類型一致,若兩個方法返回值類型均為boolean,則選取isXXX方法
* 為getterType,則無法決定哪個方法更為合適,只能拋出異常
*
* */
if (methodType.equals(getterType)) {
throw new ReflectionException("Illegal overloaded getter method with ambiguous type for property "
+ propName + " in class " + firstMethod.getDeclaringClass()
+ ". This breaks the JavaBeans " + "specification and can cause unpredicatble results.");
/**
* getterType是methodType的子類,類型上更為具體
* 則認為當前的getter 是合適的,無需做什么事情
*
* */
} else if (methodType.isAssignableFrom(getterType)) {
// OK getter type is descendant
/**
* methodType 是getterType的子類,此時認為method方法更為合適,
* 故將getter更新為method
*/
} else if (getterType.isAssignableFrom(methodType)) {
getter = method;
getterType = methodType;
} else {
throw new ReflectionException("Illegal overloaded getter method with ambiguous type for property "
+ propName + " in class " + firstMethod.getDeclaringClass()
+ ". This breaks the JavaBeans " + "specification and can cause unpredicatble results.");
}
}
// 將篩選出的方法添加到getMethods中,并將方法返回值添加到getType中
addGetMethod(propName, getter);
}
}
}
private void addGetMethod(String name, Method method) {
if (isValidPropertyName(name)) {
// 解析返回值類型
getMethods.put(name, new MethodInvoker(method));
// 將返回值類型由Type 轉為Class,并將轉換后的結果緩存到getTypes中
getTypes.put(name, method.getReturnType());
}
}
如上,該處理getter沖突的的過程,代碼較長,在這里大家只要記住處理沖突的規(guī)則就能夠理解上面的邏輯:
- 沖突方法返回值類型具有繼承關系,則認為子類的方法更加合適。
- 沖突方法返回值類型相同,則無法確定有用哪個方法,直接拋出異常。
- 沖突方法返回值類型完全不相關,則無法確定有用哪個方法,拋出異常。
我們來看看MyBatis的測試用例理解下ReflectorTest
//* ReflectorTest
@Test
public void testGetGetterType() throws Exception {
Reflector reflector = Reflector.forClass(Section.class);
Assert.assertEquals(Long.class, reflector.getGetterType("id"));
}
static interface Entity<T> {
T getId();
void setId(T id);
}
static abstract class AbstractEntity implements Entity<Long> {
private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
static class Section extends AbstractEntity implements Entity<Long> {
}
如上測試用例Section 類中有兩個 getId() 方法,一個返回值為Long( java.lang.Long), 一個返回值類型為void (java.lang.Object)。由于
Long 類是Object的子類,故認為Long 返回值類型對應的方法更適合。
分析完getter方法的解析過程之后,我們接著來分析setter方法的解析過程。
setter 方法解析過程分析
//* Reflector
private void addSetMethods(Class<?> cls) {
Map<String, List<Method>> conflictingSetters = new HashMap<String, List<Method>>();
// 獲取當前類,接口,以及父類中的方法。該方法邏輯不是很復雜,這里不展開
Method[] methods = getClassMethods(cls);
for (Method method : methods) {
String name = method.getName();
// 過濾出setter方法,且方法僅有一個參數(shù)
if (name.startsWith("set") && name.length() > 3) {
if (method.getParameterTypes().length == 1) {
name = PropertyNamer.methodToProperty(name);
/*
*setter方法發(fā)生沖突原因是:可能存在重載情況,比如:
* void setSex(int sex)
* void setSex(SexEnum sex)
*/
addMethodConflict(conflictingSetters, name, method);
}
}
}
// 解決setter沖突
resolveSetterConflicts(conflictingSetters);
}
如上,與addGetMethods 方法的執(zhí)行流程類似,addSetMethods方法的執(zhí)行流程也分為如下幾個步驟:
- 獲取當前類,接口,以及父類中的方法
- 過濾出setter方法其方法之后一個參數(shù)
- 獲取方法對應的屬性名
- 將屬性名和其方法對象放入沖突集合中
- 解決setter沖突
前四步相對而言比較簡單,我在此處就不展開分析了,我們來重點分析下解決setter沖突的邏輯。
/**
* 解決setter沖突
* @param conflictingSetters
*/
private void resolveSetterConflicts(Map<String, List<Method>> conflictingSetters) {
for (String propName : conflictingSetters.keySet()) {
List<Method> setters = conflictingSetters.get(propName);
Method firstMethod = setters.get(0);
if (setters.size() == 1) {
addSetMethod(propName, firstMethod);
} else {
/*
*獲取getter方法的返回值類型,由于getter方法不存在重載的情況,
*所以可以用它的返回值類型反推哪個setter的更為合適
*/
Class<?> expectedType = getTypes.get(propName);
if (expectedType == null) {
throw new ReflectionException("Illegal overloaded setter method with ambiguous type for property "
+ propName + " in class " + firstMethod.getDeclaringClass() + ". This breaks the JavaBeans " +
"specification and can cause unpredicatble results.");
} else {
Iterator<Method> methods = setters.iterator();
Method setter = null;
while (methods.hasNext()) {
Method method = methods.next();
// 獲取參數(shù)類型
if (method.getParameterTypes().length == 1
&& expectedType.equals(method.getParameterTypes()[0])) {
// 參數(shù)類型和返回類型一致,則認為是最好的選擇,并結束循環(huán)
setter = method;
break;
}
}
if (setter == null) {
throw new ReflectionException("Illegal overloaded setter method with ambiguous type for property "
+ propName + " in class " + firstMethod.getDeclaringClass() + ". This breaks the JavaBeans " +
"specification and can cause unpredicatble results.");
}
// 將篩選出的方法放入setMethods中,并將方法參數(shù)值添加到setTypes中
addSetMethod(propName, setter);
}
}
}
}
private void addSetMethod(String name, Method method) {
if (isValidPropertyName(name)) {
setMethods.put(name, new MethodInvoker(method));
setTypes.put(name, method.getParameterTypes()[0]);
}
}
如上,解決setter沖突執(zhí)行流程如下:
- 根據屬性名獲取其下面的方法集合,如果只有一個則直接返回,否則進入沖突處理
- 進入沖突處理分支之后首先獲取getter方法的返回值類型,由于getter方法不存在重載的情況,所以可以用它的返回值類型來反推哪個setter方法更合適
- 獲取setter方法的參數(shù)類型
- 如果setter方法的參數(shù)類型和其對應的getter方法返回類型一致,則認為是最好的選擇,并結束循環(huán)
- 如果找不到則拋出異常
小節(jié)
至此,我們對Reflector類的分析就全部完成,我們從按照三個方面對Reflector類進行了分析,重點介紹了getter 的沖突處理和setter的沖突處理。
接下來,我們來分析下之前提到的PropertyTokenizer類,該類的主要作用是對復合屬性進行分解。
PropertyTokenizer類分析
//* PropertyTokenizer
//例子: person[0].birthdate.year
private String name; //person
private String indexedName; //person[0]
private String index; //0
private String children; //birthdate.year
public PropertyTokenizer(String fullname) {
//person[0].birthdate.year
//找.(檢測傳入的參數(shù)中是否寶航了字符'.')
int delim = fullname.indexOf('.');
if (delim > -1) {
/*
以點位為界,進行分割。比如:
fullname=com.jay.mybatis
以第一個點為分界符:
name=com
children=jay.mybatis
*/
name = fullname.substring(0, delim);
children = fullname.substring(delim + 1);
} else {
//找不到.的話,取全部部分
name = fullname;
children = null;
}
indexedName = name;
//把中括號里的數(shù)字給解析出來
delim = name.indexOf('[');
if (delim > -1) {
/*
* 獲取中括號里的內容,比如:
* 1. 對于數(shù)組或List集合:[]中的內容為數(shù)組下標,
* 比如fullname=articles[1],index=1
* 2.對于Map: []中的內容為鍵,
* 比如 fullname=xxxMap[keyName],index=keyName
*
* 關于 index 屬性的用法,可以參考 BaseWrapper 的 getCollectionValue 方法
* */
index = name.substring(delim + 1, name.length() - 1);
// 獲取分解符前面的內容,比如 fullname=articles[1],name=articles
name = name.substring(0, delim);
}
}
//* MetaClass
public Class<?> getGetterType(String name) {
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) {
MetaClass metaProp = metaClassForProperty(prop);
return metaProp.getGetterType(prop.getChildren());
}
// issue #506. Resolve the type inside a Collection Object
return getGetterType(prop);
}
如上,PropertyTokenizer類的核心邏輯就在其構造器中,主要包括三部分邏輯
- 根據 ‘.’ ,如果不能找到則取全部部分
- 能找到的話則首先截取 ’ .’ 符號之前的部分,把其余部分作為children。 然后通過MetaClass類的getGetterType的方法來循環(huán)提取。下面我們來看下MetaClassTest類的shouldCheckTypeForEachGetter測試用例
@Test
public void shouldCheckTypeForEachGetter() {
MetaClass meta = MetaClass.forClass(RichType.class);
assertEquals(String.class, meta.getGetterType("richField"));
assertEquals(String.class, meta.getGetterType("richProperty"));
assertEquals(List.class, meta.getGetterType("richList"));
assertEquals(Map.class, meta.getGetterType("richMap"));
assertEquals(List.class, meta.getGetterType("richList[0]"));
assertEquals(RichType.class, meta.getGetterType("richType"));
assertEquals(String.class, meta.getGetterType("richType.richField"));
assertEquals(String.class, meta.getGetterType("richType.richProperty"));
assertEquals(List.class, meta.getGetterType("richType.richList"));
assertEquals(Map.class, meta.getGetterType("richType.richMap"));
assertEquals(List.class, meta.getGetterType("richType.richList[0]"));
}
public class RichType {
private RichType richType;
private String richField;
private String richProperty;
private Map richMap = new HashMap();
private List richList = new ArrayList() {
{
add("bar");
}
};
}
//省略get,set方法
}
richType.richProperty 等作為復合屬性,通過PropertyTokenizer的處理同樣能提取到。
至此,對Setting 元素的源碼解析就全部完成了。
總結
本文篇幅較長,先是總體介紹了MyBatis的初始化過程,然后展開來講了properties元素的解析源碼和settings元素的解析源碼,其中在對settings進行分析時又重點講了MetaClass類。在下一篇文章中,我將重點介紹其余幾個常用的元素 。希望對讀者朋友有所幫助。
參考文檔
MyBatis-源碼分析-配置文件解析過程
【深入淺出MyBatis系列十二】終結篇:MyBatis原理深入解析
源碼注釋以文檔地址:
https://github.com/XWxiaowei/mybatis
作者:碼農飛哥
微信公眾號:碼農飛哥