Mybatis mapper动态代理的原理详解
2019-08-20 18:46 全me村的希望 阅读(29537) 评论(4) 编辑 收藏 举报在开始动态代理的原理讲解以前,我们先看一下集成mybatis以后dao层不使用动态代理以及使用动态代理的两种实现方式,通过对比我们自己实现dao层接口以及mybatis动态代理可以更加直观的展现出mybatis动态代理替我们所做的工作,有利于我们理解动态代理的过程,讲解完以后我们再进行动态代理的原理解析,此讲解基于mybatis的环境已经搭建完成,并且已经实现了基本的用户类编写以及用户类的Dao接口的声明,下面是Dao层的接口代码
1 public interface UserDao { 2 /* 3 查询所有用户信息 4 */ 5 List<User> findAll(); 6 7 /** 8 * 保存用户 9 * @param user 10 */ 11 void save(User user); 12 13 /** 14 * 更新用户 15 * @return 16 */ 17 void update(User user); 18 /** 19 * 删除用户 20 */ 21 void delete(Integer userId); 22 23 /** 24 * 查找一个用户 25 * @param userId 26 * @return 27 */ 28 User findOne(Integer userId); 29 30 /** 31 * 根据名字模糊查询 32 * @param name 33 * @return 34 */ 35 List<User> findByName(String name); 36 /** 37 * 根据组合对象进行模糊查询 38 * @param vo 39 * @return 40 */ 41 List<User> findByQueryVo(QueryVo vo); 42 }
一、Mybatis dao层两种实现方式的对比
1.dao层不使用动态代理
dao层不使用动态代理的话,就需要我们自己实现dao层的接口,为了简便起见,我只是实现了Dao接口中的findAll方法,以此方法为例子来展现我们自己实现Dao的方式的情况,让我们来看代码:
1 public class UserDaoImpl implements UserDao{ 2 private SqlSessionFactory factory; 3 public UserDaoImpl(SqlSessionFactory factory){ 4 this.factory = factory; 5 } 6 public List<User> findAll() { 7 //1.获取sqlSession对象 8 SqlSession sqlSession = factory.openSession(); 9 //2.调用selectList方法 10 List<User> list = sqlSession.selectList("com.example.dao.UserDao.findAll"); 11 //3.关闭流 12 sqlSession.close(); 13 return list; 14 } 15 16 public void save(User user) { 17 18 } 19 20 public void update(User user) { 21 22 } 23 24 public void delete(Integer userId) { 25 26 } 27 28 public User findOne(Integer userId) { 29 return null; 30 } 31 32 public List<User> findByName(String name) { 33 return null; 34 } 35 36 public List<User> findByQueryVo(QueryVo vo) { 37 return null; 38 }
这里的关键代码 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,下面开始从入口方法到调用结束的过程分析。
- 调用方法的开始:
//4.获取dao的代理对象
UserDao mapper = session.getMapper(UserDao.class); 因为SqlSesseion为接口,所以我们通过Debug方式发现这里使用的实现类为DefaultSqlSession。 - 找到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, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod; } @UsesJava7 private Object invokeDefaultMethod(Object proxy, Method method, 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的实现类的方法是一样的,经过一大圈的代理又回到了原地,这就是整个动态代理的实现过程了。
1 public class MapperMethod { 2 3 private final SqlCommand command; 4 private final MethodSignature method; 5 6 public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { 7 this.command = new SqlCommand(config, mapperInterface, method); 8 this.method = new MethodSignature(config, mapperInterface, method); 9 } 10 11 public Object execute(SqlSession sqlSession, Object[] args) { 12 Object result; 13 switch (command.getType()) { 14 case INSERT: { 15 Object param = method.convertArgsToSqlCommandParam(args); 16 result = rowCountResult(sqlSession.insert(command.getName(), param)); 17 break; 18 } 19 case UPDATE: { 20 Object param = method.convertArgsToSqlCommandParam(args); 21 result = rowCountResult(sqlSession.update(command.getName(), param)); 22 break; 23 } 24 case DELETE: { 25 Object param = method.convertArgsToSqlCommandParam(args); 26 result = rowCountResult(sqlSession.delete(command.getName(), param)); 27 break; 28 } 29 case SELECT: 30 if (method.returnsVoid() && method.hasResultHandler()) { 31 executeWithResultHandler(sqlSession, args); 32 result = null; 33 } else if (method.returnsMany()) { 34 result = executeForMany(sqlSession, args); 35 } else if (method.returnsMap()) { 36 result = executeForMap(sqlSession, args); 37 } else if (method.returnsCursor()) { 38 result = executeForCursor(sqlSession, args); 39 } else { 40 Object param = method.convertArgsToSqlCommandParam(args); 41 result = sqlSession.selectOne(command.getName(), param); 42 } 43 break; 44 case FLUSH: 45 result = sqlSession.flushStatements(); 46 break; 47 default: 48 throw new BindingException("Unknown execution method for: " + command.getName()); 49 } 50 if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { 51 throw new BindingException("Mapper method '" + command.getName() 52 + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); 53 } 54 return result; 55 } 56 57 private Object rowCountResult(int rowCount) { 58 final Object result; 59 if (method.returnsVoid()) { 60 result = null; 61 } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) { 62 result = rowCount; 63 } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) { 64 result = (long)rowCount; 65 } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) { 66 result = rowCount > 0; 67 } else { 68 throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType()); 69 } 70 return result; 71 } 72 73 private void executeWithResultHandler(SqlSession sqlSession, Object[] args) { 74 MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName()); 75 if (void.class.equals(ms.getResultMaps().get(0).getType())) { 76 throw 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 } 80 Object param = method.convertArgsToSqlCommandParam(args); 81 if (method.hasRowBounds()) { 82 RowBounds rowBounds = method.extractRowBounds(args); 83 sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args)); 84 } else { 85 sqlSession.select(command.getName(), param, method.extractResultHandler(args)); 86 } 87 } 88 89 private <E> Object executeForMany(SqlSession sqlSession, Object[] args) { 90 List<E> result; 91 Object param = method.convertArgsToSqlCommandParam(args); 92 if (method.hasRowBounds()) { 93 RowBounds rowBounds = method.extractRowBounds(args); 94 result = sqlSession.<E>selectList(command.getName(), param, rowBounds); 95 } else { 96 result = sqlSession.<E>selectList(command.getName(), param); 97 } 98 // issue #510 Collections & arrays support 99 if (!method.getReturnType().isAssignableFrom(result.getClass())) { 100 if (method.getReturnType().isArray()) { 101 return convertToArray(result); 102 } else { 103 return convertToDeclaredCollection(sqlSession.getConfiguration(), result); 104 } 105 } 106 return result; 107 } 108 109 private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) { 110 Cursor<T> result; 111 Object param = method.convertArgsToSqlCommandParam(args); 112 if (method.hasRowBounds()) { 113 RowBounds rowBounds = method.extractRowBounds(args); 114 result = sqlSession.<T>selectCursor(command.getName(), param, rowBounds); 115 } else { 116 result = sqlSession.<T>selectCursor(command.getName(), param); 117 } 118 return result; 119 } 120 121 private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) { 122 Object collection = config.getObjectFactory().create(method.getReturnType()); 123 MetaObject metaObject = config.newMetaObject(collection); 124 metaObject.addAll(list); 125 return collection; 126 } 127 128 @SuppressWarnings("unchecked") 129 private <E> Object convertToArray(List<E> list) { 130 Class<?> arrayComponentType = method.getReturnType().getComponentType(); 131 Object array = Array.newInstance(arrayComponentType, list.size()); 132 if (arrayComponentType.isPrimitive()) { 133 for (int i = 0; i < list.size(); i++) { 134 Array.set(array, i, list.get(i)); 135 } 136 return array; 137 } else { 138 return list.toArray((E[])array); 139 } 140 } 141 142 private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) { 143 Map<K, V> result; 144 Object param = method.convertArgsToSqlCommandParam(args); 145 if (method.hasRowBounds()) { 146 RowBounds rowBounds = method.extractRowBounds(args); 147 result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey(), rowBounds); 148 } else { 149 result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey()); 150 } 151 return result; 152 } 153 154 public static class ParamMap<V> extends HashMap<String, V> { 155 156 private static final long serialVersionUID = -2212268410512043556L; 157 158 @Override 159 public V get(Object key) { 160 if (!super.containsKey(key)) { 161 throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet()); 162 } 163 return super.get(key); 164 } 165 166 } 167 168 public static class SqlCommand { 169 170 private final String name; 171 private final SqlCommandType type; 172 173 public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) { 174 final String methodName = method.getName(); 175 final Class<?> declaringClass = method.getDeclaringClass(); 176 MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass, 177 configuration); 178 if (ms == null) { 179 if (method.getAnnotation(Flush.class) != null) { 180 name = null; 181 type = SqlCommandType.FLUSH; 182 } else { 183 throw new BindingException("Invalid bound statement (not found): " 184 + mapperInterface.getName() + "." + methodName); 185 } 186 } else { 187 name = ms.getId(); 188 type = ms.getSqlCommandType(); 189 if (type == SqlCommandType.UNKNOWN) { 190 throw new BindingException("Unknown execution method for: " + name); 191 } 192 } 193 } 194 195 public String getName() { 196 return name; 197 } 198 199 public SqlCommandType getType() { 200 return type; 201 } 202 203 private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName, 204 Class<?> declaringClass, Configuration configuration) { 205 String statementId = mapperInterface.getName() + "." + methodName; 206 if (configuration.hasStatement(statementId)) { 207 return configuration.getMappedStatement(statementId); 208 } else if (mapperInterface.equals(declaringClass)) { 209 return null; 210 } 211 for (Class<?> superInterface : mapperInterface.getInterfaces()) { 212 if (declaringClass.isAssignableFrom(superInterface)) { 213 MappedStatement ms = resolveMappedStatement(superInterface, methodName, 214 declaringClass, configuration); 215 if (ms != null) { 216 return ms; 217 } 218 } 219 } 220 return null; 221 } 222 } 223 224 public static class MethodSignature { 225 226 private final boolean returnsMany; 227 private final boolean returnsMap; 228 private final boolean returnsVoid; 229 private final boolean returnsCursor; 230 private final Class<?> returnType; 231 private final String mapKey; 232 private final Integer resultHandlerIndex; 233 private final Integer rowBoundsIndex; 234 private final ParamNameResolver paramNameResolver; 235 236 public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) { 237 Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface); 238 if (resolvedReturnType instanceof Class<?>) { 239 this.returnType = (Class<?>) resolvedReturnType; 240 } else if (resolvedReturnType instanceof ParameterizedType) { 241 this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType(); 242 } else { 243 this.returnType = method.getReturnType(); 244 } 245 this.returnsVoid = void.class.equals(this.returnType); 246 this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray()); 247 this.returnsCursor = Cursor.class.equals(this.returnType); 248 this.mapKey = getMapKey(method); 249 this.returnsMap = (this.mapKey != null); 250 this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class); 251 this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class); 252 this.paramNameResolver = new ParamNameResolver(configuration, method); 253 } 254 255 public Object convertArgsToSqlCommandParam(Object[] args) { 256 return paramNameResolver.getNamedParams(args); 257 } 258 259 public boolean hasRowBounds() { 260 return rowBoundsIndex != null; 261 } 262 263 public RowBounds extractRowBounds(Object[] args) { 264 return hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null; 265 } 266 267 public boolean hasResultHandler() { 268 return resultHandlerIndex != null; 269 } 270 271 public ResultHandler extractResultHandler(Object[] args) { 272 return hasResultHandler() ? (ResultHandler) args[resultHandlerIndex] : null; 273 } 274 275 public String getMapKey() { 276 return mapKey; 277 } 278 279 public Class<?> getReturnType() { 280 return returnType; 281 } 282 283 public boolean returnsMany() { 284 return returnsMany; 285 } 286 287 public boolean returnsMap() { 288 return returnsMap; 289 } 290 291 public boolean returnsVoid() { 292 return returnsVoid; 293 } 294 295 public boolean returnsCursor() { 296 return returnsCursor; 297 } 298 299 private Integer getUniqueParamIndex(Method method, Class<?> paramType) { 300 Integer index = null; 301 final Class<?>[] argTypes = method.getParameterTypes(); 302 for (int i = 0; i < argTypes.length; i++) { 303 if (paramType.isAssignableFrom(argTypes[i])) { 304 if (index == null) { 305 index = i; 306 } else { 307 throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters"); 308 } 309 } 310 } 311 return index; 312 } 313 314 private String getMapKey(Method method) { 315 String mapKey = null; 316 if (Map.class.isAssignableFrom(method.getReturnType())) { 317 final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class); 318 if (mapKeyAnnotation != null) { 319 mapKey = mapKeyAnnotation.value(); 320 } 321 } 322 return mapKey; 323 } 324 }