­

Mybatis mapper動態代理的原理詳解

  • 2019 年 10 月 3 日
  • 筆記

在開始動態代理的原理講解以前,我們先看一下集成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 }

View Code

一、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     }

View Code

這裡的關鍵程式碼 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     @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   }

View Code