Mybatis源码解析之MapperProxy

[上一篇]:Mybatis源码解析之配置解析

从菜菜的Mybatis源码解析之Spring获取Mapper过程中知道了Spring与MyBatis如何连接起来的,这篇菜菜将介绍MyBatis的重中之重MapperProxy

问题

为什么MyBatis只用写接口不用写实现就可以运行起来?

思考

既然Spring与MyBatis连接起来了,那么通过MyBatis中的这些Mapper接口注入的类都是由Spring进行管理的,所以需要知道Spring中这些Mapper接口对应的Bean到底是什么。

揭秘答案-源码

Mybatis源码解析之Spring获取Mapper过程这篇里说了设置了这些Mapper接口的class对象或是类的全限定名为MapperFactoryBean ,这里再贴出源码

 // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
   definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
   definition.setBeanClass(this.mapperFactoryBean.getClass());

下面需要看下MapperFactoryBean的源码

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  private Class<T> mapperInterface;

  private boolean addToConfig = true;

  public MapperFactoryBean() {
    //intentionally empty 
  }
  
  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }
  @Override
  protected void checkDaoConfig() {
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

 //主要看这个方法
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }
  @Override
  public Class<T> getObjectType() {
    return this.mapperInterface;
  }

  @Override
  public boolean isSingleton() {
    return true;
  }
  public void setMapperInterface(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }
  public Class<T> getMapperInterface() {
    return mapperInterface;
  }
  public void setAddToConfig(boolean addToConfig) {
    this.addToConfig = addToConfig;
  }
  public boolean isAddToConfig() {
    return addToConfig;
  }
}

从MapperFactoryBean源码中看出,它继承了SqlSessionDaoSupport并实现了Spring中的FactoryBean,SqlSessionDaoSupport是一个抽象的支持类,用来为你提供SqlSession,将在Mybatis源码解析之SqlSession来自何方
中讲解。因为实现了FactoryBean接口所以MapperFactoryBean是一个FactoryBean。

我们知道Spring实例化一个类有两个时机,一个是在初始化的时候getBean实例化,第二个是对设置了lazy-init属性的在第一次向Spring索要Bean的时候getBean实例化,最终都是调用Spring中的getBean方法

MyBatis中设置Mapper接口的class对象为MapperFactoryBean,而MapperFactoryBean又是一个FactoryBean所以在实例化调用getBean最终会调用MapperFactoryBean中的getObject()方法,将改方法返回的对象作为实例化Bean,我们先看下getObeject的调用链(初始化是从refresh中的finishBeanFactoryInitialization开始进入preInstantiateSingletons再开始的,需要一层一层看),再看getObject()的方法

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

这里的getSqlSession()会得到SqlSessionTemplate,为什么是SqlSessionTemplate,详见Mybatis源码解析之SqlSession来自何方

//SqlSessionDaoSupport里的方法,返回的是SqlSessionTemplate
 public SqlSession getSqlSession() {
    return this.sqlSession;
  }

查看SqlSessionTemplate中的getMapper源码,是从Configuration中getMapper

//SqlSessionTemplate中部分方法
 @Override
  public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
  }
@Override
  public Configuration getConfiguration() {
    return this.sqlSessionFactory.getConfiguration();
  }

Configuration中获取Mapper源码,又是从mapperRegistry获取的,mapperRegistry相当于mapper的注册中心,这里能获取到所有的mapper信息

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

MapperRegistry中getMapper,看到这里答案快浮出水面了,通过MapperProxy的工厂MapperProxyFactory创建代理对象

//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的具体实现源码

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

答案至此终于揭开,最终在Spring中这些接口对应的对象为JDK动态代理生成的Mapper代理类MapperProxy。我们在程序中拿到的Mapper对象也是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 {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    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;
  }
}

在我们利用Mapper进行数据库的相关操作时会调用MapperProxy的invoke()方法,该方法先判断接口方法是否有实现类,如果有,调用实现类的方法,如果没有(MyBatis的Mapper没有实现类)就调用MapperMethod的execute方法
通过看MapperMethod源码发现最终还是调用sqlSession中的相关方法,sqlSession再委托给Excutor去执行,关于Excutor菜菜还没写相关文章,请自行搜索

如有错误,欢迎各位大佬斧正!
[下一篇]:Mybatis源码解析之SqlSession来自何方

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,423评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,339评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,241评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,503评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,824评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,262评论 1 207
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,615评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,337评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,989评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,300评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,829评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,193评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,753评论 3 230
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,970评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,708评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,295评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,207评论 2 258

推荐阅读更多精彩内容