mybatis源代码分析之binding包

在使用ibatis执行数据库访问时,会调用形如

getSqlMapClientTemplate().queryForObject("getCityByCityId", cityId);

这样的代码。这样的形式要求调用方选择需要使用的函数(queryForObject、queryForList、update),还需要告诉这个函数具体执行哪一个statement(上文中是“getCityByCityId”),在这个过程中如果有一个地方选择错误或者拼写错误,不仅没有办法达到自己的期望值,可能还会出现异常,并且这种错误只有在运行时才能够发现。

mybatis对此进行了改进,只要先声明一个接口,就可以利用IDE的自动完成功能帮助选择对应的函数,简化开发的同时增加了代码的安全性:

SqlSession session = sqlSessionFactory.openSession();
try {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  Blog blog = 
mapper.selectBlog(101
);
} finally {
  session.close();
}

这个功能就是在利用java的动态代理在binding包中实现的。对动态代理不熟悉的读者可以查看(http://www.cnblogs.com/jqyp/archive/2010/08/20/1805041.html)。

一、binding包整体介绍

这个包中包含有四个类:

  1. BindingException 该包中的异常类
  2. MapperProxy   实现了InvocationHandler接口的动态代理类
  3. MapperMethod   代理类中真正执行数据库操作的类
  4. MapperRegistry   mybatis中mapper的注册类及对外提供生成代理类接口的类

二、MapperMethod

MapperProxy关联到了这个类,MapperRegistry又关联到了MapperProxy,因而这个类是这个包中的基础类,我们首先介绍这个类的主要功能以及该类主要解决了那些问题。

这个类包含了许多属性和多个函数,我们首先来了解下它的构造函数。

//declaringInterface  已定义的mapper接口
  //method 接口中具体的一个函数
  //sqlSession 已打开的一个sqlSession
  public MapperMethod(Class<?> declaringInterface, Method method, SqlSession sqlSession) {
    paramNames = new ArrayList<String>();
    paramPositions = new ArrayList<Integer>();
    this.sqlSession = sqlSession;
    this.method = method;
    this.config = sqlSession.getConfiguration();//当前的配置
    this.hasNamedParameters = false;
    this.declaringInterface = declaringInterface;
    setupFields();//确定这个方法在mapper中的全配置名:declaringInterface.getName() + "." + method.getName();
    setupMethodSignature();//下文进行详细介绍
    setupCommandType();//设置命令类型,就是确定这个method是执行的那类操作:insert、delete、update、select
    validateStatement();
  }

在构造函数中进行了基本属性的设置和验证,这里面稍微复杂的操作是setupMethodSignature,我们来看其具体的功能:

//在具体实现时利用了Java的反射机制去获取method的各项属性
private void setupMethodSignature() {
    //首先判断方法的返回类型,这里主要判断三种:是否有返回值、返回类型是list还是map
    if( method.getReturnType().equals(Void.TYPE)){
      returnsVoid = true;
    }
    //isAssignableFrom用来判定两个类是否存在父子关系;instanceof用来判断一个对象是不是一个类的实例
    if (List.class.isAssignableFrom(method.getReturnType())) {
      returnsList = true;
    }
    if (Map.class.isAssignableFrom(method.getReturnType())) { 
      //如果返回类型是map类型的,查看该method是否有MapKey注解。如果有这个注解,将这个注解的值作为map的key
      final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
      if (mapKeyAnnotation != null) {
        mapKey = mapKeyAnnotation.value();
        returnsMap = true;
      }
    }
    
    //确定函数的参数
    final Class<?>[] argTypes = method.getParameterTypes();
    for (int i = 0; i < argTypes.length; i++) {
      //是否有为RowBounds类型的参数,如果有设置第几个参数为这种类型的  
      if (RowBounds.class.isAssignableFrom(argTypes[i])) {
        if (rowBoundsIndex == null) {
          rowBoundsIndex = i;
        } else {
          throw new BindingException(method.getName() + " cannot have multiple RowBounds parameters");
        }
      } else if (ResultHandler.class.isAssignableFrom(argTypes[i])) {//是否有为ResultHandler类型的参数,如果有设置第几个参数为这种类型的  
        if (resultHandlerIndex == null) {
          resultHandlerIndex = i;
        } else {
          throw new BindingException(method.getName() + " cannot have multiple ResultHandler parameters");
        }
      } else {
        String paramName = String.valueOf(paramPositions.size());
        //如果有Param注解,修改参数名;如果没有,参数名就是其位置
        paramName = getParamNameFromAnnotation(i, paramName);
        paramNames.add(paramName);
        paramPositions.add(i);
      }
    }
  }

前面介绍了MapperMethod类初始化相关的源代码,在初始化后就是向外提供的函数了,这个类向外提供服务主要是通过如下的函数进行:

public Object execute(Object[] args) {
    Object result = null;
    //根据初始化时确定的命令类型,选择对应的操作
    if (SqlCommandType.INSERT == type) {
      Object param = getParam(args);
      result = sqlSession.insert(commandName, param);
    } else if (SqlCommandType.UPDATE == type) {
      Object param = getParam(args);
      result = sqlSession.update(commandName, param);
    } else if (SqlCommandType.DELETE == type) {
      Object param = getParam(args);
      result = sqlSession.delete(commandName, param);
    } else if (SqlCommandType.SELECT == type) {//相比较而言,查询稍微有些复杂,不同的返回结果类型有不同的处理方法
      if (returnsVoid && resultHandlerIndex != null) {
        executeWithResultHandler(args);
      } else if (returnsList) {
        result = executeForList(args);
      } else if (returnsMap) {
        result = executeForMap(args);
      } else {
        Object param = getParam(args);
        result = sqlSession.selectOne(commandName, param);
      }
    } else {
      throw new BindingException("Unknown execution method for: " + commandName);
    }
    return result;
  }

这个函数整体上还是调用sqlSession的各个函数进行相应的操作,在执行的过程中用到了初始化时的各个参数。

三、MapperProxy

这个类继承了InvocationHandler接口,我们主要看这个类中的两个方法。一是生成具体代理类的函数newMapperProxy,另一个就是实现InvocationHandler接口的invoke。我们先看newMapperProxy方法。

public static <T> T newMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) {
    //先初始化生成代理类所需的参数
    ClassLoader classLoader = mapperInterface.getClassLoader();
    Class<?>[] interfaces = new Class[]{mapperInterface};
    MapperProxy proxy = new MapperProxy(sqlSession);//具体要代理的类
    //利用Java的Proxy类生成具体的代理类
    return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy);
  }

我们再来看invoke方法:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //并不是任何一个方法都需要执行调用代理对象进行执行,如果这个方法是Object中通用的方法(toString、hashCode等)无需执行
    if (!OBJECT_METHODS.contains(method.getName())) {
      final Class<?> declaringInterface = findDeclaringInterface(proxy, method);
      //生成MapperMethod对象
      final MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, sqlSession);
      //执行MapperMethod对象的execute方法
      final Object result = mapperMethod.execute(args);
      //对处理结果进行校验
      if (result == null && method.getReturnType().isPrimitive() && !method.getReturnType().equals(Void.TYPE)) {
        throw new BindingException("Mapper method '" + method.getName() + "' (" + method.getDeclaringClass() + ") attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
      }
      return result;
    }
    return null;
  }

四、MapperRegistry

这个类会在Configuration类作为一个属性存在,在Configuration类初始化时进行初始化:

protected MapperRegistry mapperRegistry = new MapperRegistry(this);

从类名就可以知道这个类主要用来mapper的注册,我们就首先来看addMapper函数:

public void addMapper(Class<?> type) {
    //因为Java的动态代理只能实现接口,因而在注册mapper时也只能注册接口
    if (type.isInterface()) {
      //如果已经注册过了,则抛出异常,而不是覆盖
      if (knownMappers.contains(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        //先将这个mapper添加到mybatis中,如果加载过程中出现异常需要再将这个mapper从mybatis中删除
        knownMappers.add(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);
        }
      }
    }
  }

下面我们来看下其向外提供生成代理对象的函数:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //如果不存在这个mapper,则直接抛出异常
    if (!knownMappers.contains(type))
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    try {
      //返回代理类
      return MapperProxy.newMapperProxy(type, sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

五、代理类生成的顺序图

我们在开篇时提到了如下的代码:

SqlSession session = sqlSessionFactory.openSession();
try {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  Blog blog = 
mapper.selectBlog(101
);
} finally {
  session.close();
}

 

image

posted on 2013-05-13 15:39  孙振超  阅读(6069)  评论(1编辑  收藏  举报