当前位置: 首页 > 编程笔记 >

Mybatis mapper动态代理的原理解析

岳泳
2023-03-14
本文向大家介绍Mybatis mapper动态代理的原理解析,包括了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, 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的实现类的方法是一样的,经过一大圈的代理又回到了原地,这就是整个动态代理的实现过程了。

public class MapperMethod {
 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);
 }
 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;
 }

 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(), param, 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(), param, 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(), param, 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(), param, method.getMapKey(), rowBounds);
 } else {
  result = sqlSession.<K, V>selectMap(command.getName(), param, 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, methodName,
    declaringClass, 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, 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);
  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;
 }
 }

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持小牛知识库。

 类似资料:
  • 本文向大家介绍Java 动态代理原理分析,包括了Java 动态代理原理分析的使用技巧和注意事项,需要的朋友参考一下 Java 动态代理原理分析 概要 AOP的拦截功能是由java中的动态代理来实现的。说白了,就是在目标类的基础上增加切面逻辑,生成增强的目标类(该切面逻辑或者在目标类函数执行之前,或者目标类函数执行之后,或者在目标类函数抛出异常时候执行。Spring中的动态代理是使用Cglib进行实

  • 本文向大家介绍Java动态代理语法Proxy类原理详解,包括了Java动态代理语法Proxy类原理详解的使用技巧和注意事项,需要的朋友参考一下 1、前言 写动态代理的代码涉及了一个非常重要的类 Proxy,通过Proxy的静态方法newProxyInstance才会动态创建代理对象。 2、newProxyInstance方法 public static Object newProxyInstanc

  • 本文向大家介绍浅谈Java代理(jdk静态代理、动态代理和cglib动态代理),包括了浅谈Java代理(jdk静态代理、动态代理和cglib动态代理)的使用技巧和注意事项,需要的朋友参考一下 一、代理是Java常用的设计模式,代理类通过调用被代理类的相关方法,并对相关方法进行增强。加入一些非业务性代码,比如事务、日志、报警发邮件等操作。 二、jdk静态代理 1、业务接口 2、业务实现类 3、代理类

  • 我试图通过反复阅读维基百科条目来确定我对上述原则的理解。 撇开仍然让我悲伤的协变和逆变的概念不谈,wikipedia还提到超类型的不变量必须保留在子类型和历史约束或历史规则中。基于最后两个概念,我提出了一个小例子: 所以我的问题是:基于上述两个概念,我用这个例子是否违反了原则?若否,原因为何? 事先非常感谢。

  • 本文向大家介绍十分钟理解Java中的动态代理,包括了十分钟理解Java中的动态代理的使用技巧和注意事项,需要的朋友参考一下 若代理类在程序运行前就已经存在,那么这种代理方式被成为 静态代理 ,这种情况下的代理类通常都是我们在Java代码中定义的。 通常情况下, 静态代理中的代理类和委托类会实现同一接口或是派生自相同的父类。 一、概述 1. 什么是代理 我们大家都知道微商代理,简单地说就是代替厂家卖

  • 问题内容: 展开动态代理以检索下面的原始对象的最佳方法是什么?动态代理已使用创​​建 谢谢。 问题答案: 每个代理都有一个关联。只有知道哪个对象( 如果有 )是代理的基础。如果您控制代理的创建,那么您可以提供自己的代理,该代理将具有所需的额外功能(即能够公开基础对象。)如果您不这样做,那么恐怕您会不在运气