spring的启动过程03.2-集成mybatis

概述:

讲述bean工厂后置处理器之spring与mybatis的集成原理,首先我们来看下spring集成mybatis的方式

	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<property name="basePackage" value="com.xx.pay.dao" />
	</bean>

定义MapperScannerConfigurer并指定dao包路径

	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"
		p:dataSource-ref="dataSource" p:configLocation="classpath:config/mybatis-config.xml"
		p:mapperLocations="classpath:mapper/*.xml" />
定义SqlSessionFactoryBean并指定mybatis的个性化配置文件及mapper路径


原理:

首先看下MapperScannerConfigurer类静态结构图

从接口的实现可以看出MapperScannerConfigurer类属于bean工厂后置处理器,该类被触发的时机请看《spring的启动过程03-工厂后置处理器》。

直接跟踪核心逻辑:

  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }
	//创建Mapper扫描器
    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();
	//执行核心逻辑,扫描包路径下的dao并生成对应的BeanDefinition注册到bean工厂
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }	

手动创建ClassPathMapperScanner对象,由该对象执行主逻辑,该类静态结构图如下:


继承了org.springframework.context.annotation.ClassPathBeanDefinitionScanner类,该类非常重要在spring解析context自定义命名空间过程中也会用到此类。

后续会写context命名空间的原理,请后续关注。

首先分析scanner.registerFilters()方法:

  /**
   * Configures parent scanner to search for the right interfaces. It can search
   * for all interfaces or just for those that extends a markerInterface or/and
   * those annotated with the annotationClass
   * 增加扫描类型过滤
   */
  public void registerFilters() {
    boolean acceptAllInterfaces = true;

    // if specified, use the given annotation and / or marker interface
    if (this.annotationClass != null) {
      addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
      acceptAllInterfaces = false;
    }

    // override AssignableTypeFilter to ignore matches on the actual marker interface
    if (this.markerInterface != null) {
      addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
        @Override
        protected boolean matchClassName(String className) {
          return false;
        }
      });
      acceptAllInterfaces = false;
    }

    if (acceptAllInterfaces) {
      // default include filter that accepts all classes
      addIncludeFilter(new TypeFilter() {
        @Override
        public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
          return true;
        }
      });
    }

    // exclude package-info.java
    addExcludeFilter(new TypeFilter() {
      @Override
      public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        String className = metadataReader.getClassMetadata().getClassName();
        return className.endsWith("package-info");
      }
    });
  }
继续查看核心类org.springframework.context.annotation.ClassPathBeanDefinitionScanner

	/**
	 * Perform a scan within the specified base packages.
	 * @param basePackages the packages to check for annotated classes
	 * @return number of beans registered
	 */
	public int scan(String... basePackages) {
		int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
		
		doScan(basePackages);

		// Register annotation config processors, if necessary.
		if (this.includeAnnotationConfig) {
			AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
		}

		return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
	}
跟踪 doScan(basePackages)

  /**
   * Calls the parent search that will search and register all the candidates.
   * Then the registered objects are post processed to set them as
   * MapperFactoryBeans
   */
  @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;
  }
继续看super.doScan(basePackages)。。。

	/**
	 * Perform a scan within the specified base packages,
	 * returning the registered bean definitions.
	 * <p>This method does <i>not</i> register an annotation config processor
	 * but rather leaves this up to the caller.
	 * @param basePackages the packages to check for annotated classes
	 * @return set of beans registered if any for tooling registration purposes (never {@code null})
	 */
	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.registry);
					beanDefinitions.add(definitionHolder);
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}
继续看findCandidateComponents方法

	/**
	 * Scan the class path for candidate components.
	 * @param basePackage the package to check for annotated classes
	 * @return a corresponding Set of autodetected bean definitions
	 */
	public Set<BeanDefinition> findCandidateComponents(String basePackage) {
		Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
		try {
			String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
					resolveBasePackage(basePackage) + "/" + this.resourcePattern;
			//获取包路径下所有的class资源,这里的包路径可以直接配置到dao层,减少其它类的扫描页可以重写该类指定annotationClass
			//在dao类上加上该注解
			Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
			for (Resource resource : resources) {
				if (resource.isReadable()) {
					try {
						MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
						//判断是否符合要求
						if (isCandidateComponent(metadataReader)) {
							//定义BeanDefinition对象
							ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
							sbd.setResource(resource);
							sbd.setSource(resource);
							if (isCandidateComponent(sbd)) {
								candidates.add(sbd);
							}
						}
					}
					catch (Throwable ex) {
					}
				}
			}
		}
		catch (IOException ex) {
		}
		return candidates;
	}
继续看如何判断某个类符合要求

	/**
	 * Determine whether the given class does not match any exclude filter
	 * and does match at least one include filter.
	 * @param metadataReader the ASM ClassReader for the class
	 * @return whether the class qualifies as a candidate component
	 */
	protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
		for (TypeFilter tf : this.excludeFilters) {
			if (tf.match(metadataReader, this.metadataReaderFactory)) {
				return false;
			}
		}
		for (TypeFilter tf : this.includeFilters) {
			if (tf.match(metadataReader, this.metadataReaderFactory)) {
				return isConditionMatch(metadataReader);
			}
		}
		return false;
	}
跟踪到这里spring已经完成了对符合要求的class的加载转换为BeanDefinition,完成了第一步,接着往下看如何对BeanDefinition的装饰

查看processBeanDefinitions(beanDefinitions)方法

  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      // 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
	  //这个BeanDefinition实际上是个org.mybatis.spring.mapper.MapperFactoryBean工厂bean
      definition.setBeanClass(this.mapperFactoryBean.getClass());
      definition.getPropertyValues().add("addToConfig", this.addToConfig);
      boolean explicitFactoryUsed = false;
	  // 增加sqlSessionFactory属性,这个可以手动指定,如果为空会自动采用配置的org.mybatis.spring.SqlSessionFactoryBean实例
      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)) {
        definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionTemplate != null) {
        definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
        explicitFactoryUsed = true;
      }
      if (!explicitFactoryUsed) {
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
  }
上面的方法说明了 DAO接口在spring容器中对应的BeanDefinition结构为beanName为dao的名称,beanClass为org.mybatis.spring.mapper.MapperFactoryBean工厂Bean

在实际使用的server例子

public class BankPayServiceImpl implements IBankPayService {
	@Resource
	private IBankPayDao payDao;
}

spring在初始化BankPayServiceImpl实例过程中会注入IBankPayDao属性,调用MapperFactoryBean的getObject()得到

org.apache.ibatis.binding.MapperProxy实例,这个过程涉及到spring初始化bean实例注入属性的过程,该内容会在后续章节进行详细讲解。


总结:

从上面分析的情况可以看出,spring与mybatis的集成不复杂,大概过程分为两步

第一步通过bean工厂后置处理器MapperScannerConfigurer扫描指定包路径下符合要求的class并转换成className为MapperFactoryBean的BeanDefinition实例;

第二步在实例化bean过程中,注入bean的属性时会通过MapperFactoryBean的getObject方法获取MapperProxy代理实例。


下个章节会讲解Bean后置处理器,该处理器和bean工厂后置处理器是两个不同的概念。



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
从提供的引用内容来看,这个错误提示是在运行某个程序时出现的。错误提示指出缺少一个名为libopenblasp-r0-39a31c03.2.18.so的共享对象文件,导致无法打开该文件或目录。然而,根据提供的引用内容,我没有找到直接相关的解决方案或信息。因此,我无法提供详细的解决方法。建议您进行以下几步来解决该问题: 1. 确认您的系统中是否安装了执行该程序所需的所有依赖项。您可以查看程序的文档或官方网站,以获得所需的依赖项列表。 2. 检查程序所需的共享对象文件是否存在于系统中。您可以使用命令行工具(如ls命令)搜索该文件,或者使用文件管理器进行手动搜索。 3. 如果缺少共享对象文件,您可以尝试重新安装或更新相关软件包,以确保所有依赖项得到满足。 4. 如果问题仍然存在,您可以尝试在相关的技术论坛或社区中寻求帮助。提供更多关于您的操作系统、程序版本和具体错误信息的信息可能有助于其他人更好地理解和解决问题。 请记住,我无法提供直接的解决方法,因为缺少了与该错误具体相关的信息和上下文。建议根据上述步骤进行排查,并与相关的技术论坛或社区寻求帮助,以获得更准确和具体的解决方案。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Original error was: libopenblasp-r0-39a31c03.2.18.so: cannot open shared object file: No such file o](https://blog.csdn.net/frankcreen/article/details/78910187)[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^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [leetcode-112. Path Sum](https://blog.csdn.net/frankcreen/article/details/62881115)[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^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [mysql-8.0.11-linux-glibc2.12-x86_64.tar.gz](https://download.csdn.net/download/tan3739/10401811)[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^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值