框架源码专题:Spring是如何集成Mybatis的?Spring怎么管理Mapper接口的动态代理


1. Spring集成Mybatis代码示例

        Spring在集成Mybatis时,使用SqlSessionFactoryBean来完成Configuration的解析,代码如下:

@EnableTransactionManagement
@Configuration
@MapperScan(basePackages = {"com.tuling.mapper"})
@ComponentScan(basePackages = {"com.tuling"})
@Repository
public class MyBatisConfig {    // =====>   spring.xml

   //用SqlSessionFactoryBean来代替Mybatis中解析Configuration的过程
   @Bean  
   public SqlSessionFactoryBean sqlSessionFactory( ) throws IOException {
      SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
      //设置数据源
      factoryBean.setDataSource(dataSource());
      // 设置 MyBatis 配置文件路径
      factoryBean.setConfigLocation(new ClassPathResource("mybatis/mybatis-config.xml"));
      // 设置 SQL 映射文件路径
      factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/*.xml"));
      factoryBean.setTypeAliases(User.class);

      return factoryBean;


   }
   
    //数据源
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis_example");
        return dataSource;
    }


2. Spring 如何解析Mybatis配置文件

        Spring 是根据bean对象SqlSessionFactoryBean 来解析xml文件到Configuration类中的,首先来看一下SqlSessionFactoryBean这个类:

//SqlSessionFactoryBean 实现了FactoryBean 和 InitializingBean
public class SqlSessionFactoryBean 
		implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
	
  //内部封装了Configuration 类
  private Configuration configuration;

  private Resource[] mapperLocations;

  private DataSource dataSource;
  
  //类型处理器
  private TypeHandler<?>[] typeHandlers;

  private String typeHandlersPackage;

  //类型别名
  private Class<?>[] typeAliases;

  private String typeAliasesPackage;

	。。。。。。 不一一列举

可以看到SqlSessionFactoryBean内部封装了Configuration类,
实现了FactoryBean,会创建getObject中返回的单例sqlSessionFactory对象,这个对象就是我们Mybatis中最开始的DefaultSqlSessionFactory对象!

  @Override
  public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      //如果sqlSessionFactory 为空,则先创建sqlSessionFactory !
      afterPropertiesSet();
    }
	
    return this.sqlSessionFactory;
  }

还实现了InitializingBean,InitializingBean中的afterPropertiesSet方法会在类初始化完成后调用,而上边的getObject() 方法在获取对象时会调用这个方法生成sqlSessionFactory

  @Override
  public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
              "Property 'configuration' and 'configLocation' can not specified with together");
	//创建sqlSessionFactory 的方法
    this.sqlSessionFactory = buildSqlSessionFactory();
  }

核心方法是buildSqlSessionFactory,在这个方法中,大部分都是为Configuration对象赋值!解析过程与Mybatis解析过程类似!最后通过建造者模式返回sqlSessionFactory的实现
DefaultSqlSessionFactory!

  protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
  
	//局部变量Configuration
    Configuration configuration;

 	.......
 	
    if (this.objectFactory != null) {
      configuration.setObjectFactory(this.objectFactory);
    }
    if (this.vfs != null) {
      configuration.setVfsImpl(this.vfs);
    }
    
	//设置类型别名
    if (!isEmpty(this.typeAliases)) {
      for (Class<?> typeAlias : this.typeAliases) {
        configuration.getTypeAliasRegistry().registerAlias(typeAlias);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registered type alias: '" + typeAlias + "'");
        }
      }
    }
	//添加插件
    if (!isEmpty(this.plugins)) {
      for (Interceptor plugin : this.plugins) {
        configuration.addInterceptor(plugin);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registered plugin: '" + plugin + "'");
        }
      }
    }
	//设置类型处理器
    if (!isEmpty(this.typeHandlers)) {
      for (TypeHandler<?> typeHandler : this.typeHandlers) {
        configuration.getTypeHandlerRegistry().register(typeHandler);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registered type handler: '" + typeHandler + "'");
        }
      }
    }
	//缓存
    if (this.cache != null) {
      configuration.addCache(this.cache);
    }
    
	//事务管理器
	//SpringManagedTransactionFactory是新定义的事务管理器,它使用Spring事务中的dataSource ,从而达到跟事务集成
    if (this.transactionFactory == null) {
      this.transactionFactory = new SpringManagedTransactionFactory();
    }
    
	//环境
    configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));

 	....... 省略

	//建造者模式 返回 DefaultSqlSessionFactory
    return this.sqlSessionFactoryBuilder.build(configuration);
  }

注意:

  • Spring集成mybatis,在处理事务时,事务工厂会使用一个新的new SpringManagedTransactionFactory
  • 而不是MyBatis之前的ManagedTransactionFactory, 这个SpringManagedTransactionFactory会使用Spring事务同步管理器TransactionSynchronizationManager中的dataSource , 从而达到跟事务集成


3. Spring是怎么管理Mapper接口的动态代理的

        Spring与Mybatis整合的最主要目的就是:把Mapper接口的代理对象放入Spring容器,在使用时能够像使用一个普通的bean一样去使用这个代理对象,比如能被@Autowired自动注入!

整合结果,能够像下面的方式一样使用代理对象

@Component
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public User getUserById(Integer id) {
        return userMapper.selectById(id);
    }
}

        UserService中的userMapper属性就会被自动注入为Mybatis中的代理对象。如果你基于一个已经完成整合的项目去调试即可发现,userMapper的类型为:org.apache.ibatis.binding.MapperProxy@41a0aa7d。证明确实是Mybatis中的代理对象。

那么问题来了:

        要想使用@AutoWired注入xxxMapper代理对象,Spring容器中必须要存在才行啊? 那么Spring是怎么把不同的Mapper代理对象作为一个bean放入容器中呢?

  • 猜想一:Spring中可以通过注解把类加入容器,比如@Controller、@Service、@Component等等,如果为Mapper接口加上@Component注解,是否可以放入Spring容器?
    答案:不能。
    要验证这个猜想,我们需要对 Spring的bean生成过程 有一个了解。 Spring启动过程中,大致会经过如下步骤去生成bean:
    1. 扫描@ComponentScan中指定的包路径下的class文件
    2. 根据class信息判断是否符合生成对应的BeanDefinition的条件(接口和抽象类不符合条件),如果符合则生成对应的BeanDefinition
    3. 在此处,程序员可以利用某些机制去修改BeanDefinition,实现扩展
    4. 根据BeanDefinition中的class信息反射生成bean实例
    5. 把生成的bean实例放入Spring容器中

        由此步骤可见,要想生成bean对象,首先需要有BeanDefinition。由于Mapper接口上加@Component在第2步就会被过滤掉,无法生成BeanDefinition,更无法生成实例。


  • 猜想二:使用@Bean把代理类注册进去,在@Bean代码中生成Mapper接口的动态代理!

    这种方式其实是可行的,因为Spring在进行bean实例化时有两种方式。

    • ①:带有@Component等注解的类直接利用class反射生成实例
    • ②:带有@Bean注解的则会使用工厂的方式生成实例

        显然第二种猜想是可行的,但是存在一个问题,一个项目可能有上百个Mapper,难道每一个都要写一个@Bean生成代理类?这岂不是累死个人!!Spring绝对不是靠这种方式整合的!

        
Sping的解决方案:

        先回到猜想一中的思路,在Mapper接口上加@Component注解时,Spring会在扫描@CompopnentScan指定的路径时,过滤掉接口、抽象类,不为他们生成BeanDefinition,这就导致了Mapper接口无法实例化!

        //判断是否是顶级类、非接口类、非抽象类等等,整合Mybatis时,需要重写这个方法,让Mapper接口也加入集合!
        if (isCandidateComponent(sbd)) {
            if (debugEnabled) {
                logger.debug("Identified candidate component class: " + resource);
            }
            //如果不是顶级类、接口类、抽象类,则加入到集合中!!!
            candidates.add(sbd);
        }
        
        else {
            //如果是顶级类、接口类、抽象类,则不予处理!!!
            if (debugEnabled) {
                logger.debug("Ignored because not a concrete top-level class: " + resource);
            }
        }

========  isCandidateComponent 判断是否是顶级类、非接口类、非抽象类等等=======

	protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
		AnnotationMetadata metadata = beanDefinition.getMetadata();
		/**
		 * 判断是否是顶级类、非接口类、非抽象类等等
		 */
		return (metadata.isIndependent() && (metadata.isConcrete() ||
				(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
	}

        然而,Spring在整合Mybatis时,为了让Mapper接口注册成BeanDefinition,重写了isCandidateComponent 方法!!,他让这个方法忽略了接口,让接口也可以被扫描到。

        但是接口也可以被扫描到也不能改变什么啊,因为Mapper接口只是一个接口,还没有为他生成代理类呢!!Spring为了解决这个问题,使用了FactoryBean的getObject方法进行了偷天换日,Mapper接口的BeanDefinition中设置其beanClassFactorybean.class,这样每个Mapper接口在生成实例的时候生成的是FactorybeangetObject方法中返回的代理类了!!

        再通过自定义类实现bean工厂后置处理器BeanDefinitionRegistryPostProcessor,重写postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) 方法,修改成带有Factorybean.classBeanDefinition,这样就会按照我们自定义的BeanDefinition去生成对象! 这就完美解决了问题。

伪代码如下:

    	//扫描到的 Mappers
		Set mappers = new HashSet();

		//遍历 Mappers
		for (Object mapper : mappers) {

			//新建一个bean定义
			RootBeanDefinition beanDefinition = new RootBeanDefinition();
			//偷天换日,设置MyfactoryBean的getObject方法返回的对象为mapper的Class
			beanDefinition.setBeanClass(MyfactoryBean.class);

			//每一次循环都为当前mapper创建动态代理
			beanDefinition.getPropertyValues().add("mapperInterFace",mapper.class);

			//最后注册成bean定义
			registry.registerBeanDefinition("userMapper",beanDefinition);
		}


4. Spring整合Mybatis源码大致流程

  1. spring会重写isCandidateComponent方法,来扫描到所有的mapper接口,并将所有mapper 的 bean定义中的class类型指向MapperFactoryBean
  2. Spring在创建UserServiceImpl实例的时候,发现其内部有@AutowiredUserMapper接口,那么就会去spring容器获取UserMapper实例,没有则进行创建
  3. 创建UserMapper实例的时候,根据bean定义创建的实例 实际上是MapperFactoryBean实例,然后再利用MapperFactoryBeangetObject方法获取mapper的代理实例(调用MapperFactoryBean的getObject方法,mybatis会利用jdk的动态代理创建mapper代理对象);

MapperFactoryBean 类如下:

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
    private Class<T> mapperInterface;
    private boolean addToConfig = true;

    public MapperFactoryBean() {
    }

    public MapperFactoryBean(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();
            }
        }

    }

	// getObject获取对应Mapper的代理类
    public T getObject() throws Exception {
        return this.getSqlSession().getMapper(this.mapperInterface);
    }

    public Class<T> getObjectType() {
        return this.mapperInterface;
    }

    public boolean isSingleton() {
        return true;
    }

    public void setMapperInterface(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public Class<T> getMapperInterface() {
        return this.mapperInterface;
    }

    public void setAddToConfig(boolean addToConfig) {
        this.addToConfig = addToConfig;
    }

    public boolean isAddToConfig() {
        return this.addToConfig;
    }
}

在这里插入图片描述

  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值