mybatis 源码探究:两次JDK动态代理

mybatis中所有Dao接口的实现类都是MapperProxy


问题

问题描述:在使用mybatis时,我们只定义了接口,然后在XxxMapper.xml中写sql,若使用Mapper接口,甚至连XxxMapper.xml都可以省略,这一切究竟是如何实现的呢?


大多数项目都会使用spring+mybatis来搭建,本文参照mybatis-spring(1.2.0)分析上述问题。

将mybatis集成到spring中,添加配置spring-dao.xml

    
        
        
        
        
    
    
    
        
    
    
    
        
        
    

上述配置文件为mybatis的入口,阅读源码首先要找到入口,不能漫无目的地在源码里闲逛。这里需要Spring的一点基础知识(例如FactoryBean的作用)。与Spring的集成,本质是是把自己扮成Spring的bean,然后Spring能感知到你的存在,从而对你进行管理。咱们可以看到,mybatis在这里所有的配置信息,都是在标签里的。
这三个bean中,咱们挨个分析。

  1. id为dataSource的bean,最简单。只是封装了一些数据库的连接(包括配置)信息在里面。
  2. id为sessionFactory的bean,定义了SqlSessionFactoryBean,将dataSource bean作当做了一个参数。暂且不管,继续。
  3. 最后一个bean,可以看到,没有定义id,说明没有人显示地引用它。其实它就是spring-mybatis的入口。参数basePackage 是我们的Dao接口包,用于后面接口扫描使用。后一个参数sqlSessionFactoryBeanName,只是简单地给他赋了值。

咱们现在已经知道spring-mybatis的入口了,开干吧。


image1.png

以下的分析内容,可以认为是在对image1的解释。
进入MapperScannerConfigurer,

BeanDefinitionRegistryPostProcessor, 
InitializingBean, 
ApplicationContextAware, 
BeanNameAware 

发现其实现了这四个接口。又是Spring的知识了,这些都是spring的扩展点,不多做介绍。重点看,BeanDefinitionRegistryPostProcessor。这个接口里只有一个方法postProcessBeanDefinitionRegistry,会在BeanDefinition加载完之后执行。进入该方法:

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
   //ignore unrelated code
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

忽略非重点代码。省略的代码,是在设置属性。重点看保留下来的最后一句代码。这句话的意思是扫描包。不知各位还记得spring-dao.xml中这么一句话:


那么这里的scan(),扫描了com.foo.dao包下面的所有类,即咱们定义的DAO接口。根据scan()方法层层推进,来到了,org.mybatis.spring.mapper.ClassPathMapperScanner#doScan()。这个方法里有三句话比较重要:

① definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
② definition.setBeanClass(MapperFactoryBean.class);
③ definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));

①:将我们自定义的DAO接口作为参数放入BeanDefinition中,属性名为mapperInterface;
②:设置BeanDefinition的BeanClass,MapperFactoryBean。这里对于文章开头“mybatis中所有Dao接口的实现类都是MapperProxy”的这句话,已初见端倪;
③:新增属性名为sqlSessionFactory的属性。

从这里可以看出两点:

  1. 咱们自定义的DAO接口,被新增了两个属性(mapperInterface与sqlSessionFactory),那么在创建bean的时候,会给这两个属性赋值,即调用这两个属性的set方法;
  2. DAO接口BeanDefinition的BeanClass是MapperFactoryBean。并且细心的小伙伴发现,这是一个工程bean(FactoryBean),在创建bean时,是从其getObject()方法获取真正的bean。

BeanDefinition加载完后,开始创建bean。


image2.png
开始image2的分析:

由于我们自定义DAO接口的BeanClasss是MapperFactoryBean,所以创建的是MapperFactoryBean,遂进入MapperFactoryBean源码。
上文咱们提到,会调用sqlSessionFactory的set方法(setSqlSessionFactory)。发现该方法在MapperFactoryBean的父类SqlSessionDaoSupport中,进入:

 public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (!this.externalSqlSession) {
      this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
    }
  }

层层跟进,直至最底层构造方法:

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
  }

此处咱们看到了jdk动态代理(SqlSessionInterceptor),这也是咱们image2中的标注的第一次动态代理。跟进:

private class SqlSessionInterceptor implements InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      final SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
      }
    }

与数据库的操作都是通过SqlSession来完成的。这里咱们看到生成了sqlSession,然后根据具体情况,来判断是否自动提交。
setSqlSessionFactory()方法到这算基本讲完了,新建了一个SqlSessionTemplate,回到MapperFactoryBean。
由于MapperFactoryBean是FactoryBean的实现类,所以会调用其getObject()方法来获取真正的bean。进入该方法:

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

无实际内容,重点内容都在getMapper()里面。by the way,这里的mapperInterface,咱们在image1里有提到,若遗忘,往上翻。由于咱们刚才通过setSqlSessionFactory()方法,新建了一个SqlSessionTemplate,所以这里通过SqlSessionTemplate#getMapper()层层跟进,直至出现明显有特殊意义的代码,到了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.newInstance(sqlSession):

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

这两个方法,其实又是一次动态代理(image2的第二次动态代理)。
回到getMapper(),最后返回了MapperProxy 对象。


image3.png

通过image2的分析,咱们知道,我们自定义的DAO接口,最后被封装成了MapperProxy。当我们在调用DAO接口里面的方法时,实际是调用了MapperProxy#invoke()。

进入MapperProxy的invoke()方法:

 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

这里通过动态代理获取到了MapperMethod方法,然后开始execute。进入:

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    if (SqlCommandType.INSERT == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        result = executeForMap(sqlSession, args);
      } else {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
    } else {
      throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

这一大段代码基本都是比较熟悉的代码。根据SqlCommandType判断是增删改查的哪一个,然后调用SqlSession对应的方法去执行。请记住,这里的SqlSession已经被动态代理过了,实际上是调用了SqlSessionInterceptor#invoke()方法。


As always and let me know what you think, leave the comments below. Bye :)

项目完整源码github地址

你可能感兴趣的:(mybatis 源码探究:两次JDK动态代理)