Mybatis-Spring源码分析(二) Mapper接口代理的生成

前言

上一篇博客【Mybatis-Spring源码分析(一) MapperScan】主要说了Mybatis的注解MapperScan是怎么把Mapper接口转换为一个MapperFactoryBean的。本篇则会侧重讲解一个MapperFactoryBean是怎么被动态代理并执行SQL语句的。更多Spring内容进入【Spring解读系列目录】

MapperFactoryBean生成代理对象

上一篇说过MapperFactoryBean实现了FactoryBean,因此当调用到相关类的时候执行返回的是getObject()方法里面的内容,因此我们去看下这个方法:

@Override
public T getObject() throws Exception {
    
    
  return getSqlSession().getMapper(this.mapperInterface);
}

方法里面有getSqlSession()SqlSession是Mybatis里面一个很重要的概念,原生的Mybatis中使用的是DefaultSqlSession,而在Mybatis-Spring中使用的是另一个封装SqlSessionTemplate。也正是这样一个代理导致了Mybatis一级缓存失效,当然这是后话,我们以后再说,关于Mybatis和Spring缓存的内容可以参考【Mybatis在Spring中鸡肋的一级缓存和二级缓存】。既然知道是哪个类了,就直接去SqlSessionTemplate#getMapper()里:

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

进入后发现是个空壳方法接着进入Configuration#getMapper()

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

继续进入MapperRegistry#getMapper()

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

发现这里拿到了mapperProxyFactory对象,然后调用newInstance(sqlSession)产生了这个对象,继续进入MapperProxyFactory#newInstance()

public T newInstance(SqlSession sqlSession) {
    
    
  final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  //进入newInstance(mapperProxy)到下方的代码块
  return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
    
    
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] {
    
     mapperInterface }, mapperProxy);
}

到了这里就看的十分的清楚了,Proxy.newProxyInstance()创建了一个代理,其代理的mapperInterface就是传入进来的Mapper接口,并且使用MapperProxy类作为InvocationHandler处理的具体的逻辑。相关Spring知识点参考【从山寨Spring中学习Spring 动态加载】。 也就是说到了这里MybatisMapper接口产生了一个动态代理,并且用MapperProxy处理相关逻辑。

MapperProxy

既然是作为InvocationHandler传入的,那么其执行逻辑一定就是在MapperProxy#invoke()方法里面:

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
  try {
    
     //判断是不是当前对象,肯定不是,走到else
    if (Object.class.equals(method.getDeclaringClass())) {
    
    
      return method.invoke(this, args);
    } else {
    
    
	//记住这个.invoke(proxy, method, args, sqlSession)后面还会说到
      return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
    }
  } catch (Throwable t) {
    
    
    throw ExceptionUtil.unwrapThrowable(t);
  }
}

走到这里会走到else逻辑块中,所以cachedInvoker(method).invoke(proxy, method, args, sqlSession);又是啥呢?这是一个方法,最终返回的是MapperMethodInvoker,并且调用它的invoke()方法去执行Sql语句,进入里面:

private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    
    
  try {
    
    
    MapperMethodInvoker invoker = methodCache.get(method);
    if (invoker != null) {
    
    
      return invoker;
    }
    //需要确定m是谁
    return methodCache.computeIfAbsent(method, m -> {
    
    
      //这里m肯定不是java或者Spring框架自带的,因为这里我们关注的是自己的UserMapper等等接口
      //直接去else里面
      if (m.isDefault()) {
    
     
        try {
    
    
          if (privateLookupInMethod == null) {
    
    
            return new DefaultMethodInvoker(getMethodHandleJava8(method));
          } else {
    
    
            return new DefaultMethodInvoker(getMethodHandleJava9(method));
          }
        } catch (IllegalAccessException | InstantiationException | InvocationTargetException
            | NoSuchMethodException e) {
    
    
          throw new RuntimeException(e);
        }
      } else {
    
     //到这里
        return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
      }
    });
  } catch (RuntimeException re) {
    
    
    Throwable cause = re.getCause();
    throw cause == null ? re : cause;
  }
}

看起来很复杂,但是抓住重点methodCache,这是类声明的一个Map <Method, MapperMethodInvoker> methodCache。它的value必须是一个MapperMethodInvoker类型,问题就在于m是谁。首先m一定不是default的,因为我们外部写的Mapper接口,比如UserMapperCityMapper这些接口肯定不是java自带的。因此直接跳else里面到了new PlainMethodInvoker(),点击进入这个类PlainMethodInvoker

private static class PlainMethodInvoker implements MapperMethodInvoker {
    
    
  private final MapperMethod mapperMethod;
  public PlainMethodInvoker(MapperMethod mapperMethod) {
    
    
    super();
    this.mapperMethod = mapperMethod;
  }
  @Override
  public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
    
    
    //执行sql语句
    return mapperMethod.execute(sqlSession, args);
  }
}

果然这个类就是实现了MapperMethodInvoker,并且还记得上面这个方法后面调用的invoke()吗?就是在这个类里实现的,使用mapperMethod.execute(sqlSession, args);执行的sql方法。

MapperMethod

我们看到源码里PlainMethodInvoker里面构建的MapperMethod,并且使用它的对象调用了invoke()方法执行了sql语句,那么MapperMethod是什么呢?其实MapperMethod和Spring中的BeanDefinition类似,就是在描述Mapper接口中的方法。之所以它能够执行就是因为它能获取到一个方法的所有信息,比如返回信息,比如注解的内容等等。其实我们看下构建的时候传递的什么东西进来new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())。第一个参数就是当前的接口mapperInterface,第二个参数就是当前要执行的方法method,最后的参数就是当前的SqlSessionTemplate。程序外部调用哪个方法,这里就会invoke哪个方法。比如外部调用CityMapper里面的list()method就是list();如果调用update()method就是update()。而mapperInterface就是这个CityMapper

public interface CityMapper {
    
    
    @Select("select * from city")
    public List<Map<String,Object>> list();
    @Update("update city set name='AAAA' where id=1")
    public int update();
}

总结

自此Mybatis生成代理对象,并执行Sql的流程就走完了,至于Sql是怎么执行的,也就是说mapperMethod.execute(sqlSession, args)里面的内容是怎么走的,是下一篇【Mybatis-Spring源码分析(三) 执行SQL导致的血案】的内容。

猜你喜欢

转载自blog.csdn.net/Smallc0de/article/details/109079806