(一) Mybatis原始碼分析-解析器模組
Mybatis原始碼分析-解析器模組
原創-轉載請說明出處
1. 解析器模組的作用
- 對XPath進行封裝,為mybatis-config.xml配置檔案以及對映檔案提供支援
- 為處理動態 SQL 語句中的佔位符提供支援
2. 解析器模組parsing包
3. 解析器模組parsing包
- GenericTokenParser
- package-info.java
- ParsingException
- PropertyParser
- TokenHandler
- XNode
- XPathParser
mybati-config.xml檔案
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- autoMappingBehavior should be set in each test case --> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"> <property name="" value=""/> </transactionManager> <dataSource type="UNPOOLED"> <property name="driver" value="org.hsqldb.jdbcDriver"/> <property name="url" value="jdbc:hsqldb:mem:automapping"/> <property name="username" value="sa"/> </dataSource> </environment> </environments> <mappers> <mapper resource="org/apache/ibatis/autoconstructor/AutoConstructorMapper.xml"/> </mappers> </configuration>
4. mybatis 解析mybatis-config.xml過程
4.1 分析步驟
- 開啟mybatis原始碼專案;
- 進入單元測試目錄下:org.apache.ibatis.autoconstructor.AutoConstructorTest,方法:fullyPopulatedSubject();
- 除錯模式下執行測試方法fullyPopulatedSubject(),斷點觀察,mybatis是如何對mybatis-config.xml配置檔案進行解析的。
4.2 程式碼過程解析
第一步:建立SqlSessionFactory的整體過程
try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/autoconstructor/mybatis-config.xml")) { sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); }
上面程式碼中,mybatis通過提供一個Resources的工具類來載入配置檔案,獲取輸入流。然後再通過SqlSessionFactoryBuilder的builder方法來構建SqlSessionFactory物件,其中builder方法裡的實現就是對mybatis-config.xml配置檔案進行解析的入口。下面的程式碼就是builder方法的具體實現:
1.呼叫builder方法 public SqlSessionFactory build(Reader reader) { return build(reader, null, null); } 2.呼叫builder過載方法 public SqlSessionFactory build(Reader reader, String environment, Properties properties) { try { //2.1> 建立配置檔案解析器 XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); //2.2> 呼叫parse方法解析配置檔案,將對應的屬性存入並生成Configuration物件 return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { reader.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } } 3.通過Configuration建立SqlSessionFactory public SqlSessionFactory build(Configuration config) { //建立DefaultSqlSessionFactory物件 return new DefaultSqlSessionFactory(config); }
上面程式碼中展示了建立SqlSessionFactory 的整體流程,其中最重要的是2.1:建立配置檔案解析器
和2.2:呼叫parse方法解析配置檔案,將對應的屬性存入並生成Configuration物件
第二步:建立配置檔案解析器XMLConfigBuilder
XMLConfigBuilder.class
/**
* 構造XMLConfigBuilder
* @param inputStream 輸入流
* @param environment environment環境
* @param props Properties properties物件
*/
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
/**
* 構造XMLConfigBuilder
* @param parser XPathParser
* @param environment environment環境
* @param props properties物件
*/
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
//呼叫父類(BaseBuilder)建構函式,建立Configuration:會進行別名註冊
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
//Configuration設定properties
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
上面的程式碼就是構建XMLConfigBuilder的整體過程,該過程會構建XPathParser,XPathParser主要用來解析XML封裝了Document、EntityResolver 和XPath等物件,提供了一系列的解析XML的方法。
下面的程式碼是XPathParser構建的程式碼,使用到了工程模式去建立XPath物件。
XPathParser.class
/**
* 構造 XPathParser 物件
*
* @param inputStream inputStream 輸入流
* @param validation 是否校驗 XML
* @param variables 變數 Properties 物件
* @param entityResolver XML 實體解析器
*/
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(inputStream));
}
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
this.validation = validation;
this.entityResolver = entityResolver;
this.variables = variables;
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
- 建立 Document 物件,Document 物件代表整個 XML 文件。這裡會解析XML輸入源得到Document物件。
/**
* 建立 Document 物件
*
* @param inputSource XML 的 InputSource 物件
* @return Document 物件
*/
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
// 1> 建立DocumentBuilderFactory物件
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(validation);// 設定是否檢驗XML
factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(true);
//2> 建立DocumentBuilder物件
DocumentBuilder builder = factory.newDocumentBuilder();
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 {
}
});
//3> 解析XML檔案 返回Document物件
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
第三步:建立配置檔案解析器XMLConfigBuilder
通過XMLConfigBuilder的parse()去解析mybatis-config.xml配置檔案並返回Configuration物件。
XMLConfigBuilder.class
//1> 建立配置檔案解析器
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
//2> 呼叫parse方法解析配置檔案,將對應的屬性存入並生成Configuration物件
return build(parser.parse());
/**
* 解析mybatis-config.xml
* @return configuration
*/
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//解析configuration節點<configuration></configuration>
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
XPathParser是解析xml的核心
XPathParser.class
/**
* 獲取Document中符合xpath表示式:expression的XNODE物件
* @param expression xpath表示式
* @return XNode
*/
public XNode evalNode(String expression) {
return evalNode(document, expression);
}
/**
* 獲取XNode物件
* @param root Document xml 物件
* @param expression xpath表示式
* @return XNode
*/
public XNode evalNode(Object root, String expression) {
//1> 獲得Node物件
Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
if (node == null) {
return null;
}
//2> 封裝成XNode物件
return new XNode(this, node, variables);
}
/**
* 獲得指定元素或節點的值
* 並返回指定型別的結果
*
* @param expression 表示式
* @param root 指定節點
* @param returnType 返回型別
* @return 值
*/
private Object evaluate(String expression, Object root, QName returnType) {
try {
// 獲取指定上下文中的 XPath 表示式並返回指定型別的結果。
return xpath.evaluate(expression, root, returnType);
} catch (Exception e) {
throw new BuilderException("Error evaluating XPath. Cause: " + e, e);
}
}
第四步:解析mybatis-config.xml中configuration節點下配置資訊
主要程式碼:parseConfiguration(parser.evalNode("/configuration"));
XMLConfigBuilder.class
/**
* 解析mybatis-config.xml
* @return configuration
*/
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//解析configuration節點<configuration></configuration>
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
/**
* 解析configuration下子節點的屬性
* @param root configuration節點
*/
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
// 1> 解析properties節點資訊
propertiesElement(root.evalNode("properties"));
//2> 解析settings節點資訊
Properties settings = settingsAsProperties(root.evalNode("settings"));
//2.1> 指定 VFS 的實現
loadCustomVfs(settings);
//2.2> 載入setting logImpl配置, 指定MyBatis 所用日誌的具體實現
loadCustomLogImpl(settings);
//2.3> 解析並註冊別名
typeAliasesElement(root.evalNode("typeAliases"));
//2.4 解析並載入外掛到攔截器
pluginElement(root.evalNode("plugins"));
//2.5 解析並載入物件工廠objectFactory
objectFactoryElement(root.evalNode("objectFactory"));
//2.6 解析並載入objectWrapperFactory
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//2.7 解析並載入reflectorFactory
reflectorFactoryElement(root.evalNode("reflectorFactory"));
//2.8設定settings屬性值
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
//2.9解析environments節點資訊,並設定environment屬性
environmentsElement(root.evalNode("environments"));
//2.10解析databaseIdProvider節點(資料庫廠商資訊)
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//2.11解析typeHandlers節點
typeHandlerElement(root.evalNode("typeHandlers"));
//2.12 解析mappers節點
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
4.3 程式碼過程解析
4.3.1 解析properties節點資訊
<properties resource="config.properties">
<property name="username" value="cmj"/>
</properties>
propertiesElement(root.evalNode("properties"));
解析<properties>
的整體步驟如下:
- 解析各個子節點
- 獲取
- 從resource或url的輸入流中讀取配置資訊,存入Properties物件中
- 獲取configuration物件中的Properties屬性,存入Properties物件中
- XPathParser物件和configuration物件設定最新的Properties屬性
- 注:由於解析
<properties>
的時候是先解析其子節點<property>
中的屬性,然後再讀取resource或者url中的屬性,所以這回導致同名屬性覆蓋的問題,resource或url中的屬性會覆<property>
中的屬性。
/**
* 解析properties節點
* Properties 是一個Hashtable
* @param context propertise節點xnode
*/
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
//1.獲取propertises下propertise屬性
Properties defaults = context.getChildrenAsProperties();
//2> 獲取resource屬性
String resource = context.getStringAttribute("resource");
//3> 獲取url屬性,遠端檔案
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) {
//4> resource存在,則讀取resource檔案中的配置
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
//5> url存在,則獲取url檔案中的配置
defaults.putAll(Resources.getUrlAsProperties(url));
}
//6> 獲取configuration中的Properties
Properties vars = configuration.getVariables();
if (vars != null) {
//6.1> Properties的defaults加入從configuration獲取的properties
defaults.putAll(vars);
}
//7> XPathParser 設定Properties屬性
parser.setVariables(defaults);
//8> configuration設定Properties屬性
configuration.setVariables(defaults);
}
}
1.1獲取<properties>子節點的屬性值
/**
* 獲取子節點的屬性值 name 和 value
*/
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;
}
2.1獲取resource或url屬性值: context.getStringAttribute("resource");
/**
* 獲取指定屬性的值
* @param name 屬性名稱
* @return 屬性值
*/
public String getStringAttribute(String name) {
return getStringAttribute(name, null);
}
/**
* 獲取指定屬性的值
* @param name 屬性名稱
* @param def 預設值
* @return 屬性值
*/
public String getStringAttribute(String name, String def) {
String value = attributes.getProperty(name);
if (value == null) {
return def;
} else {
return value;
}
}
4.1讀取resource檔案中的配置資訊:Resources.getResourceAsProperties(resource)
/**
* Returns a resource on the classpath as a Properties object
*
* @param resource The resource to find
* @return The resource
* @throws java.io.IOException If the resource cannot be found or read
*/
public static Properties getResourceAsProperties(String resource) throws IOException {
Properties props = new Properties();
//1> 獲取resource輸入流
try (InputStream in = getResourceAsStream(resource)) {
//2> 載入輸入流中的配置
props.load(in);
}
return props;
}
5.1讀取url檔案的配置資訊:Resources.getUrlAsProperties(url)
/**
* Gets a URL as a Properties object
*
* @param urlString - the URL to get
* @return A Properties object with the data from the URL
* @throws java.io.IOException If the resource cannot be found or read
*/
public static Properties getUrlAsProperties(String urlString) throws IOException {
Properties props = new Properties();
//1> 通過URLConnection,獲取url輸入流
try (InputStream in = getUrlAsStream(urlString)) {
//2> 載入輸入流中的配置
props.load(in);
}
return props;
}
4.3.2 解析settings節點資訊
settings中的配置比較多,具體可以看官網。
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
settingsAsProperties(root.evalNode("settings"));
解析settings的步驟分為如下幾步:
- 1.將settings下的所有子節點獲取屬性值,並存入Properties物件中
- 2.
loadCustomVfs(settings);
獲取vfsImpl配置,載入指定的vfs類 - 3.
loadCustomLogImpl(settings);
獲取logImpl配置,指定MyBatis 所用日誌的具體實現 - 4.根據Properties配置資訊,設定configuration對應的setting對應的屬性值
第一步:獲取settings下的所有子節點屬性值,這一步比較複雜,獲取<settrings>
各個子節點的屬性,然後接下來通過反射去校驗Configuration類中是否有相應的配置屬性。
/**
* 讀取settings的子節點屬性
* @param context settings節點
* @return Properties
*/
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
//1> 獲取<settings>下子節點<setting>的屬性值
Properties props = context.getChildrenAsProperties();
//2> 獲取Configuration的元資訊
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
// Check that all settings are known to the configuration class
//3> 檢查<setting>屬性配置在Configuration中是否存在相應的setter方法
for (Object key : props.keySet()) {
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.獲取子節點下的屬性資訊context.getChildrenAsProperties()
/**
* 獲取子節點的Properties屬性 name 和 value
*/
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;
}
1.獲取Configuration的元資訊: MetaClass.forClass(Configuration.class, localReflectorFactory)
MetaClass類中含有ReflectorFactory和Reflector,這是mybatis的反射核心類,用於獲取目標類的各種屬性,ReflectorFactory的實現類是DefaultReflectorFactory,這裡也涉及到工廠模式。MetaClass類的建構函式是私有的,所以不能通過建構函式建立,需要通過forClass方法去建立。
MetaClass.class
public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {
return new MetaClass(type, reflectorFactory);
}
private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
this.reflectorFactory = reflectorFactory;
//根據型別建立 Reflector
this.reflector = reflectorFactory.findForClass(type);
}
DefaultReflectorFactory.class
@Override
public Reflector findForClass(Class<?> type) {
if (classCacheEnabled) {
// synchronized (type) removed see issue #461
return reflectorMap.computeIfAbsent(type, Reflector::new);
} else {
return new Reflector(type);
}
}
上述程式碼建立了Reflector 實體類。
在原始碼上Reflector的註釋是這樣的,很清晰知道它有什麼用了
This class represents a cached set of class definition information that(這個類用於存放類定義的資訊)
allows for easy mapping between property names and getter/setter methods.(能夠方便對映屬性名稱和getter和setter方法)
Reflector.class
Reflector 類建立解析,建立Reflector 物件時會對目標類的各個變數和getter\setter方法進行解析,並分為以下幾個步驟:
- 解析目標類的無參建構函式,並賦值到defaultConstructor成員變數中
- 解析getter方法,並將解析結果存入getMethods和getTypes中
- 解析setter方法,並將解析結果存入setMethods和setTypes中
- 解析欄位屬性
/**
* 初始化Reflector
* 獲取目標類的資訊
* @param clazz 目標類
*/
public Reflector(Class<?> clazz) {
type = clazz;
//1> 獲取類的建構函式並賦值給defaultConstructor
addDefaultConstructor(clazz);
//2> 解析getter方法,並將解析結果存入getMethods和getTypes中
addGetMethods(clazz);
//3> 解析setter方法,並將解析結果存入setMethods和setTypes中
addSetMethods(clazz);
//4> 解析欄位屬性,將欄位屬性資訊存入getMethods,setMethods,getTypes,setTypes中
addFields(clazz);
//getMethods中key集合(可讀屬性名稱集合)
readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
//setMethods中key集合(可寫屬性名稱集合)
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);
}
}
Reflector:建構函式解析,解析目標類的無參建構函式
private void addDefaultConstructor(Class<?> clazz) {
Constructor<?>[] consts = clazz.getDeclaredConstructors();
for (Constructor<?> constructor : consts) {
if (constructor.getParameterTypes().length == 0) {
this.defaultConstructor = constructor;
}
}
}
Reflector:getter方法解析
getter方法解析主要分為三步
- 獲取get / is 開頭的無參方法
- 根據規則解決getter方法衝突(具體看下面程式碼解析)
/**
* 新增目標類的getter方法
* @param cls 目標類
*/
private void addGetMethods(Class<?> cls) {
Map<String, List<Method>> conflictingGetters = new HashMap<>();
Method[] methods = getClassMethods(cls);
for (Method method : methods) {
//排除有引數的方法
if (method.getParameterTypes().length > 0) {
continue;
}
String name = method.getName();
//isXXX()和getXXX()方法都獲取
if ((name.startsWith("get") && name.length() > 3)
|| (name.startsWith("is") && name.length() > 2)) {
//獲取方法名稱,並首位轉成小寫字母。如getAge()或isAge()會獲取得到age
name = PropertyNamer.methodToProperty(name);
//將方法存入Map<String, List<Method>> conflictingGetters,以待用於解決衝突
addMethodConflict(conflictingGetters, name, method);
}
}
//解決衝突的方法,並給setMethods和setTypes賦值
resolveGetterConflicts(conflictingGetters);
}
解決getter方法衝突: resolveGetterConflicts(conflictingGetters),有下面的規則:
1.如果兩個getter方法返回型別一樣且不是boolean返回型別,則丟擲異常
2.如果兩個getter方法返回型別一樣且是返回型別未boolean的isXXX()方法,則選取該方法
3.如果兩個返回型別不一樣,選取返回型別是子類的getter方法
/**
* 解決Getter衝突,如:isAge()和getAge()便是衝突方法
* 只有List<Method> > 1 時才需要解決衝突
* @param conflictingGetters 方法集合
*/
private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) {
for (Entry<String, List<Method>> entry : conflictingGetters.entrySet()) {
Method winner = null;
String propName = entry.getKey();
for (Method candidate : entry.getValue()) {
if (winner == null) {
winner = candidate;
continue;
}
Class<?> winnerType = winner.getReturnType();
Class<?> candidateType = candidate.getReturnType();
//如果兩個方法返回型別一致
if (candidateType.equals(winnerType)) {
//如果兩個方法返回型別一致,且返回型別都不是boolean型別則丟擲異常
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.");
//如果返回型別為boolean且是isXXX的方法則candidate勝出
} else if (candidate.getName().startsWith("is")) {
winner = candidate;
}
//如果winnerType是candidateType,則選取winner
} else if (candidateType.isAssignableFrom(winnerType)) {
//如果candidateType是winnerType的子類,則選取candidate
} 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.");
}
}
//將篩選出的方法新增到getMethods並將其返回值新增到getTypes
addGetMethod(propName, winner);
}
}
Reflector:setter方法解析
setter方法解析主要分為三步
- 獲取set 開頭且只有一個入參的方法
- 根據規則解決setter方法衝突(具體看下面程式碼解析)
/**
* 新增setter方法到setMethods變數中
* @param cls 目標類
*/
private void addSetMethods(Class<?> cls) {
Map<String, List<Method>> conflictingSetters = new HashMap<>();
Method[] methods = getClassMethods(cls);
for (Method method : methods) {
String name = method.getName();
//1> 獲取只有一個引數的setXXX()方法,等到方法名XXX
if (name.startsWith("set") && name.length() > 3) {
if (method.getParameterTypes().length == 1) {
name = PropertyNamer.methodToProperty(name);
//2> 新增放到到衝突列表:conflictingSetters,待進行衝突處理
addMethodConflict(conflictingSetters, name, method);
}
}
}
//3> 解決setter方法衝突
resolveSetterConflicts(conflictingSetters);
}
解決setter方法衝突:resolveSetterConflicts(conflictingSetters);
1.如果setter方法入參型別與對應的getter方法返回型別一致,則選取
2.如果存在兩個setter方法,判斷引數型別,取引數型別是子類的方法,若引數型別不是父子類關係,則丟擲異常
/**
* setter方法衝突篩選,將最終篩選出來的存入setMethods和setTypes變數中
* @param conflictingSetters setter方法集合
*/
private void resolveSetterConflicts(Map<String, List<Method>> conflictingSetters) {
for (String propName : conflictingSetters.keySet()) {
List<Method> setters = conflictingSetters.get(propName);
Class<?> getterType = getTypes.get(propName);
Method match = null;
ReflectionException exception = null;
for (Method setter : setters) {
//1> setter方法的引數型別與getter返回值型別一致,則當前setter方法為目標方法
Class<?> paramType = setter.getParameterTypes()[0];
if (paramType.equals(getterType)) {
// should be the best match
match = setter;
break;
}
//2> 如果存在兩個setter方法,判斷引數型別,取引數型別是子類的方法,若引數型別不是父子類關係,則丟擲異常
if (exception == null) {
try {
match = pickBetterSetter(match, setter, propName);
} catch (ReflectionException e) {
// there could still be the 'best match'
match = null;
exception = e;
}
}
}
if (match == null) {
throw exception;
} else {
//3> 排除衝突後的setter方法存入setMethods
addSetMethod(propName, match);
}
}
}
pickBetterSetter(match, setter, propName) 方法用於比較兩個setter方法,篩選入參是子類的setter方法
/**
* 引數型別比較,返回子類
* 如果型別不是父子類關係,直接報錯
* @param setter1 方法1
* @param setter2 方法2
* @param property property
* @return 返回是子類的方法
*/
private Method pickBetterSetter(Method setter1, Method setter2, String property) {
if (setter1 == null) {
return setter2;
}
Class<?> paramType1 = setter1.getParameterTypes()[0];
Class<?> paramType2 = setter2.getParameterTypes()[0];
//paramType2是paramType1的子類
if (paramType1.isAssignableFrom(paramType2)) {
return setter2;
//paramType1是paramType2的子類
} else if (paramType2.isAssignableFrom(paramType1)) {
return setter1;
}
throw new ReflectionException("Ambiguous setters defined for property '" + property + "' in class '"
+ setter2.getDeclaringClass() + "' with types '" + paramType1.getName() + "' and '"
+ paramType2.getName() + "'.");
}
4.3.3 解析typeAliases節點資訊
用於定義類的別名,可以在xml中的resultType中直接使用別名。
<typeAliases>
<package name="com.chen.mybatis.demo2.pojo"/>
<typeAlias type="com.chen.mybatis.demo2.pojo.BlogTypealiase" alias="blogTypealiase"/>
</typeAliases>
typeAliasesElement(root.evalNode("typeAliases"));
解析別名,並對別名進行註冊的步驟如下
1.解析package節點,將包下所有的類註冊別名。
2.解析typeAlias節點,根據type和alias資訊註冊別名。
方法typeAliasesElement(XNode parent)
解析,對package和typeAlias節點資訊分別進行解析並註冊別名,別名若存在則丟擲異常
別名的規則:
1.獲取package節點的包下的所有類進行註冊
2.獲取typeAlias節點type和alias資訊註冊別名
/**
* 解析別名typeAliases
*/
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//1> 如果是package節點,則將包下的所有類註冊到別名中
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
//2> 如果是typeAlias節點,則將每一個typeAlias節點的資訊註冊到別名中
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
//2.1> 根據typeAlias節點的type和alias資訊註冊別名
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);
}
}
}
}
}
- 4.3.3.1 註冊package節點下的包下的所有類的別名
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage)
public void registerAliases(String packageName){
registerAliases(packageName, Object.class);
}
/**
* 註冊包下類的別名
* @param packageName 報名
* @param superType 父類
*/
public void registerAliases(String packageName, Class<?> superType){
//獲取包下是Object.class的子類的類
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
for(Class<?> type : typeSet){
// Ignore inner classes and interfaces (including package-info.java)
// Skip also inner classes. See issue #6
//如果不是匿名內部類,介面類,稱員類
if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
registerAlias(type);
}
}
}
registerAlias(Class type)和registerAlias(String alias, Class value)是註冊別名的主要方法。
第一步獲取類名作為別名
第二步如果類中存在註解@Alias則獲取註解的value作為別名
第三步將別名轉換為小寫後再對該類進行別名註冊
/**
* 註冊別名
* 獲取類中@Alias註解作為別名,如果不存在則獲取類名
* @param type 目標類
*/
public void registerAlias(Class<?> type) {
//1.獲取類名
String alias = type.getSimpleName();
//2.獲取目標類中的@Alias註解資訊,value值
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
alias = aliasAnnotation.value();
}
//3.註冊別名
registerAlias(alias, type);
}
/**
* 註冊別名
* @param alias 別名
* @param value 目標類
*/
public void registerAlias(String alias, Class<?> value) {
if (alias == null) {
throw new TypeException("The parameter alias cannot be null");
}
// issue #748
//別名轉成小寫
String key = alias.toLowerCase(Locale.ENGLISH);
//判斷別名是否已經存在,存在則不予以註冊
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() + "'.");
}
TYPE_ALIASES.put(key, value);
}
- 4.3.3.2 根據typeAlias節點下type和alias資訊註冊別名
這部分程式碼跟包別名註冊差不多,,主要還是通過registerAlias(Class type)和registerAlias(String alias, Class value)這兩個方法進行別名註冊。
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
//2.1> 根據typeAlias節點的type和alias資訊註冊別名
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
typeAliasRegistry.registerAlias(clazz);
} else {
typeAliasRegistry.registerAlias(alias, clazz);
}
- 4.3 解析並載入plugins外掛到攔截器中
<plugins>
<plugin interceptor="com.chen.mybatis.demo2.plugins.ExamplePlugin">
<property name="someProperty" value="101"/>
</plugin>
</plugins>
pluginElement(root.evalNode("plugins"))
載入外掛,其中程式碼比較清晰就直接放程式碼了
/**
*載入plugins外掛到攔截器中
*/
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//1> 獲取攔截器的類全路徑
String interceptor = child.getStringAttribute("interceptor");
//2> 獲取plugin節點下property節點的資訊
Properties properties = child.getChildrenAsProperties();
//3> 建立攔截器例項
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
//4> 攔截器設定properties熟悉
interceptorInstance.setProperties(properties);
//5> configuration新增攔截器
configuration.addInterceptor(interceptorInstance);
}
}
}
4.3.4 解析並載入物件工廠objectFactory
<objectFactory type="com.chen.mybatis.demo2.objectFactory.ExampleObjectFactory">
<property name="someProperty" value="100"/>
</objectFactory>
objectFactoryElement(root.evalNode("objectFactory"));
設定objectFactory物件。
/**
* 解析並載入物件工廠objectFactory
*/
private void objectFactoryElement(XNode context) throws Exception {
if (context != null) {
//1> 獲取type屬性的值(類的全路徑)
String type = context.getStringAttribute("type");
//2> 獲取property節點資訊name和value值
Properties properties = context.getChildrenAsProperties();
//3> 構建ObjectFactory物件
ObjectFactory factory = (ObjectFactory) resolveClass(type).newInstance();
//4> ObjectFactory設定property屬性
factory.setProperties(properties);
//5> configuration設定ObjectFactory
configuration.setObjectFactory(factory);
}
}
4.3.5 解析並載入物件加工工廠ObjectWrapperFactory
<objectWrapperFactory type="com.chen.mybatis.demo2.objectFactory.ExampleObjectWrapperFactory"/>
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
程式碼就如下了,比較簡單
/**
* 解析並載入objectWrapperFactory
*/
private void objectWrapperFactoryElement(XNode context) throws Exception {
if (context != null) {
//1> 獲取type屬性的值(類的全路徑)
String type = context.getStringAttribute("type");
//2> 構建ObjectWrapperFactory物件
ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).newInstance();
//3> configuration設定ObjectWrapperFactory
configuration.setObjectWrapperFactory(factory);
}
}
4.3.6 解析並載入反射工廠ReflectorFactory
<reflectorFactory type="com.chen.mybatis.demo2.objectFactory.ExampleReflectorFactory"/>
reflectorFactoryElement(root.evalNode("reflectorFactory"));
/**
* 解析並載入reflectorFactory
*/
private void reflectorFactoryElement(XNode context) throws Exception {
if (context != null) {
//1> 獲取type屬性的值(類的全路徑)
String type = context.getStringAttribute("type");
//2> 構建ReflectorFactory物件
ReflectorFactory factory = (ReflectorFactory) resolveClass(type).newInstance();
//3> configuration設定ReflectorFactory
configuration.setReflectorFactory(factory);
}
}
4.3.7 設定settings屬性值
將之前解析得到的setting屬性資訊設定到configuration中,其中有一些是會有自己的預設值的。
settingsElement(settings);
/**
* 設定setting屬性值
* @param props Properties物件
*/
private void settingsElement(Properties props) {
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
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"), false));
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.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), 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.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
configuration.setLogPrefix(props.getProperty("logPrefix"));
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}
4.3.8 解析environments節點資訊,並設定environment屬性
事務管理器和資料來源都在environments節點下進行配置。transactionManager節點是事務管理器,dataSource節點是資料來源。
<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>
environmentsElement(root.evalNode("environments"));
/**
* 解析environments節點資訊
*/
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
//1>獲取environments節點default屬性值
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
//2>獲取environment節點id值
String id = child.getStringAttribute("id");
//3> 判斷id是否為environments節點的default值
if (isSpecifiedEnvironment(id)) {
//4>解析transactionManager節點,建立TransactionFactory物件
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
//5>解析dataSource節點,建立DataSourceFactory物件
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
//6.建立Environment.Builder物件,並設定transactionFactory和dataSource
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
//7.configuration設定environment屬性
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
4.3.9 解析databaseIdProvider節點(資料庫廠商資訊)
databaseIdProvider的配置一般如下
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql"/>
<property name="Oracle" value="oracle"/>
</databaseIdProvider>
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
配置資料庫廠商資訊,也就是設定DatabaseId值。mybatis會通過資料來源獲取到資料來源中資料庫名稱,再根據該名稱跟所配置的資訊進行對比,拿去與資料來源名稱一致的property 的value值作為DatabaseId,設定到configuration中。
/**
* 解析databaseIdProvider節點
*/
private void databaseIdProviderElement(XNode context) throws Exception {
DatabaseIdProvider databaseIdProvider = null;
if (context != null) {
//1> 獲取type屬性值
String type = context.getStringAttribute("type");
// awful patch to keep backward compatibility
if ("VENDOR".equals(type)) {
type = "DB_VENDOR";
}
//2> 獲取databaseIdProvider節點下的property節點資訊,並建立DatabaseIdProvider物件
Properties properties = context.getChildrenAsProperties();
databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
databaseIdProvider.setProperties(properties);
}
//3> 獲取databaseId,並設定configuration的databaseId
Environment environment = configuration.getEnvironment();
if (environment != null && databaseIdProvider != null) {
String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
configuration.setDatabaseId(databaseId);
}
}
先看看第二步:DatabaseIdProvider databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
- 當type = “DB_VENDOR” 時,建立的DatabaseIdProvider類實現類實際上是:VendorDatabaseIdProvider類
- 為什麼?因為Configuration建立的時候一樣註冊了別名。
typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
再來看第三步,獲取databaseId,並設定configuration的databaseId的程式碼實現。主要是這段程式碼
String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
會先通過資料來源獲取資料的產品名稱資訊,再跟property的name屬性相比較,如果是當前資料庫的產品名稱則拿去其value值作為DatabaseId。
/**
* 獲取資料庫名稱
* @param dataSource 資料來源
*/
@Override
public String getDatabaseId(DataSource dataSource) {
if (dataSource == null) {
throw new NullPointerException("dataSource cannot be null");
}
try {
return getDatabaseName(dataSource);
} catch (Exception e) {
LogHolder.log.error("Could not get a databaseId from dataSource", e);
}
return null;
}
/**
* 獲取資料庫名稱
* @param dataSource 資料來源
* @return 資料庫名稱
*/
private String getDatabaseName(DataSource dataSource) throws SQLException {
//1> 獲取資料庫產品名稱如oracle、mysql
String productName = getDatabaseProductName(dataSource);
//2> 如果資料來源中資料庫產品名稱包含databaseIdProvider節點下的property節點name屬性值,則返回對應的value值作為資料庫名稱
if (this.properties != null) {
for (Map.Entry<Object, Object> property : properties.entrySet()) {
if (productName.contains((String) property.getKey())) {
return (String) property.getValue();
}
}
// no match, return null
return null;
}
return productName;
}
/**
* 獲取資料庫產品名稱如oracle、mysql
*/
private String getDatabaseProductName(DataSource dataSource) throws SQLException {
Connection con = null;
try {
con = dataSource.getConnection();
DatabaseMetaData metaData = con.getMetaData();
return metaData.getDatabaseProductName();
} finally {
if (con != null) {
try {
con.close();
} catch (SQLException e) {
// ignored
}
}
}
}
4.3.10 解析typeHandlers節點
typeHandlers中關於TypeHandlerRegistry類走register()各種過載方法互相呼叫,一時間有點頭暈。
從入口方法中有兩種處理,一種是自動對映package下的typehandler,另一種是解析單個節點的typeHandler。
/**
* 解析typeHandlers節點,並對typehandler進行註冊
*/
private void typeHandlerElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//1> 從指定包中註冊TypeHandler
if ("package".equals(child.getName())) {
String typeHandlerPackage = child.getStringAttribute("name");
typeHandlerRegistry.register(typeHandlerPackage);
} else {
//2>解析typeHandler節點,獲取javaType,jdbcType,handler屬性值
String javaTypeName = child.getStringAttribute("javaType");
String jdbcTypeName = child.getStringAttribute("jdbcType");
String handlerTypeName = child.getStringAttribute("handler");
//3> 獲取javaTypeName為別名對應的類,如不存在,則返回javaTypeName指定的類
Class<?> javaTypeClass = resolveClass(javaTypeName);
//4> 獲取jdbcTypeName對應的JdbcType
JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
//5> 獲取handlerTypeName為別名對應的類,如不存在,則返回handlerTypeName指定的類
Class<?> typeHandlerClass = resolveClass(handlerTypeName);
//6> 註冊TypeHandler
if (javaTypeClass != null) {
if (jdbcType == null) {
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
TypeHandlerRegistry類中register過載方法的呼叫關係圖,比較清晰的看到register過載方法的呼叫
register(String packageName)
方法
該方法用於自動掃描包中的typehandler,並進行註冊
/**
* 註冊TypeHandler,自動掃描型別處理器
* @param packageName 包路徑
*/
public void register(String packageName) {
//1> 獲取包下的類
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName);
Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();
//2> 註冊TypeHandler,非內部類,介面,抽象類才能註冊
for (Class<?> type : handlerSet) {
//Ignore inner classes and interfaces (including package-info.java) and abstract classes
if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
register(type);
}
}
}
register(Class<?> typeHandlerClass)
方法
該方法主要用於判斷typehandler中javaType是否存在,從而呼叫不同的過載方法
/**
* 存在@MappedTypes,且有值,則存在javaType,呼叫register(Class<?> javaTypeClass, Class<?> typeHandlerClass)過載方法
* 不存在@MappedTypes或者沒有值,則呼叫register(TypeHandler<T> typeHandler)過載方法
*/
public void register(Class<?> typeHandlerClass) {
boolean mappedTypeFound = false;
//1> 獲取@MappedTypes註解
MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
if (mappedTypes != null) {
//2> 遍歷@MappedTypes註解中value值
for (Class<?> javaTypeClass : mappedTypes.value()) {
//3> 呼叫register過載方法進行註冊
register(javaTypeClass, typeHandlerClass);
mappedTypeFound = true;
}
}
if (!mappedTypeFound) {
register(getInstance(null, typeHandlerClass));
}
}
register(TypeHandler<T> typeHandler)
方法
/**
* 只有typeHandler引數的register過載方法
*
*/
public <T> void register(TypeHandler<T> typeHandler) {
boolean mappedTypeFound = false;
//1> 獲取@MappedTypes註解,存在javaType值,則呼叫register(Type javaType, TypeHandler<? extends T> typeHandler)方法註冊typehandler
MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
if (mappedTypes != null) {
for (Class<?> handledType : mappedTypes.value()) {
//呼叫過載方法register(Type javaType, TypeHandler<? extends T> typeHandler)
register(handledType, typeHandler);
mappedTypeFound = true;
}
}
// @since 3.1.0 - try to auto-discover the mapped type
//自動發現對映型別
if (!mappedTypeFound && typeHandler instanceof TypeReference) {
try {
TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
//呼叫過載方法register(Type javaType, TypeHandler<? extends T> typeHandler)
register(typeReference.getRawType(), typeHandler);
mappedTypeFound = true;
} catch (Throwable t) {
// maybe users define the TypeReference with a different type and are not assignable, so just ignore it
}
}
if (!mappedTypeFound) {
register((Class<T>) null, typeHandler);
}
}
register(Type javaType, TypeHandler<? extends T> typeHandler)
方法
判斷是否存在註解@MappedJdbcTypes(JdbcType)是否存在,再將jdbcType作為入參,呼叫register過載方法
private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
//1> 獲取typahandler類中@MappedJdbcTypes註解value值
MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
if (mappedJdbcTypes != null) {
//2> 遍歷MappedJdbcTypes的value值,進行遍歷註冊TypeHandler
for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
register(javaType, handledJdbcType, typeHandler);
}
//3> 如果MappedJdbcTypes註解中includeNullJdbcType=true,則註冊jdbcType=null的TypeHandler
if (mappedJdbcTypes.includeNullJdbcType()) {
register(javaType, null, typeHandler);
}
} else {
register(javaType, null, typeHandler);
}
}
register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler)
最終呼叫的方法
實現將typehandler儲存到對應的稱員變數中(map)
/**
* 註冊TypeHandler
* @param javaType
* @param jdbcType
* @param handler
*/
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
if (javaType != null) {
//1> 儲存以javaType為key的 Map<JdbcType, TypeHandler<?>>到TYPE_HANDLER_MAP變數中
Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
if (map == null || map == NULL_TYPE_HANDLER_MAP) {
map = new HashMap<>();
TYPE_HANDLER_MAP.put(javaType, map);
}
map.put(jdbcType, handler);
}
//2> 新增TypeHandler到ALL_TYPE_HANDLERS_MAP
ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
}
4.3.11 解析mappers節點
解析mappers節點這個放到之後的章節再進行描述。
附:用於學習的mybatis原始碼
https://github.com/569844962/mybatis-3.