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 extends SqlSessionDaoSupport implements FactoryBean {

  private Class mapperInterface;

  private boolean addToConfig = true;

  public MapperFactoryBean() {
    //intentionally empty 
  }
  
  public MapperFactoryBean(Class 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 getObjectType() {
    return this.mapperInterface;
  }

  @Override
  public boolean isSingleton() {
    return true;
  }
  public void setMapperInterface(Class mapperInterface) {
    this.mapperInterface = mapperInterface;
  }
  public Class 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()的方法

Mybatis源码解析之MapperProxy_第1张图片
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 getMapper(Class type) {
    return getConfiguration().getMapper(type, this);
  }
@Override
  public Configuration getConfiguration() {
    return this.sqlSessionFactory.getConfiguration();
  }

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

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

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

//MapperRegistry 中的getMapper
 public  T getMapper(Class type, SqlSession sqlSession) {
    final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) 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 {

  private final Class mapperInterface;
  private final Map methodCache = new ConcurrentHashMap();

  public MapperProxyFactory(Class mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class getMapperInterface() {
    return mapperInterface;
  }

  public Map getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

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

答案至此终于揭开,最终在Spring中这些接口对应的对象为JDK动态代理生成的Mapper代理类MapperProxy。我们在程序中拿到的Mapper对象也是MapperProxy。我们继续看下MapperProxy的源代码

public class MapperProxy implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class mapperInterface;
  private final Map methodCache;

  public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map 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来自何方

你可能感兴趣的:(Mybatis源码解析之MapperProxy)