參考 知識星球 中 芋道源碼 星球的源碼解析,一個活躍度非常高的 Java 技術社群,感興趣的小伙伴可以加入 芋道源碼 星球,一起學習😄
該系列文檔是本人在學習 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 的基礎支持層已做過介紹,包含整個 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 源碼分析》