spring扩展点一:BeanDefinitionRegistryPostProcessor在框架中的应用

上篇笔记主要记录了BeanDefinitionRegistryPostProcessor扩展机制的实现原理,这篇笔记注意记录下,自己在学习源码的过程中,看到的该扩展机制使用的地方

其实我上篇笔记有说过,这个扩展机制,太靠前,如果我们通过@Component注解去注入到spring容器中,那我们自定义的实现类是在所有的业务bean还没有放到spring容器中的时候,就执行了,此时一般也很少有业务需求是在这个时候对beanDefinition进行一些修改,但是,在底层框架中,有可能会用到这个扩展点,我看到的是mybatis底层有用到这个扩展点

mybatis是干什么的

其实mybatis就是一个持久层框架,在和spring整合的时候,简单来说,我觉得就做了一件事情,将我们程序员自己写的mapper接口交给spring去管理
但是需要想一下,一个接口,怎么交给spring去管理?这里面的一个设计思想,我觉得是值得学习的:对于spring来说,不会为了去整合mybatis而去修改自己的代码,那原生的mybatis会修改吗?当然也不会,因为mybatis如果只是为了整合spring,修改了自己的代码,那mybatis就不是mybatis了,那怎么办呢?两者想要结合,总要有一个中间粘合剂,那这时候,spring就说了,你既然想和我进行整合,那你自己去扩展一个jar包,专门负责和我spring整合用,你可以利用我暴露给你的扩展点,总之,我不管你怎么做,你只要把你的接口放到我的beanDefinitionMap中,就可以了,我负责去初始化,然后放到容器中,至于你怎么把mapper接口放到我的beanDefinitionMap,我不管
那这时候,mybatis就自己去实现类一个jar包:mybatis-spring-1.3.2.jar
也就是在这个jar包中,mybatis利用了spring的BeanDefinitionRegistryPostProcessor这个扩展点,去把我们程序员提供的mapper接口,放入到了beanDefinitionMap中

mybatis整合spring原理

我们在使用mybatis的时候,需要加一个注解,@MapperScan,在该注解中,指定要扫描的路径,这个路径是给谁用的?就是给我们这篇笔记要学习的BeanDefinitionRegistryPostProcessor的实现类MapperScannerConfigurer
在这里插入图片描述

@MapperScan

在该注解中,通过@Import引入了一个类MapperScannerRegistrar,

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware 

这个类是ImportBeanDefinitionRegistrar的实现类,这也是spring扩展点之一,我们这里不做深入说明,我们可以简单理解为,spring在初始化的过程中,会调用该接口实现类的方法
在mybatis-spring-1.3.x.jar包中,直接在MapperScannerRegistrar 的registerBeanDefinitions()方法中,进行了扫描,去扫描我们自己声明的mapper接口
但是在mybatis-spring-2.x.jar包中,在MapperScannerRegistrar 的registerBeanDefinitions()方法中,注册到beanDefinitionMap中一个BeanDefinitionRegistryPostProcessor的实现类:MapperScannerConfigurer

所以,在1.x版本中,进行扫描的时候,使用的是ImportBeanDefinitionRegistrar这个扩展点,但是在2.x的包中,使用的是BeanDefinitionRegistryPostProcessor的扩展点

我们来看下2.X版本中是如何进行mapper扫描的

MapperScannerConfigurer

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        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();
        scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
    }

可以看到,在其方法中,会自己声明一个scanner,然后去调用scan方法扫描,截止到这里,我们可以结束了,已经看到了,在mybatis-spring-2.x.jar中,就是利用了BeanDefinitionRegistryPostProcessor的机制,去进行mapper的扫描

扫描之后的后续处理

由于这里的ClassPathMapperScanner 是mybatis自己的,所以在调用scan方法的时候,会调用到自己的scan方法

public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
        if (beanDefinitions.isEmpty()) {
            this.logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
        } else {
            this.processBeanDefinitions(beanDefinitions);
        }

        return beanDefinitions;
    }

这里的扫描方法,其实和spring自己的扫描bean是几乎上一致的,只是在扫描出来包下对应的class文件之后,判断条件不一样,mybatis会判断class是否是接口等等

在扫描了之后,有一个很重要的操作:processBeanDefinitions()

修改beanDefinition的属性

我们需要考虑下,为什么在将接口扫描出来之后,要修改对应的beanDefinition属性?
首选我们来说processBeanDefinitions()方法中,做了什么修改

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();

      if (logger.isDebugEnabled()) {
        logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 
          + "' and '" + definition.getBeanClassName() + "' mapperInterface");
      }

      // 1.添加一个构造函数
	  // 2.将beanDefinition的beanClass设置为mapperFactoryBean
      definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
      definition.setBeanClass(this.mapperFactoryBean.getClass());

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

      boolean explicitFactoryUsed = false;
      if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
        definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionFactory != null) {
        definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
        explicitFactoryUsed = true;
      }

      if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
        if (explicitFactoryUsed) {
          logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionTemplate != null) {
        if (explicitFactoryUsed) {
          logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
        explicitFactoryUsed = true;
      }

      if (!explicitFactoryUsed) {
        if (logger.isDebugEnabled()) {
          logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        }
		// 3.将beanDefinition的自动注入属性设置为byType
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
}

这里我们可以认为有两个关键点:
1.将beanDefinition的beanClass设置为mapperFactoryBean
2.将beanDefinition的autowireMode设置为byType

我们考虑第一点,为什么将beanClass设置为mapperFactoryBean?
可以想下,一个接口,我们怎么将其注入到service?接口是没有业务逻辑的,所以只能是注入一个动态代理对象,所以这里的mapperFactoryBean是为了在会将mapper接口注入到service的时候,生成一个mapper接口的代理对象,注入到service,这里和mapperFactoryBean的具体逻辑有关系,不做深入说明

接着考虑第二点:为什么将自动注入模型设置为byType,需要注意,这里的byType和@Autowired的byType是完全不同的两个概念,我之前博客中有说明过,如果自动注入模型是byType,那我们在注入的时候,就不需要在属性上添加任何注解,只需要提供对应的set方法,并且set方法入参是我们要注入的类型即可
这也就是为什么要设置为byType的原因,我们想下,如果不设置为byType,那在mapper接口中注入sqlSessionFactory或者是sqlSessionTemplate的时候,还需要提供@Autowired注解,那这不就又和spring强耦合到一起了吗?所以将autoWireMode设置为byType,就不需要和spring强耦合

总结

所以,整个笔记写下来,我们也知道了,spring和mybatis在整合的时候,不仅仅用到了factoryBean、自动注入模型这个扩展点,在进行mapper接口扫描的时候,也用到了BeanDefinitionRegistryPostProcessor这个扩展机制

猜你喜欢

转载自blog.csdn.net/CPLASF_/article/details/115023146
今日推荐