mybatis-spring:@MapperScan注解

妖狐艹你老母 2021-10-13 02:33 364阅读 0赞

引言

  1. 在[demo: springboot+mybatis][demo_ springboot_mybatis]的示例中,dao层接口使用了注解@MapperScan:指定扫描com.xuxd.demo.dao.UserDao所在包路径下的所有接口类。
  2. 本文分析下@MapperScan注解做了哪些动作。

@MapperScan源码

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target(ElementType.TYPE)
  3. @Documented
  4. @Import(MapperScannerRegistrar.class)
  5. public @interface MapperScan {
  6. /**
  7. *缺省属性(==basePackages),basePackages的别名
  8. */
  9. String[] value() default {};
  10. /**
  11. * 哪些包路径下的接口被扫描注册(接口至少有一个方法),具体实现类(非接口)忽略
  12. */
  13. String[] basePackages() default {};
  14. /**
  15. * 指定类所在包下所有接口被扫描注册(接口至少有一个方法),具体实现类(非接口)忽略
  16. */
  17. Class<?>[] basePackageClasses() default {};
  18. /**
  19. * 扫描到的满足条件的接口,首先要把它们相关bean定义注册到spring容器中吧,注册bean定义
  20. * 的时候,需要定义bean名称,这个是用来自定方生成bean名称的策略组件,个人觉得很少用
  21. */
  22. Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
  23. /**
  24. * 这个注解指定的接口也要被扫描
  25. */
  26. Class<? extends Annotation> annotationClass() default Annotation.class;
  27. /**
  28. * 继承这个接口的接口也要被扫描
  29. */
  30. Class<?> markerInterface() default Class.class;
  31. /**
  32. * 多数据源的时候可能用到这个,后面单独说明这个
  33. */
  34. String sqlSessionTemplateRef() default "";
  35. /**
  36. * 多数据源的时候可能用到这个,后面单独说明这个
  37. */
  38. String sqlSessionFactoryRef() default "";
  39. /**
  40. * 多数据源的时候可能用到这个,后面单独说明这个
  41. */
  42. Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
  43. }
  44. 这个注解的重点是@Import(MapperScannerRegistrar.class),使用这个注解导入MapperScannerRegistrar主要完成两件事:
  45. 1. 扫描指定接口
  46. 2. 注册这些接口的bean定义到spring容器
  47. 接下来进入MapperScannerRegistrar类看下是如何完成这两动作:

MapperScannerRegistrar.class

  1. public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {}

这个类实现了ImportBeanDefinitionRegistrar接口:

  1. public interface ImportBeanDefinitionRegistrar {
  2. public void registerBeanDefinitions(
  3. AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
  4. }

@MapperScan注解类上使用了@Import注解导入了这个接口的实现类(MapperScannerRegistrar.class),因此spring解析MybatisConfig(源码:demo: springboot+mybatis)这个类的时候,解析到这个类上使用了注解@MapperScan,从MapperScan注解类上(注解都是一个接口,java会创建代理类)发现了@Import注解及MapperScannerRegistrar类(因为Import注解是导入配置类的)。 在加载MybatisConfig配置类的bean定义时候,找到了ImportBeanDefinitionRegistrar 的实现类MapperScannerRegistrar,便会回调这个MapperScannerRegistrar的registerBeanDefinitions方法。

总之一句话,在加载配置类MybatisConfig的bean定义的时候,会调用与之看起来有点关系的MapperScannerRegistrar的registerBeanDefinitions方法。

MapperScannerRegistrar的registerBeanDefinitions方法第一个参数importingClassMetadata指的是MybatisConfig这个类的。

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3g3NjM3OTUxNTE_size_16_color_FFFFFF_t_70

可以debug,看这个参数的信息。

  1. @Override
  2. public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  3. AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
  4. ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
  5. // this check is needed in Spring 3.1
  6. if (resourceLoader != null) {
  7. scanner.setResourceLoader(resourceLoader);
  8. }
  9. Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
  10. if (!Annotation.class.equals(annotationClass)) {
  11. scanner.setAnnotationClass(annotationClass);
  12. }
  13. Class<?> markerInterface = annoAttrs.getClass("markerInterface");
  14. if (!Class.class.equals(markerInterface)) {
  15. scanner.setMarkerInterface(markerInterface);
  16. }
  17. Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
  18. if (!BeanNameGenerator.class.equals(generatorClass)) {
  19. scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
  20. }
  21. Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
  22. if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
  23. scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
  24. }
  25. scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
  26. scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
  27. List<String> basePackages = new ArrayList<String>();
  28. for (String pkg : annoAttrs.getStringArray("value")) {
  29. if (StringUtils.hasText(pkg)) {
  30. basePackages.add(pkg);
  31. }
  32. }
  33. for (String pkg : annoAttrs.getStringArray("basePackages")) {
  34. if (StringUtils.hasText(pkg)) {
  35. basePackages.add(pkg);
  36. }
  37. }
  38. for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
  39. basePackages.add(ClassUtils.getPackageName(clazz));
  40. }
  41. scanner.registerFilters();
  42. scanner.doScan(StringUtils.toStringArray(basePackages));
  43. }

看这个方法的源码,主要完成2件事:

  1. 1. 解析MapperScan注解的各个字段的值 ,用以初始化类路径扫描器
  2. 2. 确定扫描类路径下哪些接口,如指定的包路径、指定的类所在包路径。上面倒数第2行代码,注册过滤器,用来指定包含哪些注解或接口的扫描(@MapperScanannotationClassmarkerInterface属性,如果设置的话)
  3. 因此,重点是最后一行代码doScan的调用。
  4. 这里不贴源码了,前文提到,MapperScannerRegistrar主要完成两件事,都会在这里完成,解析包路径,扫描指定接口并注册bean定义到spring容器。
  5. definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
  6. definition.setBeanClass(this.mapperFactoryBean.getClass());
  7. definition.getPropertyValues().add("addToConfig", this.addToConfig);
  8. ClassPathMapperScanner类的processBeanDefinitions方法内看到这里注册的一个spring的工厂bean
  9. public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
  10. ...
  11. @Override
  12. public T getObject() throws Exception {
  13. return getSqlSession().getMapper(this.mapperInterface);
  14. }
  15. @Override
  16. public Class<T> getObjectType() {
  17. return this.mapperInterface;
  18. }
  19. @Override
  20. public boolean isSingleton() {
  21. return true;
  22. }
  23. ...
  24. }
  25. 大部分代码删除了, 只留下这几个说明。
  26. 不了解 springFactoryBean的建议查看下相关文档。
  27. 这里用直白的话说,就是:我在service层需要注入这个Dao层接口的bean(比如[demo: springboot+mybatis][demo_ springboot_mybatis]中UserServiceImpl类的UserDao字段的自动注入),依据类型注入。spring在自己的容器里翻呀翻,如果是普通bean,一看和这个接口类型(UserDao)都不匹配就换一个,找到了这个工厂bean,一看是工厂bean,就不能直接做类型匹配了,而是调用getObjectType方法,把返回的类型和需要被注入字段的类型一比较,正好匹配(都是UserDao类型),就调用这个工厂beangetObject方法返回这个对象,然后通过反射等操作,把这个值注入到这个字段中。而调用getObject方法,其实就是我们平常直接用mybatis的接口返回的一个MapperProxy的代理对象的操作了。

发表评论

表情:
评论列表 (有 0 条评论,364人围观)

还没有评论,来说两句吧...

相关阅读

    相关 注解

    一.五个预置注解     1.@Override         2.@SafeVarargs     3.@Functionallnterface     4.

    相关 注解_

      开始学习注解时觉得注解的符号看不太习惯,加上概念上也觉得比较难得懂,后来发现这是一个很有用的东西,从新翻来看看。 注解还是很重要的在开发中,现在的开发都开始基于注解进行开