mybatis mapper解析(下) @mapperScan

参考

深入剖析 mybatis 原理(三)如何整合Spring
没有他的参考,我不可能做出这篇理解。

@mapperScan注册MapperFactoryBean

7547741-c4cbb884d0f544a9.jpg
@MapperScan.jpg

注册的那些MapperFactoryBean(是一种FactoryBean)会在spring初始化时调用其getObject方法生成具体Bean。(关于FactoryBean,可参考Spring源码学习--FactoryBean实现原理)

MapperFactoryBean初始化

7547741-2231338b1fc40010.jpg
mapperFactory初始化.jpg

但在MapperFactoryBean实例化后,调用getObject之前,由于MapperFactoryBean本身也是个Bean,spring框架会调用setSqlSessionFactory和setSqlSessionTemplate设置其属性。以便之后的getObject使用。

// SqlSessionDaoSupport.java
// MapperFactoryBean继承了SqlSessionDaoSupport

// 设置sqlSessionFactory, 顺便自动设置sqlSessionTemplate。所以如果没设定sqlSessionTemplate也没关系。
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
      this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
    }
}

// 可以手动设置
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
    this.sqlSessionTemplate = sqlSessionTemplate;
}

在之后的getObject中:

  1. getSqlSession就用到了刚才设置的sqlSessionTemplate。
  2. getMapper(this.mapperInterface)深入剖析 mybatis 原理(二)已经分析过了
    ,该方法最终会返回Mapper代理类对象。
// MapperFactoryBean.java
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }
// SqlSessionDaoSupport.java
  public SqlSession getSqlSession() {
    return this.sqlSessionTemplate;
  }

getObject的调用

你可能会问,那这个MapperFactoryBean.getObject何时会调用呢?
还记得@mapperScan注册MapperFactoryBean时的ClassPathMapperScanner.processBeanDefinitions方法吗:

  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      String beanClassName = definition.getBeanClassName();
      LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName()
          + "' and '" + beanClassName + "' mapperInterface");

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

用途有两个:

  1. 这里为Bean注册的名字beanClassName是xx.xx.xxMapper,但实际的Bean是MapperFactoryBean。
    在springboot初始化的过程中,会对每个Bean调用getBean进行实例化,因此MapperFactoryBean.getObject也会被调用。
  2. Service初始化时,spring会处理@Autowire中的Mapper,根据其类型的名字xx.xx.xxMapper找Bean,然后调用beanFactory.getBean(beanName);。但根据这个名字找到的其实是刚才注册的MapperFactoryBean。由于它是FactoryBean,对其调用getBean只会触发getObject,从而返回代理类对象。
    要验证@Autowired在Mapper上的作用,请自行调试,可参考后文。

调试@autowired的Mapper的注入

  1. AutowiredAnnotationBeanPostProcessor.inject中的Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);打断点调试。
  2. 当变量beanName名字是包含了Mapper的Service或Controller时,进入该方法。
    然后跟随进入result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);->instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);->beanFactory.getBean(beanName);。进入调试。熟悉BeanFactory.getBean的人应该不用调试就知道,这个名字对应的Bean是MapperBeanFactory,也就是一个BeanFactory,所以最终该方法beanFactory.getBean(beanName);返回的是MapperFactoryBean的getObject的返回值。这个getObject就是刚才分析的用途啦!

总结

  1. @MapperScan扫描指定的包,对每个Mapper,以它的名字注册了实际类型是MapperFactoryBean的Bean定义。
  2. 有了这些Bean定义,在spring实例化Bean时,这些MapperFactoryBean会被实例化、初始化,对应的方法也会设置
  3. 在处理@autowired标注的Mapper时,会返回MapperFactoryBean.getObject的调用结果,也就是getSqlSession().getMapper(this.mapperInterface);了。
  4. 上一步的结果会导致@Autowired SomeMapper mapper;上注入了一个Mapper代理类,该代理类会将所有数据库请求都移交给底层的SqlSession操作。
  5. 上一步中,Mapper移交到的sqlSession其实是个SqlSessionTemplate,SqlSessionTemplate又将一切数据库操作移交给sqlSessionProxy,而后者是基于SqlSessionInterceptor创建的代理类。
    也就是说,SqlSessionTemplate的数据库操作会被SqlSessionInterceptor.invoke所拦截。
  6. SqlSessionInterceptor.invoke中调用getSqlSession方法,其内部在需要的时候调用session = sessionFactory.openSession(executorType);获取新的session,其实也就在开启新的连接。
    也就是说SqlSessionTemplate的数据库操作会被SqlSessionInterceptor.invoke所拦截,每次操作前都要获取到SqlSession(实际类型是DefaultSqlSession),这个SqlSession:
    • 要么是复用现有的(比如复用当前事务使用的)
    • 要么是新建的。
      所以上文的完整图解应当改为如下:


      7547741-a30a0cacd5c8c1e5.jpg
      mybatis mapper原理.jpg

猜你喜欢

转载自blog.csdn.net/weixin_33781606/article/details/90976675