Spring与MyBatis整合源码分析

   日期:2020-10-04     浏览:87    评论:0    
核心提示:Spring与MyBatis整合原理先来说说我们通常使用在Spring环境中使用MyBatis的步骤:加上mybatis-spring的maven坐标 在启动类上加@MapperScan注解,在注解中标明你要扫描的mapper的包路径 在service上使用spring的@Autowired注解把想要的mapper注入进service中可能大家一直觉得挺神奇的一点就是,在我们不在spring环境中单独使用MyBatis的时候,mapper对象是我们手动去通过调用sqlsession.g.

Spring与MyBatis整合原理

先来说说我们通常使用在Spring环境中使用MyBatis的步骤:

  • 加上mybatis-spring的maven坐标
  • 在启动类上加@MapperScan注解,在注解中标明你要扫描的mapper的包路径
  • 在service上使用spring的@Autowired注解把想要的mapper注入进service中

 可能大家一直觉得挺神奇的一点就是,在我们不在spring环境中单独使用MyBatis的时候,mapper对象是我们手动去通过调用sqlsession.getMapper得到mapper接口的代理对象,那么也就是说这个代理对象的创建过程是由我们自己来完成的,但是当与spring整合之后,我们只需要在mapper属性对象上面加上@Autowired注解就能把mapper接口的代理对象注入进来了,而spring并不知道怎么去创建我们的代理对象的吧,为什么这个对象会被spring所创建出来放到容器中呢?其实它就是使用了spring中的FactoryBean组件去实现的。

@MapperScan注解

我们知道我们需要在配置类上面加上@MapperScan注解去扫描我们的mapper接口,所以这个注解的工作肯定是实现mapper代理对象注入到spring容器中的核心。

可以打看到这个注解里面使用了@Import注解去把MapperScannerRegister这个类放到容器中去,点进去这个类里面

可以发现该类不是简单的类,而是实现了spring的ImportBeanDefinitionRegistrar接口,熟悉spring的同学应该知道@Import注解有3种用法,其中这种用法是通过接口暴露出的BeanDefinitionRegistry对象去put一个BeanDefinition(之后spring实例化对象的时候就是根据BeanDefinition来实例化的),简单地说就是手动地往spring里面去注册一个对象吧。

接下来主要去看它实现的接口方法registerBeanDefinition

@Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

    // this check is needed in Spring 3.1
    if (resourceLoader != null) {
      scanner.setResourceLoader(resourceLoader);
    }

    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      scanner.setAnnotationClass(annotationClass);
    }

    Class<?> markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
      scanner.setMarkerInterface(markerInterface);
    }

    Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
      scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
    }

    Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
      scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
    }

    scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
    scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));

    List<String> basePackages = new ArrayList<String>();
    for (String pkg : annoAttrs.getStringArray("value")) {
      if (StringUtils.hasText(pkg)) {
        basePackages.add(pkg);
      }
    }
    for (String pkg : annoAttrs.getStringArray("basePackages")) {
      if (StringUtils.hasText(pkg)) {
        basePackages.add(pkg);
      }
    }
    for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
      basePackages.add(ClassUtils.getPackageName(clazz));
    }
    scanner.registerFilters();
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }

可以看到这里主要是读取了注解上面的一些属性,然后给一个ClassPathMapperScanner对象去设置这些属性值,那么这个ClassPathMapperScanner对象的是什么来的呢?这个对象的父类在spring里面及其重要,因为它的父类就是用来进行包扫描的,把扫描到的类封装成一个个的BeanDefinition放在了spring的BeanDefinitionMap里面,其中的细节是spring的核心知识点了,这里就不展开说了。而上面的代码重点是scanner.doScan,在看这句代码之前我们先来看下ClassPathMapperScanner的类继承结构图:

其中ClassPathScanningCandidateComponentProvider和ClassPathBeanDefinitionScanner这两个类是spring里面提供的,ClassPathMapperScanner是mybatus-spring包提供的,我们先看它的初始化方法:

ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
public ClassPathMapperScanner(BeanDefinitionRegistry registry) {
    super(registry, false);
  }

 这里的重点就是它的构造方法里面调用了父类的构造方法,并且把父类的useDefaultFilters属性设置成false,这个useDefaultFilters的作用就是是否启动spring里面默认的扫描规则(其中就包括了扫描@Component注解的filter),这里设置了false表示不启动:

protected void registerDefaultFilters() {
		this.includeFilters.add(new AnnotationTypeFilter(Component.class));
		ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
		try {
			this.includeFilters.add(new AnnotationTypeFilter(
					((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
			logger.debug("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
		}
		catch (ClassNotFoundException ex) {
			// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
		}
		try {
			this.includeFilters.add(new AnnotationTypeFilter(
					((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
			logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
		}
		catch (ClassNotFoundException ex) {
			// JSR-330 API not available - simply skip.
		}
	}

进去它的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;
  }

可以看到这个方法是重写了父类的doScan方法,进去父类的doScan方法:

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
		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.registry);
					beanDefinitions.add(definitionHolder);
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}

这个方法最关键的就是findCandidateComponents(basePackage),而这个方法是ClassPathBeanDefinitionScanner的父类ClassPathScanningCandidateComponentProvider提供的,功能就是扫描你提供的包路径下面所有的类(默认是扫描加上了有@Component注解的类,但是上面我们把useDefaultFilter设置为false所以该过滤规则不存在了)并且封装成一个个的BeanDefinition放在了一个set集合里面,ClassPathBeanDefinitionScanner的doScan方法拿到这个set集合之后,然后把集合里面的BeanDefinition用BeanDefinitionRegistry进行注册(put到容器中的BeanDefinitionMap中),但是仔细一想我们不是只需要扫描这个路径下面的接口类吗,那么肯定有个地方是判断扫描结果的吧,没错,ClassPathScanningCandidateComponentProvider的findCandidateComponents方法里面调用了一个isCandidateComponent方法用来判断扫描出来的类是否符合自己想要的规则

protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
		AnnotationMetadata metadata = beanDefinition.getMetadata();
		return (metadata.isIndependent() && (metadata.isConcrete() ||
				(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
	}

而ClassPathMapperScanner 重写了这个方法,定义了自己对于扫描出来的类是够符合自己想要的规则

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

 可以看到这里定义了扫描出来的类必须是一个接口类型的才会被注册进容器中。

所以综上所述

Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

这句代码的意思就是把指定的包名下面的所有接口类都扫描出来变成一个个的BeanDefinition并且都放进了容器的BeanDefinitionMap中。

@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;
  }

因此就来到了processBeanDefinition方法了,我们上面说过mybatis与spring整合的原理就是利用了FactoryBean生成动态代理的接口对象,但是我们上面说了这么多都还没讲到FactoryBean,直到这句代码,FactoryBean就登场了。

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      String beanClassName = definition.getBeanClassName();
      LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName()
          + "' and '" + beanClassName + "' mapperInterface");

      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // 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) {
        LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
  }

我们可以看到这个方法把我们已经put进BeanDefinitionMap的每个BeanDefinition对象都拿出来然后里面有一句代码

definition.setBeanClass(this.mapperFactoryBean.getClass());

那么这个this.mapperFactoryBean是什么呢?

Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
      scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
    }

它是在创建scanner的时候读取@MapperScan注解的factoryBean属性去拿到的,而@MapperScan注解的factoryBean属性默认就是MapperFactoryBean

Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;

所以把原来的BeanDefinition对象的BeanClass属性换成了MapperFactoryBean,所以在根据这个BeanDefinition对象去实例化的时候就会实例化吃MapperFactoryBean的实例,但是由于MapperFactoryBean是一个FactoryBean,所以实例化的过程与普通的对象不一样,这个过程会调用它的getObject方法去实例化真正的我们想要的对象。

还有上面的definition.getPropertyValues().add()就是在实例化MapperFactoryBean的时候把这些属性设置到MapperFactoryBean里面去。

接下来我们来看下MapperFactoryBean的getObject方法

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

 这句代码最终调用了getMapper方法去产生代理对象,深入看下可以看到来到了SqlSessionTemplate类中

@Override
  public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
  }

最终还是调用了mybatis源码里面的Configuration对象产生接口的动态代理对象并返回,此时产生的动态代理对象最终就会放进了spring的单例缓冲池中了,所以我们就能在类里面去进行注入对象了。

总结:整个流程的话很清晰,就是mybatis-spring这个包里面有一个ClassPathMapperScan类,这个类重写了ClassPathBeanDefinition类,目的就是为了能够利用ClassPathBeanDefinition类里面的doScan方法去进行扫描,而且还重写了里面的isCandidateComponent方法去对扫描出来的BeanDefinition对象进行一个过滤(只需要接口类型的BeanDefinition),在调用完了父类的doScan方法后此时符合条件的BeanDefinition已经被put到了spring容器中的BeanDefinitionMap中了,但是之后执行的processBeanDefinition方法又对刚才扫描到并且已经注册进BeanDefinitionMap中的BeanDefinition进行一个加工处理,使得每一个BeanDefinition里面的beanClass属性都为MapperFactoryBean,而MapperFactoryClass这个类又是实现了FactoryBean接口,所以在实例化的时候会调用它的getObject方法产生我们的动态代理对象(这个就是我们上面说的对象实例化的过程完成由我们自己去掌控)并且放进了spring容器中。其实整个过程的核心思想第一就是能够干预到spring的BeanDefinitionMap,因为后续的实例化bean都是根据BeanDefinitionMap里面的BeanDefinition对象来进行的,所以能够干预到BeanDefinition的话我们还可以想到的就是BeanDefinitionRegistryPostProcessor,创建一个类继承自该类,利用该类暴露出的BeanDefinitionRegistry对象就能干预到BeanDefinitionMap了,其实mybatis-spring包中还有一个类叫MapperScannerConfigurer,该类就是使用的上面的思路去干扰了BeanDefinitionMap的,但是这个类一般是用在mybatis与spring整合的xml配置中,而注解版的配置并没有使用到;第二就是利用了FactoryBean这个组件的特性,使得对象实例化的主动权完全交托在我们开发者手里然后再把实例化完的对象放进spring容器中,这样就能实现利用自己的一套实例化对象逻辑去实例化对象并且注入到了容器中的效果了。

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服