Mapper代理对象解析

使用MyBatis的Mapper时,只声明了Mapper接口,而没有具体的实现。而使用时却可以通过VersionMapper mapper = session.getMapper(VersionMapper.class);返回具体的对象。准确的说,这个过程中MyBatis通过JDK的动态代理,创建了实现Mapper接口的代理对象,然后通过改变代理对象的执行行为来达到实现Mapper的具体功能。

回顾一下MyBatis启动时,先解析配置的Mapper信息,然后通过MapperRegistry注册。容器是Map<Class<?>, MapperProxyFactory<?>>,注册时,为每个Mapper新建一个MapperProxyFactory对象。

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }
获取Mapper时,取得注册时的Mapper工厂,然后创建一下Mapper的代理对象。

  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);
    }
  }
然后通过动态代理实例化出代理对象。

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
newProxyInstance方法有三个参数,第一个是类加载器,代理对象的class信息将装载到该加载器中。第二个参数是要实现的接口,就是开发时写的Mapper接口。第三个是代理行为类,决定了代理对象执行方法时,要怎么执行。MyBatis就是通过定义mapperProxy来控制mapper的执行行为的。

MapperProxy有三个属性SqlSession sqlSession,提供数据源;Class<T> mapperInterface,用类型来标识Mapper唯一的性;Map<Method, MapperMethod> methodCache方法的缓存,使得调用过的方法,不需要重复去包装。此外,MapperProxy实现了InvocationHandler接口,使得它具有改变代理方法行为的能力。接下来看一下它是如何控制代理对象的执行行为的。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    }
    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;
  }
invoke方法就是调用代理对象方法时,具体执行到的方法。通过判断声明这个方法的类是否是Object。是的话,直接调用Object的方法。如果不是,将从缓存中取,如果没有取到,就新建一个包装方法,然后放到方法缓存中。最后调用包装方法的execute真正执行。

接下来看调用包装方法的execute发生了什么。

 public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    if (SqlCommandType.INSERT == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
      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 {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
    } else {
      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;
  }
在新建包装方法对象的时候,根据Mapper类名作为命名空间,加上方法名称,在全局配置中取到对应的MapperStatement对象。然后判断SQL的类型,转换入参,调用sqlSession对应的API来执行。




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值