Mapper接口的注册流程


介绍

Mapper接口用于定义执行SQL语句相关的方法,方法名一般和Mapper XML配置文件中<select|update|delete|insert>标签的id属性相同,接口的完全限定名一般对应Mapper XML配置文件的命名空间。


事例

在介绍Mapper接口之前,我们先来回顾一下如何执行Mapper中定义的方法,可参考下面的代码:

 @Test
    public  void testMybatis () throws IOException {
        // 获取配置文件输入流
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        // 通过SqlSessionFactoryBuilder的build()方法创建SqlSessionFactory实例
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 调用openSession()方法创建SqlSession实例
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 获取UserMapper代理对象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        // 执行Mapper方法,获取执行结果
        List<UserEntity> userList = userMapper.listAllUser();
        System.out.println(JSON.toJSONString(userList));
    }

如上面的代码所示,在创建SqlSession实例后,需要调用SqlSession的getMapper()方法获取一个UserMapper的引用,然后通过该引用调用Mapper接口中定义的方法。

注册流程

MapperProxy类

我们知道,接口中定义的方法必须通过某个类实现该接口,然后创建该类的实例,才能通过实例调用方法。所以SqlSession对象的getMapper()方法返回的一定是某个类的实例。具体是哪个类的实例呢?实际上getMapper()方法返回的是一个动态代理对象。

MyBatis中通过MapperProxy类实现动态代理。下面是MapperProxy类的关键代码:

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 {
      // 从Object类继承的方法不做处理
      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);
    }
    // 对Mapper接口中定义的方法进行封装,生成MapperMethod对象
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 执行方法
    return mapperMethod.execute(sqlSession, args);
  }
......
}

MapperProxy使用的是JDK内置的动态代理,实现了InvocationHandler接口,invoke()方法中为通用的拦截逻辑,具体内容在介绍Mapper方法调用过程时再做介绍。

MapperProxyFactory类

使用JDK内置动态代理,通过MapperProxy类实现InvocationHandler接口,定义方法执行拦截逻辑后,还需要调用java.lang.reflect.Proxy类的newProxyInstance()方法创建代理对象。

MyBatis对这一过程做了封装,使用MapperProxyFactory创建Mapper动态代理对象。MapperProxyFactory代码如下:

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

}

如上面的代码所示,MapperProxyFactory类的工厂方法newInstance()是非静态的。也就是说,使用MapperProxyFactory创建Mapper动态代理对象首先需要创建MapperProxyFactory实例。MapperProxyFactory实例是什么时候创建的呢?

MapperRegistry类

Configuration对象中有一个mapperRegistry属性,具体如下:

protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

MyBatis通过mapperRegistry属性注册Mapper接口与MapperProxyFactory对象之间的对应关系。下面是MapperRegistry类的关键代码:

public class MapperRegistry {
  // Configuration对象引用
  private final Configuration config;
  // 用于注册Mapper接口Class对象,和MapperProxyFactory对象对应关系
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }
  // 根据Mapper接口Class对象获取Mapper动态代理对象
  @SuppressWarnings("unchecked")
  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);
    }
  }
  
  public <T> boolean hasMapper(Class<T> type) {
    return knownMappers.containsKey(type);
  }
  // 根据Mapper接口Class对象,创建MapperProxyFactory对象,并注册到knownMappers属性中
  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);
        }
      }
    }
  }

......
  
}

如上面的代码所示,MapperRegistry类有一个knownMappers属性,用于注册Mapper接口对应的Class对象和MapperProxyFactory对象之间的关系。另外,MapperRegistry提供了addMapper()方法,用于向knownMappers属性中注册Mapper接口信息。在addMapper()方法中,为每个Mapper接口对应的Class对象创建一个MapperProxyFactory对象,然后添加到knownMappers属性中。

MapperRegistry还提供了getMapper()方法,能够根据Mapper接口的Class对象获取对应的MapperProxyFactory对象,然后就可以使用MapperProxyFactory对象创建Mapper动态代理对象了。

MyBatis框架在应用启动时会解析所有的Mapper接口,然后调用MapperRegistry对象的addMapper()方法将Mapper接口信息和对应的MapperProxyFactory对象注册到MapperRegistry对象中。

模拟手写一个代理流程

1.创建一个Mapper接口

我们首先创建一个Mapper接口,用来模拟

public interface DevMapper {
    //表示获取设备信息
    DevPo getDev(int id);
}

2.代理接口

这个类可以看做MapperProxy,就是实现对接口的动态代理

public class MapperProxy implements InvocationHandler {


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("参数为" + args);
        //这里看做Mybatis一顿解析最后成功查询出数据并返回
        DevPo devPo = new DevPo();
        devPo.setId(5);
        devPo.setName("zhixuchen");
        devPo.setNo("sadas");
        return devPo;
    }
}

3.创建代理类

这个类可以看做MapperProxyFactory,生成动态代理类

public class Test {
    public static void main(String[] args) throws ClassNotFoundException {
        DevMapper devMapper = (DevMapper) Proxy.newProxyInstance(
                DevMapper.class.getClassLoader(),
                new Class[]{Resources.classForName(DevMapper.class.getName())},
                new MapperProxy()
        );
        DevPo dev = devMapper.getDev(5);
        System.out.println(dev.toString());
    }
}
输出结果:
参数为[Ljava.lang.Object;@20ad9418
DevPo{id=5, name='zhixuchen', no='sadas'}

学习记录,不断沉淀,终究会成为一个优秀的程序员,加油!
您的点赞、关注与收藏是我分享博客的最大赞赏!
博主博客地址: https://blog.csdn.net/qq_45701514

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhixuChen200

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值