MyBatis源碼學習(一)——@MapperScan註解掃描Mapper接口文件,創建BeanDefinition

先上小結:

  1. @MapperScan註解生效。觸發@Import({MapperScannerRegistrar.class})註解。
  2. @Import({MapperScannerRegistrar.class})註解生效。調用MapperScannerRegistrar.registerBeanDefinitions()。
  3. MapperScannerRegistrar創建掃描工具類Scanner。
  4. Scanner從@MapperScan註解中獲取Mapper接口路徑。
  5. 調用Scanner.doScan(),創建BeanDefinitionHolder,掃描接口路徑中的Mapper接口,創建BeanDefinition,進而封裝爲BeanDefinitionHolder,向BeanFactory註冊。
  6. BeanDefinition中的類名從原來的Mapper類名改爲MapperFactoryBean.class。
  7. BeanDefinition創建完成。

 

正文:

當我們打開@MapperScan註解的代碼,大概是這樣的:

package org.mybatis.spring.annotation;

import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.Import;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MapperScannerRegistrar.class})
public @interface MapperScan {
    String[] value() default {};

    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};

    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

    Class<? extends Annotation> annotationClass() default Annotation.class;

    Class<?> markerInterface() default Class.class;

    String sqlSessionTemplateRef() default "";

    String sqlSessionFactoryRef() default "";

    Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
}

首先,我們看到有個factoryBean()方法,默認返回的Class是MapperFactoryBean.class,全路徑是org.mybatis.spring.mapper.MapperFactoryBean.class,這個class在後面定義BeanDefinition時會用到。

 

下面開始正題,注意到其中有個@Import註解:

@Import({MapperScannerRegistrar.class})

因爲這個註解,使得被@MapperScan註解標識的類被Spring初始化時,其中的@Import註解會生效,效果是調用@Import中定義的類的registerBeanDefinitions()方法,在@MapperScan註解的@Import註解中,調用的是MapperScannerRegistrar類的registerBeanDefinitions()方法:

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    if (this.resourceLoader != null) {
        scanner.setResourceLoader(this.resourceLoader);
    }

    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((BeanNameGenerator)BeanUtils.instantiateClass(generatorClass));
    }

    Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
        scanner.setMapperFactoryBean((MapperFactoryBean)BeanUtils.instantiateClass(mapperFactoryBeanClass));
    }

    scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
    scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
    List<String> basePackages = new ArrayList();
    String[] var10 = annoAttrs.getStringArray("value");
    int var11 = var10.length;

    int var12;
    String pkg;
    for(var12 = 0; var12 < var11; ++var12) {
        pkg = var10[var12];
        if (StringUtils.hasText(pkg)) {
            basePackages.add(pkg);
        }
    }

    var10 = annoAttrs.getStringArray("basePackages");
    var11 = var10.length;

    for(var12 = 0; var12 < var11; ++var12) {
        pkg = var10[var12];
        if (StringUtils.hasText(pkg)) {
            basePackages.add(pkg);
        }
    }

    Class[] var14 = annoAttrs.getClassArray("basePackageClasses");
    var11 = var14.length;

    for(var12 = 0; var12 < var11; ++var12) {
        Class<?> clazz = var14[var12];
        basePackages.add(ClassUtils.getPackageName(clazz));
    }

    scanner.registerFilters();
    scanner.doScan(StringUtils.toStringArray(basePackages));
}

其中創建了掃描工具類scanner,然後從@MapperScan註解中獲取Mapper接口路徑:

String[] var10 = annoAttrs.getStringArray("value");

得到的就是在@MapperScan註解中配置的Mapper接口路徑,在上面的路徑中,就是com.macro.mall.mapper和com.macro.mall.dao兩個路徑,最後把這兩個路徑傳入scanner.doScan()方法。

scanner.doScan()方法代碼:

public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
    if (beanDefinitions.isEmpty()) {
        this.logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
    } else {
        this.processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
}

doScan()方法有兩個關鍵點,第一個是調用父類ClassPathMapperScanner的同名方法:

super.doScan()

用於創建BeanDefinitionHolder,爲生成BeanDefinition做準備。

另一個關鍵點是,調用本類的processBeanDefinitions()方法。

先看一下父類的doScan()方法:

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet();
    String[] var3 = basePackages;
    int var4 = basePackages.length;

    for(int var5 = 0; var5 < var4; ++var5) {
        String basePackage = var3[var5];
        Set<BeanDefinition> candidates = this.findCandidateComponents(basePackage);
        Iterator var8 = candidates.iterator();

        while(var8.hasNext()) {
            BeanDefinition candidate = (BeanDefinition)var8.next();
            ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
            candidate.setScope(scopeMetadata.getScopeName());
            String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
            if (candidate instanceof AbstractBeanDefinition) {
                this.postProcessBeanDefinition((AbstractBeanDefinition)candidate, beanName);
            }

            if (candidate instanceof AnnotatedBeanDefinition) {
                AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition)candidate);
            }

            if (this.checkCandidate(beanName, candidate)) {
                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                beanDefinitions.add(definitionHolder);
                this.registerBeanDefinition(definitionHolder, this.registry);
            }
        }
    }

    return beanDefinitions;
}

可以看到,對於每一個路徑來說,調用:

Set<BeanDefinition> candidates = this.findCandidateComponents(basePackage);

得到初步的Mapper接口的BeanDefinition,其中findCandidateComponents()方法的代碼在父類ClassPathScanningCandidateComponentProvider中:

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
    return this.componentsIndex != null && this.indexSupportsIncludeFilters() ? this.addCandidateComponentsFromIndex(this.componentsIndex, basePackage) : this.scanCandidateComponents(basePackage);
}

此處componentsIndex是null,代碼會調用後面的this.scanCandidateComponents()方法:

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
    LinkedHashSet candidates = new LinkedHashSet();

    try {
        String packageSearchPath = "classpath*:" + this.resolveBasePackage(basePackage) + '/' + this.resourcePattern;
        Resource[] resources = this.getResourcePatternResolver().getResources(packageSearchPath);
        boolean traceEnabled = this.logger.isTraceEnabled();
        boolean debugEnabled = this.logger.isDebugEnabled();
        Resource[] var7 = resources;
        int var8 = resources.length;

        for(int var9 = 0; var9 < var8; ++var9) {
            Resource resource = var7[var9];
            if (traceEnabled) {
                this.logger.trace("Scanning " + resource);
            }

            if (resource.isReadable()) {
                try {
                    MetadataReader metadataReader = this.getMetadataReaderFactory().getMetadataReader(resource);
                    if (this.isCandidateComponent(metadataReader)) {
                        ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                        sbd.setResource(resource);
                        sbd.setSource(resource);
                        if (this.isCandidateComponent((AnnotatedBeanDefinition)sbd)) {
                            if (debugEnabled) {
                                this.logger.debug("Identified candidate component class: " + resource);
                            }

                            candidates.add(sbd);
                        } else if (debugEnabled) {
                            this.logger.debug("Ignored because not a concrete top-level class: " + resource);
                        }
                    } else if (traceEnabled) {
                        this.logger.trace("Ignored because not matching any filter: " + resource);
                    }
                } catch (Throwable var13) {
                    throw new BeanDefinitionStoreException("Failed to read candidate component class: " + resource, var13);
                }
            } else if (traceEnabled) {
                this.logger.trace("Ignored because not readable: " + resource);
            }
        }

        return candidates;
    } catch (IOException var14) {
        throw new BeanDefinitionStoreException("I/O failure during classpath scanning", var14);
    }
}

方法首先拼了一個文件路徑:

String packageSearchPath = "classpath*:" + this.resolveBasePackage(basePackage) + '/' + this.resourcePattern;

如果配置的路徑是com.macro.mall,那麼這裏拼的路徑就是:classpath*:com/macro/mall/**/*.class

然後按此路徑加載Resource:

Resource[] resources = this.getResourcePatternResolver().getResources(packageSearchPath);

這裏的Resource數組就是Mapper接口類的數組了。

在後面的循環中,就是使用這些Resource來初始化BeanDefinition了,可以看到,BeanDefinition使用的具體實現類是ScannedGenericBeanDefinition類,也就是這段:

ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);

這個scanCandidateComponents()方法返回的就是一個ScannedGenericBeanDefinition的一個Set。

 

此方法完成後,回到ClassPathBeanDefinitionScanner的doScan()方法,this.findCandidateComponents()方法執行完成,我們得到了經過初始化的BeanDefinition列表,在後面的代碼中,循環BeanDefinition並繼續對其進行組裝,其中創建了一下bean的名字:

String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);

這個名字是Mapper接口類的名字,默認只有類名沒有路徑,且爲駝峯格式,比如com.macro.mall.mapper.CmsHelpCategoryMapper類得到的beanName就是cmsHelpCategoryMapper。

在循環的最後,創建了BeanDefinitionHolder,也就是這一段:

if (this.checkCandidate(beanName, candidate)) {
    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
    definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
    beanDefinitions.add(definitionHolder);
    this.registerBeanDefinition(definitionHolder, this.registry);
}

BeanDefinitionHolder實際上只是對原來得到的BeanDefinition的一個簡單封裝,其中的屬性有:

  • private final BeanDefinition beanDefinition;
  • private final String beanName;
  • private final String[] aliases;

除了把剛纔的BeanDefinition直接賦值進來之外,還賦值了beanName,aliases是別名,但是這裏沒用到。

後面調用this.registerBeanDefinition()方法把這個BeanDefinitionHolder往BeanFactory註冊了一下,實際上就是維護了一個Map,key是beanName,value是BeanDefinitionHolder中的BeanDefinition。

當這個循環完成,我們得到了一個BeanDefinitionHolder的Set,至此ClassPathMapperScanner的doScan方法第一步,super.doScan()方法執行完成,再貼一下這段代碼:

public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
    if (beanDefinitions.isEmpty()) {
        this.logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
    } else {
        this.processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
}

下面進行第二步,執行this.processBeanDefinitions()方法,開始對BeanDefinition列表進行處理。

首先,貼一下this.processBeanDefinitions()方法代碼:

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    Iterator var3 = beanDefinitions.iterator();

    while(var3.hasNext()) {
        BeanDefinitionHolder holder = (BeanDefinitionHolder)var3.next();
        GenericBeanDefinition definition = (GenericBeanDefinition)holder.getBeanDefinition();
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + definition.getBeanClassName() + "' mapperInterface");
        }

        definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
        definition.setBeanClass(this.mapperFactoryBean.getClass());
        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) {
                this.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) {
                this.logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
            }

            definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
            explicitFactoryUsed = true;
        }

        if (!explicitFactoryUsed) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
            }

            definition.setAutowireMode(2);
        }
    }

}

很直接,上來就開始循環,從BeanDefinitionHolder中拿到之前初始化好的BeanDefinition,首先這一行:

definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());

把BeanClassName作爲了將來用於構造的參數。

然後這一行:

definition.setBeanClass(this.mapperFactoryBean.getClass());

注意,這行代碼把BeanDefinition的beanClass給改了,原來的beanClass是Mapper本身那個interface,這裏改成了mapperFactoryBean,也就是最開始@MapperScan註解裏的MapperFactoryBean.class,這是個工廠類,將來生成Mapper接口代理工廠的時候會用到。

後面的sqlSessionFactoryBeanName和sqlSessionTemplateBeanName,如果配置了相關的參數,也會加到BeanDefinition的參數列表裏。

 

至此,Mapper接口相關的BeanDefinition初始化完成,後面Spring進行依賴注入和生成代理的時候,會用到上面的BeanDefinition。

(本文結束)

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章