@MapperScan 源码解析

2022-08-30 20:25:12 浏览数 (1)

大家好,又见面了,我是你们的朋友全栈君。

@MapperScan

代码语言:javascript复制
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan { 
    ... }

@Import 的作用就是向spring容器中导入一个BeanDefinition对象

MapperScannerRegistrar

代码语言:javascript复制
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware { 
    ... }

通过源码看出这个导入的类还是一个ImportBeanDefinitionRegistrar,这个接口下面有一个registerBeanDefinitions(…) 通过这个方法的 BeanDefinitionRegistry ,就可以完成BeanDefinition 的注册

代码语言:javascript复制
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { 
   
// 获取MapperScan注解的信息
    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);
    }

	//annotationClass 的设置,也就是扫描出来的类必须标注了annotationClass ,否则不会扫描
    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"));
    // 获取指定的sqlSessionFactoryRef 在@MapperScan注解上有一个 SqlSessionFactoryRef的属性可以指定
    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));
  }
  1. 获取MapperScan注解上的一些信息
  2. 定义一个扫描器 ClassPathMapperScanner ,这个扫描器其实是继承自spring的ClassPathBeanDefinitionScanner,但是在实例化的时后 第二个参数值为false , super(registry, false); 意思是说不适用spring提供的默认的过滤方式 对于spring的扫描器而言,需要满足两个条件才会被扫描,首先不能是接口,其次是不需要标注@Component注解,所以这里想要扫描 mapper 的接口就需要自己去实现过滤的规则,到那时这里的annotationClass 只是记录一下,具体的操作在registerFilters() 3.注册Filters 4.进行doScan 扫描

还有一点需要注意的是,spring在进行扫描的时候,会去调用一个方法isCandidateComponent(),这个方法在spring的scanner中的实现如下

代码语言:javascript复制
	protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { 
   
		AnnotationMetadata metadata = beanDefinition.getMetadata();
		// 不是接口或抽象类,如果是抽象类那么抽象类上得是Lookup注解
		return (metadata.isIndependent() && (metadata.isConcrete() ||
				(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
	}

这个方法可以进行接口的过滤,但是mybatis的接口是需要扫描到spring 容器的,所以对于@MapperScan的扫描器重写了这个方法

代码语言:javascript复制
  @Override
  protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { 
   
    return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
  }

也就是@MapperScan 中的扫描器只会扫描接口

接下来思考一个问题,通过上述方式的确是能让spring帮助mybatis进行扫描,但是扫描出来的BeanClass是什么呢??? 是mapper接口??? 显然不能是这些,接口是无法直接进行实例化的,所以这些扫描出来的BeanDefinition还需要进行处理 处理的逻辑就在doScan()==>processBeanDefinitions()

代码语言:javascript复制
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");
      }

	  //修改构造方法的参数 
      definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
      definition.setBeanClass(this.mapperFactoryBean.getClass());//修改BeanClass

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

主要关注两行 definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); definition.setBeanClass(this.mapperFactoryBean.getClass()); 第一步是修改了构造方法的参数,就是在通过构造方法实例化的时候的参数,那么mapperFactoryBean是什么呢?

代码语言:javascript复制
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> { 
   
  private Class<T> mapperInterface;

  public MapperFactoryBean() { 
   
    //intentionally empty 
  }
  
  public MapperFactoryBean(Class<T> mapperInterface) { 
   
    this.mapperInterface = mapperInterface;
  }
 
  @Override
  public T getObject() throws Exception { 
   
    return getSqlSession().getMapper(this.mapperInterface);
  }

  @Override
  public Class<T> getObjectType() { 
   
    return this.mapperInterface;
  }
...
...
}

可以看出来MapperFactoryBean 就是一个 FactoryBean,并且提供了一个构造方法,然后再进行构造的时候出入进来的依然还是接口的类型,但是实际的类型就不一样了,因为FactoryBean 可以通过getObject的方法来返回实际的类型 可以看到getObject 返回的是 getSqlSession().getMapper(this.mapperInterface),SqlSession在mybatis是可以返回一个Mapper的实现类的,所以真正的对象的创建,代理依然是Mybatis完成的,spring只不过是做对象的管理而已

但是这里是直接 getSqlsession 的,那么这个SqlSession是哪里来的? 在Mybatis中 SqlSession是需要通过SqlSessionFactory 这个对象生成出来的

回到processBeanDefinitions()

代码语言:javascript复制
 //当前的ClasspathMapperScanner中有没有指定 sqlSessionFactoryBeanName,如果有
 //就修改BeanDefinition中的参数值,也就是说在实例化的时候,机会通过set方法去出入这个参数值
 //这样就有了一个SqlSessionFactory 了
 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()   "'.");
        }
        //如果说在spring中没有指定SqlSessionFactoryBean ,那么就会将当前Bean的自动注入的模型改为by_type,那么
        //在实例化当前bean的时候,就会自动注入通过byType找到的Bean
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/144735.html原文链接:https://javaforall.cn

0 人点赞