該系列文件是本人在學習 Mybatis 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋(Mybatis原始碼分析 GitHub 地址、Mybatis-Spring 原始碼分析 GitHub 地址、Spring-Boot-Starter 原始碼分析 GitHub 地址)進行閱讀
MyBatis 版本:3.5.2
MyBatis-Spring 版本:2.0.3
MyBatis-Spring-Boot-Starter 版本:2.1.4
基礎支援層
在《精盡 MyBatis 原始碼分析 - 整體架構》中對 MyBatis 的基礎支援層已做過介紹,包含整個 MyBatis 的基礎模組,為核心處理層的功能提供了良好的支撐,本文對基礎支援層的每個模組進行分析
- 解析器模組
- 反射模組
- 異常模組
- 資料來源模組
- 事務模組
- 快取模組
- 型別模組
- IO模組
- 日誌模組
- 註解模組
- Binding模組
解析器模組
主要包路徑:org.apache.ibatis.parsing
主要功能:初始化時解析mybatis-config.xml配置檔案、為處理動態SQL語句中佔位符提供支援
主要檢視以下幾個類:
-
org.apache.ibatis.parsing.XPathParser
:基於Java XPath 解析器,用於解析MyBatis的mybatis-config.xml和**Mapper.xml等XML配置檔案 -
org.apache.ibatis.parsing.GenericTokenParser
:通用的Token解析器 -
org.apache.ibatis.parsing.PropertyParser
:動態屬性解析器
XPathParser
org.apache.ibatis.parsing.XPathParser
:基於Java XPath 解析器,用於解析MyBatis的mybatis-config.xml和**Mapper.xml等XML配置檔案
主要程式碼如下:
public class XPathParser {
/**
* XML Document 物件
*/
private final Document document;
/**
* 是否檢驗
*/
private boolean validation;
/**
* XML實體解析器
*/
private EntityResolver entityResolver;
/**
* 變數物件
*/
private Properties variables;
/**
* Java XPath 物件
*/
private XPath xpath;
public XPathParser(String xml) {
commonConstructor(false, null, null);
this.document = createDocument(new InputSource(new StringReader(xml)));
}
public String evalString(String expression) {
return evalString(document, expression);
}
public String evalString(Object root, String expression) {
// <1> 獲得值
String result = (String) evaluate(expression, root, XPathConstants.STRING);
// <2> 基於 variables 替換動態值,如果 result 為動態值
result = PropertyParser.parse(result, variables);
return result;
}
private Object evaluate(String expression, Object root, QName returnType) {
try {
// 通過XPath結合表示式獲取Document物件中的結果
return xpath.evaluate(expression, root, returnType);
} catch (Exception e) {
throw new BuilderException("Error evaluating XPath. Cause: " + e, e);
}
}
public XNode evalNode(String expression) {
return evalNode(document, expression);
}
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);
}
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
// 1> 建立 DocumentBuilderFactory 物件
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setValidating(validation);
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 {
// NOP
}
});
// 3> 解析 XML 檔案,將檔案載入到Document中
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
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檔案被解析後生成對應的org.w3c.dom.Document 物件 |
boolean | validation | 是否校驗XML檔案,一般情況下為true |
EntityResolver | entityResolver | org.xml.sax.EntityResolver 物件,XML實體解析器,一般通過自定義的org.apache.ibatis.builder.xml.XMLMapperEntityResolver 從本地獲取DTD檔案解析 |
Properties | variables | 變數Properties物件,用來替換需要動態配置的屬性值,例如我們在MyBatis的配置檔案中使用變數將使用者名稱密碼放在另外一個配置檔案中,那麼這個配置會被解析到Properties物件用,用於替換XML檔案中的動態值 |
XPath | xpath | javax.xml.xpath.XPath 物件,用於查詢XML中的節點和元素 |
建構函式有很多,基本都相似,內部都是呼叫commonConstructor
方法設定相關屬性和createDocument
方法為該XML檔案建立一個Document物件
提供了一系列的eval*
方法,用於獲取Document物件中的元素或者節點:
- eval*元素的方法:根據表示式獲取我們常用型別的元素的值,其中會基於
variables
呼叫PropertyParser
的parse
方法替換掉其中的動態值(如果存在),這就是MyBatis如何替換掉XML中的動態值實現的方式 - eval*節點的方法:根據表示式獲取到
org.w3c.dom.Node
節點物件,將其封裝成自己定義的XNode
物件,方便主要為了動態值的替換
PropertyParser
org.apache.ibatis.parsing.PropertyParser
:動態屬性解析器
主要程式碼如下:
public class PropertyParser {
public static String parse(String string, Properties variables) {
// <2.1> 建立 VariableTokenHandler 物件
VariableTokenHandler handler = new VariableTokenHandler(variables);
// <2.2> 建立 GenericTokenParser 物件
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
// <2.3> 執行解析
return parser.parse(string);
}
}
parse
方法:建立VariableTokenHandler物件和GenericTokenParser物件,然後呼叫GenericTokenParser的parse方法替換其中的動態值
GenericTokenParser
org.apache.ibatis.parsing.GenericTokenParser
:通用的Token解析器
定義了是三個屬性:
public class GenericTokenParser {
/**
* 開始的 Token 字串
*/
private final String openToken;
/**
* 結束的 Token 字串
*/
private final String closeToken;
/**
* Token處理器
*/
private final TokenHandler handler;
}
根據開始字串和結束字串解析出裡面的表示式(例如${name}->name),然後通過TokenHandler進行解析處理
VariableTokenHandler
VariableTokenHandler
,是PropertyParser
的內部靜態類,變數Token處理器,根據Properties variables
變數物件將Token動態值解析成實際值
總結
- 將XML檔案解析成
XPathParser
物件,其中會解析成對應的Document
物件,內部的Properties物件儲存動態變數的值 PropertyParser
用於解析XML檔案中的動態值,根據GenericTokenParser
獲取動態屬性的名稱(例如${name}->name),然後通過VariableTokenHandler
根據Properties物件獲取到動態屬性(name)對應的值
反射模組
主要功能:對Java原生的反射進行了良好的封裝,提供更加簡單易用的API,用於解析類物件
反射這一模組的程式碼寫得很漂亮,值得參考!!!
主要包路徑:org.apache.ibatis.reflection
如下所示:
主要檢視以下幾個類:
org.apache.ibatis.reflection.Reflector
:儲存Class類中定義的屬性相關資訊並進行了簡單的對映org.apache.ibatis.reflection.invoker.MethodInvoker
:Class類中屬性對應set方法或者get方法的封裝org.apache.ibatis.reflection.DefaultReflectorFactory
:Reflector的工廠介面,用於建立和快取Reflector物件org.apache.ibatis.reflection.MetaClass
:Class類的後設資料,包裝Reflector,基於PropertyTokenizer(分詞器)提供對Class類的後設資料一些操作,可以理解成對Reflector操作的進一步增強org.apache.ibatis.reflection.DefaultObjectFactory
:實現了ObjectFactory工廠介面,用於建立Class類物件org.apache.ibatis.reflection.wrapper.BeanWrapper
:實現了ObjectWrapper物件包裝介面,繼承BaseWrapper抽象類,根據Object物件與其MetaClass後設資料物件提供對該Object物件的一些操作org.apache.ibatis.reflection.MetaObject
:物件後設資料,提供了操作物件的屬性等方法。 可以理解成對ObjectWrapper操作的進一步增強org.apache.ibatis.reflection.SystemMetaObject
:用於建立MetaObject物件,提供了ObjectFactory、ObjectWrapperFactory、空MetaObject的單例org.apache.ibatis.reflection.ParamNameResolver
:方法引數名稱解析器,用於解析我們定義的Mapper介面的方法
Reflector
org.apache.ibatis.reflection.Reflector
:儲存Class類中定義的屬性相關資訊並進行了簡單的對映
部分程式碼如下:
public class Reflector {
/**
* Class類
*/
private final Class<?> type;
/**
* 可讀屬性集合
*/
private final String[] readablePropertyNames;
/**
* 可寫屬性集合
*/
private final String[] writablePropertyNames;
/**
* 屬性對應的 setter 方法的對映。
*
* key 為屬性名稱
* value 為 Invoker 物件
*/
private final Map<String, Invoker> setMethods = new HashMap<>();
/**
* 屬性對應的 getter 方法的對映。
*
* key 為屬性名稱 value 為 Invoker 物件
*/
private final Map<String, Invoker> getMethods = new HashMap<>();
/**
* 屬性對應的 setter 方法的方法引數型別的對映。{@link #setMethods}
*
* key 為屬性名稱
* value 為方法引數型別
*/
private final Map<String, Class<?>> setTypes = new HashMap<>();
/**
* 屬性對應的 getter 方法的返回值型別的對映。{@link #getMethods}
*
* key 為屬性名稱
* value 為返回值的型別
*/
private final Map<String, Class<?>> getTypes = new HashMap<>();
/**
* 預設構造方法
*/
private Constructor<?> defaultConstructor;
/**
* 所有屬性集合
* key 為全大寫的屬性名稱
* value 為屬性名稱
*/
private Map<String, String> caseInsensitivePropertyMap = new HashMap<>();
public Reflector(Class<?> clazz) {
// 設定對應的類
type = clazz;
// <1> 初始化 defaultConstructor 預設構造器,也就是無參構造器
addDefaultConstructor(clazz);
// <2> 初始化 getMethods 和 getTypes
addGetMethods(clazz);
// <3> 初始化 setMethods 和 setTypes
addSetMethods(clazz);
// <4> 可能有些屬性沒有get或者set方法,則直接將該Field欄位封裝成SetFieldInvoker或者GetFieldInvoker,然後分別儲存至上面4個變數中
addFields(clazz);
// <5> 初始化 readablePropertyNames、writeablePropertyNames、caseInsensitivePropertyMap 屬性
readablePropertyNames = getMethods.keySet().toArray(new String[0]);
writablePropertyNames = setMethods.keySet().toArray(new String[0]);
for (String propName : readablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
for (String propName : writablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
}
}
通過上面的程式碼可以看到Reflector
在初始化的時候會通過反射機制進行解析該Class類,整個解析過程並不複雜,我這裡就不全部講述了,可閱讀相關程式碼,已做好註釋???
解析後儲存了Class類的以下資訊:
型別 | 欄位 | 說明 |
---|---|---|
Class<?> | type | Class類 |
String[] | readablePropertyNames | 可讀屬性集合 |
String[] | writablePropertyNames | 可寫屬性集合 |
Map<String, Invoker> | setMethods | 屬性對應的 setter 方法的對映:<屬性名稱, MethodFieldInvoker物件> |
Map<String, Invoker> | getMethods | 屬性對應的 getter 方法的對映:<屬性名稱, MethodFieldInvoker物件> |
Map<String, Class<?>> | setTypes | 屬性對應的 setter 方法的方法引數型別的對映:<屬性名稱, 方法引數型別> |
Map<String, Class<?>> | getTypes | 屬性對應的 getter 方法的返回值型別的對映:<屬性名稱, 方法返回值型別> |
Constructor<?> | defaultConstructor | 預設構造方法 |
Map<String, String> | caseInsensitivePropertyMap | 所有屬性集合:<屬性名稱(全大寫), 屬性名稱> |
MethodInvoker
org.apache.ibatis.reflection.invoker.MethodInvoker
:Class類中屬性對應set方法或者get方法的封裝
程式碼如下:
public class MethodInvoker implements Invoker {
/**
* 型別
*/
private final Class<?> type;
/**
* 指定方法
*/
private final Method method;
public MethodInvoker(Method method) {
this.method = method;
if (method.getParameterTypes().length == 1) {
// 引數大小為 1 時,一般是 setter 方法,設定 type 為方法引數[0]
type = method.getParameterTypes()[0];
} else {
// 否則,一般是 getter 方法,設定 type 為返回型別
type = method.getReturnType();
}
}
@Override
public Object invoke(Object target, Object[] args) throws IllegalAccessException, InvocationTargetException {
try {
return method.invoke(target, args);
} catch (IllegalAccessException e) {
if (Reflector.canControlMemberAccessible()) {
method.setAccessible(true);
return method.invoke(target, args);
} else {
throw e;
}
}
}
@Override
public Class<?> getType() {
return type;
}
}
-
在其建構函式中,設定set方法或者get方法,並獲取其引數型別或者返回值型別進行儲存,也就是該屬性的型別
-
如果Class類中有些屬性沒有set或者get方法,那麼這些屬性會被封裝成下面兩個物件(final static修飾的欄位不會被封裝),用於設定或者獲取他們的值
org.apache.ibatis.reflection.invoker.SetFieldInvoker
、org.apache.ibatis.reflection.invoker.GetFieldInvoker
DefaultReflectorFactory
org.apache.ibatis.reflection.DefaultReflectorFactory
:Reflector的工廠介面,用於建立和快取Reflector物件
程式碼如下:
public class DefaultReflectorFactory implements ReflectorFactory {
/**
* 是否快取
*/
private boolean classCacheEnabled = false;
/**
* Reflector 的快取對映
*
* KEY:Class 物件
* VALUE:Reflector 物件
*/
private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<>();
public DefaultReflectorFactory() {
}
@Override
public boolean isClassCacheEnabled() {
return classCacheEnabled;
}
@Override
public void setClassCacheEnabled(boolean classCacheEnabled) {
this.classCacheEnabled = classCacheEnabled;
}
@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);
}
}
}
根據Class物件建立Reflector物件,程式碼比較簡單
MetaClass
org.apache.ibatis.reflection.MetaClass
:Class類的後設資料,包裝Reflector,基於PropertyTokenizer(分詞器)提供對Class類的後設資料各種操作,可以理解成對Reflector操作的進一步增強
其中包含了ReflectorFactory和Reflector兩個欄位,通過PropertyTokenizer分詞器提供了獲取屬性的名稱和返回值型別等等方法,也就是在Reflector上面新增了一些方法
org.apache.ibatis.reflection.propertyPropertyTokenizer
分詞器用於解析類似於'map[qm].user'這樣的屬性,將其分隔儲存方便獲取,可閱讀相關程式碼哦???
DefaultObjectFactory
org.apache.ibatis.reflection.DefaultObjectFactory
:實現了ObjectFactory工廠介面,用於建立Class類物件
部分程式碼如下:
public class DefaultObjectFactory implements ObjectFactory, Serializable {
@Override
public <T> T create(Class<T> type) {
return create(type, null, null);
}
@SuppressWarnings("unchecked")
@Override
public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
// 獲取需要建立的類
Class<?> classToCreate = resolveInterface(type);
// 建立指定類的物件
return (T) instantiateClass(classToCreate, constructorArgTypes, constructorArgs);
}
protected Class<?> resolveInterface(Class<?> type) {
Class<?> classToCreate;
if (type == List.class || type == Collection.class || type == Iterable.class) {
classToCreate = ArrayList.class;
} else if (type == Map.class) {
classToCreate = HashMap.class;
} else if (type == SortedSet.class) { // issue #510 Collections Support
classToCreate = TreeSet.class;
} else if (type == Set.class) {
classToCreate = HashSet.class;
} else {
classToCreate = type;
}
return classToCreate;
}
private <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
try {
Constructor<T> constructor;
if (constructorArgTypes == null || constructorArgs == null) {
// 使用預設的構造器
constructor = type.getDeclaredConstructor();
try {
// 返回例項
return constructor.newInstance();
} catch (IllegalAccessException e) {
if (Reflector.canControlMemberAccessible()) {
constructor.setAccessible(true);
return constructor.newInstance();
} else {
throw e;
}
}
}
// 通過引數型別列表獲取構造器
constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[constructorArgTypes.size()]));
try {
// 返回例項
return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));
} catch (IllegalAccessException e) {
if (Reflector.canControlMemberAccessible()) {
constructor.setAccessible(true);
return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));
} else {
throw e;
}
}
} catch (Exception e) {
...
}
}
}
通過Class物件獲取建構函式,然後通過建構函式建立一個例項物件
BeanWrapper
org.apache.ibatis.reflection.wrapper.BeanWrapper
:實現了ObjectWrapper物件包裝介面,繼承BaseWrapper抽象類,根據Object物件與其MetaClass後設資料物件提供對該Object物件的一些操作,部分程式碼如下:
public class BeanWrapper extends BaseWrapper {
/**
* 普通物件
*/
private final Object object;
private final MetaClass metaClass;
public BeanWrapper(MetaObject metaObject, Object object) {
super(metaObject);
this.object = object;
// 建立 MetaClass 物件
this.metaClass = MetaClass.forClass(object.getClass(), metaObject.getReflectorFactory());
}
@Override
public Object get(PropertyTokenizer prop) {
// <1> 獲得集合型別的屬性的指定位置的值
if (prop.getIndex() != null) {
// 獲得集合型別的屬性
Object collection = resolveCollection(prop, object);
// 獲得指定位置的值
return getCollectionValue(prop, collection);
// <2> 獲得屬性的值
} else {
return getBeanProperty(prop, object);
}
}
@Override
public void set(PropertyTokenizer prop, Object value) {
// 設定集合型別的屬性的指定位置的值
if (prop.getIndex() != null) {
// 獲得集合型別的屬性
Object collection = resolveCollection(prop, object);
// 設定指定位置的值
setCollectionValue(prop, collection, value);
} else { // 設定屬性的值
setBeanProperty(prop, object, value);
}
}
}
get
方法:根據分詞器從object
物件中獲取對應的屬性值
set
方法:根據分詞器往object
物件中設定屬性值
還包含了其他很多操作object
物件的方法,這裡不全部羅列出來了,請閱讀其相關程式碼進行檢視???
MetaObject
org.apache.ibatis.reflection.MetaObject
:物件後設資料,提供了操作物件的屬性等方法,可以理解成對 ObjectWrapper 操作的進一步增強
在Mybatis中如果需要操作某個物件(例項類或者集合),都會轉換成MetaObject型別,便於操作
主要程式碼如下:
public class MetaObject {
/**
* 原始 Object 物件
*/
private final Object originalObject;
/**
* 封裝過的 Object 物件
*/
private final ObjectWrapper objectWrapper;
private final ObjectFactory objectFactory;
private final ObjectWrapperFactory objectWrapperFactory;
private final ReflectorFactory reflectorFactory;
private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory,
ReflectorFactory reflectorFactory) {
this.originalObject = object;
this.objectFactory = objectFactory;
this.objectWrapperFactory = objectWrapperFactory;
this.reflectorFactory = reflectorFactory;
if (object instanceof ObjectWrapper) {
this.objectWrapper = (ObjectWrapper) object;
} else if (objectWrapperFactory.hasWrapperFor(object)) {
this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
} else if (object instanceof Map) {
this.objectWrapper = new MapWrapper(this, (Map) object);
} else if (object instanceof Collection) {
this.objectWrapper = new CollectionWrapper(this, (Collection) object);
} else {
this.objectWrapper = new BeanWrapper(this, object);
}
}
/**
* 建立 MetaObject 物件
*
* @param object 原始 Object 物件
* @param objectFactory 生產 Object 的例項工廠
* @param objectWrapperFactory 建立 ObjectWrapper 工廠,沒有預設實現,沒有用到
* @param reflectorFactory 建立 Object 對應 Reflector 的工廠
* @return MetaObject 物件
*/
public static MetaObject forObject(Object object, ObjectFactory objectFactory,
ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
if (object == null) {
return SystemMetaObject.NULL_META_OBJECT;
} else {
return new MetaObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
}
}
public String findProperty(String propName, boolean useCamelCaseMapping) {
return objectWrapper.findProperty(propName, useCamelCaseMapping);
}
/**
* 獲取指定屬性的值,遞迴處理
*
* @param name 屬性名稱
* @return 屬性值
*/
public Object getValue(String name) {
// 建立 PropertyTokenizer 物件,對 name 分詞
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) { // 有子表示式
// 建立 MetaObject 物件
MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
// <2> 遞迴判斷子表示式 children ,獲取值
if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
return null;
} else {
return metaValue.getValue(prop.getChildren());
}
} else { // 無子表示式
// <1> 獲取值
return objectWrapper.get(prop);
}
}
/**
* 設定指定屬性值
*
* @param name 屬性名稱
* @param value 屬性值
*/
public void setValue(String name, Object value) {
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) {
MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
if (value == null) {
// don't instantiate child path if value is null
return;
} else {
// <1> 建立值
metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory);
}
}
metaValue.setValue(prop.getChildren(), value);
} else {
// <1> 設定值
objectWrapper.set(prop, value);
}
}
/**
* 建立指定屬性的 MetaObject 物件
*
* @param name 屬性名稱
* @return MetaObject 物件
*/
public MetaObject metaObjectForProperty(String name) {
// 獲得屬性值
Object value = getValue(name);
// 建立 MetaObject 物件
return MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory);
}
}
我們可以看到構造方法是私有的,無法直接通過建構函式建立例項物件,提供了一個forObject
靜態方法來建立一個MetaObject物件:
-
如果原始Object物件為null,則返回空的MetaObject物件
NULL_META_OBJECT
,在SystemMetaObject
中定義的一個單例物件,實際就是將MetaObject內部的Object原始物件設定為NullObject
(一個靜態類) -
否則通過建構函式建立MetaObject物件,在它的建構函式中可以看到,根據Object物件的型別來決定建立什麼型別的
ObjectWrapper
,並沒有用到ObjectWrapperFactory
工廠介面(預設實現也丟擲異常)
對於一個已經初始化好的MetaObject物件,可以通過getValue
方法獲取指定屬性的值,setValue
設定指定屬性值
SystemMetaObject
org.apache.ibatis.reflection.SystemMetaObject
:系統級別的MetaObject物件,提供了ObjectFactory、ObjectWrapperFactory、空MetaObject的單例
程式碼如下:
public final class SystemMetaObject {
/**
* ObjectFactory 的單例
*/
public static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
/**
* ObjectWrapperFactory 的單例
*/
public static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
/**
* 空物件的 MetaObject 物件單例
*/
public static final MetaObject NULL_META_OBJECT = MetaObject.forObject(NullObject.class, DEFAULT_OBJECT_FACTORY,
DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
private SystemMetaObject() {
// Prevent Instantiation of Static Class
}
private static class NullObject {
}
/**
* 建立 MetaObject 物件
*
* @param object 指定物件
* @return MetaObject 物件
*/
public static MetaObject forObject(Object object) {
return MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY,
new DefaultReflectorFactory());
}
}
內部就定義了一個forObject(Object object)
靜態方法,用於建立MetaObject物件
我們一般會將Entity實體類或者Map集合解析成MetaObject物件,然後可以對其屬性進行操作
ParamNameResolver
org.apache.ibatis.reflection.ParamNameResolver
:方法引數名稱解析器,用於解析我們定義的Mapper介面的方法
在org.apache.ibatis.binding.MapperMethod
的MethodSignature
內部類會用到
主要程式碼如下:
public class ParamNameResolver {
private static final String GENERIC_NAME_PREFIX = "param";
/**
* 引數名對映
* KEY:引數順序
* VALUE:引數名
*/
private final SortedMap<Integer, String> names;
/**
* 是否有 {@link Param} 註解的引數
*/
private boolean hasParamAnnotation;
public ParamNameResolver(Configuration config, Method method) {
// 獲取方法的引數型別集合
final Class<?>[] paramTypes = method.getParameterTypes();
// 獲取方法的引數上面的註解集合
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap<Integer, String> map = new TreeMap<>();
int paramCount = paramAnnotations.length;
// get names from @Param annotations
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
// 忽略 RowBounds、ResultHandler引數型別
if (isSpecialParameter(paramTypes[paramIndex])) {
// skip special parameters
continue;
}
String name = null;
// <1> 首先,從 @Param 註解中獲取引數名
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {
hasParamAnnotation = true;
name = ((Param) annotation).value();
break;
}
}
if (name == null) {
// @Param was not specified.
// <2> 其次,獲取真實的引數名
if (config.isUseActualParamName()) { // 預設開啟
name = getActualParamName(method, paramIndex);
}
// <3> 最差,使用 map 的順序,作為編號
if (name == null) {
// use the parameter index as the name ("0", "1", ...)
// gcode issue #71
name = String.valueOf(map.size());
}
}
// 新增到 map 中
map.put(paramIndex, name);
}
// 構建不可變的 SortedMap 集合
names = Collections.unmodifiableSortedMap(map);
}
/**
* 根據引數值返回引數名稱與引數值的對映關係
*
* @param args 引數值陣列
* @return 引數名稱與引數值的對映關係
*/
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
// 無引數,則返回 null
if (args == null || paramCount == 0) {
return null;
// 只有1個引數,並且沒有 @Param 註解,則直接返回該值
} else if (!hasParamAnnotation && paramCount == 1) {
return args[names.firstKey()];
} else {
/*
* 引數名稱與值的對映,包含以下兩種組合資料:
* 組合1:(引數名,值)
* 組合2:(param+引數順序,值)
*/
final Map<String, Object> param = new ParamMap<>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
// 組合 1 :新增到 param 中
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
// 組合 2 :新增到 param 中
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
}
在建構函式中可以看到,目的是獲取到該方法的引數名,將引數順序與引數名進行對映儲存在UnmodifiableSortedMap
一個不可變的SortedMap集合中,大致邏輯:
-
如果新增了
@Param
註解,則引數名稱為該註解的value值 -
沒有新增@Param註解則嘗試獲取真實的引數名
說明:通過反射獲取方法的引數名,我們只能獲取到 arg0,arg1 的名稱,因為jdk8之後這些變數名稱沒有被編譯到class檔案中,編譯時需要指定
-parameters
選項,方法的引數名才會記錄到class檔案中,執行時我們就可以通過反射機制獲取到 -
還是沒有獲取到引數名則使用序號標記,一般不會走到這一步
還有一個getNamedParams
方法,根據實際入引數組返回引數名與引數值的對映,大致邏輯:
-
實際引數為null或者引數個數為0,則直接返回null
-
沒有使用
@Param
註解並且引數個數為1,則直接返回引數值 -
根據引數順序與引數名的對映獲取到
引數名與引數值的對映
,而且還會將(param+引數順序)與引數值進行對映
,最後將兩種組合的對映返回
總結
- 將一個Entity實體類或者Map集合轉換成MetaObject物件,該物件通過反射機制封裝了各種簡便的方法,使更加方便安全地操作該物件,建立過程:
- 通過
Configuration
全域性配置物件的newMetaObject(Object object)
方法建立,會傳入DefaultObjectFactory
、DefaultObjectWrapperFactory
和DefaultReflectorFactory
幾個預設實現類- 內部呼叫
MetaObject
的forObject
靜態方法,通過它的構造方法建立一個例項物件- 在MetaObject的建構函式中,會根據Object物件的型別來建立
ObjectWrapper
物件- 如果是建立
BeanWrapper
,則在其建構函式中,會再呼叫MetaClass的forClass方法建立MetaClass
物件,也就是通過其建構函式建立一個例項物件- 如果是
MapWrapper
,則直接複製給內部的Map<String, Object> map
屬性即可,其他集合物件類似- 在MetaClass的建構函式中,會通過呼叫
DefaultReflectorFactory
的findForClass方法建立Reflector
物件- 在Reflector的建構函式中,通過反射機制解析該Class類,屬性的set和get方法會被封裝成
MethodInvoker
物件
- ParamNameResolver工具類提供方法引數解析功能(主要解析Mapper介面裡面的方法引數哦~)
異常模組
MyBatis的幾個基本的Exception異常類在org.apache.ibatis.exceptions包路徑下
-
org.apache.ibatis.exceptions.IbatisException
:實現 RuntimeException 類,MyBatis 的異常基類 -
org.apache.ibatis.exceptions.PersistenceException
:繼承 IbatisException 類,目前 MyBatis 真正的異常基類 -
org.apache.ibatis.exceptions.ExceptionFactory
:異常工廠
每個模組都有自己都有的異常類,程式碼都是相同的,這裡就不一一展示了
資料來源模組
MyBatis支援三種資料來源配置,分別為UNPOOLED
、POOLED
和JNDI
。內部提供了兩種資料來源實現,分別是UnpooledDataSource
和PooledDataSource
。在三種資料來源配置中,UNPOOLED 和 POOLED 是常用的兩種配置。至於 JNDI,MyBatis 提供這種資料來源的目的是為了讓其能夠執行在 EJB 或應用伺服器等容器中,這一點官方文件中有所說明。由於 JNDI 資料來源在日常開發中使用甚少,因此,本篇文章不打算分析 JNDI 資料來源相關實現。大家若有興趣,可自行分析。
實際場景下,我們基本不用 MyBatis 自帶的資料庫連線池的實現,這裡是讓我們是對資料庫連線池的實現有個大體的理解。
主要包路徑:org.apache.ibatis.datasource
主要功能:資料來源的實現,與MySQL連線的管理
主要檢視以下幾個類:
org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory
:實現 DataSourceFactory 介面,非池化的 DataSourceFactory 實現類org.apache.ibatis.datasource.unpooled.UnpooledDataSource
:實現 DataSource 介面,非池化的 DataSource 物件org.apache.ibatis.datasource.pooled.PooledDataSourceFactory
:繼承 UnpooledDataSourceFactory 類,池化的 DataSourceFactory 實現類org.apache.ibatis.datasource.pooled.PooledDataSource
:實現 DataSource 介面,池化的 DataSource 實現類org.apache.ibatis.datasource.pooled.PoolState
:連線池狀態,記錄空閒和啟用的 PooledConnection 集合,以及相關的資料統計org.apache.ibatis.datasource.pooled.PooledConnection
:實現 InvocationHandler 介面,池化的 Connection 物件
UnpooledDataSourceFactory
org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory
:實現 DataSourceFactory 介面,非池化的 DataSourceFactory 實現類
程式碼如下:
public class UnpooledDataSourceFactory implements DataSourceFactory {
private static final String DRIVER_PROPERTY_PREFIX = "driver.";
private static final int DRIVER_PROPERTY_PREFIX_LENGTH = DRIVER_PROPERTY_PREFIX.length();
protected DataSource dataSource;
public UnpooledDataSourceFactory() {
this.dataSource = new UnpooledDataSource();
}
@Override
public void setProperties(Properties properties) {
Properties driverProperties = new Properties();
// 建立 dataSource 對應的 MetaObject 物件,其中是BeanWrapper
MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
// 遍歷 properties 屬性,初始化到 driverProperties 和 MetaObject 中
for (Object key : properties.keySet()) {
String propertyName = (String) key;
// 初始化到 driverProperties 中
if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
String value = properties.getProperty(propertyName);
driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
// 如果該屬性在UnpooledDataSource中有setter方法,則初始化到 MetaObject 中
} else if (metaDataSource.hasSetter(propertyName)) {
String value = (String) properties.get(propertyName);
Object convertedValue = convertValue(metaDataSource, propertyName, value);
// 將資料設定到UnpooledDataSource中去
metaDataSource.setValue(propertyName, convertedValue);
} else {
throw new DataSourceException("Unknown DataSource property: " + propertyName);
}
}
if (driverProperties.size() > 0) {
// 設定 driverProperties 到 MetaObject 中
metaDataSource.setValue("driverProperties", driverProperties);
}
}
@Override
public DataSource getDataSource() {
return dataSource;
}
private Object convertValue(MetaObject metaDataSource, String propertyName, String value) {
Object convertedValue = value;
// 獲得該屬性的 setting 方法的引數型別
Class<?> targetType = metaDataSource.getSetterType(propertyName);
// 轉化
if (targetType == Integer.class || targetType == int.class) {
convertedValue = Integer.valueOf(value);
} else if (targetType == Long.class || targetType == long.class) {
convertedValue = Long.valueOf(value);
} else if (targetType == Boolean.class || targetType == boolean.class) {
convertedValue = Boolean.valueOf(value);
}
return convertedValue;
}
}
setProperties(Properties properties)
方法:
-
該
dataSource
建立對應的MetaObject
物件,便於設定相應的屬性 -
將入參
Properties
物件中的配置往dataSource
設定 -
如果是以
driver.
開頭的配置則統一放入一個Properties中,然後設定到dataSource
的driverProperties
屬性中
getDataSource()
方法:直接返回建立好的資料來源dataSource
UnpooledDataSource
org.apache.ibatis.datasource.unpooled.UnpooledDataSource
:實現 DataSource 介面,非池化的 DataSource 物件
獲取資料庫連線的方法:
public class UnpooledDataSource implements DataSource {
private Connection doGetConnection(String username, String password) throws SQLException {
// 建立 Properties 物件
Properties props = new Properties();
// 設定 driverProperties 到 props 中
if (driverProperties != null) {
props.putAll(driverProperties);
}
// 設定 user 和 password 到 props 中
if (username != null) {
props.setProperty("user", username);
}
if (password != null) {
props.setProperty("password", password);
}
return doGetConnection(props);
}
private Connection doGetConnection(Properties properties) throws SQLException {
// <1> 初始化 Driver
initializeDriver();
// <2> 獲得 Connection 物件
Connection connection = DriverManager.getConnection(url, properties);
// <3> 配置 Connection 物件
configureConnection(connection);
return connection;
}
private synchronized void initializeDriver() throws SQLException {
// 判斷 registeredDrivers 是否已經存在該 driver ,若不存在,進行初始化
if (!registeredDrivers.containsKey(driver)) {
Class<?> driverType;
try {
// <2> 獲得 driver 類
if (driverClassLoader != null) {
driverType = Class.forName(driver, true, driverClassLoader);
} else {
driverType = Resources.classForName(driver);
}
// DriverManager requires the driver to be loaded via the system ClassLoader.
// http://www.kfu.com/~nsayer/Java/dyn-jdbc.html
// <3> 建立 Driver 物件
Driver driverInstance = (Driver) driverType.getDeclaredConstructor().newInstance();
// 建立 DriverProxy 物件(為了使用自己定義的Logger物件),並註冊到 DriverManager 中
DriverManager.registerDriver(new DriverProxy(driverInstance));
// 新增到 registeredDrivers 中
registeredDrivers.put(driver, driverInstance);
} catch (Exception e) {
throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
}
}
}
private void configureConnection(Connection conn) throws SQLException {
if (defaultNetworkTimeout != null) {
conn.setNetworkTimeout(Executors.newSingleThreadExecutor(), defaultNetworkTimeout);
}
if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
conn.setAutoCommit(autoCommit);
}
if (defaultTransactionIsolationLevel != null) {
conn.setTransactionIsolation(defaultTransactionIsolationLevel);
}
}
}
doGetConnection(Properties properties)
方法:
- 初始化Driver,將其包裝成DriverProxy,主要用於使用自己的Logger物件
- 獲得 Connection 物件
- 配置 Connection 物件,網路超時時間、是否自動提交事務、預設的事務隔離級別
該類還包含了資料庫連線的基本資訊以及其他方法,我這裡沒有全部列出來,感興趣的可以看下這個類?
PooledDataSourceFactory
org.apache.ibatis.datasource.pooled.PooledDataSourceFactory
:繼承 UnpooledDataSourceFactory 類,池化的 DataSourceFactory 實現類
和 UnpooledDataSourceFactory 的區別是它建立了 PooledDataSource 物件
PooledDataSource
org.apache.ibatis.datasource.pooled.PooledDataSource
:實現 DataSource 介面,池化的 DataSource 實現類,包含資料庫的連線資訊以及連線池的配置資訊
下面的方法都有點長,我就】就不列出來了,可以根據流程圖並結合註釋進行檢視?
getConnection()
方法:用於獲取連線,流程圖:
pushConnection
方法:"關閉"一個連線,放入空閒連線佇列中或者關設定為無效狀態並關閉真正的連線,在PooledConnection
的invoke
方法中,如果方法名稱為close
,表示需要關閉該連線,則呼叫該方法,流程圖:
pingConnection
方法:通過向資料庫發起 poolPingQuery
語句來發起"ping"操作,以判斷資料庫連線是否有效,在PooledConnection
的isValid
方法中被呼叫,PooledDataSource的獲取連線和關閉連線時都會判斷該連線是否有效
forceCloseAll
方法:用於關閉所有的連線,釋放資源,在定義的finalize()
方法中會被呼叫,即當前PooledDataSource物件被釋放時
PoolState
org.apache.ibatis.datasource.pooled.PoolState
:連線池狀態,記錄空閒和啟用的PooledConnection
集合,以及相關的資料統計
相當於一個池子,儲存了空閒的或者正在被使用的連線以及一些連線池的配置資訊
PooledConnection
org.apache.ibatis.datasource.pooled.PooledConnection
:實現InvocationHandler
介面,池化的 Connection 物件
連線池中儲存的都是封裝成PooledConnection的連線,使用JDK動態代理
的方式,呼叫PooledConnection的所有方法都會委託給invoke
方法:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
// <1> 判斷是否為 CLOSE 方法,則將連線放回到連線池中,避免連線被關閉
if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
dataSource.pushConnection(this);
return null;
}
try {
// <2.1> 判斷非 Object 的方法,則先檢查連線是否可用
if (!Object.class.equals(method.getDeclaringClass())) {
// issue #579 toString() should never fail
// throw an SQLException instead of a Runtime
checkConnection();
}
// <2.2> 反射呼叫對應的方法
return method.invoke(realConnection, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
因為我們將資料庫連線Connection封裝成了PooledConnection,代理的時候進行了以下操作:
-
呼叫Connection的close方法時,有時候不用真正的關閉,需要進行一些處理
-
呼叫Connection其他方法之前,需要先檢測該連線的可用性,然後再執行該方法
總結
實際我們不會用到Mybatis內部的資料來源,都是通過Druid
或者HikariCP
等第三方元件,這裡我們來看看PooledConnection使用的JDK動態代理
PooledConnection實現了InvocationHandler
介面,在invoke方法中進行了預處理,例如檢查連線是否有效,然後在通過真實的Connection操作
在它的構造方法中初始化了代理類例項:
this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
通過java.lang.reflect.Proxy
的newProxyInstance
方法例項化代理類物件,入參分別是類載入器,代理的介面,呼叫處理程式
在使用PooledConnection時,實際上MyBatis是使用內部的proxyConnection代理類的例項物件
事務模組
主要包路徑:org.apache.ibatis.transaction
MyBatis的JdbcTransaction事務和純粹的JDBC事務幾乎沒什麼差別,僅是擴充了支援連線池的連線,事務管理也支援簡單的實現,程式碼都比較簡單,這裡不展開討論
在我們結合Spring一起使用MyBatis的時候,一般使用Spring的事務與事務管理
快取模組
主要包路徑:org.apache.ibatis.cache
MyBatis 提供了一級快取和二級快取,這兩種快取都依賴於該快取模組,提供很多種的型別的快取容器,使用了常見的裝飾者模式
提供的這兩種快取效能不好且存在缺陷,很雞肋,一般不使用,如果需要使用快取可閱讀我的另一篇文件:JetCache原始碼分析
org.apache.ibatis.cache.Cache
:快取容器介面,類似於HashMap,存放快取資料。
有以下快取容器的實現:
-
org.apache.ibatis.cache.impl.PerpetualCache
:基於HashMap,永不過期 -
org.apache.ibatis.cache.decorators.LoggingCache
:裝飾Cache,提供日誌列印與快取命中統計 -
org.apache.ibatis.cache.decorators.BlockingCache
:裝飾Cache,阻塞實現,防止重複新增 -
org.apache.ibatis.cache.decorators.SynchronizedCache
:裝飾Cache,同步實現,新增synchronized
修飾符 -
org.apache.ibatis.cache.decorators.SerializedCache
:裝飾Cache,支援序列化快取值 -
org.apache.ibatis.cache.decorators.ScheduledCache
:裝飾Cache,定時清空整個容器 -
org.apache.ibatis.cache.decorators.FifoCache
:裝飾Cache,可以設定容量,採用先進先出的淘汰機制 -
org.apache.ibatis.cache.decorators.LruCache
:裝飾Cache,容量為1024,基於 LinkedHashMap 實現淘汰機制 -
org.apache.ibatis.cache.decorators.WeakCache
:裝飾Cache,使用快取值的強引用
-
org.apache.ibatis.cache.decorators.SoftCache
:裝飾Cache,使用快取值的軟引用
另外也定義了快取鍵org.apache.ibatis.cache.CacheKey
,可以理解成將多個物件放在一起,計算快取鍵
二級快取和Executor執行器有很大關聯,在後面的文件中進行解析
型別模組
主要包路徑:org.apache.ibatis.type
主要功能:
- 簡化配置檔案提供別名機制
- 實現 Jdbc Type 與 Java Type 之間的轉換
這個模組涉及到很多類,因為不同型別需要有對應的型別處理器,邏輯都差不多
主要檢視以下幾個類:
-
org.apache.ibatis.type.TypeHandler
:型別處理器介面 -
org.apache.ibatis.type.BaseTypeHandler
:實現 TypeHandler 介面,繼承 TypeReference 抽象類,TypeHandler 基礎抽象類 -
org.apache.ibatis.type.IntegerTypeHandler
:繼承 BaseTypeHandler 抽象類,Integer 型別的 TypeHandler 實現類 -
org.apache.ibatis.type.UnknownTypeHandler
:繼承 BaseTypeHandler 抽象類,未知的 TypeHandler 實現類,通過獲取對應的 TypeHandler ,進行處理 -
org.apache.ibatis.type.TypeHandlerRegistry
:TypeHandler 登錄檔,相當於管理 TypeHandler 的容器,從其中能獲取到對應的 TypeHandler -
org.apache.ibatis.type.TypeAliasRegistry
:型別與別名的登錄檔,通過別名我們可以在 XML 對映檔案中配置resultType
和parameterType
屬性時,直接配置為別名而不用寫全類名
TypeHandler
org.apache.ibatis.type.TypeHandler
:型別處理器介面,程式碼如下:
public interface TypeHandler<T> {
/**
* 設定 PreparedStatement 的指定引數
*
* Java Type => JDBC Type
*
* @param ps PreparedStatement 物件
* @param i 引數佔位符的位置
* @param parameter 引數
* @param jdbcType JDBC 型別
* @throws SQLException 當發生 SQL 異常時
*/
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
/**
* 獲得 ResultSet 的指定欄位的值
*
* JDBC Type => Java Type
*
* @param rs ResultSet 物件
* @param columnName 欄位名
* @return 值
* @throws SQLException 當發生 SQL 異常時
*/
T getResult(ResultSet rs, String columnName) throws SQLException;
/**
* 獲得 ResultSet 的指定欄位的值
*
* JDBC Type => Java Type
*
* @param rs ResultSet 物件
* @param columnIndex 欄位位置
* @return 值
* @throws SQLException 當發生 SQL 異常時
*/
T getResult(ResultSet rs, int columnIndex) throws SQLException;
/**
* 獲得 CallableStatement 的指定欄位的值
*
* JDBC Type => Java Type
*
* @param cs CallableStatement 物件,支援呼叫儲存過程
* @param columnIndex 欄位位置
* @return 值
* @throws SQLException
*/
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
#setParameter(...)
方法,是 Java Type => Jdbc Type 的過程
#getResult(...)
方法,是 Jdbc Type => Java Type 的過程
BaseTypeHandler
org.apache.ibatis.type.BaseTypeHandler
:實現 TypeHandler 介面,繼承 TypeReference 抽象類,TypeHandler 基礎抽象類
在方法的實現中捕獲異常,內部呼叫抽象方,交由子類去實現
IntegerTypeHandler
org.apache.ibatis.type.IntegerTypeHandler
:繼承 BaseTypeHandler 抽象類,Integer 型別的 TypeHandler 實現類
往java.sql.PreparedStatement
設定引數或者從java.sql.ResultSet
獲取結果,邏輯不復雜,程式碼如下:
public class IntegerTypeHandler extends BaseTypeHandler<Integer> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
throws SQLException {
// 直接設定引數即可
ps.setInt(i, parameter);
}
@Override
public Integer getNullableResult(ResultSet rs, String columnName) throws SQLException {
// 獲得欄位的值
int result = rs.getInt(columnName);
// 先通過 rs 判斷是否空,如果是空,則返回 null ,否則返回 result
return result == 0 && rs.wasNull() ? null : result;
}
@Override
public Integer getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
int result = rs.getInt(columnIndex);
return result == 0 && rs.wasNull() ? null : result;
}
@Override
public Integer getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
int result = cs.getInt(columnIndex);
return result == 0 && cs.wasNull() ? null : result;
}
}
UnknownTypeHandler
org.apache.ibatis.type.UnknownTypeHandler
:繼承 BaseTypeHandler 抽象類,未知的 TypeHandler 實現類,通過獲取對應的 TypeHandler ,進行處理
內部有一個TypeHandlerRegistry
物件,TypeHandler 登錄檔,儲存了Java Type、JDBC Type與TypeHandler 之間的對映關係
程式碼如下:
public class UnknownTypeHandler extends BaseTypeHandler<Object> {
/**
* ObjectTypeHandler 單例
*/
private static final ObjectTypeHandler OBJECT_TYPE_HANDLER = new ObjectTypeHandler();
/**
* TypeHandler 登錄檔
*/
private TypeHandlerRegistry typeHandlerRegistry;
private TypeHandler<?> resolveTypeHandler(Object parameter, JdbcType jdbcType) {
TypeHandler<?> handler;
// 引數為空,返回 OBJECT_TYPE_HANDLER
if (parameter == null) {
handler = OBJECT_TYPE_HANDLER;
} else { // 引數非空,使用引數型別獲得對應的 TypeHandler
handler = typeHandlerRegistry.getTypeHandler(parameter.getClass(), jdbcType);
// check if handler is null (issue #270)
// 獲取不到,則使用 OBJECT_TYPE_HANDLER
if (handler == null || handler instanceof UnknownTypeHandler) {
handler = OBJECT_TYPE_HANDLER;
}
}
return handler;
}
private TypeHandler<?> resolveTypeHandler(ResultSetMetaData rsmd, Integer columnIndex) {
TypeHandler<?> handler = null;
// 獲得 JDBC Type 型別
JdbcType jdbcType = safeGetJdbcTypeForColumn(rsmd, columnIndex);
// 獲得 Java Type 型別
Class<?> javaType = safeGetClassForColumn(rsmd, columnIndex);
//獲得對應的 TypeHandler 物件
if (javaType != null && jdbcType != null) {
handler = typeHandlerRegistry.getTypeHandler(javaType, jdbcType);
} else if (javaType != null) {
handler = typeHandlerRegistry.getTypeHandler(javaType);
} else if (jdbcType != null) {
handler = typeHandlerRegistry.getTypeHandler(jdbcType);
}
return handler;
}
}
上面我只是列出其兩個關鍵方法:
resolveTypeHandler(ResultSet rs, String column)
:根據引數型別獲取對應的TypeHandler型別處理器
resolveTypeHandler(ResultSetMetaData rsmd, Integer columnIndex)
:通過ResultSetMetaData獲取某列的Jdbc Type和Java Type,然後獲取對應的TypeHandler型別處理器
TypeHandlerRegistry
org.apache.ibatis.type.TypeHandlerRegistry
:TypeHandler 登錄檔,相當於管理 TypeHandler 的容器,從其中能獲取到對應的 TypeHandler 型別處理器,部分程式碼如下:
public final class TypeHandlerRegistry {
/**
* JDBC Type 和 {@link TypeHandler} 的對映
*
* {@link #register(JdbcType, TypeHandler)}
*/
private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
/**
* {@link TypeHandler} 的對映
*
* KEY1:Java Type
* VALUE1:{@link jdbcTypeHandlerMap} 物件,例如Date對應多種TypeHandler,所以採用Map
* KEY2:JDBC Type
* VALUE2:{@link TypeHandler} 物件
*/
private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
/**
* {@link UnknownTypeHandler} 物件
*/
private final TypeHandler<Object> unknownTypeHandler = new UnknownTypeHandler(this);
/**
* 所有 TypeHandler 的“集合”
*
* KEY:{@link TypeHandler#getClass()}
* VALUE:{@link TypeHandler} 物件
*/
private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();
/**
* 空 TypeHandler 集合的標識,即使 {@link #TYPE_HANDLER_MAP} 中,某個 KEY1 對應的 Map<JdbcType, TypeHandler<?>> 為空。
*
* @see #getJdbcHandlerMap(Type)
*/
private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
/**
* 預設的列舉型別的 TypeHandler 物件
*/
private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
public TypeHandlerRegistry() {
register(Boolean.class, new BooleanTypeHandler());
register(boolean.class, new BooleanTypeHandler());
register(JdbcType.BOOLEAN, new BooleanTypeHandler());
register(JdbcType.BIT, new BooleanTypeHandler());
register(JdbcType.CHAR, new StringTypeHandler());
register(JdbcType.VARCHAR, new StringTypeHandler());
register(JdbcType.CLOB, new ClobTypeHandler());
register(JdbcType.LONGVARCHAR, new StringTypeHandler());
register(JdbcType.NVARCHAR, new NStringTypeHandler());
register(JdbcType.NCHAR, new NStringTypeHandler());
register(JdbcType.NCLOB, new NClobTypeHandler());
// ... 省略 ...
// issue #273
register(Character.class, new CharacterTypeHandler());
register(char.class, new CharacterTypeHandler());
}
public void register(String packageName) {
// 掃描指定包下的所有 TypeHandler 類
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName);
Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();
// 遍歷 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);
}
}
}
}
在建構函式中可以看到會將 Java 的常用型別、JDBC 的型別與對應的 TypeHandler 型別處理器進行對映然後儲存起來
提供了register(String packageName)
方法,可以註冊指定包路徑下的 TypeHandler 型別處理器
TypeAliasRegistry
org.apache.ibatis.type.TypeAliasRegistry
:型別與別名的登錄檔通過別名,主要程式碼如下:
public class TypeAliasRegistry {
/**
* 型別與別名的對映
*/
private final Map<String, Class<?>> typeAliases = new HashMap<>();
/**
* 初始化預設的型別與別名
*
* 另外,在 {@link org.apache.ibatis.session.Configuration} 構造方法中,也有預設的註冊
*/
public TypeAliasRegistry() {
registerAlias("string", String.class);
registerAlias("byte[]", Byte[].class);
registerAlias("_byte", byte.class);
registerAlias("_byte[]", byte[].class);
registerAlias("date", Date.class);
registerAlias("date[]", Date[].class);
registerAlias("map", Map.class);
// ... 省略 ...
registerAlias("ResultSet", ResultSet.class);
}
@SuppressWarnings("unchecked")
// throws class cast exception as well if types cannot be assigned
public <T> Class<T> resolveAlias(String string) {
// 獲得別名對應的型別
try {
if (string == null) {
return null;
}
// issue #748
// <1> 轉換成小寫
String key = string.toLowerCase(Locale.ENGLISH);
Class<T> value;
// <2.1> 首先,從 TYPE_ALIASES 中獲取
if (typeAliases.containsKey(key)) {
value = (Class<T>) typeAliases.get(key);
} else { // <2.2> 其次,直接獲得對應類
value = (Class<T>) Resources.classForName(string);
}
return value;
} catch (ClassNotFoundException e) {
throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e);
}
}
/**
* 註冊指定包下的別名與類的對映
*
* @param packageName 指定包
*/
public void registerAliases(String packageName) {
registerAliases(packageName, Object.class);
}
/**
* 註冊指定包下的別名與類的對映。另外,要求類必須是 {@param superType} 型別(包括子類)。
*
* @param packageName 指定包
* @param superType 指定父類
*/
public void registerAliases(String packageName, Class<?> superType) {
// 獲得指定包下的類門
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);
}
}
}
public void registerAlias(Class<?> type) {
// <1> 預設為,簡單類名
String alias = type.getSimpleName();
// <2> 如果有註解,使用註冊上的名字
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
alias = aliasAnnotation.value();
}
// <3> 註冊型別與別名的登錄檔
registerAlias(alias, type);
}
public void registerAlias(String alias, Class<?> value) {
if (alias == null) {
throw new TypeException("The parameter alias cannot be null");
}
// issue #748
// <1> 轉換成小寫
// 將別名轉換成**小寫**。這樣的話,無論我們在 Mapper XML 中,寫 `String` 還是 `string` 甚至是 `STRING` ,都是對應的 String 型別
String key = alias.toLowerCase(Locale.ENGLISH);
// <2> 衝突,丟擲 TypeException 異常
// 如果已經註冊,並且型別不一致,說明有衝突,丟擲 TypeException 異常
if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
throw new TypeException("The alias '" + alias + "' is already mapped to the value '"
+ typeAliases.get(key).getName() + "'.");
}
typeAliases.put(key, value);
}
public void registerAlias(String alias, String value) {
try {
registerAlias(alias, Resources.classForName(value));
} catch (ClassNotFoundException e) {
throw new TypeException("Error registering type alias " + alias + " for " + value + ". Cause: " + e, e);
}
}
/**
* @since 3.2.2
*/
public Map<String, Class<?>> getTypeAliases() {
return Collections.unmodifiableMap(typeAliases);
}
}
在建構函式中可以看到,將 Java 常用型別的別名新增到Map<String, Class<?>> typeAliases
物件中
registerAliases(String packageName)
方法,註冊指定包下別名與Class物件的對映
-
如果使用了
@Alias
註解,則獲取其value作為別名,否則的話使用類的簡單類名 -
別名都會轉換成小寫,這樣的話,無論我們在 Mapper XML 中,寫
String
還是string
甚至是STRING
,都是對應的String型別
resolveAlias(String string)
方法,根據引數名稱獲取其Class物件
-
如果引數名稱在
typeAliases
中存在,則直接返回對應的Class物件 -
否則根據該引數名稱呼叫
Class.forName
方法建立一個Class物件
總結
通過配置別名的方式(指定包路徑或者實體類新增@Alias
註解)可以簡化我們的XML對映檔案的配置
在別名註冊中心已經提供了Java常用型別與Class物件的對映,且對映的key值全部轉換成小寫了,更加便於我們編寫XML對映檔案
提供多種型別處理器,實現了Jdbc Type與Java Type之間的轉換,在TypeHandlerRegistry登錄檔中已經初始化好了許多需要用到的型別處理器,便於其他模組進行解析
IO模組
主要包路徑:org.apache.ibatis.io
主要功能:載入類檔案以及其他資原始檔,例如載入某個包名下面所有的Class物件
主要看以下幾個類:
-
org.apache.ibatis.io.ClassLoaderWrapper
:ClassLoader類載入器的包裝器 -
org.apache.ibatis.io.Resources
:Resources工具類,通過ClassLoaderWrapper獲取資源 -
org.apache.ibatis.io.ResolverUtil
:解析器工具類,用於獲得指定目錄符合條件的Class物件 -
org.apache.ibatis.io.VFS
:虛擬檔案系統(Virtual File System)抽象類,用來查詢指定路徑下的檔案們 -
org.apache.ibatis.io.DefaultVFS
:繼承VFS抽象類,預設的VFS實現類
雙親委派機制
我們將.java檔案編譯成.class檔案後,需要通過ClassLoader類載入器將.class檔案將其轉換成Class物件,然後"載入"到JVM中,這樣我們就可以通過該Class物件建立例項和初始化等操作,其中ClassLoader類載入器使用了雙親委派機制來載入檔案
類載入器:
- 啟動類載入器:為null,JVM啟動時建立,由HotSpot實現,負責載入jre/lib/rt.jar包下的核心類
- 擴充套件類載入器:定義在sun.misc.Launcher的一個內部類,sun.misc.Launcher$ExtClassLoader,負責載入jre/lib/ext/*.jar包下的擴充套件類
- 應用類載入器:定義在sun.misc.Launcher的一個內部類,sun.misc.Launcher$AppClassLoader,負責載入ClassPath路徑下面的類
- 自定義類載入器:使用者自定義的類載入器,可以通過重寫findClass()方法進行載入類
注意:除了啟動類載入器外,所有的類載入器都是ClassLoader的子類,原因在於啟動類載入器是由C語言而不是Java實現的,ClassLoader中有兩個重要的方法: loadClass(String name,boolean resolve)和findClass(String name), loadClass方法實現雙親委派機制子一般不進行重寫,各子類載入器通過重寫findClass方法實現自己的類載入業務。
可以看到除了除了啟動類載入器外,每個類載入器都有父載入器,當一個類載入器載入一個類時,首先會把載入動作委派給他的父載入器,如果父載入器無法完成這個載入動作時才由該類載入器進行載入。由於類載入器會向上傳遞載入請求,所以一個類載入時,首先嚐試載入它的肯定是啟動類載入器(逐級向上傳遞請求,直到啟動類載入器,它沒有父載入器),之後根據是否能載入的結果逐級讓子類載入器嘗試載入,直到載入成功。
意義:防止載入同一個.class檔案、保證核心API不會被篡改
ClassLoaderWrapper
org.apache.ibatis.io.ClassLoaderWrapper
:ClassLoader類載入器的包裝器
在構造器中會獲取系統的構造器,也就是應用類載入器
提供的功能:
- 獲取一個資源返回URL
- 獲取一個資源返回InputStream
- 獲取指定類名的Class物件
說明:都是通過ClassLoader類載入器載入資源的,可以通過多個ClassLoader進行載入,直到有一個成功則返回資源
Resources
org.apache.ibatis.io.Resources
:Resources工具類,通過ClassLoaderWrapper獲取資源
相當於對ClassLoaderWrapper在進行一層包裝,如果沒有獲取到資源則可能丟擲異常,還提供更多的方法,例如獲取資源返回Properties物件,獲取資源返回Reader物件,獲取資源返回File物件
ResolverUtil
org.apache.ibatis.io.ResolverUtil
:解析器工具類,用於獲得指定目錄符合條件的Class物件
在通過包名載入Mapper介面,需要使用別名的類,TypeHandler型別處理器類需要使用該工具類
內部定義了一個介面和兩個實現類:
public class ResolverUtil<T> {
public interface Test {
boolean matches(Class<?> type);
}
/**
* 判斷是匹配指定類
*/
public static class IsA implements Test {
private Class<?> parent;
public IsA(Class<?> parentType) {
this.parent = parentType;
}
/**
* Returns true if type is assignable to the parent type supplied in the constructor.
*/
@Override
public boolean matches(Class<?> type) {
return type != null && parent.isAssignableFrom(type);
}
@Override
public String toString() {
return "is assignable to " + parent.getSimpleName();
}
}
/**
* 判斷是否匹配指定註解
*/
public static class AnnotatedWith implements Test {
private Class<? extends Annotation> annotation;
public AnnotatedWith(Class<? extends Annotation> annotation) {
this.annotation = annotation;
}
/**
* Returns true if the type is annotated with the class provided to the constructor.
*/
@Override
public boolean matches(Class<?> type) {
return type != null && type.isAnnotationPresent(annotation);
}
@Override
public String toString() {
return "annotated with @" + annotation.getSimpleName();
}
}
}
上面兩個內部類很簡單,用於判斷是否匹配指定類或者指定註解
我們來看看主要的幾個方法:
public class ResolverUtil<T> {
private Set<Class<? extends T>> matches = new HashSet<>();
public Set<Class<? extends T>> getClasses() {
return matches;
}
/**
* 獲取包路徑下 parent 的子類
*/
public ResolverUtil<T> findImplementations(Class<?> parent, String... packageNames) {
if (packageNames == null) {
return this;
}
Test test = new IsA(parent);
for (String pkg : packageNames) {
find(test, pkg);
}
return this;
}
/**
* 獲取包路徑下 annotation 的子註解
*/
public ResolverUtil<T> findAnnotated(Class<? extends Annotation> annotation, String... packageNames) {
if (packageNames == null) {
return this;
}
Test test = new AnnotatedWith(annotation);
for (String pkg : packageNames) {
find(test, pkg);
}
return this;
}
/**
* 獲得指定包下,符合條件的類
*/
public ResolverUtil<T> find(Test test, String packageName) {
// <1> 獲得包的路徑
String path = getPackagePath(packageName);
try {
// <2> 獲得路徑下的所有檔案
List<String> children = VFS.getInstance().list(path);
// <3> 遍歷
for (String child : children) {
// 是 Java Class
if (child.endsWith(".class")) {
// 如果匹配,則新增到結果集
addIfMatching(test, child);
}
}
} catch (IOException ioe) {
log.error("Could not read package: " + packageName, ioe);
}
return this;
}
protected String getPackagePath(String packageName) {
return packageName == null ? null : packageName.replace('.', '/');
}
@SuppressWarnings("unchecked")
protected void addIfMatching(Test test, String fqn) {
try {
// 獲得全類名
String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
ClassLoader loader = getClassLoader();
if (log.isDebugEnabled()) {
log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
}
// 載入類
Class<?> type = loader.loadClass(externalName);
// 判斷是否匹配
if (test.matches(type)) {
matches.add((Class<T>) type);
}
} catch (Throwable t) {
log.warn("Could not examine class '" + fqn + "'" + " due to a " + t.getClass().getName() + " with message: "
+ t.getMessage());
}
}
}
如果我們需要獲取某個包路徑下的所有類,則可以設定父類為java.lang.Object,呼叫findImplementations
方法即可
再來看到find(Test test, String packageName)
方法,需要呼叫VFS
的list
方法獲取到所有的檔案,然後將匹配的.class檔案通過ClassLoader載入成Class物件
VFS
org.apache.ibatis.io.VFS
:虛擬檔案系統(Virtual File System)抽象類,用來查詢指定路徑下的檔案們
提供幾個反射的相關方法,list
方法則交由子類實現
預設實現類有JBoss6VFS和DefaultVFS
,當然可以自定義 VFS 實現類,我們來看看如何返回VFS例項的,程式碼如下:
public abstract class VFS {
public static final Class<?>[] IMPLEMENTATIONS = { JBoss6VFS.class, DefaultVFS.class }; // 內建的 VFS 實現類的陣列
public static final List<Class<? extends VFS>> USER_IMPLEMENTATIONS = new ArrayList<>(); // 自定義的 VFS 實現類的陣列
public static VFS getInstance() {
return VFSHolder.INSTANCE;
}
private static class VFSHolder {
static final VFS INSTANCE = createVFS();
@SuppressWarnings("unchecked")
static VFS createVFS() {
// Try the user implementations first, then the built-ins
List<Class<? extends VFS>> impls = new ArrayList<>();
impls.addAll(USER_IMPLEMENTATIONS);
impls.addAll(Arrays.asList((Class<? extends VFS>[]) IMPLEMENTATIONS));
// Try each implementation class until a valid one is found
VFS vfs = null;
// 建立 VFS 物件,選擇最後一個符合的
for (int i = 0; vfs == null || !vfs.isValid(); i++) {
Class<? extends VFS> impl = impls.get(i);
try {
vfs = impl.getDeclaredConstructor().newInstance();
if (!vfs.isValid()) {
if (log.isDebugEnabled()) {
log.debug("VFS implementation " + impl.getName() + " is not valid in this environment.");
}
}
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException
| InvocationTargetException e) {
log.error("Failed to instantiate " + impl, e);
return null;
}
}
if (log.isDebugEnabled()) {
log.debug("Using VFS adapter " + vfs.getClass().getName());
}
return vfs;
}
}
}
可以看到獲取的VFS例項會進入createVFS()
方法,從提供的幾個VFS實現類中選一個符合的,最後可以看到會返回最後一個符合的,也就是定義的DefaultVFS
DefaultVFS
org.apache.ibatis.io.DefaultVFS
:繼承VFS抽象類,預設的VFS實現類
DefaultVFS實現類VFS的list(URL url, String path)
方法,在ResolverUtil
的find
方法中會被呼叫,由於程式碼冗長,這裡就不列出來了,請自行檢視該類???
大致邏輯如下:
-
如果url指向一個JAR Resource,那麼從這個JAR Resource中獲取path路徑下的檔名稱
-
否則獲取該url中path下面所有的檔案,會不斷的遞迴獲取到所有檔案(包含我們需要的.class檔案),最後將獲取到的所有檔名稱返回
這樣我們就可以在ResolverUtil
將我們需要的.class檔案載入成對應的Class物件
總結
瞭解Java中類載入的雙親委派機制
Mybatis中需要載入類是通過該模組中的ResolverUtil來實現的,大家可以先順著org.apache.ibatis.binding.MapperRegistry.addMappers(String packageName)
掃描Mapper介面這個方法看看整個的解析過程
日誌模組
主要包路徑:org.apache.ibatis.logging
主要功能:整合第三方日誌框架,Debug模組輸出JDBC操作日誌
LogFactory
org.apache.ibatis.logging.LogFactory
:日誌工廠
主要程式碼如下:
public final class LogFactory {
public static final String MARKER = "MYBATIS";
/**
* 使用的 Log 的構造方法
*/
private static Constructor<? extends Log> logConstructor;
static {
// <1> 逐個嘗試,判斷使用哪個 Log 的實現類,即初始化 logConstructor 屬性
tryImplementation(LogFactory::useSlf4jLogging);
tryImplementation(LogFactory::useCommonsLogging);
tryImplementation(LogFactory::useLog4J2Logging);
tryImplementation(LogFactory::useLog4JLogging);
tryImplementation(LogFactory::useJdkLogging);
tryImplementation(LogFactory::useNoLogging);
}
private LogFactory() {
// disable construction
}
public static Log getLog(Class<?> aClass) {
return getLog(aClass.getName());
}
public static Log getLog(String logger) {
try {
return logConstructor.newInstance(logger);
} catch (Throwable t) {
throw new LogException("Error creating logger for logger " + logger + ". Cause: " + t, t);
}
}
public static synchronized void useCustomLogging(Class<? extends Log> clazz) {
setImplementation(clazz);
}
public static synchronized void useSlf4jLogging() {
setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
}
public static synchronized void useCommonsLogging() {
setImplementation(org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.class);
}
public static synchronized void useLog4JLogging() {
setImplementation(org.apache.ibatis.logging.log4j.Log4jImpl.class);
}
public static synchronized void useLog4J2Logging() {
setImplementation(org.apache.ibatis.logging.log4j2.Log4j2Impl.class);
}
private static void tryImplementation(Runnable runnable) {
if (logConstructor == null) {
try {
runnable.run();
} catch (Throwable t) {
// ignore
}
}
}
private static void setImplementation(Class<? extends Log> implClass) {
try {
// 獲得引數為 String 的構造方法
Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
// 建立 Log 物件
Log log = candidate.newInstance(LogFactory.class.getName());
if (log.isDebugEnabled()) {
log.debug("Logging initialized using '" + implClass + "' adapter.");
}
// 建立成功,意味著可以使用,設定為 logConstructor
logConstructor = candidate;
} catch (Throwable t) {
throw new LogException("Error setting Log implementation. Cause: " + t, t);
}
}
}
-
在靜態程式碼塊中會逐個嘗試,判斷使用哪個
Log
的實現類,即初始化logConstructor
日誌構造器 -
在
getLog(String logger)
方法獲取日誌的例項物件時,通過logConstructor
建立一個例項出來,這樣就完成了日誌的適配
BaseJdbcLogger
在org.apache.ibatis.logging.jdbc包路徑下有幾個BaseJdbcLogger類,用於DEBUG模式下列印JDBC操作日誌
這裡也使用了JDK動態代理,對JDBC介面進行增強,列印執行日誌,和資料來源模組類似
可以在org.apache.ibatis.executor.BaseExecutor
的getConnection
方法中看到,如果開啟了DEBUG模式,則建立Connection的動態代理物件,可以順著下去看
註解模組
在實際使用MyBatis過程中,我們大部分都是使用XML的方式,這樣我們便於維護
當然MyBatis也提供了許多註解,讓我們在Mapper介面上面可以通過註解編寫SQL
主要包路徑:org.apache.ibatis.annotations
@Param
用於設定Mapper介面中的方法的引數名稱
如果我們Mapper介面的方法中有多個入參,我們需要通過該註解來為每個引數設定一個名稱
在反射模組的ParamNameResolver
工具類中有講到會通過該註解設定引數名稱
@Mapper
用於標記介面為Mapper介面
在Spring Boot專案中通過mybatis-spring-boot-starter
使用MyBatis時,如果你沒有通過mybatis-spring
子專案中的三種方式(配置MapperScannerConfigurer掃描器、@MapperScan註解或者<mybatis:scan />標籤)配置Mapper介面包路徑,那麼在mybatis-spring-boot
中則會掃描Spring Boot專案設定的基礎包路徑,如果設定了@Mapper
註解,則會當成Mapper介面進行解析,後面的文件中會講到?
其他註解
@Select、@Insert、@Update、@Delete,CURD註解
Binding模組
主要包路徑:org.apache.ibatis.binding
在使用MyBatis時,我們通常定義Mapper介面,然後在對應的XML對映檔案中編寫SQL語句,那麼當我們呼叫介面的方法時,為什麼可以執行對應的SQL語句?通過該模組我們可以對它們是如何關聯起來的有個大致的瞭解
Mybatis會自動為Mapper介面建立一個動態代理例項,通過該動態代理物件的例項進行相關操作
主要涉及到以下幾個類:
org.apache.ibatis.binding.MapperRegistry
:Mapper介面註冊中心,將Mapper介面與其動態代理物件工廠進行儲存org.apache.ibatis.binding.MapperProxyFactory
:Mapper介面的動態代理物件工廠,用於生產Mapper介面的動態代理物件org.apache.ibatis.binding.MapperProxy
:Mapper介面的動態代理物件,使用JDK動態代理,實現了java.lang.reflect.InvocationHandler
介面org.apache.ibatis.binding.MapperMethod
:Mapper介面中定義的方法對應的Mapper方法,通過它來執行SQL
MapperRegistry
org.apache.ibatis.binding.MapperRegistry
:Mapper介面註冊中心,將Mapper介面與其MapperProxyFactory
動態代理物件工廠進行儲存
主要程式碼如下:
public class MapperRegistry {
/**
* MyBatis Configuration 物件
*/
private final Configuration config;
/**
* MapperProxyFactory 的對映
*
* KEY:Mapper 介面
*/
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// <1> 獲得 MapperProxyFactory 物件
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
// 不存在,則丟擲 BindingException 異常
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 建立 Mapper Proxy 物件
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
public <T> boolean hasMapper(Class<T> type) {
return knownMappers.containsKey(type);
}
public <T> void addMapper(Class<T> type) {
// <1> 判斷,必須是介面。
if (type.isInterface()) {
// <2> 已經新增過,則丟擲 BindingException 異常
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// <3> 將Mapper介面對應的代理工廠新增到 knownMappers 中
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the mapper parser.
// If the type is already known, it won't try.
// <4> 解析 Mapper 的註解配置
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
// 解析 Mapper 介面上面的註解和 Mapper 介面對應的 XML 檔案
parser.parse();
// <5> 標記載入完成
loadCompleted = true;
} finally {
// <6> 若載入未完成,從 knownMappers 中移除
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
/**
* @since 3.2.2
*/
public Collection<Class<?>> getMappers() {
return Collections.unmodifiableCollection(knownMappers.keySet());
}
/**
* 用於掃描指定包中的Mapper介面,並與XML檔案進行繫結
* @since 3.2.2
*/
public void addMappers(String packageName, Class<?> superType) {
// <1> 掃描指定包下的指定類
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
// <2> 遍歷,新增到 knownMappers 中
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
/**
* @since 3.2.2
*/
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
}
addMappers(String packageName, Class<?> superType)
方法:
- 獲取該包路徑下的Mapper介面Class物件,然後呼叫
addMapper(Class<T> type)
進行解析,這裡使用了ResolverUtil工具類,獲取到該包路徑下所有匹配Object.class
的類,這個工具類在IO模組中已經講過
addMapper(Class<T> type)
方法:
-
建立Mapper介面對應的動態代理物件工廠MapperProxyFactory,新增到
knownMappers
中 -
通過
org.apache.ibatis.builder.annotation.MapperAnnotationBuilder
對該介面進行解析,解析對應的XML對映檔案(介面名稱+'.xml'檔案)整個的解析過程比較複雜,在後續的《Mybatis的初始化》文件中進行分析
getMapper(Class<T> type, SqlSession sqlSession)
方法:根據Mapper介面的Class物件和SqlSession物件建立一個動態代理物件
- 根據Mapper介面的Class物件獲取對應的MapperProxyFactory工廠
- 通過MapperProxyFactory工廠建立一個動態代理物件例項
MapperProxyFactory
org.apache.ibatis.binding.MapperProxyFactory
:Mapper介面的動態代理物件工廠,用於生產Mapper介面動態代理物件的例項,程式碼如下:
public class MapperProxyFactory<T> {
/**
* Mapper 介面
*/
private final Class<T> mapperInterface;
/**
* 方法與 MapperMethod 的對映
*/
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface },
mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
newInstance(SqlSession sqlSession)
方法:建立Mapper介面對應的動態代理物件的例項,可以看到是通過MapperProxy的構造方法建立了一個動態代理物件的
MapperProxy
org.apache.ibatis.binding.MapperProxy
:Mapper介面的動態代理物件,使用JDK動態代理,實現了java.lang.reflect.InvocationHandler
介面,部分程式碼如下:
public class MapperProxy<T> implements InvocationHandler, Serializable {
/**
* SqlSession 物件
*/
private final SqlSession sqlSession;
/**
* Mapper 介面
*/
private final Class<T> mapperInterface;
/**
* 方法與 MapperMethod 的對映
*
* 從 {@link MapperProxyFactory#methodCache} 傳遞過來
*/
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// <1> 如果是 Object 定義的方法,直接呼叫
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (method.isDefault()) { // 是否有 default 修飾的方法
// 針對Java7以上版本對動態型別語言的支援
if (privateLookupInMethod == null) {
return invokeDefaultMethodJava8(proxy, method, args);
} else {
return invokeDefaultMethodJava9(proxy, method, args);
}
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// <2.1> 獲得 MapperMethod 物件
final MapperMethod mapperMethod = cachedMapperMethod(method);
// <2.2> 執行 MapperMethod 方法
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
// 從methodCache快取中獲取MapperMethod方法,如果為空則建立一下新的並新增至快取中
return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
}
在這個Mapper介面的動態代理物件中,覆蓋的invoke
方法處理邏輯:
- 如果是Object.class定義的方法,則直接呼叫
- 如果該方法有預設實現則呼叫其預設實現,在jdk8中支援介面中的方法可以通過
default
修飾符定義他的預設實現 - 否則的話,獲取該Mapper介面中的該方法對應的MapperMethod物件
- 工廠中定義了一個
ConcurrentHashMap<Method, MapperMethod> methodCache
物件,用於儲存該Mapper介面中方法對應的MapperMethod物件 - 根據方法獲取
MapperMethod
物件,如果沒有則通過MapperMethod
的建構函式建立一個例項並新增到快取中
- 工廠中定義了一個
- 通過Mapper介面中該方法對應的
MapperMethod
物件,執行相應的SQL操作
MapperMethod
org.apache.ibatis.binding.MapperMethod
:Mapper介面中定義的方法對應的Mapper方法,通過它來執行SQL,構造方法:
public class MapperMethod {
/**
* 該方法對應的 SQL 的唯一編號與型別
*/
private final SqlCommand command;
/**
* 該方法的簽名,包含入參和出參的相關資訊
*/
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
}
構造方法初始化了兩個物件,分別為SqlCommand
和MethodSignature
,都是其內部類
還定義了一個execute(SqlSession sqlSession, Object[] args)
方法,具體的SQL語句執行邏輯,在後續模組的文件中進行分析
SqlCommand
public class MapperMethod {
public static class SqlCommand {
/**
* SQL的唯一編號:namespace+id(Mapper介面名稱+'.'+方法名稱),{# MappedStatement#id}
*/
private final String name;
/**
* SQL 命令型別 {# MappedStatement#sqlCommandType}
*/
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
final String methodName = method.getName();
final Class<?> declaringClass = method.getDeclaringClass();
// 獲取該方法對應的 MappedStatement
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);
if (ms == null) {
// 如果有 @Flush 註解,則標記為 FLUSH 型別
if (method.getAnnotation(Flush.class) != null) {
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException(
"Invalid bound statement (not found): " + mapperInterface.getName() + "." + methodName);
}
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
Class<?> declaringClass, Configuration configuration) {
// 生成 MappedStatement 唯一編號:介面名稱+'.'+方法名稱
String statementId = mapperInterface.getName() + "." + methodName;
// 在全域性物件 Configuration 中獲取對應的 MappedStatement
if (configuration.hasStatement(statementId)) {
return configuration.getMappedStatement(statementId);
} else if (mapperInterface.equals(declaringClass)) {
// 如果方法就是定義在該介面中,又沒找到則直接返回 null
return null;
}
// 遍歷父介面,獲取對應的 MappedStatement
for (Class<?> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = resolveMappedStatement(superInterface, methodName, declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
return null;
}
}
}
在構造方法中就是通過statementId(Mapper介面名稱+'.'+方法名稱
)獲取到對應的MappedStatement
物件(在XML對映檔案中該方法對應的SQL語句生成的MappedStatement物件),在後續的MyBatis的初始化文件中會分析該物件是如何建立的
所以我們定義的XML檔案的
namespace
通常是介面名稱,<select />
等節點定義的id就是方法名稱,這樣才能對應起來同一個Mapper介面中不能有過載方法也是這個道理,兩個方法對應同一個statementId就出錯了
獲取到了MappedStatement物件則設定name
為它的id(Mapper介面名稱+'.'+方法名稱
),type
為它的SqlCommandType(UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH
)
MethodSignature
public class MapperMethod {
public static class MethodSignature {
/**
* 返回資料是否包含多個
*/
private final boolean returnsMany;
/**
* 返回型別是否為Map的子類,並且該方法上面使用了 @MapKey 註解
*/
private final boolean returnsMap;
/**
* 返回型別是否為 void
*/
private final boolean returnsVoid;
/**
* 返回型別是否為 Cursor
*/
private final boolean returnsCursor;
/**
* 返回型別是否為 Optional
*/
private final boolean returnsOptional;
/**
* 返回型別
*/
private final Class<?> returnType;
/**
* 方法上 @MapKey 註解定義的值
*/
private final String mapKey;
/**
* 用來標記該方法引數列表中 ResultHandler 型別引數得位置
*/
private final Integer resultHandlerIndex;
/**
* 用來標記該方法引數列表中 RowBounds 型別引數得位置
*/
private final Integer rowBoundsIndex;
/**
* ParamNameResolver 物件,主要用於解析 @Param 註解定義的引數,引數值與引數得對映等
*/
private final ParamNameResolver paramNameResolver;
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
// 獲取該方法的返回型別
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class<?>) {
this.returnType = (Class<?>) resolvedReturnType;
} else if (resolvedReturnType instanceof ParameterizedType) { // 泛型型別
// 獲取該引數化型別的實際型別
this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
} else {
this.returnType = method.getReturnType();
}
// 是否為無返回結果
this.returnsVoid = void.class.equals(this.returnType);
// 返回型別是否為集合或者陣列型別
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
// 返回型別是否為遊標型別
this.returnsCursor = Cursor.class.equals(this.returnType);
// 返回結果是否則 Optional 型別
this.returnsOptional = Optional.class.equals(this.returnType);
// 解析方法上面的 @MapKey 註解
this.mapKey = getMapKey(method);
this.returnsMap = this.mapKey != null;
// 方法引數型別為 RowBounds 的位置
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
// 方法引數型別為 ResultHandler 的位置
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
/*
* 解析該方法引數名稱生成引數位置與引數名稱的對映
* @Param 註解則取其值作為引數名稱,否則取其真實的引數名稱,在沒有則為引數位置
*/
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
/**
* 根據入參返回引數名稱與入參的對映
*
* @param args 入參
* @return 引數名稱與入參的對映
*/
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
Integer index = null;
final Class<?>[] argTypes = method.getParameterTypes();
for (int i = 0; i < argTypes.length; i++) {
if (paramType.isAssignableFrom(argTypes[i])) {
if (index == null) {
index = i;
} else {
throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
}
}
}
return index;
}
}
}
在建構函式中解析Mapper介面中該方法的相關資訊並設定到對應的屬性中,會解析出以下資訊:
-
返回值型別
-
@MapKey註解,使用示例參考:@MapKey註解的使用
-
引數型別為 RowBounds 的位置
-
引數型別為 ResultHandler 的位置
-
生成引數名稱解析器
ParamNameResolver
,在反射模組有講到,用於根據引數值返回引數名稱與引數值的對映關係
總結
每一個Mapper介面會有一個MapperProxyFactory
動態代理物件工廠,儲存於MapperRegistry
註冊中心
呼叫介面的方法時,會進入MapperProxy
動態代理物件中,然後通過該方法對應的MapperMethod
方法執行SQL語句的相關操作
疑問:
Mapper介面的動態代理物件會在哪裡建立?
通過DefaultSqlSession的
getMapper(Class<T> type)
方法可以獲取到Mapper介面的動態代理物件Mapper介面是怎麼作為物件注入到其他Spring Bean中?
通過實現BeanDefinitionRegistryPostProcessor介面,修改Mapper介面的BeanDefiniton物件,修改為MapperFactoryBean型別,在FactoryBean中的getObject()方法中獲取到對應的動態代理物件
參考文章:芋道原始碼《精盡 MyBatis 原始碼分析》