前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Mybatis整合Spring

Mybatis整合Spring

作者头像
路行的亚洲
发布2021-03-04 15:47:30
9090
发布2021-03-04 15:47:30
举报
文章被收录于专栏:后端技术学习

前面我们已经知道如果mybatis中进行查询时,在getMapper之后,会执行查询动作,而执行查询动作的时候,会触发动态代理操作,这个过程是在binding中完成的,即此时会触发MapperMethod#invoke操作->mapperMethod.execute(sqlSession, args),这个操作的过程中,会执行到具体的查询操作,而具体的查询动作中,我们可以此时会走到一个sqlCommandType的判断中,也即case语句中:

代码语言:javascript
复制
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    Object param;
    switch(this.command.getType()) {
    case INSERT:
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
        break;
    case UPDATE:
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
        break;
    case DELETE:
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
        break;
    case SELECT:
        if (this.method.returnsVoid() && this.method.hasResultHandler()) {
            this.executeWithResultHandler(sqlSession, args);
            result = null;
        } else if (this.method.returnsMany()) {
            result = this.executeForMany(sqlSession, args);
        } else if (this.method.returnsMap()) {
            result = this.executeForMap(sqlSession, args);
        } else if (this.method.returnsCursor()) {
            result = this.executeForCursor(sqlSession, args);
        } else {
            param = this.method.convertArgsToSqlCommandParam(args);
            result = sqlSession.selectOne(this.command.getName(), param);
            if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
                result = Optional.ofNullable(result);
            }
        }
        break;
    case FLUSH:
        result = sqlSession.flushStatements();
        break;
    default:
        throw new BindingException("Unknown execution method for: " + this.command.getName());
    }
}

此时会走到baseExecutor中,然后走到SimpleExecutor中或者CachingExecutor中,进行结果的查询,然后经过原生的jdbc中,执行操作,完成查询。

那么mybatis和spring又是怎样整合的呢?

mybatis的xml的相关配置:

代码语言:javascript
复制
<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
       <property name="driverClassName" value="com.mysql.jdbc.Driver" />
       <property name="url" value="${project.db.main.jdbcUrl}" />
   <property name="username" value="${project.db.main.user}" />
   <property name="password" value="${project.db.main.password}" />
   </bean>

   <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
       <property name="dataSource" ref="dataSource" />  
       <property name="mapperLocations" value="classpath*:/META-INF/mapping/*.xml" />  
   </bean>  

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

可以看到mybatis整合spring的时候,首先会填充数据源信息,然后根据数据源,填写sqlSessionFactory的相关信息,而此时会将数据源的信息和mapper的xml文件引入配置。同时需要对mapper中的基包,也即对mapper接口进行扫描。如果需要还可能会配置事务TransactionManager相关的配置,除此之外,还需要配置切点和切面的相关信息。

从配置中,我们可以看到需要关注的两个类:

代码语言:javascript
复制
org.mybatis.spring.SqlSessionFactoryBean
org.mybatis.spring.mapper.MapperScannerConfigurer

此时我们需要关心的是它是如何整合spring的,同时如何实现基包扫描的?

可以看到上面的两个类涉及到了基包扫描的问题,同时整合spring的问题,而整合spring的实质是将bean注入到spring中,让spring来管理mybatis中bean的问题。

下面我们先来看扫描的问题:

代码语言:javascript
复制
//mapper扫描元注解:包含了两个主要的注解:
//  @Import(MapperScannerRegistrar.class)和@Repeatable(MapperScans.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {

  //{@code @MapperScan("org.my.pkg")} instead of {@code @MapperScan(basePackages = "org.my.pkg"})}.
  String[] value() default {};

   //基包扫描 提供给mybatis的接口列表  
  String[] basePackages() default {};

  //基包类  
  Class<?>[] basePackageClasses() default {};

  //用于spring容器  
  Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

   //扫描注解类 
  Class<? extends Annotation> annotationClass() default Annotation.class;

  //标记接口 扫描将会注册所有的接口在基包里面的,同时也包含父类接口  
  Class<?> markerInterface() default Class.class;

  //特定的 sqlSessionTemplate 多个数据源时需要
  String sqlSessionTemplateRef() default "";

  //特定的 sqlSessionFactory 多个数据源时需要
  String sqlSessionFactoryRef() default "";

 // 特定的自定义mapperFactoryBean 返回一个mapper代理作为spring bean
  Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;

 //是否是懒加载初始化mapper bean,默认是false
  String lazyInitialization() default "";

  //扫描特定的范围
  String defaultScope() default AbstractBeanDefinition.SCOPE_DEFAULT;
}    

可以看到元注解中的两个方法:

代码语言:javascript
复制
@Import(MapperScannerRegistrar.class) //导入类
@Repeatable(MapperScans.class) //可多次出现的类

同时可以看到基包扫描类:其增强的方法是导入的MapperScannerRegistrar.RepeatingRegistear

代码语言:javascript
复制
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.RepeatingRegistrar.class)
//mapper扫描
public @interface MapperScans {
  MapperScan[] value();
}

因此我们可以将目光移位到:

代码语言:javascript
复制
 * {@inheritDoc}
 * 注册beanDefinitons
 */
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  AnnotationAttributes mapperScanAttrs = AnnotationAttributes
      .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
  if (mapperScanAttrs != null) {
    registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
        generateBaseBeanName(importingClassMetadata, 0));
  }
}

同时扫描中也会进行beanDefintion的注册操作:

代码语言:javascript
复制
/**
 * A {@link MapperScannerRegistrar} for {@link MapperScans}.
 * 为mapper扫描提供一个mapper扫描注册
 * @since 2.0.0
 */
static class RepeatingRegistrar extends MapperScannerRegistrar {
  /**
   * {@inheritDoc}
   */
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    //通过注解属性从map中拿到注解属性  通过导入类元数据拿到注解属性
    AnnotationAttributes mapperScansAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScans.class.getName()));
    //如果mapper扫描属性不为空,则通过mapper扫描属性拿到注解数组属性,遍历注解属性,注册beanDefiniton到spring中
    if (mapperScansAttrs != null) {
      AnnotationAttributes[] annotations = mapperScansAttrs.getAnnotationArray("value");
      //遍历注解,执行注册beanDefintions,其最终是将beanDefintion注册到beanDefintionMap中
      for (int i = 0; i < annotations.length; i++) {
        registerBeanDefinitions(importingClassMetadata, annotations[i], registry,
            generateBaseBeanName(importingClassMetadata, i));
      }
    }
  }
}

在spring中,我们知道注册BeanDefintions的过程的实质是将beanDefintion放入到BeanDefintionMap中的过程。

代码语言:javascript
复制
//注册beanDefintions:填充配置信息到BeanDefintionBuilder中,然后执行beanDefintion注册,也即registrBeanDefintion操作,
// 然后执行spring的后续操作,将其放入到beanDefintionMap中
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
    BeanDefinitionRegistry registry, String beanName) {

  //通用beanDefintion
  BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
  //添加配置值 占位符
  builder.addPropertyValue("processPropertyPlaceHolders", true);

  //beanDefintionBuilder beanDefintion构建器->添加配置:处理配置占位符处理器、注解类、标记接口、名称通用、mapper工厂bean类
  //sqlSession模板引用、sqlSession模板bean名称、
  Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
  if (!Annotation.class.equals(annotationClass)) {
    builder.addPropertyValue("annotationClass", annotationClass);
  }

  Class<?> markerInterface = annoAttrs.getClass("markerInterface");
  if (!Class.class.equals(markerInterface)) {
    builder.addPropertyValue("markerInterface", markerInterface);
  }

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

  Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
  if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
    builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
  }

  String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
  if (StringUtils.hasText(sqlSessionTemplateRef)) {
    builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
  }

  String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
  if (StringUtils.hasText(sqlSessionFactoryRef)) {
    builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
  }

  //基包添加:属性值、基包信息、基包类信息、
  List<String> basePackages = new ArrayList<>();
  basePackages.addAll(
      Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));

  basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
      .collect(Collectors.toList()));

  basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
      .collect(Collectors.toList()));

  if (basePackages.isEmpty()) {
    basePackages.add(getDefaultBasePackage(annoMeta));
  }

  //构建起中添加懒加载配置、默认范围配置、基包信息
  String lazyInitialization = annoAttrs.getString("lazyInitialization");
  if (StringUtils.hasText(lazyInitialization)) {
    builder.addPropertyValue("lazyInitialization", lazyInitialization);
  }

  String defaultScope = annoAttrs.getString("defaultScope");
  if (!AbstractBeanDefinition.SCOPE_DEFAULT.equals(defaultScope)) {
    builder.addPropertyValue("defaultScope", defaultScope);
  }

  builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));

  //注册beanDefintion
  registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

}

可以看到其实质是将mybatis中的bean信息注册到spirng bean中。同时可以看到是我们熟悉的BeanDefinitionRegistry的registerBeanDefintion这个方法。跟踪spring的源码,你会发现就是将bean放入到beanDefintionMap中的过程。

DefaultListableBeanFactory#registerBeanDefinition源码中的操作

代码语言:javascript
复制
 this.beanDefinitionMap.put(beanName, beanDefinition);

而beanDefintionMap的数据结构则是ConcurrentHashMap

代码语言:javascript
复制
this.beanDefinitionMap = new ConcurrentHashMap(256);

将需要注册的bean信息放入spring bean中之后,还要完成注解扫描操作。

前面我们定义好mybatis的相关配置信息了,但是光是注入是不够的,还需要能够解析xml中的文件信息。

我们知道mybatis是第三方框架,因此如果需要和spring整合,必然是需要自定义标签的解析器和自定义handler文件,因此我们可以找到mybatis的自定义标签解析器

代码语言:javascript
复制
1.自定义标签的使用:
2.首先创建一个需要扩展的组件
3.定义一个xsd文件描述组件内容
4.创建一个文件,实现beanDefintionParser接口,用来解析xsd文件中的定义和组件定义
5.创建一个Handler文件,扩展自NamespaceHandlerSupport,目的是将组件注册到spring容器
6.编写Spring.handlers和Spring.schemas文件

因此此时必然需要看到我们的命名空间处理器和beanDefintion的解析器:

代码语言:javascript
复制
//命名空间处理器 继承 命名空间处理器support
public class NamespaceHandler extends NamespaceHandlerSupport {

  /**
   * {@inheritDoc}
   */
  @Override
  public void init() {
    registerBeanDefinitionParser("scan", new MapperScannerBeanDefinitionParser());
  }
}

可以看到这里需要执行扫描的信息:mapper扫描beanDefintion解析器会将下面的这些属性放入到BeanDefinitionBuilder中。

代码语言:javascript
复制
//mapper扫描beanDefintion解析器 继承 抽象beanDefintionParser
public class MapperScannerBeanDefinitionParser extends AbstractBeanDefinitionParser {

  //基包
  private static final String ATTRIBUTE_BASE_PACKAGE = "base-package";
  //注解
  private static final String ATTRIBUTE_ANNOTATION = "annotation";
  //标记接口
  private static final String ATTRIBUTE_MARKER_INTERFACE = "marker-interface";
  //nameGenerator
  private static final String ATTRIBUTE_NAME_GENERATOR = "name-generator";
  //模板引用
  private static final String ATTRIBUTE_TEMPLATE_REF = "template-ref";
  //工厂引用
  private static final String ATTRIBUTE_FACTORY_REF = "factory-ref";
  //映射工厂bean类
  private static final String ATTRIBUTE_MAPPER_FACTORY_BEAN_CLASS = "mapper-factory-bean-class";
  //懒加载初始化
  private static final String ATTRIBUTE_LAZY_INITIALIZATION = "lazy-initialization";
  //默认scope
  private static final String ATTRIBUTE_DEFAULT_SCOPE = "default-scope";
}   

除了添加这些配置外,还添加了builder.addPropertyValue("processPropertyPlaceHolders", true);注解扫描的添加,此时会在mapperScanConfigurer中会看到这个属性的判断,如果为true,则执行处理占位符操作。

下面我们来看它是怎样完成注解扫描的:

代码语言:javascript
复制
public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
    }

从MapperScannerConfigurer中,我们可以看到它实现了BeanDefinitionRegistryPostProcessor后置处理器,InitializingBean初始化bean的方法,同时还实现了ApplicationContextAware和BeanNameAware。而这里我们需要重点关注的方法是后置处理器方法:

afterPropertiesSet只做了一个判空的操作:

代码语言:javascript
复制
/**
 * {@inheritDoc}
 * 判断基包不为空
 */
@Override
public void afterPropertiesSet() throws Exception {
  notNull(this.basePackage, "Property 'basePackage' is required");
}

显然这个不是我们关注的重点。

代码语言:javascript
复制
/**
 * {@inheritDoc}
 *
 * @since 1.0.2
 * 后置处理beanDefintion注册 重要
 */
@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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
  if (StringUtils.hasText(lazyInitialization)) {
    scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
  }
  if (StringUtils.hasText(defaultScope)) {
    scanner.setDefaultScope(defaultScope);
  }
  //注册过滤器
  scanner.registerFilters();
  //进行扫描 首先执行doScan操作,然后将其注册到registerAnnotationConfigProcessors 重要
  scanner.scan(
      StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

为什么要做占位符扫描操作,不做可以吗?

代码语言:javascript
复制
processPropertyPlaceHolders属性处理,在进行解析的过程中埋下了伏笔,为属性处理做好铺垫。同时我们可以从英文注释可以看到这是为了避免propertyResourceConfigurers没有被加载而失效的问题。因为BeanDefinitionRegistries在应用程序启动之前的BeanFactoryPostProcessors之前被调用。这意味着将不会加载PropertyResourceConfigurers,并且此类属性的任何属性替换都将失败。为了避免这种情况,找到在上下文中定义的任何“属性资源配置器”,然后在此类的bean定义上运行它们。 然后更新值。

下面的代码就是交代这个:

代码语言:javascript
复制
/**
 * BeanDefinitionRegistries are called early in application startup, before BeanFactoryPostProcessors. This means that
 * PropertyResourceConfigurers will not have been loaded and any property substitution of this class' properties will
 * fail. To avoid this, find any PropertyResourceConfigurers defined in the context and run them on this class' bean
 * definition. Then update the values.
 *
 * BeanDefinitionRegistries在应用程序启动之前的BeanFactoryPostProcessors之前被调用。
 * 这意味着将不会加载PropertyResourceConfigurers,并且此类属性的任何属性替换都将失败。
 * 为了避免这种情况,找到在上下文中定义的任何“属性资源配置器”,然后在此类的bean定义上运行它们。 然后更新值。
 */
private void processPropertyPlaceHolders() {
  //通过上下文获取bean
  Map<String, PropertyResourceConfigurer> prcs = applicationContext.getBeansOfType(PropertyResourceConfigurer.class,
      false, false);

  if (!prcs.isEmpty() && applicationContext instanceof ConfigurableApplicationContext) {
    BeanDefinition mapperScannerBean = ((ConfigurableApplicationContext) applicationContext).getBeanFactory()
        .getBeanDefinition(beanName);

    // PropertyResourceConfigurer does not expose any methods to explicitly perform
    // property placeholder substitution. Instead, create a BeanFactory that just
    // contains this mapper scanner and post process the factory.
    //属性资源配置器没有公开任何方法来显式执行属性占位符替换。 相反,创建一个仅包含此映射器扫描器的BeanFactory并对工厂进行后处理。  
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    factory.registerBeanDefinition(beanName, mapperScannerBean);

    for (PropertyResourceConfigurer prc : prcs.values()) {
      prc.postProcessBeanFactory(factory);
    }

    PropertyValues values = mapperScannerBean.getPropertyValues();

    this.basePackage = getPropertyValue("basePackage", values);
    this.sqlSessionFactoryBeanName = getPropertyValue("sqlSessionFactoryBeanName", values);
    this.sqlSessionTemplateBeanName = getPropertyValue("sqlSessionTemplateBeanName", values);
    this.lazyInitialization = getPropertyValue("lazyInitialization", values);
    this.defaultScope = getPropertyValue("defaultScope", values);
  }
  this.basePackage = Optional.ofNullable(this.basePackage).map(getEnvironment()::resolvePlaceholders).orElse(null);
  this.sqlSessionFactoryBeanName = Optional.ofNullable(this.sqlSessionFactoryBeanName)
      .map(getEnvironment()::resolvePlaceholders).orElse(null);
  this.sqlSessionTemplateBeanName = Optional.ofNullable(this.sqlSessionTemplateBeanName)
      .map(getEnvironment()::resolvePlaceholders).orElse(null);
  this.lazyInitialization = Optional.ofNullable(this.lazyInitialization).map(getEnvironment()::resolvePlaceholders)
      .orElse(null);
  this.defaultScope = Optional.ofNullable(this.defaultScope).map(getEnvironment()::resolvePlaceholders).orElse(null);
}

这个方法做的操作就是:找到所有已经注册的PropertyResourceConfigurer类型的bean,模拟spring中的环境来yoga处理器。这里通过使用new DefaultListableBeanFactory()来模拟spring中的环境,将映射的bean,也即MapperScannerConfigurer类型的bean注册到环境中来进行后置处理器的调用,处理器PropertyPlaceHolderConfigurer调用完成的功能,即找出所有Bean中的应用属性文件的变量并替换属性值。

接下来执行两个操作:

代码语言:javascript
复制
 //注册过滤器
  scanner.registerFilters();
  //进行扫描 首先执行doScan操作,然后将其注册到registerAnnotationConfigProcessors
  scanner.scan(
      StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));

注册过滤器:

代码语言:javascript
复制
/**
 * 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
 *
 * 配置父扫描程序以搜索正确的接口。 它可以搜索所有接口,也可以仅搜索扩展markerInterface的接口或/和带有注释类注解的接口。
 */
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((metadataReader, metadataReaderFactory) -> true);
  }

  // exclude package-info.java
  addExcludeFilter((metadataReader, metadataReaderFactory) -> {
    String className = metadataReader.getClassMetadata().getClassName();
    return className.endsWith("package-info");
  });
}

可以看到四种类型的类的处理:

annotationClass、markerInterface、全局默认处理(acceptAllInterfaces)默认接收所有的class、package-info.java处理器是排掉

接着进行扫描操作:

代码语言:javascript
复制
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
    <property name="basePackage" value="com.study.**.dao" />
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>  

对接口进行扫描:在spring-context中可以看到这个类ClassPathBeanDefinitionScanner#scan

代码语言:javascript
复制
public int scan(String... basePackages) {
    int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
    this.doScan(basePackages);
    if (this.includeAnnotationConfig) {
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }
    return this.registry.getBeanDefinitionCount() - beanCountAtScanStart;
}

可以看到ClassMapperScanner实现了Spring#ClassPathBeanDefinitionScanner类,重新了doScan方法。

代码语言:javascript
复制
/**
 * 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) {
  //通过父类拿到doScan操作
  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 {
    //如果beanDefintions不为空,则执行处理beanDefintions操作
    processBeanDefinitions(beanDefinitions);
  }

  return beanDefinitions;
}

可以看到其处理BeanDefintions的过程就是注册BeanDefintions的过程:

代码语言:javascript
复制
//处理beanDefintionS
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
  AbstractBeanDefinition definition;
  //获取beanDefintion注册,遍历beanDefintions,通过beanDefintion持有者拿到beanDefintion
  BeanDefinitionRegistry registry = getRegistry();
  for (BeanDefinitionHolder holder : beanDefinitions) {
    definition = (AbstractBeanDefinition) holder.getBeanDefinition();
    boolean scopedProxy = false;
    //如果是范围代理,则执行获取装饰defintion操作
    if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) {
      definition = (AbstractBeanDefinition) Optional
          .ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition())
          .map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException(
              "The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]"));
      scopedProxy = true;
    }
    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.mapperFactoryBeanClass);

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

    // Attribute for MockitoPostProcessor
    // https://github.com/mybatis/spring-boot-starter/issues/475
    //设置属性
    definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName);

    boolean explicitFactoryUsed = false;
    //如果sqlSession工厂bean名称存在下一个
    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);
    }

    //设置懒加载初始化
    definition.setLazyInit(lazyInitialization);

    if (scopedProxy) {
      continue;
    }

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

    //如果defintion不是单例,则执行创建范围代理操作,如果注册中包含beanDefintion,则移除beanDefintion操作
    if (!definition.isSingleton()) {
      BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true);
      if (registry.containsBeanDefinition(proxyHolder.getBeanName())) {
        registry.removeBeanDefinition(proxyHolder.getBeanName());
      }
      //注册beanDefintion操作
      registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition());
    }

  }
}

下面来着重看doScan操作:

代码语言:javascript
复制
/**
 * 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})
 */
//在指定的基本程序包中执行扫描,返回已注册的bean定义。此方法不注册注解配置处理器,而是将其留给调用方。
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
 Assert.notEmpty(basePackages, "At least one base package must be specified");
 Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
 //遍历基包,findCandidateComponents方法是根据传入的包路径信息并结合类文件路径拼接成文件的绝对路径,同时完成了文件的扫描过程并且根据对应的文件生成了对应的bean,使用ScannedGenericBeanDefintion类型的bean承载信息,bean中只记录了resource和source信息。 
 for (String basePackage : basePackages) {
   //重要 筛选出符合要求的组件,而筛选则是根据之前的includeFilter和excludeFilter来筛选的  
  Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
  for (BeanDefinition candidate : candidates) {
   //解析scope元数据,并设置scope   
   ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
   candidate.setScope(scopeMetadata.getScopeName());
   String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
   //对candidate的类型进行实例化,看匹配的实例化类型,其中有:AbstractBeanDefinition、AnnotatedBeanDefinition,然后执行处理,检查当前的bean是否已经注册   
   if (candidate instanceof AbstractBeanDefinition) {
    //后置处理beanDefintion   
    postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
   }
    //处理beanDefintion注解  
   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;
}

执行扫描类路径信息:

代码语言:javascript
复制
/**
 * 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) {
 if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
  return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
 }
 else {
  return scanCandidateComponents(basePackage);
 }
}

进行扫描:

代码语言:javascript
复制
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
 Set<BeanDefinition> candidates = new LinkedHashSet<>();
 try {
  //包搜索路径:classpath:resolveBasePackage(basePackage) + '/' + this.resourcePattern  
  String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
    resolveBasePackage(basePackage) + '/' + this.resourcePattern;
  //资源   
  Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
  boolean traceEnabled = logger.isTraceEnabled();
  boolean debugEnabled = logger.isDebugEnabled();
  for (Resource resource : resources) {
   if (traceEnabled) {
    logger.trace("Scanning " + resource);
   }
   //判断资源是否可读   
   if (resource.isReadable()) {
    try {
     // 如果可读,则通过元数据读工厂拿到元数据reader,也即拿到扫描文件信息
     MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
     //判断当前扫描的文件是否符合要求,includeFilters和excludeFilter,将符合要求的资源添加到扫描通用beanDefintion中,然后将其放入到candidate中   
     if (isCandidateComponent(metadataReader)) {
      ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
      sbd.setSource(resource);
      if (isCandidateComponent(sbd)) {
       if (debugEnabled) {
        logger.debug("Identified candidate component class: " + resource);
       }
       candidates.add(sbd);
      }
      else {
       if (debugEnabled) {
        logger.debug("Ignored because not a concrete top-level class: " + resource);
       }
      }
     }
     else {
      if (traceEnabled) {
       logger.trace("Ignored because not matching any filter: " + resource);
      }
     }
    }
    catch (Throwable ex) {
     throw new BeanDefinitionStoreException(
       "Failed to read candidate component class: " + resource, ex);
    }
   }
   else {
    if (traceEnabled) {
     logger.trace("Ignored because not readable: " + resource);
    }
   }
  }
 }
 catch (IOException ex) {
  throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
 }
 return candidates;
}

执行判断操作,对符合要求的文件:

代码语言:javascript
复制
/**
  *确定给定的类是否不匹配任何排除过滤器并且是否匹配至少一个包含过滤器。
  * @param metadataReader 该类的ASM ClassReader
  * @返回类是否有资格作为候选组件
  **/
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
 //排查过滤器   
 for (TypeFilter tf : this.excludeFilters) {
  if (tf.match(metadataReader, getMetadataReaderFactory())) {
   return false;
  }
 }
 //类型过滤器   
 for (TypeFilter tf : this.includeFilters) {
  if (tf.match(metadataReader, getMetadataReaderFactory())) {
   return isConditionMatch(metadataReader);
  }
 }
 return false;
}

而筛选的匹配的具体过程是在isConditionMatch中实现的。

我们来总结一下:

首先mybatis整合spring需要注意第三方框架整合spring,是需要自定义标签和自定义的namespaceHandler实现namesapceHandlerSupport,同时需要解析标签,此时需要实现BeanDefintionParser接口来解析xsd文件和组件定义。编写Spring.handlers和Spring.schemas文件。

这里需要关注的是mybatis中相关bean的注册工作和mybatis中mapper映射文件的扫描操作。

可以看到相关bean的注册是在MapperScannerRegistrar中的registerBeanDefinitions中完成的。也即处理的过程其实是一个注册的过程,最终就是放入到spring的BeanDefintionMap中。

此时可以看到在我们定义标签的过程中,需要解析的标签在BeanDefintionParser中可以看到,同时此时看到的自定义的BeanDefintionParser中,看到processPropertyPlaceHolders标签的属性值是true,这是为MapperScannerConfigurer中的postProcessBeanDefinitionRegistry方法做铺垫的。因为在这个方法中,我们可以看到需要对配置占位符进行处理,而BeanDefinitionRegistries在应用程序启动之前的BeanFactoryPostProcessors之前被调用。这意味着将不会加载属性资源配置器PropertyResourceConfigurers,并且此类属性的任何属性替换都将失败。为了避免这种情况,找到在上下文中定义的任何“属性资源配置器PropertyResourceConfigurers”,然后在此类的bean定义上运行它们。然后更新值。因此会在这个过程中将mybatis的相关配置信息放入到BeanDefintion中,此时完成占位符的相关属性值的替换方便后续的调用。

在执行扫描之前,会执行filter过滤器的注册操作,可以看到过滤器,markerInterface、、annotationClasses、默认过滤器(所有接口)、page-info排出excludeFilter过滤器,扫描的过程是在doScanner中执行的,主要分为两类includeFilter和excludeFilter。这个是扫描中的判断的依据。

在扫描过程中,我们可以看到在doScan中,最为重要的判断依据方法:findCandidateComponents(basePackage)这个方法。这个方法根据传入的包路径和类文件路径信息拼接成文件的绝对路径,同时完成了文件的扫描过程并且根据对应的文件生成了对应的bean,使用ScannerGenricBeanDefintion类型的bean承载信息,bean中只记录了resource和source信息。而判断则是在isCandidateComponent(metadataReader)中,用于判断当前扫描的文件是否符合要求,可以判断条件是includeFilter和excludeFilter,然后对应返回,具体实现则是在isConditionMatch中。metadata中则是需要过滤的数据。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-02-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 后端技术学习 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档