Mybatis源码分析系列之第四篇:Mybatis中代理设计模型源码详解

一: 前言

我们尝试在前几篇文章的内容中串联起来,防止各位不知所云。

1:背景

我们基于Mybatis作为后台Orm框架进行编码的时候,有两种方式。

//编码方式1
UserDao userDao = sqlSession.getMapper(UserDao.class);
userDao.queryAllUser(Map map);
//有同学质疑为毛我从来没有sqlSesseion.getMapper(..)是因为我们使用Spring或者
//boot的时候右边给你封装了,导致你直接在Service里边把UserDao进行了注入,直接
//=右边的,你可能压根就没有机会写。

//编码方式2
sqlSession.select("com.shit.user.UserDao.queryAllUser",Map map);

我们提到了,最为底层的是下边这种方式,第一种方式是基于动态代理的技术实现的,底层也是第二种方式,基于此我们开始探索了动态代理的这种方式,整套流程下来是怎么玩的。

UserDao userDao = SqlSession.getMapper(User.class)
userDao.queryuserById();
userDao.queryUsers();

2:代理设计模式(动态代理)的一些特点(简述)

这里的userDao是DAO接口的实现类的对象,那么我们就会有一个疑问,UserDao这个接口实现类在哪里呢?
这个Dao接口的实现类我们是见不到的,因为这里是一个典型的动态字节码技术,这个类是在虚拟机当中运行时创建的。虚拟机运行结束的时候,这个类就消失了

这和我们之前的这些类不一样,我们之前写的这些类就是实实在在的文件,在我们的硬盘当中,通过JVM的类加载的方式读到虚拟机的内存当中。这是我们传统意义上的一个类的创建的方式,通过复杂的类加载机制将这个类加载到JVM虚拟机当中之后,我们在这个类的Class对象创建出来。

动态字节码技术的特点是类的文件自始至终就没有,直接就在虚拟机当中的内存当中去创建,进而创建这个类的Class对象。是JVM在运行时的时候基于动态字节码技术将这个类创建出来的操作。

这就是为什么我们知道有这么一个类的存在,有这么一个类的对象的存在,但是我们就看不到的原因。

创建完成之后,就按照多态的特点,代理对象地址保存在这个接口的引用类型下边。按照多态的原则,所以,我们才可以有这个效果。

3:使用代理设计模式场景

1):为原始对象(目标)增加额外功能

ps:如果是和原始功能没有半毛钱关系的内容才叫做额外功能,与原始功能有关系的功能就不太算是额外功能了,这种情况下推进使用装饰器模式。
这个场景典型的Spring的AOP的功能。

2):远程代理(远程代理)

ps:远程代理:这时候额外功能就变成1:网络连接:2:数据传输嘛。这个最典型的就是Dubbo,远程代理代理的是远程服务。

3):无中生有(只有接口,除了接口毛都没有)

ps:这个最符合的就是Mybatis的Dao层的实现类的处理了。接口实现类这个东西,我们真的看不见,但是运行时确实可以体现出来。

在这三个场景下,我们需要考虑用到代理设计模式,第三种是典型的动态代理。而动态代理的典型的编码是:

        Proxy.newProxyInstance(AssistantToHistoryPriceRule.class.getClassLoader(),new Class[]{BillSystemConfigGateWay.class}, new InvocationHandler(){
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("额外功能输出......");
                return null;
            }
        });

二:实现类该如何去实现预定的功能呢?

1:猜想Mybatis的代理流程

我们现在有一个Ineterface接口中有这样的一个方法,实现类实现之后应该大致张这个样子:
以下这个是我们的大致流程猜想,当然最终结果肯定是与Mybatis的具体实现大差不差的。

interface  UserDao{
	List<User> queryAllUsers();
	save(User user)
}

UserDaoImpl implements UserDAO {
	queryAllUsers(){
		sqlSession.select(....);
	}
	save(){
		sqlSession.insert("namespace.id",参数);
	}
}

Mybatis源码分析系列之第四篇:Mybatis中代理设计模型源码详解_第1张图片
类加载器,接口,额外功能处理Handler三个条件满足之后,运行时会把这个代理对象给创建出来。

2:模拟Mybatis代理对象创建

    @Test
    public void testProxy() throws IOException {
       InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession sqlSession = sqlSessionFactory.openSession();

        Class[] interfaces = new Class[]{UserDAO.class};

        UserDAO userDAO = (UserDAO) Proxy.newProxyInstance(TestMybatis.class.getClassLoader(),interfaces
                                                 ,new MyMapperProxy(sqlSession,UserDAO.class));

        List<User> users = userDAO.queryAllUsersByPage();

        for (User user : users) {
            System.out.println("user = " + user);
        }

    }
public class MyMapperProxy implements InvocationHandler {

    private SqlSession sqlSession;
    private Class daoClass;

    public MyMapperProxy(SqlSession sqlSession, Class daoClass) {
        this.sqlSession = sqlSession;
        this.daoClass = daoClass;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(daoClass.getName() + "." + method.getName());
        return sqlSession.selectList(daoClass.getName() + "." + method.getName());
    }
}

这一步的创建代理的操作就等同于我们之前当中的SqlSession.getMapper(UserDao.class)的操作。
Mybatis源码分析系列之第四篇:Mybatis中代理设计模型源码详解_第2张图片

三:Mybatis代理对象创建的源码如下

Mybatis当中完成代理创建的核心类型是:MapperProxy和MapperProcyFactory

MapperProcyFactory:是个工厂,他的作用就是创建代理,工厂底层设计的过程中完成的就是Proxy.newProxyInstance(),为我们创建代理对象
MapperProxy:实现了InvocationHandler接口,他就是用调用具体method的。

/**
 * @author Lasse Voss
 */
public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethod> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  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);
  }

}
/**
 * @author Clinton Begin
 * @author Eduardo Macarron
 */
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();
  }
}
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 (!StatementType.CALLABLE.equals(ms.getStatementType())
        && 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;
    }
  }

}

Mybatis源码分析系列之第四篇:Mybatis中代理设计模型源码详解_第3张图片

2:核心对象的拆解

1:MapMethod对象

  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);
  }
}

MapMethod当中需要两个成员变量:SqlCommand+MethodSignature
把Method(Dao接口中的某一个方法的对象,再次包装了一层,包装成了MapMethod对象),增添了SqlCommand+MethodSignature两个成员。

2:SqlCommand对象
包含两个关键成员:

private final String name;
private final SqlCommandType type;

    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);
        }
      }
    }

name是nameSpace+id,type是insert,delete,update,select
3:MethodSignature
方法的入参类型和返回值类型。对SQL入参和SQL执行后的返回值的处理提供类型机制。

到这里我们是不是就有点清晰了,Mybatis中对代理设计模式的应用是这个样子的:
1:这个流程中几个核心的成员

MappedProxyFactory:执行Proxy.newProxyInstance(ClassLoader,UserDao.class,InvocationHandler);
来创建代理对象
MappedProxy:InvocationHandler的实习类,原始功能都没有的这种代理设计模式中的功能模块,
具体进行sqlSession.select,insert,update,delete的模块
MapMethod 是对方法对象的二次封装,我们知道MybatisUserDao中的method,可以提供nameSpace.id嘛,
这就是他的作用。
SqlCommand:对象nameSpace.id基于构造方法,放到了name属性里边,type是method增删改查的类型描述
MethodSignature:对方法入参和方法返参的描述

Mybatis源码分析系列之第四篇:Mybatis中代理设计模型源码详解_第4张图片

你可能感兴趣的:(MyBatis专栏,mybatis,java,jvm)