精盡 MyBatis 原始碼分析 - 基礎支援層

月圓吖發表於2020-11-22

該系列文件是本人在學習 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呼叫PropertyParserparse方法替換掉其中的動態值(如果存在),這就是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

如下所示:

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;
	}
}
  1. 在其建構函式中,設定set方法或者get方法,並獲取其引數型別或者返回值型別進行儲存,也就是該屬性的型別

  2. 如果Class類中有些屬性沒有set或者get方法,那麼這些屬性會被封裝成下面兩個物件(final static修飾的欄位不會被封裝),用於設定或者獲取他們的值

    org.apache.ibatis.reflection.invoker.SetFieldInvokerorg.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物件:

  1. 如果原始Object物件為null,則返回空的MetaObject物件NULL_META_OBJECT,在SystemMetaObject中定義的一個單例物件,實際就是將MetaObject內部的Object原始物件設定為NullObject(一個靜態類)

  2. 否則通過建構函式建立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.MapperMethodMethodSignature內部類會用到

主要程式碼如下:

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集合中,大致邏輯:

  1. 如果新增了@Param註解,則引數名稱為該註解的value值

  2. 沒有新增@Param註解則嘗試獲取真實的引數名

    說明:通過反射獲取方法的引數名,我們只能獲取到 arg0,arg1 的名稱,因為jdk8之後這些變數名稱沒有被編譯到class檔案中,編譯時需要指定-parameters選項,方法的引數名才會記錄到class檔案中,執行時我們就可以通過反射機制獲取到

  3. 還是沒有獲取到引數名則使用序號標記,一般不會走到這一步

還有一個getNamedParams方法,根據實際入引數組返回引數名與引數值的對映,大致邏輯:

  1. 實際引數為null或者引數個數為0,則直接返回null

  2. 沒有使用@Param註解並且引數個數為1,則直接返回引數值

  3. 根據引數順序與引數名的對映獲取到引數名與引數值的對映,而且還會將(param+引數順序)與引數值進行對映,最後將兩種組合的對映返回

總結

  • 將一個Entity實體類或者Map集合轉換成MetaObject物件,該物件通過反射機制封裝了各種簡便的方法,使更加方便安全地操作該物件,建立過程:
  1. 通過Configuration全域性配置物件的newMetaObject(Object object)方法建立,會傳入DefaultObjectFactoryDefaultObjectWrapperFactoryDefaultReflectorFactory幾個預設實現類
  2. 內部呼叫MetaObjectforObject靜態方法,通過它的構造方法建立一個例項物件
  3. 在MetaObject的建構函式中,會根據Object物件的型別來建立ObjectWrapper物件
  4. 如果是建立BeanWrapper,則在其建構函式中,會再呼叫MetaClass的forClass方法建立MetaClass物件,也就是通過其建構函式建立一個例項物件
  5. 如果是MapWrapper,則直接複製給內部的Map<String, Object> map屬性即可,其他集合物件類似
  6. 在MetaClass的建構函式中,會通過呼叫DefaultReflectorFactory的findForClass方法建立Reflector物件
  7. 在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支援三種資料來源配置,分別為UNPOOLEDPOOLEDJNDI。內部提供了兩種資料來源實現,分別是UnpooledDataSourcePooledDataSource。在三種資料來源配置中,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)方法:

  1. dataSource建立對應的MetaObject物件,便於設定相應的屬性

  2. 將入參Properties物件中的配置往dataSource設定

  3. 如果是以driver.開頭的配置則統一放入一個Properties中,然後設定到dataSourcedriverProperties屬性中

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)方法:

  1. 初始化Driver,將其包裝成DriverProxy,主要用於使用自己的Logger物件
  2. 獲得 Connection 物件
  3. 配置 Connection 物件,網路超時時間、是否自動提交事務、預設的事務隔離級別

該類還包含了資料庫連線的基本資訊以及其他方法,我這裡沒有全部列出來,感興趣的可以看下這個類?

PooledDataSourceFactory

org.apache.ibatis.datasource.pooled.PooledDataSourceFactory:繼承 UnpooledDataSourceFactory 類,池化的 DataSourceFactory 實現類

和 UnpooledDataSourceFactory 的區別是它建立了 PooledDataSource 物件

PooledDataSource

org.apache.ibatis.datasource.pooled.PooledDataSource:實現 DataSource 介面,池化的 DataSource 實現類,包含資料庫的連線資訊以及連線池的配置資訊

下面的方法都有點長,我就】就不列出來了,可以根據流程圖並結合註釋進行檢視?

getConnection()方法:用於獲取連線,流程圖:

PolledDataSource-popConnection

pushConnection方法:"關閉"一個連線,放入空閒連線佇列中或者關設定為無效狀態並關閉真正的連線,在PooledConnectioninvoke方法中,如果方法名稱為close,表示需要關閉該連線,則呼叫該方法,流程圖:

PooledDataSource-pushConnection

pingConnection方法:通過向資料庫發起 poolPingQuery 語句來發起"ping"操作,以判斷資料庫連線是否有效,在PooledConnectionisValid方法中被呼叫,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,代理的時候進行了以下操作:

  1. 呼叫Connection的close方法時,有時候不用真正的關閉,需要進行一些處理

  2. 呼叫Connection其他方法之前,需要先檢測該連線的可用性,然後再執行該方法

總結

實際我們不會用到Mybatis內部的資料來源,都是通過Druid或者HikariCP等第三方元件,這裡我們來看看PooledConnection使用的JDK動態代理

PooledConnection實現了InvocationHandler介面,在invoke方法中進行了預處理,例如檢查連線是否有效,然後在通過真實的Connection操作

在它的構造方法中初始化了代理類例項:

this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);

通過java.lang.reflect.ProxynewProxyInstance方法例項化代理類物件,入參分別是類載入器,代理的介面,呼叫處理程式

在使用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 對映檔案中配置 resultTypeparameterType 屬性時,直接配置為別名而不用寫全類名

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物件的對映

  1. 如果使用了@Alias註解,則獲取其value作為別名,否則的話使用類的簡單類名

  2. 別名都會轉換成小寫,這樣的話,無論我們在 Mapper XML 中,寫String還是string甚至是STRING ,都是對應的String型別

resolveAlias(String string)方法,根據引數名稱獲取其Class物件

  1. 如果引數名稱在typeAliases中存在,則直接返回對應的Class物件

  2. 否則根據該引數名稱呼叫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類載入器使用了雙親委派機制來載入檔案

類載入器:

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)方法,需要呼叫VFSlist方法獲取到所有的檔案,然後將匹配的.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)方法,在ResolverUtilfind方法中會被呼叫,由於程式碼冗長,這裡就不列出來了,請自行檢視該類???

大致邏輯如下:

  1. 如果url指向一個JAR Resource,那麼從這個JAR Resource中獲取path路徑下的檔名稱

  2. 否則獲取該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);
		}
	}
}
  1. 在靜態程式碼塊中會逐個嘗試,判斷使用哪個Log的實現類,即初始化logConstructor日誌構造器

  2. getLog(String logger)方法獲取日誌的例項物件時,通過logConstructor建立一個例項出來,這樣就完成了日誌的適配

BaseJdbcLogger

在org.apache.ibatis.logging.jdbc包路徑下有幾個BaseJdbcLogger類,用於DEBUG模式下列印JDBC操作日誌

這裡也使用了JDK動態代理,對JDBC介面進行增強,列印執行日誌,和資料來源模組類似

可以在org.apache.ibatis.executor.BaseExecutorgetConnection方法中看到,如果開啟了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)方法:

  1. 獲取該包路徑下的Mapper介面Class物件,然後呼叫addMapper(Class<T> type)進行解析,這裡使用了ResolverUtil工具類,獲取到該包路徑下所有匹配Object.class的類,這個工具類在IO模組中已經講過

addMapper(Class<T> type)方法:

  1. 建立Mapper介面對應的動態代理物件工廠MapperProxyFactory,新增到knownMappers

  2. 通過org.apache.ibatis.builder.annotation.MapperAnnotationBuilder對該介面進行解析,解析對應的XML對映檔案(介面名稱+'.xml'檔案)

    整個的解析過程比較複雜,在後續的《Mybatis的初始化》文件中進行分析

getMapper(Class<T> type, SqlSession sqlSession)方法:根據Mapper介面的Class物件和SqlSession物件建立一個動態代理物件

  1. 根據Mapper介面的Class物件獲取對應的MapperProxyFactory工廠
  2. 通過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方法處理邏輯:

  1. 如果是Object.class定義的方法,則直接呼叫
  2. 如果該方法有預設實現則呼叫其預設實現,在jdk8中支援介面中的方法可以通過default修飾符定義他的預設實現
  3. 否則的話,獲取該Mapper介面中的該方法對應的MapperMethod物件
    1. 工廠中定義了一個ConcurrentHashMap<Method, MapperMethod> methodCache物件,用於儲存該Mapper介面中方法對應的MapperMethod物件
    2. 根據方法獲取MapperMethod物件,如果沒有則通過MapperMethod的建構函式建立一個例項並新增到快取中
  4. 通過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);
	}
}

構造方法初始化了兩個物件,分別為SqlCommandMethodSignature,都是其內部類

還定義了一個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語句的相關操作

疑問:

  1. Mapper介面的動態代理物件會在哪裡建立?

    通過DefaultSqlSession的getMapper(Class<T> type)方法可以獲取到Mapper介面的動態代理物件

  2. Mapper介面是怎麼作為物件注入到其他Spring Bean中?

    通過實現BeanDefinitionRegistryPostProcessor介面,修改Mapper介面的BeanDefiniton物件,修改為MapperFactoryBean型別,在FactoryBean中的getObject()方法中獲取到對應的動態代理物件

參考文章:芋道原始碼《精盡 MyBatis 原始碼分析》

相關文章