Mybatis|Mybatis mapper动态代理的原理详解

在开始动态代理的原理讲解以前,我们先看一下集成mybatis以后dao层不使用动态代理以及使用动态代理的两种实现方式,通过对比我们自己实现dao层接口以及mybatis动态代理可以更加直观的展现出mybatis动态代理替我们所做的工作,有利于我们理解动态代理的过程,讲解完以后我们再进行动态代理的原理解析,此讲解基于mybatis的环境已经搭建完成,并且已经实现了基本的用户类编写以及用户类的Dao接口的声明,下面是Dao层的接口代码

1 public interface UserDao { 2/* 3查询所有用户信息 4*/ 5List findAll(); 6 7/** 8* 保存用户 9* @param user 10*/ 11void save(User user); 12 13/** 14* 更新用户 15* @return 16*/ 17void update(User user); 18/** 19* 删除用户 20*/ 21void delete(Integer userId); 22 23/** 24* 查找一个用户 25* @param userId 26* @return 27*/ 28User findOne(Integer userId); 29 30/** 31* 根据名字模糊查询 32* @param name 33* @return 34*/ 35List findByName(String name); 36/** 37* 根据组合对象进行模糊查询 38* @param vo 39* @return 40*/ 41List findByQueryVo(QueryVo vo); 42 }

一、Mybatis dao层两种实现方式的对比
1.dao层不使用动态代理 dao层不使用动态代理的话,就需要我们自己实现dao层的接口,为了简便起见,我只是实现了Dao接口中的findAll方法,以此方法为例子来展现我们自己实现Dao的方式的情况,让我们来看代码:
1 public class UserDaoImpl implements UserDao{ 2private SqlSessionFactory factory; 3public UserDaoImpl(SqlSessionFactory factory){ 4this.factory = factory; 5} 6public List findAll() { 7//1.获取sqlSession对象 8SqlSession sqlSession = factory.openSession(); 9//2.调用selectList方法 10List list = sqlSession.selectList("com.example.dao.UserDao.findAll"); 11//3.关闭流 12sqlSession.close(); 13return list; 14} 15 16public void save(User user) { 17 18} 19 20public void update(User user) { 21 22} 23 24public void delete(Integer userId) { 25 26} 27 28public User findOne(Integer userId) { 29return null; 30} 31 32public List findByName(String name) { 33return null; 34} 35 36public List findByQueryVo(QueryVo vo) { 37return null; 38}

这里的关键代码 List 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 list = mapper.findAll(); 获取到dao层的代理对象以后通过代理对象调用查询方法就可以实现查询所有用户列表的功能。
二、Mybatis动态代理实现方式的原理解析
动态代理中最重要的类:SqlSession、MapperProxy、MapperMethod,下面开始从入口方法到调用结束的过程分析。调用方法的开始: //4.获取dao的代理对象
UserDao mapper = session.getMapper(UserDao.class); 因为SqlSesseion为接口,所以我们通过Debug方式发现这里使用的实现类为DefaultSqlSession。找到DeaultSqlSession中的getMapper方法,发现这里没有做其他的动作,只是将工作继续抛到了Configuration类中,Configuration为类不是接口,可以直接进入该类的getMapper方法中
@Override public T getMapper(Class type) { return configuration.getMapper(type, this); }3

. 找到Configuration类的getMapper方法,这里也是将工作继续交到MapperRegistry的getMapper的方法中,所以我们继续向下进行。
public T getMapper(Class type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }4

. 找到MapperRegistry的getMapper的方法,看到这里发现和以前不一样了,通过MapperProxyFactory的命名方式我们知道这里将通过这个工厂生成我们所关注的MapperProxy的代理类,然后我们通过
mapperProxyFactory.newInstance(sqlSession); 进入MapperProxyFactory的newInstance方法中public T getMapper(Class type, SqlSession sqlSession) { final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) 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. 找到Map

【Mybatis|Mybatis mapper动态代理的原理详解】perProxyFactory的newIntance方法,通过参数类型SqlSession可以得知,上面的调用先进入第二个newInstance方法中并创建我们所需要重点关注的MapperProxy对象,第二个方法中再调用第一个newInstance方法并将MapperProxy对象传入进去,根据该对象创建代理类并返回。这里已经得到需要的代理类了,但是我们的代理类所做的工作还得继续向下看MapperProxy类。
protected T newInstance(MapperProxy mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }public T newInstance(SqlSession sqlSession) { final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }6. 找到M

apperProxy类,发现其确实实现了JDK动态代理必须实现的接口InvocationHandler,所以我们重点关注invoke()方法,这里看到在invoke方法里先获取MapperMethod类,然后调用mapperMethod.execute(),所以我们继续查看MapperMethod类的execute方法。
public class MapperProxy implements InvocationHandler, Serializable {private static final long serialVersionUID = -6424540398559729838L; private final SqlSession sqlSession; private final Class mapperInterface; private final Map methodCache; public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map 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, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod; }@UsesJava7 private Object invokeDefaultMethod(Object proxy, Method method, Object[] args) throws Throwable { final Constructor 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的实现类的方法是一样的,经过一大圈的代理又回到了原地,这就是整个动态代理的实现过程了。
```java 1public class MapperMethod { 2 3private final SqlCommand command; 4private final MethodSignature method; 5 6public MapperMethod(Class mapperInterface, Method method, Configuration config) { 7this.command = new SqlCommand(config, mapperInterface, method); 8this.method = new MethodSignature(config, mapperInterface, method); 9} 10 11public Object execute(SqlSession sqlSession, Object[] args) { 12Object result; 13switch (command.getType()) { 14case INSERT: { 15Object param = method.convertArgsToSqlCommandParam(args); 16result = rowCountResult(sqlSession.insert(command.getName(), param)); 17break; 18} 19case UPDATE: { 20Object param = method.convertArgsToSqlCommandParam(args); 21result = rowCountResult(sqlSession.update(command.getName(), param)); 22break; 23} 24case DELETE: { 25Object param = method.convertArgsToSqlCommandParam(args); 26result = rowCountResult(sqlSession.delete(command.getName(), param)); 27break; 28} 29case SELECT: 30if (method.returnsVoid() && method.hasResultHandler()) { 31executeWithResultHandler(sqlSession, args); 32result = null; 33} else if (method.returnsMany()) { 34result = executeForMany(sqlSession, args); 35} else if (method.returnsMap()) { 36result = executeForMap(sqlSession, args); 37} else if (method.returnsCursor()) { 38result = executeForCursor(sqlSession, args); 39} else { 40Object param = method.convertArgsToSqlCommandParam(args); 41result = sqlSession.selectOne(command.getName(), param); 42} 43break; 44case FLUSH: 45result = sqlSession.flushStatements(); 46break; 47default: 48throw new BindingException("Unknown execution method for: " + command.getName()); 49} 50if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { 51throw new BindingException("Mapper method '" + command.getName() 52+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); 53} 54return result; 55} 56 57private Object rowCountResult(int rowCount) { 58final Object result; 59if (method.returnsVoid()) { 60result = null; 61} else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) { 62result = rowCount; 63} else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) { 64result = (long)rowCount; 65} else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) { 66result = rowCount > 0; 67} else { 68throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType()); 69} 70return result; 71} 72 73private void executeWithResultHandler(SqlSession sqlSession, Object[] args) { 74MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName()); 75if (void.class.equals(ms.getResultMaps().get(0).getType())) { 76throw new BindingException("method " + command.getName() 77+ " needs either a @ResultMap annotation, a @ResultType annotation," 78+ " or a resultType attribute in XML so a ResultHandler can be used as a parameter."); 79} 80Object param = method.convertArgsToSqlCommandParam(args); 81if (method.hasRowBounds()) { 82RowBounds rowBounds = method.extractRowBounds(args); 83sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args)); 84} else { 85sqlSession.select(command.getName(), param, method.extractResultHandler(args)); 86} 87} 88 89private Object executeForMany(SqlSession sqlSession, Object[] args) { 90List result; 91Object param = method.convertArgsToSqlCommandParam(args); 92if (method.hasRowBounds()) { 93RowBounds rowBounds = method.extractRowBounds(args); 94result = sqlSession.selectList(command.getName(), param, rowBounds); 95} else { 96result = sqlSession.selectList(command.getName(), param); 97} 98// issue #510 Collections & arrays support 99if (!method.getReturnType().isAssignableFrom(result.getClass())) { 100if (method.getReturnType().isArray()) { 101return convertToArray(result); 102} else { 103return convertToDeclaredCollection(sqlSession.getConfiguration(), result); 104} 105} 106return result; 107} 108 109private Cursor executeForCursor(SqlSession sqlSession, Object[] args) { 110Cursor result; 111Object param = method.convertArgsToSqlCommandParam(args); 112if (method.hasRowBounds()) { 113RowBounds rowBounds = method.extractRowBounds(args); 114result = sqlSession.selectCursor(command.getName(), param, rowBounds); 115} else { 116result = sqlSession.selectCursor(command.getName(), param); 117} 118return result; 119} 120 121private Object convertToDeclaredCollection(Configuration config, List list) { 122Object collection = config.getObjectFactory().create(method.getReturnType()); 123MetaObject metaObject = config.newMetaObject(collection); 124metaObject.addAll(list); 125return collection; 126} 127 128@SuppressWarnings("unchecked") 129private Object convertToArray(List list) { 130Class arrayComponentType = method.getReturnType().getComponentType(); 131Object array = Array.newInstance(arrayComponentType, list.size()); 132if (arrayComponentType.isPrimitive()) { 133for (int i = 0; i < list.size(); i++) { 134Array.set(array, i, list.get(i)); 135} 136return array; 137} else { 138return list.toArray((E[])array); 139} 140} 141 142private Map executeForMap(SqlSession sqlSession, Object[] args) { 143Map result; 144Object param = method.convertArgsToSqlCommandParam(args); 145if (method.hasRowBounds()) { 146RowBounds rowBounds = method.extractRowBounds(args); 147result = sqlSession.selectMap(command.getName(), param, method.getMapKey(), rowBounds); 148} else { 149result = sqlSession.selectMap(command.getName(), param, method.getMapKey()); 150} 151return result; 152} 153 154public static class ParamMap extends HashMap { 155 156private static final long serialVersionUID = -2212268410512043556L; 157 158@Override 159public V get(Object key) { 160if (!super.containsKey(key)) { 161throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet()); 162} 163return super.get(key); 164} 165 166} 167 168public static class SqlCommand { 169 170private final String name; 171private final SqlCommandType type; 172 173public SqlCommand(Configuration configuration, Class mapperInterface, Method method) { 174final String methodName = method.getName(); 175final Class declaringClass = method.getDeclaringClass(); 176MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, 177configuration); 178if (ms == null) { 179if (method.getAnnotation(Flush.class) != null) { 180name = null; 181type = SqlCommandType.FLUSH; 182} else { 183throw new BindingException("Invalid bound statement (not found): " 184+ mapperInterface.getName() + "." + methodName); 185} 186} else { 187name = ms.getId(); 188type = ms.getSqlCommandType(); 189if (type == SqlCommandType.UNKNOWN) { 190throw new BindingException("Unknown execution method for: " + name); 191} 192} 193} 194 195public String getName() { 196return name; 197} 198 199public SqlCommandType getType() { 200return type; 201} 202 203private MappedStatement resolveMappedStatement(Class mapperInterface, String methodName, 204Class declaringClass, Configuration configuration) { 205String statementId = mapperInterface.getName() + "." + methodName; 206if (configuration.hasStatement(statementId)) { 207return configuration.getMappedStatement(statementId); 208} else if (mapperInterface.equals(declaringClass)) { 209return null; 210} 211for (Class superInterface : mapperInterface.getInterfaces()) { 212if (declaringClass.isAssignableFrom(superInterface)) { 213MappedStatement ms = resolveMappedStatement(superInterface, methodName, 214declaringClass, configuration); 215if (ms != null) { 216return ms; 217} 218} 219} 220return null; 221} 222} 223 224public static class MethodSignature { 225 226private final boolean returnsMany; 227private final boolean returnsMap; 228private final boolean returnsVoid; 229private final boolean returnsCursor; 230private final Class returnType; 231private final String mapKey; 232private final Integer resultHandlerIndex; 233private final Integer rowBoundsIndex; 234private final ParamNameResolver paramNameResolver; 235 236public MethodSignature(Configuration configuration, Class mapperInterface, Method method) { 237Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface); 238if (resolvedReturnType instanceof Class) { 239this.returnType = (Class) resolvedReturnType; 240} else if (resolvedReturnType instanceof ParameterizedType) { 241this.returnType = (Class) ((ParameterizedType) resolvedReturnType).getRawType(); 242} else { 243this.returnType = method.getReturnType(); 244} 245this.returnsVoid = void.class.equals(this.returnType); 246this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray()); 247this.returnsCursor = Cursor.class.equals(this.returnType); 248this.mapKey = getMapKey(method); 249this.returnsMap = (this.mapKey != null); 250this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class); 251this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class); 252this.paramNameResolver = new ParamNameResolver(configuration, method); 253} 254 255public Object convertArgsToSqlCommandParam(Object[] args) { 256return paramNameResolver.getNamedParams(args); 257} 258 259public boolean hasRowBounds() { 260return rowBoundsIndex != null; 261} 262 263public RowBounds extractRowBounds(Object[] args) { 264return hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null; 265} 266 267public boolean hasResultHandler() { 268return resultHandlerIndex != null; 269} 270 271public ResultHandler extractResultHandler(Object[] args) { 272return hasResultHandler() ? (ResultHandler) args[resultHandlerIndex] : null; 273} 274 275public String getMapKey() { 276return mapKey; 277} 278 279public Class getReturnType() { 280return returnType; 281} 282 283public boolean returnsMany() { 284return returnsMany; 285} 286 287public boolean returnsMap() { 288return returnsMap; 289} 290 291public boolean returnsVoid() { 292return returnsVoid; 293} 294 295public boolean returnsCursor() { 296return returnsCursor; 297} 298 299private Integer getUniqueParamIndex(Method method, Class paramType) { 300Integer index = null; 301final Class[] argTypes = method.getParameterTypes(); 302for (int i = 0; i < argTypes.length; i++) { 303if (paramType.isAssignableFrom(argTypes[i])) { 304if (index == null) { 305index = i; 306} else { 307throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters"); 308} 309} 310} 311return index; 312} 313 314private String getMapKey(Method method) { 315String mapKey = null; 316if (Map.class.isAssignableFrom(method.getReturnType())) { 317final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class); 318if (mapKeyAnnotation != null) { 319mapKey = mapKeyAnnotation.value(); 320} 321} 322return mapKey; 323} 324}


    推荐阅读