打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Mybatis mapper动态代理的原理详解

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

 1 public interface UserDao { 2     /* 3     查询所有用户信息 4      */ 5     List<User> findAll(); 6  7     /** 8      * 保存用户 9      * @param user10      */11     void save(User user);12 13     /**14      * 更新用户15      * @return16      */17     void update(User user);18     /**19      * 删除用户20      */21     void delete(Integer userId);22 23     /**24      * 查找一个用户25      * @param userId26      * @return27      */28     User findOne(Integer userId);29 30     /**31      * 根据名字模糊查询32      * @param name33      * @return34      */35     List<User> findByName(String name);36     /**37      * 根据组合对象进行模糊查询38      * @param vo39      * @return40      */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,下面开始从入口方法到调用结束的过程分析。

  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的实现类的方法是一样的,经过一大圈的代理又回到了原地,这就是整个动态代理的实现过程了。

  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     @Override159     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   }
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Mybatis接口编程原理分析
Mybatis之工作原理
用反射的方式获取父类中的所有属性和方法
Method类invoke方法的使用
通过java的反射机制实现Map、JavaBean、JSON的相互转换工具类
源码分析(1.4万字) | Mybatis接口没有实现类为什么可以执行增删改查
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服