MyBatis Spring整合源码解析

MapperScan源码分析?mapperScan如何生效的?

在项目中大多数用的都是@MapperScan注解,指定basePackages,扫描mybatis Mapper接口类,另外一种方式是用@Mapper注解,其实这两种方法扫描配置用的是一个地方,只是扫描入口不同。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
//自动配置
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
}

怎样让Spring 加载自己自定义的注解?

@MapperScan中加入Spring的注解@Import ,即导入一个配置类MapperScannerRegister,@MapperScan是根据其注解上MapperScannerRegistrar进行自动配置的,这个类会被Spring加载,Spring发现他实现了ImpoerBeanDefinitionRegistar这个接口,就会调用这个registerBeanDefinition的方法,顾名思义就是按照Spring的规则,注册自己的bean definition到Spring 的注册机中


怎样找到了mapper? 又是怎样将mapper 注册到 spring 容器中

下面就是自己实现的类扫描ClassPathMapperScanner,继承自Spring原生的ClassPathBeanDefinitionScanner

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  private ResourceLoader resourceLoader;

  /**
   * {@inheritDoc}
   */
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    //拿到MapperScan类里面的属性
    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
   //自定义注册机registry
    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#doScan方法调用父类ClassPathBeanDefinitionScanner#doScan来实现扫描并注册到spring ioc中,关于processBeanDefinitions方法后面我们会分析。

  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    //调用父类ClassPathBeanDefinitionScanner#doScan来实现扫描并注册到spring ioc中
    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) {
            //获取配置的路径下面所有的mapper全限定类名
			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);
				//如果是基包下的一个抽象类,我们配置的mapper接口如果不是注解方式也会走这处理
				if (candidate instanceof AbstractBeanDefinition) {
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}

				//如果基包下的类是有注解的
				if (candidate instanceof AnnotatedBeanDefinition) {
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
            //是否beanName查看是否存在以及注册过
				if (checkCandidate(beanName, candidate)) {
                     //包装一下
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					definitionHolder =
							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                    //加入到当前列表中
					beanDefinitions.add(definitionHolder);
                   //注册到IOC中
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}

我们使用MapperScan注解时,会将包路径一起设置进入。通过扫描MapperScan注解的类,通过AnnotationConfigUtils.processCommonDefinitionAnnotations来解析。然后包装为BeanDefinitionHolder,然后注册到Spring IOC里面

	protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
//如果spring中没注册这个beanName对应的bean那么就是mybaits需要的类,一般mybaits的mapper类是一个接口不会被spring实例化加载到IOC容器中
		if (!this.registry.containsBeanDefinition(beanName)) {
			return true;
		}
//如果spring中有这个类的bean定义
		BeanDefinition existingDef = this.registry.getBeanDefinition(beanName);
		BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition();
		if (originatingDef != null) {
			existingDef = originatingDef;
		}

		//如果重复扫描此类或者或者重复获取同一个类的源文件或者是其它类的子类则是不兼容的,这样的类是不符合mybaits加载要求返回false
		if (isCompatible(beanDefinition, existingDef)) {
			return false;
		}
		throw new ConflictingBeanDefinitionException("Annotation-specified bean name '" + beanName +
				"' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " +
				"non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]");
	}

回过头来看看ClassPathMapperScanner#doScan#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");
      }

      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is 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() + "'.");
        }
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
  }

这就是开始注册自己的bean ,在这里发现获取到spring 给的后,类名没有修改,但是将类修改成了MapperFactoryBean, 即修改了具体的实现。通过构造器注入了原来的mapper的类名,下面开始往MapperFactoryBean里注入数据源等属性,这就是Spring bean 定义的修改器。
 

既然通过mapper类名这个ID, 找到的是MapperFactoryBean ,那这个类有什么用呢?

其实这个bean只是一个工厂方法,实现了Spring 定义的FactoryBean,所有得调用他的getObject()方法,才是真正注入的对象。

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
     @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }
}

Spring发现这个bean是一个 工厂bean,会直接获取它真实的 bean.那这个sqlSession.getMapper(mapper接口类) 返回的对象是什么呢?

//sqlsession getMapper方法,返回Configuration的getMapper方法 
@Override
  public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }
//Configuration getMapper方法
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

发现每个Mapper 都映射成MapperProxyFactory,  顾名思义就是mapper的代理工厂,具体的对象就是返回一个代理对象

 protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

MapperProxy通过method找到mapperMethod,每个mapper 的方法都映射成一个MapperMethod.  MapperMethod 有sqlCommand ,有他来执行方法。

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

最后调用execute方法转换成sqlSession执行对应的crud。所以mapper最后还是转到了SqlSession(Configuration,Executor具体执行者)

 

我们再来看看MapperFactoryBean的checkDaoConfig函数,看它是怎么将DAO关联到对应的mapper.xml的:

  @Override
  protected void checkDaoConfig() {

	//调用父类同名方法
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

//获取sqlSessionFactoryBean中配置文件在java中的对象Configuration
    //如果没有mapperInterface的定义则加入此mapper的定义
    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

这样Mapper接口就被注册到sqlSession中,每当操作sqlSession时就会根据这个mapperInterface去查找对应的mapper.xml构建mapper从而完成数据库操作。经过以上过程mybaits找到了所有mapper并将其加载到了spring ioc容器里。


 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring整合Mybatis码分析可以分为以下几个步骤: 1. 创建Spring容器并加载配置文件。在Spring Boot中,可以通过@SpringBootApplication注解来创建Spring容器,并在配置文件中配置Mybatis相关的属性。 2. 创建Mybatis的SqlSessionFactory。Spring Boot会自动配置Mybatis的SqlSessionFactory,通过读取配置文件中的数据信息和Mybatis的配置信息,创建SqlSessionFactory对象。 3. 注册Mybatis的Mapper接口。Spring Boot会自动扫描项目中的Mapper接口,并将其注册到Spring容器中。 4. 创建Mapper代理对象。Spring Boot使用Mybatis的MapperFactoryBean来创建Mapper接口的代理对象。在创建代理对象时,会使用SqlSessionFactory来创建SqlSession,并将SqlSession注入到Mapper接口中。 5. 使用Mapper代理对象进行数据库操作。通过调用Mapper接口的方法,可以实现对数据库的增删改查操作。 整个过程中,Spring Boot通过自动配置和注解扫描的方式,简化了Spring整合Mybatis的配置和使用过程,使得开发者可以更方便地使用Mybatis进行数据库操作。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* [SpringBoot整合Mybatis解析](https://blog.csdn.net/u013521882/article/details/120624374)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Spring-Mybatis整合码分析](https://blog.csdn.net/qq_42651904/article/details/111059652)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Spring解析整合Mybatis](https://blog.csdn.net/heroqiang/article/details/79135500)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值