MyBatis 整合Spring解析 【侧重mapper怎么变成bean】

前提:

已经熟悉mybatis机制,或者熟悉源码原理等等。
比如mapper最后是一个mapperProxyBean()

开始

基于spring提供的接口,ImportBeanDefinitionRegistrar
接口介绍:

  • ImportBeanDefinitionRegistrar类只能通过其他类@Import的方式来加载,通常是启动类或配置类。(在mybatis 通过Mapper Scan 注解实现)
  • 使用@Import,如果括号中的类是ImportBeanDefinitionRegistrar的实现类,则会调用接口方法,将其中要注册的类注册成bean。
  • 实现该接口的类拥有注册bean的能力。

官方说明:


/**
 * Interface to be implemented by types that register additional bean definitions when
 * processing @{@link Configuration} classes. Useful when operating at the bean definition
 * level (as opposed to {@code @Bean} method/instance level) is desired or necessary.
 *
 * <p>Along with {@code @Configuration} and {@link ImportSelector}, classes of this type
 * may be provided to the @{@link Import} annotation (or may also be returned from an
 * {@code ImportSelector}).
 *
 * <p>An {@link ImportBeanDefinitionRegistrar} may implement any of the following
 * {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective
 * methods will be called prior to {@link #registerBeanDefinitions}:
 * <ul>
 * <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li>
 * <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}
 * <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}
 * <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}
 * </ul>
 *
 * <p>Alternatively, the class may provide a single constructor with one or more of
 * the following supported parameter types:
 * <ul>
 * <li>{@link org.springframework.core.env.Environment Environment}</li>
 * <li>{@link org.springframework.beans.factory.BeanFactory BeanFactory}</li>
 * <li>{@link java.lang.ClassLoader ClassLoader}</li>
 * <li>{@link org.springframework.core.io.ResourceLoader ResourceLoader}</li>
 * </ul>
 *
 * <p>See implementations and associated unit tests for usage examples.
 *
 * @author Chris Beams
 * @author Juergen Hoeller
 * @since 3.1
 * @see Import
 * @see ImportSelector
 * @see Configuration
 */

可以看下这篇文章的介绍死磕源码系列【ImportBeanDefinitionRegistrar源码详解】

这是spring提供的一个默认的bean注册接口,容器启动的时候会调用这个接口,且接口两个方法会注册其中的bean定义。 同时 会吧BeanDefinitionRegistry 这东西交出来。

在这个接口 注册了一个bean MapperScannerConfigurer 这是一个BeanFactoryPostProcessor。

我们知道 其他框架整合spring 基本都是使用BeanFactoryPostProcessor

这个类只有一个方法

/**
	 * Modify the application context's internal bean definition registry after its
	 * standard initialization. All regular bean definitions will have been loaded,
	 * but no beans will have been instantiated yet. This allows for adding further
	 * bean definitions before the next post-processing phase kicks in.
	 * @param registry the bean definition registry used by the application context
	 * @throws org.springframework.beans.BeansException in case of errors
	 */
	void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;

so,可以通过这个方法注册bean。

看看mybatis 对这个方法做了什么?

  • 代码
@Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

    // 扫描器,继承自spring ClassPathBeanDefinitionScanner
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    // 设置类的部分属性,大部分是与mybatis 有关,与spring 扫描bean 无关
    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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
    if (StringUtils.hasText(lazyInitialization)) {
      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
    }
    if (StringUtils.hasText(defaultScope)) {
      scanner.setDefaultScope(defaultScope);
    }
    // 注册拦截器,影响spring 扫描结果
    scanner.registerFilters();
    // 开始扫描,真正实现 doScan 
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

方法调用了一个扫描方法,且将注册器对象传给他就结束了。
扫描器是spring的实现

  • Mapper 扫描器 ClassPathMapperScanner 分析

看看doScan ()

 @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    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;
  }

发现他拿到了父类扫描的bean 定义 然后调用了processBeanDefinitions

 definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
      definition.setBeanClass(this.mapperFactoryBeanClass);

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

      definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName);

      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) {
        }
        definition.getPropertyValues().add("sqlSessionTemplate",
            new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionTemplate != null) {
        if (explicitFactoryUsed) {
        }
        definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
        explicitFactoryUsed = true;
      }

      if (!explicitFactoryUsed) {
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }

      definition.setLazyInit(lazyInitialization);

      if (scopedProxy) {
        continue;
      }

      if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null) {
        definition.setScope(defaultScope);
      }

这些bean 定义对象都被做了增强。class 设置了factoryBean。~~~

要知道spring对factoryBean的处理是getBean的时候会去factory的getObject 方法获取的
如果想要获取factoryBean本身,需要添加&符号。
因此,在getMapper的时候,是调用的MapperFactoryBean的Getobject
看看getObject


  @Override
  public T getObject() throws Exception {
  // 实际就是从sqlsession 中 mapperResister 中获取mapper. 
    return getSqlSession().getMapper(this.mapperInterface);
  }
 public SqlSession getSqlSession() {
    return this.sqlSessionTemplate;
  }
 
 // sqlTemplate 其实就是sqlSession
 public class SqlSessionTemplate implements SqlSession, DisposableBean {}
  
  • 那么是如何将之注册成BeanDefinition 的呢?
    这要依靠ClassPathMapperScanner 的父类。
    因为刚刚看到在执行完doscan内没有做任何的register的操作,doScan 只是坐了一些beanDef的增强,那么这个beanDef 肯定就是在父类中被注册了。
    往上追一下代码:
//org.springframework.context.annotation.ClassPathBeanDefinitionScanner#scan
// 看看 父类的scan、
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

// 父类扫描过程.
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
		// 扫描所有的基础包
		for (String basePackage : basePackages) {
		// 获取所有可以使用的beanDef
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			// 单独操作每一个beanDef
			for (BeanDefinition candidate : candidates) {
				// 删除部分代码,不影响看主流程
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					definitionHolder =
							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					// 这是最终返回的beanDefs 即我们子类可以操作的defintion 对象
					beanDefinitions.add(definitionHolder);
					// 注册 bean 定义对象
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}

大概就这样吧, 语言比较凌乱,记录一个思路。如果想自己实现一套mybatis 和spring整合的框架大体 思路也是这样

  • 思考一个问题?
    为什么doscan 中会扫描mapper ? 不是什么类都能扫描? 默认是只扫描类的!
    在源代码中看到:
    org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents
    在这里插入图片描述
if (isCandidateComponent(sbd)) {
							
	candidates.add(sbd);
}

而当前this 是: this = > classpathmapperscanner
因此只要方法 isCandidateComponent 返回true 该类就会被扫描到

因此重写方法即可。
mb 的实现:

 @Override
  protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
  }

第一个判断是是否是接口?
第二个判断只要不是静态内部类都返回true。
因此,我们可以接口里边套接口,,测试是可以被读取的。比较弱智,除了写的时候省事阅读性并不好。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值