MyBatis 中 MapperScannerConfigurer分析

本文探讨了MyBatis中的Mapper接口如何通过MapperFactoryBean和MapperScannerConfigurer转换为代理对象并注入到Spring容器中。详细介绍了Mapper接口的注册过程及其实现原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

myBatis源码学了一段时间, 突然对接口的Mapper类怎么变成FactoryBean注入到项目中很好奇, 找时间研究了下, 做个总结.

在以前的分析代码中, 手动调用Mapper访问数据的过程如下;

 IUserMapper mapper = session.GetMapper(IUserMapper.class);
 UserBean bean = mapper.selectByPrimaryKey(1);
 System.out.println(bean.getName());

即由SqlSession对象(Spring中为SqlSessionTemplate),直接调用getMapper方法获取对应的Mapper.然后访问数据库, 但是真正在项目中,Mapper的使用方式如下:

@Service
public class xxxService {
    @Resource
    private IUserMapper mapper ;

     public Map<String, Object> getXXXmethod(String xx) {
         UserBean bean = mapper.selectByPrimaryKey(1);
    }
}

即Mapper是直接被注入到项目中的,

问题: 这个注入是什么时候实现的?

调试程序发现此时的Mapper实际类型是MapperProxy类型:
这里写图片描述

原来所有Mapper接口都被转变为MapperProxyBean放入Ioc, 回头查看下Mybatis文档.
http://www.mybatis.org/spring/zh/mappers.html#MapperFactoryBean, 有如下说明:

MyBatis-Spring 提供了一个动态代理的实现:MapperFactoryBean。这个类 可以让你直接注入数据映射器接口到你的 service 层 bean 中。当使用映射器时,你仅仅如调 用你的 DAO 一样调用它们就可以了,但是你不需要编写任何 DAO 实现的代码,因为 MyBatis-Spring 将会为你创建代理。

单个数据映射器接口可以按照如下做法加入到 Spring 中:

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

MapperFactoryBean 创建的代理类实现了 UserMapper 接口,并且注入到应用程序中

问题来了,如果每个Mapper接口都如上配置的话, XML文件还不暴涨, 所以Spring 又提供了类MapperScannerConfigurer,
,你可以使用一个 MapperScannerConfigurer , 它 将 会 查 找 类 路 径 下 的 映 射 器 并 自 动 将 它 们 创 建 成 MapperFactoryBean。

要创建 MapperScannerConfigurer,可以在 Spring 的配置中添加如下代码:

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
  <property name="basePackage" value="org.mybatis.spring.sample.mapper" />
</bean>

下面看类MapperScannerConfigurer是怎么实现将接口Mapper变为MapperFactoryBean然后注入到Ioc中的.
首先看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;

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      //如果是Object类的方法如:toSting等, 直接调用原方法,
    if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //被代理为SqlSession.executor来执行数据库查询
    return mapperMethod.execute(sqlSession, args);
  }
}

这不就是个典型的代理类嘛, 具体不细说,请参照本人mybaits源码分析相关博客:
https://blog.csdn.net/yangsnow_rain_wind/article/category/7552504

下面分析类MapperScannerConfigurer

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, 
InitializingBean, ApplicationContextAware, BeanNameAware {


//1. 
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        if(this.processPropertyPlaceHolders) {
            this.processPropertyPlaceHolders();
        }

        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
        scanner.setAddToConfig(this.addToConfig);
        scanner.setAnnotationClass(this.annotationClass);
        scanner.setMarkerInterface(this.markerInterface);
        scanner.setSqlSessionFactory(this.sqlSessionFactory);
        scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
        scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
        scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
        scanner.setResourceLoader(this.applicationContext);
        scanner.setBeanNameGenerator(this.nameGenerator);
        scanner.registerFilters();
        //此处调用ClassPathMapperScanner  扫描类
        scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
    }
}

MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,
该接口有个postProcessBeanDefinitionRegistry方法, 可以在standard initialization之后 修改definition, 这些Bean definitions 已经被加载到仓库,但是还没来得及初始化,

Mapper的加载由doScan方法来实现

//org.mybatis.spring.mapper.ClassPathMapperScanner#doScan

  public Set<BeanDefinitionHolder> doScan(String... basePackages) {

    //1
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    for (BeanDefinitionHolder holder : beanDefinitions) {
       GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();

    //这里记录原始接口的名称
       definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());

        //2.
       //这里类的名称被改写为MapperFactoryBean,
       definition.setBeanClass(MapperFactoryBean.class);

       definition.getPropertyValues().add("addToConfig", this.addToConfig);
       definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
      }

    return beanDefinitions;
  }

1处. 调用的是父类的方法 org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan 该方法中,用来扫描指定package下所有的类,并包装为BeanDefinitionHolder,
即返回指定包路径下的所有Mapper接口, 并封装为BeanDefinitionHolder

//mapper扫描路径
 <property name="basePackage" value="com.xx.xxx.model.dao"/>

2处 :关键点,BeanDefinition的类名字由原来的Mapper被更改为MapperFactoryBean,
举个栗子: 假如本Bean的名称依然是”userMapper”, 但其定义的类型已经被指定为MapperFactoryBean, 这在该bean被IOC初始化的时候,则会按照新的类型被初始化为一个代理类MapperFactoryBean,

MapperFactoryBean继承了SqlSessionDaoSupport类,SqlSessionDaoSupport类继承DaoSupport抽象类,DaoSupport抽象类实现了InitializingBean接口,因此实例个MapperFactoryBean的时候,都会调用InitializingBean接口的afterPropertiesSet方法

源码如下:


//org.springframework.dao.support.DaoSupport#afterPropertiesSet
public final void afterPropertiesSet()  {
        checkDaoConfig();
        initDao();
    }

//org.mybatis.spring.mapper.MapperFactoryBean#checkDaoConfig
  protected void checkDaoConfig() {
    super.checkDaoConfig();
    Configuration configuration = getSqlSession().getConfiguration();

    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
        //至此, 世界一下子清净了, Mapper接口定义的Bean被注册进Ioc中
        configuration.addMapper(this.mapperInterface);
    }
 }  

Mapper被代理为MapperFactoryBean,注册进ioc中, 至此基本回到开始的原点:

 IUserMapper mapper = session.GetMapper(IUserMapper.class);

mapper是一个代理好的对象, mapper的所有调用都被代理给sqlsessionTemplate来实现,

完……………………..

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值