wayne
wayne
发布于 2025-07-11 / 2 阅读
0
0

Spring之我见 - 代理模式及FactoryBean的实际运用 - Mybatis Mapper文件的华丽转身

在日常开发中,我们使用 MyBatis Mapper 时只需要定义一个接口并编写 SQL 映射,就能直接调用数据库操作方法。这种看似简单的背后,实际上是一套精妙的 Spring 整合机制在发挥作用。本文将深入剖析 MyBatis Mapper 如何从"无实现"的接口转变为功能完整的 Spring Bean。

FactoryBean

Spring 的标准 Bean 实例化流程是通过反射调用类的构造函数完成的。但对于某些复杂场景(如 MyBatis Mapper),Spring 提供了 FactoryBean 接口来支持定制化的 Bean 创建逻辑。

FactoryBean 的核心价值在于:

  1. 将复杂的对象构建过程封装起来

  2. 提供比 XML/注解配置更灵活的实例化方式

  3. 支持动态决定返回对象的类型

  4. 自 Spring 3.0 起支持泛型,增强了类型安全性

MyBatis 正是通过 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;

  }

  /**

   * {@inheritDoc}

   */

  @Override

  public T getObject() throws Exception {

    return getSqlSession().getMapper(this.mapperInterface);

  }

}

我们把debug断点打在getObject方法,发现操作跟我们刚学Mybatis写的demo一模一样。通过传入接口实例返回经过包装的代理类。

Mapper 接口的注册与发现

我们暂时别跟着断点往下走,我们可能想知道spring是如何加载这些Mapper接口和MapperFactoryBean 。起点就在MapperScan注解。看MapperScan源码,这个注解为 Import 了MapperScannerRegistrar类。

@MapperScan(basePackages = "包名")

MapperScannerRegistrar类的registerBeanDefinitions方法利用ClassPathMapperScanner对象读取解析了指定包名下所有的接口,并且通过构建BeanDefinitions对象,让每个MapperFactoryBean拥有不同的mapperInterface class属性。

整个流程的起点是 @MapperScan 注解:

  1. @MapperScan 通过 @Import 引入了 MapperScannerRegistrar

  2. MapperScannerRegistrar 使用 ClassPathMapperScanner 扫描指定包下的接口

  3. 为每个 Mapper 接口创建对应的 BeanDefinition

  4. 将这些 BeanDefinition 注册到 Spring 容器

关键点在于,注册的不是接口本身的 BeanDefinition,而是以该接口作为 mapperInterface 属性的 MapperFactoryBean 的定义。

BeanDefinitions收集的途径非常多,比如还有BeanDefinitionRegistryPostProcessors动态注入BeanDefinition。对于spring ioc初始化的过程 比较模糊的同学,可以看看下面的系列化文章补补基础,写的非常好:

【死磕 Spring】—– IOC 总结

Spring Bean 实例化的关键阶段

在 Spring 容器的 refresh() 过程中,finishBeanFactoryInitialization() 方法会触发非延迟加载的单例 Bean 的初始化:

  1. 调用 getBean() 开始加载流程

  2. 根据 BeanDefinition 创建 MapperFactoryBean 实例

  3. 通过 getObjectForBeanInstance() 判断是否需要调用 FactoryBeangetObject()

  4. 对于 FactoryBean 类型,最终调用 getObjectFromFactoryBean()

protected Object getObjectForBeanInstance(Object beanInstance, String name, ...) {
    // 情况1:如果是普通 Bean(非FactoryBean),直接返回
    if (!(beanInstance instanceof FactoryBean)) {
        return beanInstance;
    }

    // 情况2:如果是 FactoryBean,且名称以 & 开头(表示要获取FactoryBean本身),直接返回
    if (BeanFactoryUtils.isFactoryDereference(name)) {
        return beanInstance;
    }

    // 情况3:如果是 FactoryBean 且需要获取其产品(getObject的结果)
    return getObjectFromFactoryBean((FactoryBean<?>) beanInstance, name);
}

继续走读源码,我们会看到终于调用了FactoryBean的getObject方法,而我们也开始介绍下一章。

动态代理:无实现接口的魔法

当调用链最终到达 MapperFactoryBean.getObject() 时,真正的魔法开始了:

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

深入这个调用,我们会发现 MyBatis 使用了 JDK 动态代理:

  1. MapperProxyFactory 创建代理实例

  2. Proxy.newProxyInstance() 生成接口的代理实现

  3. MapperProxy 作为 InvocationHandler 处理所有方法调用

public class MapperProxy<T> implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        // 处理 Object 方法
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        }
        
        // 获取缓存的 MapperMethod
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        return mapperMethod.execute(sqlSession, args);
    }
}

每个 Mapper 方法调用都会被 MapperProxy 拦截,然后:

  1. 解析对应的 SQL 语句

  2. 处理参数绑定

  3. 执行数据库操作

  4. 处理返回结果

Proxy做了什么事可以看我之前的博文

从代理模式再出发!Proxy.newProxyInstance的秘密

总结:从定义到实现的完整链条

  1. 定义阶段:开发者编写 Mapper 接口和 SQL 映射

  2. 注册阶段@MapperScan 扫描接口并注册 MapperFactoryBean 定义

  3. 实例化阶段:Spring 创建 MapperFactoryBean 并调用其 getObject()

  4. 代理阶段:MyBatis 通过动态代理生成接口实现

  5. 执行阶段:方法调用被代理拦截,转换为真正的数据库操作

这种设计完美结合了 Spring 的扩展性和 MyBatis 的灵活性,使得开发者可以专注于业务逻辑,而无需关心繁琐的数据库访问实现细节。

修订

- 2019.10.23 补充了spring ioc方面的知识,更有利于理解spring与Mybatis的结合过程。


评论