Mybatis-Spring整合原理:MapperFactoryBean和MapperScannerConfigurer的区别及源码剖析

文章目录

    • 引言
    • MapperFactoryBean的用法和优缺点
    • MapperScannerConfigurer的用法和优缺点
    • MapperFactoryBean源码分析
    • MapperScannerConfigurer源码分析
      • Spring容器初始化流程回顾
      • 核心方法:postProcessBeanDefinitionRegistry
        • BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor
        • 执行步骤
        • ClassPathBeanDefinitionScanner的scan方法解析
    • MapperScannerConfigurer和MapperFactoryBean的区别、联系
    • 总结

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

在这里插入图片描述

引言

在使用Spring+Mybatis开发数据访问层时,我们通常需要定义一些数据映射器接口(Mapper Interface),用来执行SQL语句并返回结果。为了让Spring能够管理这些映射器接口,我们需要将它们注入到service层的bean中,这样就可以在service层调用映射器接口的方法来操作数据库。

为了实现这一目的,Mybatis提供了两种方式:MapperFactoryBeanMapperScannerConfigurer。这两种方式都可以让Spring识别和管理映射器接口,但是它们的使用方法和原理有所不同。本文将对比分析这两种方式的优缺点,以及MapperScannerConfigurer的源码实现。
至于MapperFactoryBean的源码实现:Mybatis与Spring结合深探——MapperFactoryBean的奥秘,在之前的文章已有解析,这里再来简单回顾一下

MapperFactoryBean的用法和优缺点

MapperFactoryBean是一个实现了FactoryBean接口的类,它可以用来创建和初始化一个映射器接口的代理对象,并将其注册到Spring容器中。我们可以在Spring的配置文件中为每个映射器接口定义一个MapperFactoryBean的bean,指定其映射器接口的类型和sqlSessionFactory的引用,例如:

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

这样,我们就可以在service层的bean中注入userMapper这个bean,然后调用其方法来执行SQL语句,例如:

@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Overridepublic User getUserById(int id) {return userMapper.getUserById(id);}
}

使用MapperFactoryBean的优点是:

  • 简化了DAO层的编写,不需要再为每个映射器接口编写实现类,只需要定义接口和SQL映射文件即可。
  • 自动管理了sqlSession的开启和关闭,不需要手动编写try-catch-finally代码,也不需要担心sqlSession的泄露和性能问题。
  • 可以给予开发者更多控制权,适用于需要对各个Mapper做特别配置的场景。

使用MapperFactoryBean的缺点是:

  • 需要为每个映射器接口手动配置一个bean,如果映射器接口很多,那么配置文件会很冗长和繁琐,也不利于维护和修改。

MapperScannerConfigurer的用法和优缺点

上面说到使用MapperFactoryBean的缺点是:需要为每个映射器接口手动配置一个bean,如果映射器接口很多,那么配置文件会很冗长和繁琐,也不利于维护和修改。为了解决这个问题,我们可以使用MapperScannerConfigurer,让它扫描特定的包,自动帮我们成批的创建映射器。这样一来,就能大大减少配置的工作量。

MapperScannerConfigurer是一个实现了BeanDefinitionRegistryPostProcessor接口的类,它可以用来自动扫描和注册映射器接口,而不需要为每个映射器接口配置一个bean。我们可以在Spring的配置文件中定义一个MapperScannerConfigurer的bean,指定其要扫描的包名和sqlSessionFactory或sqlSessionTemplate的bean名称,例如:

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="com.example.mapper"/><property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>

这样,MapperScannerConfigurer会在Spring容器初始化时,扫描指定包下的所有映射器接口,并为每个接口创建一个MapperFactoryBean的bean,然后将其注册到Spring容器中。我们就可以在service层的bean中直接注入映射器接口的类型,然后调用其方法来执行SQL语句,例如:

@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Overridepublic User getUserById(int id) {return userMapper.getUserById(id);}
}

使用MapperScannerConfigurer的优点是:

  • 进一步简化了配置,不需要为每个映射器接口配置一个bean,只需要配置一个MapperScannerConfigurer的bean即可,可以减少配置文件的长度和复杂度,也更容易修改和维护。
  • 支持多种过滤条件,可以根据注解、接口、父类等条件来过滤和选择要扫描的映射器接口,可以更灵活地控制映射器接口的注册。

MapperFactoryBean源码分析

关于MapperFactoryBean的分析,可以查看之前的文章:Mybatis与Spring结合深探——MapperFactoryBean的奥秘,这里我们重点讲解MapperScannerConfigurer

MapperScannerConfigurer源码分析

Spring容器初始化流程回顾

如果对于BeanDefinitionRegistryPostProcessor不熟的话,这里需要学习下Spring容器初始化的流程了才更能理解这里的内容,这里我简单提下ApplicaitonContext容器大致关键的步骤。

MapperScannerConfigurer这个类实现了如下接口,用来对扫描的Mapper进行处理:

  • BeanDefinitionRegistryPostProcessor
  • InitializingBean
  • ApplicationContextAware
  • BeanNameAware

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

执行流程如下:

微信公众号:bugstack虫洞栈 & 执行流程图

  1. 读取配置文件,将标签解析成一个个的BeanDefinition
  2. 执行BeanDefinitionRegistryPostProcessor接口的postProcessBeanDefinitionRegistry方法。
  3. 执行BeanFactoryPostProcessor接口的postProcessBeanFactory方法,在这里可以扩展修改第一步的BeanDefinition
  4. 初始化bean实例(调用构造方法)。
  5. 调用setter方法设置属性。
  6. 执行BeanPostProcessor接口的postProcessBeforeInitialization方法。
  7. 执行InitializingBean接口的afterPropertiesSet方法。
  8. 执行bean的init-method。
  9. 执行BeanPostProcessor接口的postProcessAfterInitialization方法。

从上述容器的初始化流程中,可知Spring解析完配置文件生成一个个BeanDefinition后,便会调用所有实现了BeanDefinitionRegistryPostProcessor接口的bean中的postProcessBeanDefinitionRegistry方法。因此MapperScannerConfigurer中的postProcessBeanDefinitionRegistry将会得到执行。

值得注意的一个点就是对于实现了BeanDefinitionRegistryPostProcessor或者BeanFactoryPostProcessor的bean来说,要向执行这两个接口的方法,需要先有bean的实例。因此会先创建实例,这样就会导致4-9这些步骤提早触发。因此MapperScannerConfigurer的执行流程为

  • 构造方法实例化
  • 调用setter方法设置属性
  • 调用setBeanName以及setApplicationContext初始化beanName以及applicationContext属性,Aware接口里的方法是由ApplicationContextAwareProcessor回调的,ApplicationContextAwareProcessor是一个BeanPostProcessor,回调逻辑是在postProcessBeforeInitialization方法中
  • 执行afterPropertiesSet方法
  • 执行postProcessBeanDefinitionRegistry方法
  • 执行postProcessBeanFactory方法
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {/** 需要扫描的包,多个包名可以使用逗号分割 **/private String basePackage;/** 同MapperFactoryBean **/private boolean addToConfig = true;/** * 被sqlSessionFactoryBeanName取代* 被取代的原因放在总结里,如果好奇可以看下*/private SqlSessionFactory sqlSessionFactory;/** 被sqlSessionTemplateBeanName取代 **/private SqlSessionTemplate sqlSessionTemplate;/** SqlSessionFactory的名字 **/private String sqlSessionFactoryBeanName;/*** sqlSessionTemplate的名字,创建MapperFactoryBean时需要依赖* 一个SqlSessionFactory或者sqlSessionTemplate实例。*/private String sqlSessionTemplateBeanName;/** 扫描条件,只有接口被此注解标注才会被注册,默认为null **/private Class<? extends Annotation> annotationClass;/** 扫描条件,只有接口继承了指定的这个接口才会被注册,默认为null **/private Class<?> markerInterface;private ApplicationContext applicationContext;/** 代表MapperScannerConfigurer这个bean的名字 **/private String beanName;/** * 如果为true, 代表MapperScannerConfigurer配置使用了${}表达式* 需要先解析${}表达式* 因此如果配置MapperScannerConfigurer时有使用${}表达式,需要将这个值设置成true*/private boolean processPropertyPlaceHolders;/*** 注册接口时使用这个字段来生成bean的名字*/private BeanNameGenerator nameGenerator;@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {// left intentionally blank}@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {if (this.processPropertyPlaceHolders) {// 处理占位符,processPropertyPlaceHolders默认为false// 因为MapperScannerConfigurer先于Spring中处理占位符的BeanFactoryPostProcessor执行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);// 注册TypeFilter,也就是筛选条件scanner.registerFilters();// 扫描包,多个包名可以使用逗号分割scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}@Overridepublic void afterPropertiesSet() throws Exception {notNull(this.basePackage, "Property 'basePackage' is required");}// other methods and setters and getters ...
}
  • 从源码中可以看出,MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor,InitializingBean,ApplicationContextAware和BeanNameAware这四个接口。它有很多的属性,其中最重要的是basePackage,表示要扫描的包名,以及sqlSessionFactoryBeanName和sqlSessionTemplateBeanName,表示sqlSessionFactory或sqlSessionTemplate的bean名称。其他的属性都是用来设置扫描的过滤条件和MapperFactoryBean的属性的。

  • MapperScannerConfigurer实现了InitializingBean接口的afterPropertiesSet方法,该方法是在Bean的属性设置完成后调用的,用来检查和初始化Bean的状态。在该方法中,MapperScannerConfigurer只做了一件事,就是判断了basePackage是否为空,如果为空则抛出异常,如果不为空则什么也不做。这是因为basePackage是MapperScannerConfigurer的必须属性,如果没有设置basePackage,那么MapperScannerConfigurer就无法扫描任何映射器接口,所以要在这里进行检查。

  • MapperScannerConfigurer还实现了ApplicationContextAware和BeanNameAware这两个接口,这两个接口都是用来让Bean获取到Spring容器的上下文信息的。ApplicationContextAware接口的方法是setApplicationContext,用来设置applicationContext的引用,这样就可以访问Spring容器中的其他Bean或资源。BeanNameAware接口的方法是setBeanName,用来设置beanName的值,这样就可以知道自己在Spring容器中的名称。MapperScannerConfigurer实现了这两个接口的方法,分别将applicationContext和beanName的值保存到了自己的属性中,以便于在后续的操作中使用。

核心方法:postProcessBeanDefinitionRegistry

BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor
  • MapperScannerConfigurer重写了postProcessBeanFactory和postProcessBeanDefinitionRegistry这两个方法,这两个方法都是BeanDefinitionRegistryPostProcessor接口的方法,用来在BeanDefinition阶段对Bean的定义进行修改或注册。在postProcessBeanFactory方法中,MapperScannerConfigurer什么也没有做,这是因为它不需要在这个阶段对Bean的定义进行修改,而是在postProcessBeanDefinitionRegistry方法中进行注册。

  • BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor这两者区别总结如下:更详细的可以自己另外查询资料

    BeanDefinitionRegistryPostProcessor允许在 BeanFactoryPostProcessor 之前注册其他的 BeanDefinition ,这个是重中之重!也就是 BeanDefinitionRegistryPostProcessor 的执行时机比 BeanFactoryPostProcessor 更早BeanFactoryPostProcessor 一般只用来修改、扩展 BeanDefinition 中的信息,而 BeanDefinitionRegistryPostProcessor 则可以在 BeanFactoryPostProcessor 处理 BeanDefinition 之前,向 BeanFactory 注册新的 BeanDefinition ,甚至注册新的 BeanFactoryPostProcessor 用于下一个阶段的回调。

这段话干说可能很难理解,我们来借助一张图理解:
img

这样就好理解多了吧!不过实际执行的机制跟上面的图还有一点点小差别:由于实现了 BeanDefinitionRegistryPostProcessor 的类同时也实现了 BeanFactoryPostProcessorpostProcessBeanFactory 方法,所以在执行完所有 BeanDefinitionRegistryPostProcessor 的接口方法后,会立即执行这些类的 postProcessBeanFactory 方法,之后才是执行那些普通的只实现了 BeanFactoryPostProcessorpostProcessBeanFactory 方法。

执行步骤

调用链路如下所示:

MapperScannerConfigurer#postProcessBeanDefinitionRegistry-->ClassPathBeanDefinitionScanner#registerFilters-->ClassPathBeanDefinitionScanner#scan(basePackages)-->ClassPathBeanDefinitionScanner#doScan-->ClassPathBeanDefinitionScanner#processBeanDefinitions-:核心目的就是definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName)和definition.setBeanClass(this.mapperFactoryBeanClass)

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {//该方法会解析配置文件中的占位符,例如${…},并替换为对应的值,这样就可以动态地设置basePackage等属性的值。if (this.processPropertyPlaceHolders) {processPropertyPlaceHol·ders();}@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {if (this.processPropertyPlaceHolders) {// 处理占位符,processPropertyPlaceHolders默认为false// 因为MapperScannerConfigurer先于Spring中处理占位符的BeanFactoryPostProcessor执行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);// 注册TypeFilter,也就是筛选条件scanner.registerFilters();// 扫描包,多个包名可以使用逗号分割scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
}

1、在postProcessBeanDefinitionRegistry方法中,MapperScannerConfigurer首先判断了processPropertyPlaceHolders是否为true,如果是则调用processPropertyPlaceHolders方法,该方法会解析配置文件中的占位符,例如${…},并替换为对应的值,这样就可以动态地设置basePackage等属性的值。

2、接下来,MapperScannerConfigurer创建了一个ClassPathMapperScanner对象,该对象是一个继承了ClassPathBeanDefinitionScanner的类,用来扫描指定路径下的类,并根据过滤条件生成和注册Bean的定义。

3、MapperScannerConfigurer将自己的属性值都设置到了ClassPathMapperScanner对象中,然后调用了其registerFilters方法,该方法会根据annotationClass,markerInterface等属性,创建相应的TypeFilter对象,并添加到ClassPathMapperScanner对象的过滤器列表中,这样就可以根据不同的条件来过滤和选择要扫描的映射器接口。

// 注册TypeFilter
public void registerFilters() {boolean acceptAllInterfaces = true;// 添加一个AnnotationTypeFilter,需要接口被此注解标注if (this.annotationClass != null) {addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));acceptAllInterfaces = false;}// 添加一个AssignableTypeFilter,需要接口继承markerInterface// AssignableTypeFilter会接受参数本身以及参数的派生类if (this.markerInterface != null) {addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {// 这里是去掉markerInterface自己@Overrideprotected boolean matchClassName(String className) {return false;}});acceptAllInterfaces = false;}// 如果没有指定TypeFilter筛选,注册一个接受报下所有接口的TypeFilterif (acceptAllInterfaces) {addIncludeFilter(new TypeFilter() {@Overridepublic boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {return true;}});}// exclude package-info.javaaddExcludeFilter(new TypeFilter() {@Overridepublic boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {String className = metadataReader.getClassMetadata().getClassName();return className.endsWith("package-info");}});
}

4、最后,MapperScannerConfigurer调用了ClassPathMapperScanner对象的scan方法,该方法会根据basePackage的值,扫描指定包下的所有类,然后根据过滤器列表,筛选出符合条件的映射器接口,为每个映射器接口创建一个MapperFactoryBean的BeanDefinition对象,并注册到BeanDefinitionRegistry中,这样就完成了映射器接口的自动扫描和注册。

ClassPathBeanDefinitionScanner的scan方法解析

ClassPathBeanDefinitionScanner#scan(basePackages)-->ClassPathBeanDefinitionScanner#doScan-->ClassPathBeanDefinitionScanner#processBeanDefinitions-:核心目的就是definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName)和definition.setBeanClass(this.mapperFactoryBeanClass)

// 这个完全就是父类ClassPathBeanDefinitionScanner的方法,以前讲过,因此不再展开说了,
// 主要关注子类重写的doScan方法
public int scan(String... basePackages) {int beanCountAtScanStart = this.registry.getBeanDefinitionCount();doScan(basePackages);if (this.includeAnnotationConfig) {AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);}return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

doScan方法:

  • 优先调用父类的super.doScan(basePackages),进行注册Bean信息,用于Mapper扫描和Bean的定义以及注册到DefaultListableBeanFactory。{DefaultListableBeanFactory是Spring中IOC容器的始祖,所有需要实例化的类都需要注册进来,之后在初始化}

  • findCandidateComponents(basePackage),扫描package包路径,对于注解类的有另外的方式,大同小异

  • registerBeanDefinition(definitionHolder, this.registry);注册Bean信息的过程,最终会调用到:org.springframework.beans.factory.support.DefaultListableBeanFactory

@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {// 调用父类ClassPathBeanDefinitionScanner的doScan方法// 获取所有接口的BeanDefinition,此时这些BeanDefinition已经被注册到容器中了// 但是还没有实例化,因此可以对这些BeanDefinition做些修改// 接下来就要对一个个的BeanDefinition加工了// 将这一个个的BeanDefinition转成代表BeanFactoryBean的BeanDefinition// BeanFactoryBean会为接口生成代理实现Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);if (beanDefinitions.isEmpty()) {logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");} else {processBeanDefinitions(beanDefinitions);}return beanDefinitions;
}protected Set<BeanDefinitionHolder> doScan(String... basePackages) {Assert.notEmpty(basePackages, "At least one base package must be specified");Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();for (String basePackage : basePackages) {Set<BeanDefinition> candidates = findCandidateComponents(basePackage);for (BeanDefinition candidate : candidates) {ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);candidate.setScope(scopeMetadata.getScopeName());String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);if (candidate instanceof AbstractBeanDefinition) {postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);}if (candidate instanceof AnnotatedBeanDefinition) {AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate)}if (checkCandidate(beanName, candidate)) {BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);definitionHolder =AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.regibeanDefinitions.add(definitionHolder);registerBeanDefinition(definitionHolder, this.registry);}}}return beanDefinitions;
}

processBeanDefinitions方法:

  • definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); 设置BeanName参数,也就是我们的:ISchoolDao、IUserDao

  • definition.setBeanClass(this.mapperFactoryBean.getClass());修改beanClass为MapperFactoryBean.class,接口本身是没有类的,那么这里将MapperFactoryBean类设置进来,最终所有的dao层接口类都是这个MapperFactoryBean,通过MapperFactoryBean的getObject()方法就能获取动态代理对象

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");}// 构造方法注入接口名字,此时definition.getBeanClassName()获取的是接口的完全限定名,如果看的仔细点知道MapperFactoryBean构造方法参数是接口Class对象,为什么传字符串就行了呢? 这是Spring在初始化实例时会将字符串根据PropertyEditor转化规则转成相对应的类型,将String -> Class的//PropertyEditor实现ClassEditor.在初始化SqlSessionFactoryBean时指定映射文件,也用的是字符串,但实际类型是Resource// 将String -> Resource是ResourceEditor的功能definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59// 修改beanClass为MapperFactoryBean.class,接口本身是没有类的,那么这里将MapperFactoryBean类设置进来,最终所有的dao层接口类都是这个MapperFactoryBeandefinition.setBeanClass(this.mapperFactoryBean.getClass());// 属性注入addToConfigdefinition.getPropertyValues().add("addToConfig", this.addToConfig);boolean explicitFactoryUsed = false;// 属性注入sqlSessionFactoryif (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {// 加入this.sqlSessionFactoryBeanName的值为sqlSessionFactory,那么// 相当于<property="sqlSessionFactory" ref="sqlSessionFactory" />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)) {// 同时使用sqlSessionTemplate、sqlSessionFactory时,根据sqlSessionFactory// 创建的sqlSession将被覆盖掉,这点可以去看MapperFactoryBean父类// SqlSessionDaoSupport的setSqlSessionFactory以及setSqlSessionTemplate方法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;}// SqlSessionFactory和sqlSessionTemplate都没有指定,开启自动根据类型注入。if (!explicitFactoryUsed) {if (logger.isDebugEnabled()) {logger.debug("Enabling autowire by type for MapperFactoryBean with"+ "name '" + holder.getBeanName() + "'.");}definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);}}
}

上面这个地方definition.setBeanClass(this.mapperFactoryBean.getClass())definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); 设置完毕以后,为什么就能够通过MapperFactoryBean的getObject()方法就能获取动态代理对象(也就是在哪里能起作用?)

这里其实也是跟spring生命周期过程中的createBeanInstance(实例化bean)有关:简化了步骤

1、beanDefinition.getBeanClass(): 获取我们设置好的MapperFactoryBean.class:
2、getInstantiationStrategy().instantiate(beanDefinition, beanName, constructorToUse, args) :根据MapperFactoryBean.class和对应的beanName进行实例化

    protected Object createBeanInstance(BeanDefinition beanDefinition, String beanName, Object[] args) {Constructor constructorToUse = null;//1、获取我们设置好的MapperFactoryBean.classClass<?> beanClass = beanDefinition.getBeanClass();Constructor<?>[] declaredConstructors = beanClass.getDeclaredConstructors();for (Constructor ctor : declaredConstructors) {if (null != args && ctor.getParameterTypes().length == args.length) {constructorToUse = ctor;break;}}//2、根据MapperFactoryBean.class和对应的beanName进行实例化return getInstantiationStrategy().instantiate(beanDefinition, beanName, constructorToUse, args);}

MapperScannerConfigurer和MapperFactoryBean的区别、联系

从上面的源码分析中,我们可以看出,MapperFactoryBean和MapperScannerConfigurer的区别和联系如下:

  • 区别:
    • MapperFactoryBean是一个实现了FactoryBean接口的类,它用来创建和初始化一个映射器接口的代理对象,并将其注册到Spring容器中。MapperScannerConfigurer是一个实现了BeanDefinitionRegistryPostProcessor接口的类,它用来自动扫描和注册映射器接口,而不需要为每个映射器接口配置一个bean。
    • MapperFactoryBean是在Bean实例化阶段执行的,它可以获取到Bean的引用,所以它可以直接设置sqlSessionFactory或sqlSessionTemplate的引用。MapperScannerConfigurer是在BeanDefinition阶段执行的,它不能获取到Bean的引用,所以它只能设置sqlSessionFactory或sqlSessionTemplate的bean名称。
    • MapperFactoryBean需要为每个映射器接口手动配置一个bean,如果映射器接口很多,那么配置文件会很冗长和繁琐,也不利于维护和修改。MapperScannerConfigurer只需要配置一个bean,就可以自动扫描和注册映射器接口,可以减少配置文件的长度和复杂度,也更容易修改和维护。
    • MapperFactoryBean不支持过滤条件,它会将所有配置的映射器接口都添加到Mybatis的配置中,不管它们是否有注解或继承了某个接口。MapperScannerConfigurer支持多种过滤条件,它可以根据注解、接口、父类等条件来过滤和选择要扫描的映射器接口,可以更灵活地控制映射器接口的注册。
  • 联系:
    • MapperFactoryBean和MapperScannerConfigurer都是用来让Spring识别和管理映射器接口的,它们都是Mybatis-Spring的核心组件,它们都依赖于sqlSession或sqlSessionTemplate的引用,它们都会将映射器接口添加到Mybatis的配置中,它们都会为映射器接口创建代理对象,并返回给service层。
    • MapperScannerConfigurer实际上是基于MapperFactoryBean的,它在扫描映射器接口时,会为每个映射器接口创建一个MapperFactoryBean的BeanDefinition对象,并注册到Spring容器中,所以MapperScannerConfigurer的最终效果和MapperFactoryBean是一样的,只是它省去了手动配置的步骤,而是通过自动扫描和过滤来实现。

总结

1. MapperScannerConfigurer是一个包扫描的配置类,其中basePackage属性是必需的,多个包时可以使用逗号隔开。sqlSessionFactoryBeanNamesqlSessionTemplateBeanName二选一即可。如果都选了那么根据sqlSessionFactoryBeanName引用的SqlSessionFactory创建的SqlSession会被覆盖掉。如果两个都没配置,将会根据类型自动装配,这种方式适合应用程序中只有一个SqlSessionFactory或者SqlSessionTemplat的bean存在。其它配置就不总结了。

2. 具体实现扫描以及注册接口的功能被委托给ClassPathMapperScanner类了。具体做法就是将Spring扫描接口后生成的BeanDefinition修改成一个表示MapperFactoryBeanBeanDefinition,而MapperFactoryBean是一个实现FactoryBean的特殊bean,相当于返回Mapper接口实例的工厂。具体返回接口实例的原理是MyBatis本身的接口绑定功能,底层其实是动态代理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/228178.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java 并发编程(六)-Fork/Join异步回调

一、并发编程 1、Fork/Join分支合并框架 Fork/Join它可以将一个大的任务拆分成多个子任务进行并行处理&#xff0c;最后将子任务结果合并成最后的计算结果&#xff0c;并进行输出。Fork/Join框架要完成两件事情&#xff1a; Fork&#xff1a;把一个复杂任务进行分拆&#xff0…

下午好~ 我的论文【CV边角料】(第三期)

文章目录 CV边角料Pixel ShuffleSENetCBAMGlobal Context Block (GC)Criss-Cross Attention modules (CC) CV边角料 Pixel Shuffle Real-Time Single Image and Video Super-Resolution Using an Efficient Sub-Pixel Convolutional Neural Network pixelshuffle算法的实现流…

EXCEL SUM类函数

目录 一. SUM二. SUMIF三. SUMIFS四. SUMPRODUCT 一. SUM ⏹对一列或一组单元格中的数字进行求和。 基本语法 SUM(number1, [number2], ...)✅统计所有产品的总数量 SUM(C2:C13) 二. SUMIF ⏹按照特定条件对范围内的单元格进行求和的函数。 基本语法 SUMIF(条件区域, 指定…

山西电力市场日前价格预测【2023-12-16】

日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2023-12-16&#xff09;山西电力市场全天平均日前电价为259.00元/MWh。其中&#xff0c;最高日前电价为333.74元/MWh&#xff0c;预计出现在18:00。最低日前电价为0.00元/MWh&#xff0c;预计出…

C语言训练:三个字符串比较大小,实现两个整数数的交换统计二进制中1的个数

目录 一、编写程序&#xff0c;输入三个字符串&#xff0c;比较它们的大小&#xff0c;并将它们按由小到大的顺序输出。要求用函数、指针实现。要求:要采用函数调用&#xff0c;并用指向函数的指针作为函数的参数。 1.不使用函数指针作为参数&#xff0c;并自己模拟strcmp。 …

001 Windows虚拟机

一、虚拟机安装Windows10 选自定义安装 升级是针对你电脑上有系统的情况下&#xff0c;你要升级&#xff1b;没有系统就选择自定义。 硬盘60G 直接单击下一步就是一个盘 如果你想对磁盘进行分区 分第一个区的时候它会去创建系统的保留分区和系统分区&#xff0c;然后还剩20…

流量分析基础

定义&#xff1a; 流量分析&#xff08;Traffic Analysis&#xff09;是指对网络流量数据进行分析和解释&#xff0c;以获得有关网络中通信的信息和情报。这种技术可以用于网络安全、网络管理和网络优化等领域。 网络流量包含了许多有关网络通信的细节信息&#xff0c;如源IP地…

Linux c++开发-06-使用Linux API 进行文件的读写

先简单的介绍一下open,read,write 先用open接口去打开文件&#xff0c;flag表示打开文件的权限不同。 int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode);示例 结果&#xff1a;

经典深度学习算法【1】:K-近邻算法(KNN)概述

最简单最初级的分类器是将全部的训练数据所对应的类别都记录下来&#xff0c;当测试对象的属性和某个训练对象的属性完全匹配时&#xff0c;便可以对其进行分类。但是怎么可能所有测试对象都会找到与之完全匹配的训练对象呢&#xff0c;其次就是存在一个测试对象同时与多个训练…

迭代器的分类

迭代器的分类&#xff1a; 这里的前置后置递增是a和a&#xff1b; 这里的前值后置递减是a--和--a&#xff1b; 各迭代器的继承关系&#xff1a; 当使用双向迭代器时&#xff0c;可以使用随机迭代器&#xff1b;

THEMIS---Beta Sprint Summary Essay Blog

Which course does this assignment belong to2301-MUSE社区-CSDN社区云What are the requirements for this assignmentbeta SprintThe goal of this assignmentTo summarize the beta task progress and the teams sprintsTeam NameThemisTop-of-the-line collection of essa…

maui中实现加载更多 RefreshView跟ListView 跳转到详情页 传参(3)

效果如图 这里的很多数据是通过传参过来的的。 代码 例表页加入跳转功能&#xff1a; <ListView ItemsSource"{Binding Items}" ItemAppearing"OnItemAppearing" ItemTapped"OnItemTapped" RowHeight"70" Margin"20"…

visual studio 2019 移除/卸载项目已经如何再加载项目

文章目录 移除解决方案下的某个项目添加已移除的项目移除项目加载已卸载的项目注意事项 移除解决方案下的某个项目 在项目名称上&#xff0c;点击鼠标右键&#xff0c;弹出右键工具栏&#xff0c;找到 移除 功能。 然后鼠标左键点击 移除。 弹出的模态框&#xff0c;选择确定…

代码随想录刷题题Day15

刷题的第十五天&#xff0c;希望自己能够不断坚持下去&#xff0c;迎来蜕变。&#x1f600;&#x1f600;&#x1f600; 刷题语言&#xff1a;C Day15 任务 ● 513.找树左下角的值 ● 112. 路径总和 113.路径总和ii ● 106.从中序与后序遍历序列构造二叉树 105.从前序与中序遍历…

MYSQL备份和恢复

数据库的备份和恢复&#xff1a; 备份&#xff1a;完全备份 增量备份 完全备份&#xff1a;将整个数据库完整的进行备份 增量备份&#xff1a;在完全备份的基础之上&#xff0c;对后续新增的内容进行备份 备份的需求&#xff1a; 在生产环境中&#xff0c;数据的安全至关重…

Docker安装Redis哨兵

目录 Redis哨兵 一、哨兵模式的主要概念和组件 二、哨兵模式的工作流程 三、哨兵配置流程 1、创建Redis哨兵配置文件 2、启动哨兵 3、命令解读 4、 查看哨兵是否正常启动 5、测试主机宕机 四、哨兵运行流程 五、哨兵选举算法 六、哨兵使用建议 Redis哨兵 Redis哨兵…

josef约瑟 时间继电器 DS-23/C AC220V 10S柜内板前接线

系列型号&#xff1a; DS-21时间继电器 &#xff1b;DS-22时间继电器&#xff1b; DS-23时间继电器&#xff1b;DS-24时间继电器&#xff1b; DS-21C时间继电器&#xff1b;DS-22C时间继电器&#xff1b; DS-23C时间继电器&#xff1b; DS-25时间继电器&#xff1b;DS-26…

python/c++ Leetcode题解——746. 使用最小花费爬楼梯

目录 方法一&#xff1a;动态规划 复杂度分析 方法一&#xff1a;动态规划 假设数组 cost 的长度为 n&#xff0c;则 n 个阶梯分别对应下标 0 到 n−1&#xff0c;楼层顶部对应下标 n&#xff0c;问题等价于计算达到下标 n 的最小花费。可以通过动态规划求解。 创建长度为 n…

孩子都能学会的FPGA:第三十三课——用FPGA实现一个通用的SPI主机接收模块

&#xff08;原创声明&#xff1a;该文是作者的原创&#xff0c;面向对象是FPGA入门者&#xff0c;后续会有进阶的高级教程。宗旨是让每个想做FPGA的人轻松入门&#xff0c;作者不光让大家知其然&#xff0c;还要让大家知其所以然&#xff01;每个工程作者都搭建了全自动化的仿…

如何从 iPhone 上恢复已删除的照片教程分享

您是否错误地删除了 iPhone 上的错误照片&#xff1f;或者您可能已将手机恢复出厂设置&#xff0c;但现在所有照片都消失了&#xff1f;如果您现在遇到这样的情况&#xff0c;我们可以为您提供解决方案。 在本文中&#xff0c;我们将向您展示七种数据恢复方法&#xff0c;可以…