Mybatis源碼分析——MethodSignature 對象


前言

剛開始使用Mybaits的同學有沒有這樣的疑惑,為什么我們沒有編寫Mapper的實現類,卻能調用Mapper的方法呢?本篇文章我帶大家一起來解決這個疑問

上一篇文章我們獲取到了DefaultSqlSession,接着我們來看第一篇文章測試用例后面的代碼

//獲取對應的mapper UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //執行方法 List<User> list = userMapper.getAll(); 

為 Mapper 接口創建代理對象

我們先從 DefaultSqlSession 的 getMapper 方法開始看起,如下:

public class DefaultSqlSession implements SqlSession { private final Configuration configuration; @Override public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); } } public class Configuration { protected final MapperRegistry mapperRegistry = new MapperRegistry(this); public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } } public class MapperRegistry { private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>(); @SuppressWarnings("unchecked") public <T> T getMapper(Class<T> type, SqlSession sqlSession) { // 從 knownMappers 中獲取與 type 對應的 MapperProxyFactory final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { // 創建代理對象 return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } } } 

這里最重要就是加了注釋的兩行代碼。

獲取MapperProxyFactory

根據名稱看,可以理解為Mapper代理的創建工廠,是不是Mapper的代理對象由它創建呢?我們先來回顧一下knownMappers 集合中的元素是何時存入的。這要在我前面的文章中找答案,MyBatis 在解析配置文件的 <mappers> 節點的過程中,會調用 MapperRegistry 的 addMapper 方法將 Class 到 MapperProxyFactory 對象的映射關系存入到 knownMappers。有興趣的同學可以看看我之前的文章,我們來回顧一下源碼:

public class XMLMapperBuilder extends BaseBuilder { private final XPathParser parser; private final MapperBuilderAssistant builderAssistant; private void bindMapperForNamespace() { // 獲取映射文件的命名空間 String namespace = builderAssistant.getCurrentNamespace(); if (namespace != null) { Class<?> boundType = null; try { // 根據命名空間解析 mapper 類型 boundType = Resources.classForName(namespace); } catch (ClassNotFoundException e) { //ignore, bound type is not required } if (boundType != null) { // 檢測當前 mapper 類是否被綁定過 if (!configuration.hasMapper(boundType)) { // Spring may not know the real resource name so we set a flag // to prevent loading again this resource from the mapper interface // look at MapperAnnotationBuilder#loadXmlResource configuration.addLoadedResource("namespace:" + namespace); // 綁定 mapper 類 configuration.addMapper(boundType); } } } } } public class Configuration { protected final MapperRegistry mapperRegistry = new MapperRegistry(this); public <T> void addMapper(Class<T> type) { // 通過 MapperRegistry 綁定 mapper 類 mapperRegistry.addMapper(type); } } public class MapperRegistry { private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>(); public <T> void addMapper(Class<T> type) { if (type.isInterface()) { if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { /* * 將 type 和 MapperProxyFactory 進行綁定,MapperProxyFactory 可為 mapper 接口生成代理類 */ knownMappers.put(type, new MapperProxyFactory<T>(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. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); // 解析注解中的信息 parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } } } 

在解析Mapper.xml的最后階段,獲取到Mapper.xml的namespace,然后利用反射,獲取到namespace的Class,並創建一個MapperProxyFactory的實例,namespace的Class作為參數,最后將namespace的Class為key,MapperProxyFactory的實例為value存入knownMappers。

注意,我們這里是通過映射文件的命名空間的Class當做knownMappers的Key。然后我們看看getMapper方法,是通過參數User.class也就是Mapper接口的Class來獲取MapperProxyFactory,所以我們明白了為什么要求xml配置中的namespace要和和對應的Mapper接口的全限定名了。

生成代理對象

我們看return mapperProxyFactory.newInstance(sqlSession);,很明顯是調用了MapperProxyFactory的一個工廠方法,我們跟進去看看

public class MapperProxyFactory<T> { //存放Mapper接口Class private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>(); 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) { //生成mapperInterface的代理類 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { /* * 創建 MapperProxy 對象,MapperProxy 實現了 InvocationHandler 接口,代理邏輯封裝在此類中 * 將sqlSession傳入MapperProxy對象中,第二個參數是Mapper的接口,並不是其實現類 */ final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } } 

上面的代碼首先創建了一個 MapperProxy 對象,該對象實現了 InvocationHandler 接口。然后將對象作為參數傳給重載方法,並在重載方法中調用 JDK 動態代理接口為 Mapper接口 生成代理對象。

這里要注意一點,MapperProxy這個InvocationHandler 創建的時候,傳入的參數並不是Mapper接口的實現類,我們以前是怎么創建JDK動態代理的?先創建一個接口,然后再創建一個接口的實現類,最后創建一個InvocationHandler並將實現類傳入其中作為目標類,創建接口的代理類,然后調用代理類方法時會回調InvocationHandler的invoke方法,最后在invoke方法中調用目標類的方法,但是我們這里調用Mapper接口代理類的方法時,需要調用其實現類的方法嗎?不需要,我們需要調用對應的配置文件的SQL,所以這里並不需要傳入Mapper的實現類到MapperProxy中,那Mapper接口的代理對象是如何調用對應配置文件的SQL呢?下面我們來看看。

Mapper代理類如何執行SQL?

上面一節中我們已經獲取到了EmployeeMapper的代理類,並且其InvocationHandler為MapperProxy,那我們接着看Mapper接口方法的調用

List<User> list = userMapper.getAll(); 

知道JDK動態代理的同學都知道,調用代理類的方法,最后都會回調到InvocationHandler的Invoke方法,那我們來看看這個InvocationHandler(MapperProxy)

public class MapperProxy<T> implements InvocationHandler, Serializable { private static final long serialVersionUID = -6424540398559729838L; private final SqlSession sqlSession; private final Class<T> mapperInterface; 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 { // 如果方法是定義在 Object 類中的,則直接調用 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); //如果是接口中的default方法,JDK8的新特性之一 } else if (isDefaultMethod(method)) { //如果用戶執行的是接口中的default方法的話,MyBatis就需要為用戶提供正常的代理流程。 return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } // 從緩存中獲取 MapperMethod 對象,若緩存未命中,則創建 MapperMethod 對象 final MapperMethod mapperMethod = cachedMapperMethod(method); // 調用 execute 方法執行 SQL return mapperMethod.execute(sqlSession, args); } private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod = methodCache.get(method); if (mapperMethod == null) { //創建一個MapperMethod,參數為mapperInterface和method還有Configuration mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod; } } 

如上,回調函數invoke邏輯會首先檢測被攔截的方法是不是定義在 Object 中的,比如 equals、hashCode 方法等。對於這類方法,直接執行即可。緊接着從緩存中獲取或者創建 MapperMethod 對象,然后通過該對象中的 execute 方法執行 SQL。我們先來看看如何創建MapperMethod

創建 MapperMethod 對象

public class MapperMethod { //包含SQL相關信息,比喻MappedStatement的id屬性,(mapper.UserMapper.getAll) private final SqlCommand command; //包含了關於執行的Mapper方法的參數類型和返回類型。 private final MethodSignature method; public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { // 創建 SqlCommand 對象,該對象包含一些和 SQL 相關的信息 this.command = new SqlCommand(config, mapperInterface, method); // 創建 MethodSignature 對象,從類名中可知,該對象包含了被攔截方法的一些信息 this.method = new MethodSignature(config, mapperInterface, method); } } 

MapperMethod包含SqlCommand 和MethodSignature 對象,我們來看看其創建過程

1、創建 SqlCommand 對象
public static class SqlCommand { //name為MappedStatement的id,也就是namespace.methodName(mapper.UserMapper.getAll) private final String name; //SQL的類型,如insert,delete,update private final SqlCommandType type; public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) { //拼接Mapper接口名和方法名,(mapper.UserMapper.getAll) final String methodName = method.getName(); final Class<?> declaringClass = method.getDeclaringClass(); //檢測configuration是否有key為mapper.UserMapper.getAll的MappedStatement //獲取MappedStatement MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration); // 檢測當前方法是否有對應的 MappedStatement if (ms == null) { 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 和 type 變量 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) { String statementId = mapperInterface.getName() + "." + methodName; //檢測configuration是否有key為statementName的MappedStatement if (configuration.hasStatement(statementId)) { return configuration.getMappedStatement(statementId); } else if (mapperInterface.equals(declaringClass)) { return null; } for (Class<?> superInterface : mapperInterface.getInterfaces()) { if (declaringClass.isAssignableFrom(superInterface)) { MappedStatement ms = resolveMappedStatement(superInterface, methodName, declaringClass, configuration); if (ms != null) { return ms; } } } return null; } } 

通過拼接接口名和方法名,在configuration獲取對應的MappedStatement,並設置設置 name 和 type 變量,代碼很簡單

2、創建 MethodSignature 對象

MethodSignature 包含了被攔截方法的一些信息,如目標方法的返回類型,目標方法的參數列表信息等。下面,我們來看一下 MethodSignature 的構造方法。

public static class MethodSignature { private final boolean returnsMany; private final boolean returnsMap; private final boolean returnsVoid; private final boolean returnsCursor; private final Class<?> returnType; private final String mapKey; private final Integer resultHandlerIndex; private final Integer rowBoundsIndex; 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(); } // 檢測返回值類型是否是 void、集合或數組、Cursor、Map 等 this.returnsVoid = void.class.equals(this.returnType); this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray(); this.returnsCursor = Cursor.class.equals(this.returnType); // 解析 @MapKey 注解,獲取注解內容 this.mapKey = getMapKey(method); this.returnsMap = this.mapKey != null; /* * 獲取 RowBounds 參數在參數列表中的位置,如果參數列表中 * 包含多個 RowBounds 參數,此方法會拋出異常 */ this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class); // 獲取 ResultHandler 參數在參數列表中的位置 this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class); // 解析參數列表 this.paramNameResolver = new ParamNameResolver(configuration, method); } } 

執行 execute 方法

前面已經分析了 MapperMethod 的初始化過程,現在 MapperMethod 創建好了。那么,接下來要做的事情是調用 MapperMethod 的 execute 方法,執行 SQL。傳遞參數sqlSession和method的運行參數args。

return mapperMethod.execute(this.sqlSession, args); 

我們去MapperMethod 的execute方法中看看

MapperMethod

public class MapperMethod { //包含SQL相關信息,比喻MappedStatement的id屬性,(mapper.UserMapper.getAll) private final SqlCommand command; //包含了關於執行的Mapper方法的參數類型和返回類型。 private final MethodSignature method; public Object execute(SqlSession sqlSession, Object[] args) { Object result; // 根據 SQL 類型執行相應的數據庫操作 switch (command.getType()) { case INSERT: { // 對用戶傳入的參數進行轉換,下同 Object param = method.convertArgsToSqlCommandParam(args); // 執行插入操作,rowCountResult 方法用於處理返回值 result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); // 執行更新操作 result = rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); // 執行刪除操作 result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } case SELECT: // 根據目標方法的返回類型進行相應的查詢操作 if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { // 執行查詢操作,並返回多個結果 result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { // 執行查詢操作,並將結果封裝在 Map 中返回 result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { // 執行查詢操作,並返回一個 Cursor 對象 result = executeForCursor(sqlSession, args); } else { Object param = method.convertArgsToSqlCommandParam(args); // 執行查詢操作,並返回一個結果 result = sqlSession.selectOne(command.getName(), param); } break; case FLUSH: // 執行刷新操作 result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + command.getName()); } if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); } return result; } } 

如上,execute 方法主要由一個 switch 語句組成,用於根據 SQL 類型執行相應的數據庫操作。我們先來看看是參數的處理方法convertArgsToSqlCommandParam是如何將方法參數數組轉化成Map的。

public class MapperMethod { public static class MethodSignature { private final ParamNameResolver paramNameResolver; public Object convertArgsToSqlCommandParam(Object[] args) { return paramNameResolver.getNamedParams(args); } } } public class ParamNameResolver { private static final String GENERIC_NAME_PREFIX = "param"; private final SortedMap<Integer, String> names; private boolean hasParamAnnotation; public Object getNamedParams(Object[] args) { final int paramCount = names.size(); if (args == null || paramCount == 0) { return null; } else if (!hasParamAnnotation && paramCount == 1) { return args[names.firstKey()]; } else { //創建一個Map,key為method的參數名,值為method的運行時參數值 final Map<String, Object> param = new ParamMap<Object>(); int i = 0; for (Map.Entry<Integer, String> entry : names.entrySet()) { // 添加 <參數名, 參數值> 鍵值對到 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)) { param.put(genericParamName, args[entry.getKey()]); } i++; } return param; } } } 

我們看到,將Object[] args轉化成了一個Map<參數名, 參數值> ,接着我們就可以看查詢過程分析了,如下

// 執行查詢操作,並返回一個結果 result = sqlSession.selectOne(command.getName(), param); 

我們看到是通過sqlSession來執行查詢的,並且傳入的參數為command.getName()和param,也就是namespace.methodName(mapper.EmployeeMapper.getAll)和方法的運行參數。

查詢操作在下一篇文章單獨來講。




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM