@MapperScan 源码解析

@MapperScan 源码解析aa

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

@MapperScan

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan { 
    ... }

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

MapperScannerRegistrar

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware { 
    ... }

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

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中的实现如下

	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的扫描器重写了这个方法

  @Override
  protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { 
   
    return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
  }

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

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

	  //修改构造方法的参数 
      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是什么呢?

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()

 //当前的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.net/144735.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 最全的sublime插件整理

    最全的sublime插件整理PackageControl1.插件管理器1)在Sublime中打开View–>ShowConsole,将以下代码复制到输入框中后按回车键 importurllib.request,os;pf=’PackageControl.sublime-package’;ipp=sublime.installed_packages_path();urllib.request.i

    2022年6月24日
    67
  • windows,cmd中进行盘符的切换「建议收藏」

    windows,cmd中进行盘符的切换「建议收藏」需求描述:  在工作中,有的时候需要在cmd中进行盘符的切换,以前总有些时候 通过cd来切,就是切换不过去,每次都要进行百度查询,所以,这次就记录下,  留着以后再用。操作过程:1.通过win+r->cmd的方式进入到cmd命令行中2.通过盘符加冒号的方式,切换到不同的磁盘根目录下,举例:切换到d盘下,执行d:然后按回车备注:此时,就切换到了D盘的根…

    2022年10月3日
    1
  • 几款Android 应用自动化测试工具「建议收藏」

    简述:本文介绍几款流行的Android应用自动化测试工具。Monkey测试:随机测试,压力测试,运行在模拟器或实际设备中。MonkeyRunner测试:操作简单,可录制测试脚本,可视化操作,主要生成坐标的自动化操作,移植性不强Robotium 测试Ronaorex 测试Appium测试UIAutomator测试Test

    2022年4月11日
    96
  • CAS 认证原理[通俗易懂]

    CAS 认证原理[通俗易懂]一CAS原理简介CAS官方网站上的介绍图:主要原理:用户第一次访问一个CAS服务的客户web应用时(访问URL:http://192.168.7.90:8081/web1),部署在客户web应用的casAuthenticationFilter,会截获此请求,生成s…

    2022年6月24日
    68
  • python 写入文件时编码问题

    python 写入文件时编码问题本文仅供学习交流使用,如侵立删!联系方式及demo下载见文末python写入文件时编码错误UnicodeDecodeError:’utf-8’codeccan’tdecodebyte..解决:增加errors=‘ignore’withopen(‘数据.csv’,”r”,encoding=’utf-8′,errors=’ignore’,newline=””)asf1:本文仅供学习交流使用,如侵立删!企鹅、WX:1033383881(备注来意)

    2022年10月2日
    3
  • C++ XML 库 TinyXML2 的基本使用

    C++ XML 库 TinyXML2 的基本使用0.前言TinyXML-2是一个简单,小型,高效的C++XML解析器,可以轻松集成到其他程序中,直接引用源文件的话只需要包含两个文件(h和cpp,此外还有个测试文件里面带有demo)。TinyXML-2解析XML文档,并以此为基础构建可读取,修改和保存的文档对象模型(DOM)。文档说,在解释XML时仅使用UTF-8,假定所有XML为UTF-8(看了下使用MSVC编译器时生成的XML文件文本编码使用的本地编码)。该库还支持打印到文件或内存,使用XMLPr

    2022年5月6日
    142

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号