Mybatis mapper動態代理的原理解析
前言
在開始動態代理的原理講解以前,我們先看一下整合mybatis以後dao層不使用動態代理以及使用動態代理的兩種實現方式,通過對比我們自己實現dao層介面以及mybatis動態代理可以更加直觀的展現出mybatis動態代理替我們所做的工作,有利於我們理解動態代理的過程,講解完以後我們再進行動態代理的原理解析,此講解基於mybatis的環境已經搭建完成,並且已經實現了基本的使用者類編寫以及使用者類的Dao介面的宣告,下面是Dao層的介面程式碼
public interface UserDao { /* 查詢所有使用者資訊 */ List<User> findAll(); /** * 儲存使用者 * @param user */ void save(User user); /** * 更新使用者 * @return */ void update(User user); /** * 刪除使用者 */ void delete(Integer userId); /** * 查詢一個使用者 * @param userId * @return */ User findOne(Integer userId); /** * 根據名字模糊查詢 * @param name * @return */ List<User> findByName(String name); /** * 根據組合物件進行模糊查詢 * @param vo * @return */ List<User> findByQueryVo(QueryVo vo); }
一、Mybatis dao層兩種實現方式的對比
1.dao層不使用動態代理
dao層不使用動態代理的話,就需要我們自己實現dao層的介面,為了簡便起見,我只是實現了Dao介面中的findAll方法,以此方法為例子來展現我們自己實現Dao的方式的情況,讓我們來看程式碼:
public class UserDaoImpl implements UserDao{ private SqlSessionFactory factory; public UserDaoImpl(SqlSessionFactory factory){ this.factory = factory; } public List<User> findAll() { //1.獲取sqlSession物件 SqlSession sqlSession = factory.openSession(); //2.呼叫selectList方法 List<User> list = sqlSession.selectList("com.example.dao.UserDao.findAll"); //3.關閉流 sqlSession.close(); return list; } public void save(User user) { } public void update(User user) { } public void delete(Integer userId) { } public User findOne(Integer userId) { return null; } public List<User> findByName(String name) { return null; } public List<User> findByQueryVo(QueryVo vo) { return null; }
這裡的關鍵程式碼 List<User> list = sqlSession.selectList("com.example.dao.UserDao.findAll"),需要我們自己手動呼叫SqlSession裡面的方法,基於動態代理的方式最後的目標也是成功的呼叫到這裡。
注意:如果是新增,更新或者刪除操作的話需要在方法中增加事務的提交。
2.dao層使用Mybatis的動態代理
使用動態代理的話Dao層的介面宣告完成以後只需要在使用的時候通過SqlSession物件的getMapper方法獲取對應Dao介面的代理物件,關鍵程式碼如下:
//3.獲取SqlSession物件 SqlSession session = factory.openSession(); //4.獲取dao的代理物件 UserDao mapper = session.getMapper(UserDao.class); //5.執行查詢所有的方法 List<User> list = mapper.findAll();
獲取到dao層的代理物件以後通過代理物件呼叫查詢方法就可以實現查詢所有使用者列表的功能。
二、Mybatis動態代理實現方式的原理解析
動態代理中最重要的類:SqlSession、MapperProxy、MapperMethod,下面開始從入口方法到呼叫結束的過程分析。
1.呼叫方法的開始:
//4.獲取dao的代理物件
UserDao mapper = session.getMapper(UserDao.class); 因為SqlSesseion為介面,所以我們通過Debug方式發現這裡使用的實現類為DefaultSqlSession。
2.找到DeaultSqlSession中的getMapper方法,發現這裡沒有做其他的動作,只是將工作繼續拋到了Configuration類中,Configuration為類不是介面,可以直接進入該類的getMapper方法中
@Override public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type,this); }
3. 找到Configuration類的getMapper方法,這裡也是將工作繼續交到MapperRegistry的getMapper的方法中,所以我們繼續向下進行。
public <T> T getMapper(Class<T> type,SqlSession sqlSession) { return mapperRegistry.getMapper(type,sqlSession); }
4. 找到MapperRegistry的getMapper的方法,看到這裡發現和以前不一樣了,通過MapperProxyFactory的命名方式我們知道這裡將通過這個工廠生成我們所關注的MapperProxy的代理類,然後我們通過mapperProxyFactory.newInstance(sqlSession);進入MapperProxyFactory的newInstance方法中
public <T> T getMapper(Class<T> type,SqlSession sqlSession) { 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); } }
5. 找到MapperProxyFactory的newIntance方法,通過引數型別SqlSession可以得知,上面的呼叫先進入第二個newInstance方法中並建立我們所需要重點關注的MapperProxy物件,第二個方法中再呼叫第一個newInstance方法並將MapperProxy物件傳入進去,根據該物件建立代理類並返回。這裡已經得到需要的代理類了,但是我們的代理類所做的工作還得繼續向下看MapperProxy類。
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<T>(sqlSession,mapperInterface,methodCache); return newInstance(mapperProxy); }
6. 找到MapperProxy類,發現其確實實現了JDK動態代理必須實現的介面InvocationHandler,所以我們重點關注invoke()方法,這裡看到在invoke方法裡先獲取MapperMethod類,然後呼叫mapperMethod.execute(),所以我們繼續檢視MapperMethod類的execute方法。
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 { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this,args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy,method,args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession,args); } private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod = methodCache.get(method); if (mapperMethod == null) { mapperMethod = new MapperMethod(mapperInterface,sqlSession.getConfiguration()); methodCache.put(method,mapperMethod); } return mapperMethod; } @UsesJava7 private Object invokeDefaultMethod(Object proxy,Object[] args) throws Throwable { final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class .getDeclaredConstructor(Class.class,int.class); if (!constructor.isAccessible()) { constructor.setAccessible(true); } final Class<?> declaringClass = method.getDeclaringClass(); return constructor .newInstance(declaringClass,MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC) .unreflectSpecial(method,declaringClass).bindTo(proxy).invokeWithArguments(args); } /** * Backport of java.lang.reflect.Method#isDefault() */ private boolean isDefaultMethod(Method method) { return ((method.getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC) && method.getDeclaringClass().isInterface(); } }
7. 找到類MapperMethod類的execute方法,發現execute中通過呼叫本類中的其他方法獲取並封裝返回結果,我們來看一下MapperMethod整個類。
public Object execute(SqlSession sqlSession,Object[] args) { Object result; switch (command.getType()) { case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); 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()) { result = executeForMap(sqlSession,args); } else if (method.returnsCursor()) { 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; }
8. MapperMethod類是整個代理機制的核心類,對SqlSession中的操作進行了封裝使用。
該類裡有兩個內部類SqlCommand和MethodSignature。 SqlCommand用來封裝CRUD操作,也就是我們在xml中配置的操作的節點。每個節點都會生成一個MappedStatement類。
MethodSignature用來封裝方法的引數以及返回型別,在execute的方法中我們發現在這裡又回到了SqlSession中的介面呼叫,和我們自己實現UerDao介面的方式中直接用SqlSession物件呼叫DefaultSqlSession的實現類的方法是一樣的,經過一大圈的代理又回到了原地,這就是整個動態代理的實現過程了。
public class MapperMethod { private final SqlCommand command; private final MethodSignature method; public MapperMethod(Class<?> mapperInterface,Configuration config) { this.command = new SqlCommand(config,method); this.method = new MethodSignature(config,method); } public Object execute(SqlSession sqlSession,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; } private Object rowCountResult(int rowCount) { final Object result; if (method.returnsVoid()) { result = null; } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) { result = rowCount; } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) { result = (long)rowCount; } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) { result = rowCount > 0; } else { throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType()); } return result; } private void executeWithResultHandler(SqlSession sqlSession,Object[] args) { MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName()); if (void.class.equals(ms.getResultMaps().get(0).getType())) { throw new BindingException("method " + command.getName() + " needs either a @ResultMap annotation,a @ResultType annotation," + " or a resultType attribute in XML so a ResultHandler can be used as a parameter."); } Object param = method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); sqlSession.select(command.getName(),param,rowBounds,method.extractResultHandler(args)); } else { sqlSession.select(command.getName(),method.extractResultHandler(args)); } } private <E> Object executeForMany(SqlSession sqlSession,Object[] args) { List<E> result; Object param = method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); result = sqlSession.<E>selectList(command.getName(),rowBounds); } else { result = sqlSession.<E>selectList(command.getName(),param); } // issue #510 Collections & arrays support if (!method.getReturnType().isAssignableFrom(result.getClass())) { if (method.getReturnType().isArray()) { return convertToArray(result); } else { return convertToDeclaredCollection(sqlSession.getConfiguration(),result); } } return result; } private <T> Cursor<T> executeForCursor(SqlSession sqlSession,Object[] args) { Cursor<T> result; Object param = method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); result = sqlSession.<T>selectCursor(command.getName(),rowBounds); } else { result = sqlSession.<T>selectCursor(command.getName(),param); } return result; } private <E> Object convertToDeclaredCollection(Configuration config,List<E> list) { Object collection = config.getObjectFactory().create(method.getReturnType()); MetaObject metaObject = config.newMetaObject(collection); metaObject.addAll(list); return collection; } @SuppressWarnings("unchecked") private <E> Object convertToArray(List<E> list) { Class<?> arrayComponentType = method.getReturnType().getComponentType(); Object array = Array.newInstance(arrayComponentType,list.size()); if (arrayComponentType.isPrimitive()) { for (int i = 0; i < list.size(); i++) { Array.set(array,i,list.get(i)); } return array; } else { return list.toArray((E[])array); } } private <K,V> Map<K,V> executeForMap(SqlSession sqlSession,Object[] args) { Map<K,V> result; Object param = method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); result = sqlSession.<K,V>selectMap(command.getName(),method.getMapKey(),rowBounds); } else { result = sqlSession.<K,method.getMapKey()); } return result; } public static class ParamMap<V> extends HashMap<String,V> { private static final long serialVersionUID = -2212268410512043556L; @Override public V get(Object key) { if (!super.containsKey(key)) { throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet()); } return super.get(key); } } public static class SqlCommand { private final String name; private final SqlCommandType type; public SqlCommand(Configuration configuration,Class<?> mapperInterface,Method method) { final String methodName = method.getName(); final Class<?> declaringClass = method.getDeclaringClass(); MappedStatement ms = resolveMappedStatement(mapperInterface,methodName,declaringClass,configuration); 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 = ms.getId(); type = ms.getSqlCommandType(); if (type == SqlCommandType.UNKNOWN) { throw new BindingException("Unknown execution method for: " + name); } } } public String getName() { return name; } public SqlCommandType getType() { return type; } private MappedStatement resolveMappedStatement(Class<?> mapperInterface,String methodName,Class<?> declaringClass,Configuration configuration) { String statementId = mapperInterface.getName() + "." + methodName; 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,configuration); if (ms != null) { return ms; } } } return null; } } 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,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); this.mapKey = getMapKey(method); this.returnsMap = (this.mapKey != null); this.rowBoundsIndex = getUniqueParamIndex(method,RowBounds.class); this.resultHandlerIndex = getUniqueParamIndex(method,ResultHandler.class); this.paramNameResolver = new ParamNameResolver(configuration,method); } public Object convertArgsToSqlCommandParam(Object[] args) { return paramNameResolver.getNamedParams(args); } public boolean hasRowBounds() { return rowBoundsIndex != null; } public RowBounds extractRowBounds(Object[] args) { return hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null; } public boolean hasResultHandler() { return resultHandlerIndex != null; } public ResultHandler extractResultHandler(Object[] args) { return hasResultHandler() ? (ResultHandler) args[resultHandlerIndex] : null; } public String getMapKey() { return mapKey; } public Class<?> getReturnType() { return returnType; } public boolean returnsMany() { return returnsMany; } public boolean returnsMap() { return returnsMap; } public boolean returnsVoid() { return returnsVoid; } public boolean returnsCursor() { return returnsCursor; } 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; } private String getMapKey(Method method) { String mapKey = null; if (Map.class.isAssignableFrom(method.getReturnType())) { final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class); if (mapKeyAnnotation != null) { mapKey = mapKeyAnnotation.value(); } } return mapKey; } }
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。