Mybatis与Spring结合深探——MapperFactoryBean的奥秘

2023-12-14 11:45:37

系列相关相关文章
究竟FactoryBean是什么?深入理解Spring的工厂神器
超硬核解析Mybatis动态代理原理!只有接口没实现也能跑?
Mybatis与Spring结合深探——MapperFactoryBean的奥秘
后续TODO:MapperScannerConfigurer

前言

在这里插入图片描述

在没有Spring单独使用Mybatis的时候,我在之前的文章超硬核解析Mybatis动态代理原理!只有接口没实现也能跑? 讲解到了调用链路new SqlSessionFactoryBuilder().build(xml)-->XMLConfigBuilder#parse-->>XMLConfigBuilder#parseConfiguration--->XMLConfigBuilder#mapperElement-->XMLMapperBuilder#mapperParser.parse()-->XMLMapperBuilder#configurationElement-->XMLMapperBuilder#bindMapperForNamespace-->Configuration#MapperRegistry#addMappper()

在SqlSessionFactoryBuilder().build方法 中最终调用Configuration对象的addMappper()方法(实际上是委托给MapperRegistry的addMapper)添加对应的MapperProxyFactory代理工厂类,最终通过这个工厂类生成对应的代理对象MapperProxy 。

也就是MapperRegistry内部维护一个映射关系,每个接口对应一个MapperProxyFactory(生成动态代理工厂类)

private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

这样便于在后面调用MapperRegistry的getMapper()时,直接从Map中获取某个接口对应的动态代理工厂类,然后再利用工厂类针对其接口生成真正的动态代理类。


如果想了解什么是FactoryBean是什么,可以查看前文究竟FactoryBean是什么?深入理解Spring的工厂神器

更详细的内容可以查看我之前的文章:超硬核解析Mybatis动态代理原理!只有接口没实现也能跑?


而我们现在在Spring框架中整合Mybatis时,我们通常会使用MapperFactoryBean来生成Mapper的代理实例,也就是不需要再通过new SqlSessionFactoryBuilder().build(xml)的方式去注册动态代理接口。这是一种更简单且易于配置的方式,可让我们以Spring的形式操作Mybatis的持久层。本文将深入探索MapperFactoryBean的工作原理,并说明如何将Mybatis和Spring框架结合起来,以构建一个响应迅速而又易于维护的数据访问层。

MapperFactoryBean的工作原理

当应用启动时,Spring容器会为每个MapperFactoryBean生成一个相应的Bean实例。这个过程包含了几个关键步骤:

  • Bean的定义:在Spring配置文件中定义MapperFactoryBean,这包括指定其sqlSessionFactorysqlSessionTemplate
  • Bean的实例化:Spring容器将调用MapperFactoryBeangetObject()方法,这个方法内部又会调用Mybatis的SqlSession.getMapper()
  • 生成Mapper代理:正如前面提到的,Mybatis使用动态代理技术生成代理对象。这个过程由Mybatis内部的MapperProxyFactory完成。
  • Bean的使用:最终创建的Mapper被注入到其他组件中,这样,业务代码就可以通过普通的Java方法调用来执行SQL操作了。

下面我们看看实际的配置代码示例:

<!-- Mybatis的SqlSessionFactory配置 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="mapperLocations" value="classpath*:mapper/*.xml" />
</bean>

<!-- Mapper接口对应的FactoryBean配置 -->
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
    <property name="mapperInterface" value="com.example.mapper.UserMapper" />
    <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

当然,在Spring的Java配置中,我们通常用注解来代替上述XML配置,得益于Spring的@MapperScan,可以大幅简化这个配置:

@Configuration
@MapperScan("com.example.mapper")
public class AppConfig {
    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        // ...其他配置...
        return sessionFactory.getObject();
    }
}

底层实现剖析

MapperFactoryBean的checkDaoConfig()方法

MapperFactoryBean本身extend自SqlSessionDaoSupport,SqlSessionDaoSupport又extend自DaoSupport接口,DaoSupport接口实现了InitializingBean接口,在对象初始化的时候会调用它的afterPropertiesSet方法,该方法中首先调用了checkDaoConfig()方法,MapperFactoryBean重载的checkDaoConfig()如下面所示—>这里最终会将调用configuration.addMapper(this.mapperInterface)(实际也是委托给MapperRegistry)

微信公众号:bugstack虫洞栈 & MapperFactoryBean类图

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
    
    private Class<T> mapperInterface;
    
    public void setMapperInterface(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

      protected void checkDaoConfig() {
        super.checkDaoConfig();
        Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required");
        Configuration configuration = this.getSqlSession().getConfiguration();
        if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
            try {
                configuration.addMapper(this.mapperInterface);
            } catch (Exception var6) {
                this.logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", var6);
                throw new IllegalArgumentException(var6);
            } finally {
                ErrorContext.instance().reset();
            }
        }

    }

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

    // ...
}

checkDaoConfig方法中,会检查mapperInterface是否已设置,符合Spring管理Bean生命周期的要求。

接着通过configuration.addMapper(this.mapperInterface)方法重点关注,最终实现是在MapperRegistry中:
到这里以后,跟我之前文章超硬核解析Mybatis动态代理原理!只有接口没实现也能跑?解析的步骤又是一样的了

new SqlSessionFactoryBuilder().build(xml)-->XMLConfigBuilder#parse-->>XMLConfigBuilder#parseConfiguration--->XMLConfigBuilder#mapperElement-->XMLMapperBuilder#mapperParser.parse()-->XMLMapperBuilder#configurationElement-->XMLMapperBuilder#bindMapperForNamespace-->Configuration#MapperRegistry#addMappper()

  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

parser.parse()完成了对mapper对应xml的解析成MappedStatement,并添加到了configuration对象中,这里的configuration也就是我们上面提到的new Configuration()创建的那个对象(非常重要)。

总结

mapper接口的定义在bean加载阶段会被替换成MapperFactoryBean类型,在spring容器初始化的时候会给我们生成MapperFactoryBean类型的对象,在该对象生成的过程中调用afterPropertiesSet()方法,为我们生成了一个MapperProxyFactory类型的对象存放于Configuration里的MapperRegistry对象中,同时解析了mapper接口对应的xml文件,把每一个方法解析成一个MappedStatement对象,存放于Configuration里mappedStatements这个Map集合中。

MapperFactoryBean的getObject()方法

MapperFactoryBean实现了FactoryBean接口,实现了FactoryBean接口的类型在调用getBean(beanName)既通过名称获取对象时,返回的对象不是本身类型的对象,而是通过实现接口中的getObject()方法返回的对象。

这里需要对spring的声明周期有一定的了解:下面是简化版的MapperFactoryBean的链路调用

getObject()--->doGetBean()--->getObjectForBeanInstance()--->getObjectFromFactoryBean()--->doGetObjectFromFactoryBean--->MapperFactoryBean.getObject()方法

protected <T> T doGetBean(final String name, final Object[] args) {
        Object sharedInstance = getSingleton(name);
        if (sharedInstance != null) {
            // 如果是 FactoryBean,则需要调用 FactoryBean#getObject
            return (T) getObjectForBeanInstance(sharedInstance, name);
        }

        BeanDefinition beanDefinition = getBeanDefinition(name);
        //这里如果是MapperFactoryBean对象,初始化完成以后会进入下面的判断
        Object bean = createBean(name, beanDefinition, args);
        //这里如果是MapperFactoryBean对象,初始化完成以后会进入下面的判断
        return (T) getObjectForBeanInstance(bean, name);
    }

    private Object getObjectForBeanInstance(Object beanInstance, String beanName) {
        if (!(beanInstance instanceof FactoryBean)) {
            return beanInstance;
        }
		
        Object object = getCachedObjectForFactoryBean(beanName);

        if (object == null) {
            FactoryBean<?> factoryBean = (FactoryBean<?>) beanInstance;
            //这里如果是MapperFactoryBean对象,初始化完成以后会进入这里的逻辑
            object = getObjectFromFactoryBean(factoryBean, beanName);
        }

        return object;
    }

FactoryBeanRegistrySupport的方法getObjectFromFactoryBean--->doGetObjectFromFactoryBean()--->factory.getObject()方法

    protected Object getObjectFromFactoryBean(FactoryBean factory, String beanName) {
        if (factory.isSingleton()) {
            Object object = this.factoryBeanObjectCache.get(beanName);
            if (object == null) {
                //这里如果是MapperFactoryBean对象,初始化完成以后会进入这里的逻辑
                object = doGetObjectFromFactoryBean(factory, beanName);
                this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT));
            }
            return (object != NULL_OBJECT ? object : null);
        } else {
            return doGetObjectFromFactoryBean(factory, beanName);
        }
    }
	

    private Object doGetObjectFromFactoryBean(final FactoryBean factory, final String beanName){
        try {
        	//最终调用MapperFactoryBean的getObject方法获取实际的对象
            return factory.getObject();
        } catch (Exception e) {
            throw new BeansException("FactoryBean threw exception on object[" + beanName + "] creation", e);
        }
    }

MapperFactoryBean的getObject()方法

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

走到这里,是不是也跟我之前文章超硬核解析Mybatis动态代理原理!只有接口没实现也能跑?解析的步骤又是一样的了

getMapper方法的大致调用逻辑链是: SqlSession#getMapper() ——> Configuration#getMapper() ——> MapperRegistry#getMapper() ——> MapperProxyFactory#newInstance() ——> Proxy#newProxyInstance()–>MapperProxy#invoke–>MapperMethod#execute

思考联想

  • 在之前的文章中超硬核解析Mybatis动态代理原理!只有接口没实现也能跑?我们通过new SqlSessionFactoryBuilder().build(xml)最终调用委托给Configuration#MapperRegistry#addMappper() 方法进行mapper接口的注册,而在Spring结合mybatis的过程中,我们通过MapperFactoryBean的checkDaoConfig()最终调用委托给Configuration#MapperRegistry#addMappper() 方法进行mapper接口的注册方法实现,本质上是一样的。

  • 在之前的文章中超硬核解析Mybatis动态代理原理!只有接口没实现也能跑?,我们又通过SqlSessionFactory的openSession()新建一个SqlSession,然后通过session#getMapper()最终调用委托给MapperRegistry#getMapper()——> MapperProxyFactory#newInstance() 方法实现代理对象的生成,而在Spring结合mybatis的过程中,我们通过MapperFactoryBean的getObject()调用this.getSqlSession().getMapper(this.mapperInterface)最终也是委托给MapperRegistry#getMapper()——> MapperProxyFactory#newInstance() 方法实现代理对象的生成,本质也是一样的道理。

后续

刚刚上面的例子我们可以发现:每配置一个mapper,都需要写一个对应的MapperFactoryBean,如果mapper多了这样是很繁琐的。

<!-- Mapper接口对应的FactoryBean配置 -->
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
    <property name="mapperInterface" value="com.example.mapper.UserMapper" />
    <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

为了解决这个问题,我们可以使用MapperScannerConfigurer,让它扫描特定的包,自动帮我们成批的创建映射器。这样一来,就能大大减少配置的工作量。具体的实现原理我们后面再进行讲解。

<!-- 配置扫描Dao接口包,动态实现Dao接口,注入到spring容器中 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 注入sqlSessionFactory -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <!-- 给出需要扫描Dao接口包 -->
        <property name="basePackage" value="com.joe.dao"/>
    </bean>

文章来源:https://blog.csdn.net/belongtocode/article/details/134917066
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。