mybatis-spring整合源码解析

Mybatis对Spring的整合实现

本文只讨论整合Spring,Mybatis是如何整合到Spring生态中的

接口扫描的MapperScan的实现和扩展

@MapperScan, 元标注了@Import注解,导入了一个MapperScannerRegistrarConfiguration Class , 申明如下

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware

我们都知道ImportBeanDefinitionRegistrar 是Spring注入Configuration Class到容器中的一种常见手段, 常见的还有@ImportImportSelector.. , 该实现类的核心逻辑在registerBeanDefinitions()中,如下

 //importingClassMetadata 为当前标注了@Import的Configuration Class的注解元信息
//BeanDefinitionRegistry registry 为当前BeanFacatory的引用
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
   //1. 获取@MapperScan注解的属性信息
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
     // 2. 注册一个名为 MapperScannerConfigurer的 Bean到IOC容器中
      registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
    }
  }
    
  // 具体注册Bean的逻辑
  void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
        //1. 构造BeanDefinition
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    builder.addPropertyValue("processPropertyPlaceHolders", true);
        //2. 设置自定义的注解,在后面自定义扫描有大用处
    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      builder.addPropertyValue("annotationClass", annotationClass);
    }
        //3. 设置自定义的接口 Class,在后面自定义扫描有大用处
    Class<?> markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
      builder.addPropertyValue("markerInterface", markerInterface);
    }
    
    //....省略部分PropertyValues的属性赋值

MapperScannerConfigurer的用处以及实现原理

是一个BeanDefinitionRegistryPostProcessor的实现类,该类型会在Spring容器启动刷新时进行回调

查看源码发现其类的声明如下

//1. 发现其是一个BeanDefinitionRegistryPostProcessor , 该类型接口会在IOC容器刷新的时候进行回调
public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware
  
//2. 能在回调方法中发现其核心做了两件事情
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders(); //解析相关包名占位符
    }
        //2.1 创建自定义的ClassPathBeanDefinitionScanner(Spring中@ComponentScan核心处理类)并添加自定义的扫描类型
    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));
    }
    scanner.registerFilters();
   //2.2 进行扫描获取BeanDefinition,并注册到容器中
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

接下来我们来看ClassPathMapperScanner组件的用处 , 他其实是扩展了spring的@ComponentScan的组件扫描方式,核心看registerFilters()方法,里面添加了要扫描的TypeFilter的方式

  public void registerFilters() {
    boolean acceptAllInterfaces = true; //1. 这个标志位是否要扫描包下所有的接口
        
        //2. 这里的annotationClass是前面注册MapperScannerConfigurer时传递进来的自定义注解属性
    if (this.annotationClass != null) {
        // 2.1 这里添加IncludeFilter表示,要添加一个允许的扫描注解,只要标注了该注解就会被ClassLoader扫描到
      addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
      acceptAllInterfaces = false;
    }

        //3. 这里的annotationClass是前面注册MapperScannerConfigurer时传递进来的自定义接口Class
    if (this.markerInterface != null) {
      //3.1 扫描自定义的接口类型,并且
      addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
        @Override
        protected boolean matchClassName(String className) {
          //不能实现类Class,只能是抽象接口或者抽象类
          return false;
        }
      });
      acceptAllInterfaces = false;
    }
        //4. 如果没有自定义注解或者自定义接口扫描,那么添加一个TypeFilter默认全部扫描所有
    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");
    });
  }

扫描时如何根据IncludeFilter/ExcludeFilter进行扫描和过滤?核心方法调用链如下

//调用链
//ClassPathMapperScanner#doScan() -> ClassPathBeanDefinitionScanner#doScan() -> ClassPathScanningCandidateComponentProvider#findCandidateComponents()  -> scanCandidateComponents()
 
 //其中scanCandidateComponents()方法具体实现如下
  private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
        Set<BeanDefinition> candidates = new LinkedHashSet<>();
        try {
            //1. 获取传递进来的扫描包路径
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                    resolveBasePackage(basePackage) + '/' + this.resourcePattern;
      //2. 使用ResourceLoader加载资源
            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 {
           //3. 使用ASM进行元信息读取
                        MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
            //4. 这里很关键里面会进行IncludeFilter和ExcludeFilter的判断,也是能自定义扩展组件扫描的核心方法
                        if (isCandidateComponent(metadataReader)) {
            // 5. 拼装成BeanDefinition,后面会给BeanDefinition设置beanClass为MapperFactoryBean代理对象
             //6. 最后注册到IOC容器中,此时我们已经可以使用Mybatis的Mapper来完成依赖注入和依赖查找了
                            ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                            sbd.setSource(resource);
                            if (isCandidateComponent(sbd)) {
                                if (debugEnabled) {
                                    logger.debug("Identified candidate component class: " + resource);
                                }
                                candidates.add(sbd);
                            }
    //省略部分无关源码...

其中isCandidateComponent()实现如下

    protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
        for (TypeFilter tf : this.excludeFilters) { //遍历所有的ExcludeFilter,若有匹配的则返回false不进行扫描
            if (tf.match(metadataReader, getMetadataReaderFactory())) {
                return false;
            }
        }
            //遍历所有的IncludeFilter,若匹配则进行Conditional条件注解判断,这里includeFilters中就包括了之前
            //ClassPathMapperScanner#registerFilters()方法中注册的includeFilters。这也是为什么我们配置了
            // @MapperScan(basePakages="xxxx")就能扫描到xxx包下的所有类到ioc容器中的所有原理
        for (TypeFilter tf : this.includeFilters) { 
            if (tf.match(metadataReader, getMetadataReaderFactory())) {
                return isConditionMatch(metadataReader);
            }
        }
        return false;
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容