Mybatis源码(三)

三、获取Mapper对象

在老的版本中,DefaultSqlSession的selectOne()方法可以直接根据Mapper.xml中的StatementID,找到SQL执行。但是这种方法属于硬编码,不以查找和修改。

并且如果是参数传入错误,在编译阶段也是不会报错的,不利于提前发现问题。

User user = (User)session.selectOne("com.xx.mapper.UserMapper.selectUserById", 1);

在Mybatis后期的版本中提供了第二种调用方式,就是定义一个接口,然后再调用Mapper接口的方法。

我们定义的接口名称和Mapper.xml的namespace是对应的,接口的方法跟statementID也都是相对应的,所以根据方法就能找到对应的要执行的SQL。

UserMapper mapper = session.getMapper(UserMapper.class);

QA:

1.getMapper获得的是一个什么对象?为什么可以执行它的方法?

2.到底是怎么根据Mapper找到XML的SQL执行的?
1、getMapper()

DefaultSqlSession的getMapper()方法,调用了Configuration的getMapper方法。

 public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }

Configuration的getMapper()方法,又调用了MapperRegistry的getMapper()方法。

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

在解析mapper标签和Mapper.xml的时候已经把接口类型和类型对应的MapperProxyFactory放到一个Map中。获取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);
    }
  }

在newInstance()方法中,先创建MapperProxy。

MapperProxy实现了InvocationHandler接口,主要属性有三个sqlSession、mapperInterface、methodCache

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

最终通过JDK动态代理模式创建、返回代理对象:

  protected T newInstance(MapperProxy<T> mapperProxy) {
    // 1:类加载器:2:被代理类实现的接口、3:实现了 InvocationHandler 的触发管理类
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

也就是说,getMapper()返回的是一个JDK动态代理对象(类型是$Proxy数字)。这个代理对象会继承Proxy类,实现被代理的接口,里面持有一个MapperProxy类型的触发管理类。

QA:为什么要在MapperRegistry中保存一个工厂类?

创建返回代理类,这是一个非常经典的应用。

QA:为什么要直接代理一个接口呢?

2、MapperProxy如何实现对接口的代理

我们知道,JDK的动态代理,有三个核心角色:被代理类(实现类)、接口、实现了InvocationHandler的触发管理类,用来生成代理对象。

被代理类必须实现接口,因为要通过接口获取方法,而且代理也要实现这个接口。

但是Mybatis里面的Mapper没有实现类,怎么被代理?他忽略了实现类,直接对接口进行代理。

Mybatis的动态代理:

那么Mybatis里面,动态代理为什么不需要实现类?

我们这里的目的是根据一个可以执行的方法,直接找到Mapper.xml中的StatementID,方便调用。

根据接口类型+方法的名称找到StatementID这个逻辑在Handler类(MapperProxy)中就可以完成,所以这里也就不需要实现类了。

总结

获得Mapper对象的过程,实质上是获得了一个JDK动态代理对象(类型是$Proxy数字)。这个代理类会继承Proxy类,实现被代理的接口,里面持有了一个MapperProxy类型的触发管理类。

获取代理对象

image

posted @ 2020-07-06 14:38  snail灬  阅读(148)  评论(0编辑  收藏  举报