zoukankan      html  css  js  c++  java
  • mybatis源码分析(四)---------------代理对象的生成

    mybatis两种开发方式这边文章中,我们提到了Mapper动态代理开发这种方式,现在抛出一个问题:通过sqlSession.getMapper(XXXMapper.class)来获取代理对象的过程是怎样的?生成的代理对象是通过怎样的方式来调用Mapper接口指定的方法的?

    我们根据源码来一步步分析:

    首先进入getMapper方法,通过一步步追踪,我们可以进入到MapperRegistry类中的getMapper方法,现在对整个类做分析:

    /**
     * @author Clinton Begin
     * @author Eduardo Macarron
     * @author Lasse Voss
     */

    // MapperRegistry整个类的作用就是注册Mapper接口和生成Mapper接口的代理对象 public class MapperRegistry { // 配置文件对象 private Configuration config;
    // 这是一个HashMap,key是Mapper接口的类型对象,value是MapperProxyFactory
    // 至于MapperProxyFactory是一个创建Mapper接口代理对象的工厂,后面会介绍
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>(); public MapperRegistry(Configuration config) { this.config = config; }
    // 整个就是生成代理对象的方法 @SuppressWarnings(
    "unchecked") public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 根据Mapper接口的类型对象获取对应的 生成该Mapper接口代理对象的工厂
    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); } } public <T> boolean hasMapper(Class<T> type) { return knownMappers.containsKey(type); }
    // 注册Mapper接口
    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 {
    // 把Mapper接口的类型对象和MapperProxyFactory对象,放到HashMap中 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); } } } } /** * @since 3.2.2 */ public Collection<Class<?>> getMappers() { return Collections.unmodifiableCollection(knownMappers.keySet()); } /** * @since 3.2.2 */ public void addMappers(String packageName, Class<?> superType) { ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); resolverUtil.find(new ResolverUtil.IsA(superType), packageName); Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses(); for (Class<?> mapperClass : mapperSet) { addMapper(mapperClass); } } /** * @since 3.2.2
    * 通过包名扫描其下的所有Mapper接口
    */ public void addMappers(String packageName) { addMappers(packageName, Object.class); }

    然后进入 mapperProxyFactory.newInstance(sqlSession);这个方法,

    // 该类就是用来生成Mapper接口的代理对象的工厂
    public
    class MapperProxyFactory<T> { // Mapper接口的class对象 private final Class<T> mapperInterface;
    // key是Method方法对象,MapperMethod是对Mapper接口中方法的封装,这个MapperMethod值得探究,后面会有讲解
    private 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) {
    //通过JDK动态代理的方式生成了Mapper接口的代理对象,所以当代理对象调用Mapper接口的方法时,会触发mapperProxy对象中的invoke方法,下面对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); } }

    贴出MapperProxy类的源码:

    // 该类实现了InvocationHandler接口
    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; }
    // 代理对象调用Mapper接口中的方法时,会触发该方法
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { try { return method.invoke(this, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } }
    // 通过方法名找到MapperMethod,MapperMethod是对方法及其他参数的封装,稍后会提到
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 此处是对SqlSession调用的封装,在另外一篇文章中,我们提到了Mapper动态代理的方法底层还是通过SQLSession来和数据库交互的,在这里就能看出来了(核心代码就在这个方法中)
    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; } }

    进入上面提到的方法,这里只贴出了MapperMethod类的属性和部分方法:

    public class MapperMethod {
      // SqlCommand是对方法的全名(方法的全名 = 包名+类名+方法名(也就是Mapper.xml中的id))和操作类型(Mapper.xml中的insert,select,update等)的封装
      private final SqlCommand command;
    // MethodSignature 封装了方法的参数,返回类型等信息
    private final MethodSignature method; public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { this.command = new SqlCommand(config, mapperInterface, method); this.method = new MethodSignature(config, method); } // 核心逻辑在这里 public Object execute(SqlSession sqlSession, Object[] args) { Object result; if (SqlCommandType.INSERT == command.getType()) {
    // 对参数做处理 Object param
    = method.convertArgsToSqlCommandParam(args);
    // 通过sqlSession.insert(方法全名,param)这种方式来和数据库交互,并对返回的结果做处理,以下都是类似的情况:update,delete,select 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));
    // 这个是查询 sqlSession.select().... }
    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动态代理的方式使用mybatis,其实就是利用了JDK的动态代理特性来生成对象,然后通过反射的方式来调用方法,最终底层还是通过mybatis提供的API sqlSession来和数据库进行交互的。

    最后,再对SqlCommand 和 MethodSignature做一个简单的分析:

    SqlCommand类有两个属性:name和type分别代表什么呢?通过例子来理解 

     public static class SqlCommand {
    
        private final String name;  // com.yht.mybatisTest.dao.GoodsDao.selectGoodsById  方法的全名
        private final SqlCommandType type; //SELECT  Mapper.xml文件中节点的类型:SELECT,INSERT,DELETE或者UPDATE
    }

    MethodSignature类的属性如下:

    public static class MethodSignature {
    
        private final boolean returnsMany; //是否返回多条数据
        private final boolean returnsMap; //是否返回Map数据类型
        private final boolean returnsVoid; // 是否返回void
        private final Class<?> returnType; // 具体的返回的数据对象类型,如:com.yht.mybatisTest.entity.Goods
        private final String mapKey;
        private final Integer resultHandlerIndex;
        private final Integer rowBoundsIndex;
        private final SortedMap<Integer, String> params; // 参数
        private final boolean hasNamedParameters;
    }

    到此,就结束了。

  • 相关阅读:
    JSP九大内置对象及四个作用域
    JSP九大隐式对象
    8-5接口测试用例设计与编写-3发送消息与图片
    8-5接口测试用例设计与编写2 rest-assured
    8-5接口测试用例设计与编写1
    7-29接口测试入门
    校园商铺-4店铺注册功能模块-11店铺类别区域信息的获取
    校园商铺-4店铺注册功能模块-10店铺注册之js实现
    校园商铺-4店铺注册功能模块-9店铺注册之前端设计
    校园商铺-4店铺注册功能模块-8店铺注册之Controller层的改造
  • 原文地址:https://www.cnblogs.com/51life/p/9530746.html
Copyright © 2011-2022 走看看