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。

(本文结束)

 

您可能感兴趣的与本文相关的镜像

Llama Factory

Llama Factory

模型微调
LLama-Factory

LLaMA Factory 是一个简单易用且高效的大型语言模型(Large Language Model)训练与微调平台。通过 LLaMA Factory,可以在无需编写任何代码的前提下,在本地完成上百种预训练模型的微调

<think>首先,用户的问题是:“请问MyBatis@MapperScan注解是否需要手动import引入”。这涉及到Java和SpringBoot中的注解使用。从提供的引用中,我可以提取相关信息:-引用[1]:解释了@MapperScan的作用,但没有直接提到是否需要手动导入。-引用[2]:提到了@MapperScan的使用示例,但也没有明确说导入。-引用[3]:展示了@MapperScan的使用方法,包括在配置类上使用,但没有说导入。-引用[4]:描述了@MapperScan的主要作用,但没有提及导入。-引用[5]:详细说明了@MapperScan的内部机制,包括如何注册BeanDefinition,但没有直接回答是否需要手动导入。关键点:在Java中,使用任何类或注解时,都需要导入它。@MapperScanMyBatis-Spring集成的部分,通常位于org.mybatis.spring.annotation包中。所以,要使用@MapperScan,你需要在代码中导入它。例如,在你的配置类中,你需要添加导入语句:importorg.mybatis.spring.annotation.MapperScan;用户的问题是关于“手动import引入”,意思是是否需要显式地在代码中写导入语句。答案是:是的,在Java源代码中,你需要手动导入这个注解,以便编译器知道它。在SpringBoot项目中,当你添加了MyBatis-SpringBootStarter依赖时,这个注解会自动可用,但你仍然需要在代码中导入它。从引用[3]:它展示了使用@MapperScan的代码片段,但没有显示导入部分。在实际代码中,你需要导入它。类似地,在引用[2]中,它提到了注解,但没有讨论导入。因此,回答应该是:是的,需要手动导入@MapperScan注解。现在,关于回答结构:-系统级指令要求:使用中文回答,生成相关问题,引用标识添加到段落末尾。-回答要清晰、可靠。结构:1.直接回答问题。2.解释原因或背景。3.提供示例。4.生成相关问题。最后,添加引用标识。引用来自提供的上下文,所以我可以引用它们。例如:-引用[3]展示了如何使用@MapperScan。-引用[5]解释了其内部机制。但用户的问题是关于导入的,所以可能引用[3]或般知识。在回答中,末尾添加引用标识。生成相关问题:基于主题,如MyBatis注解的使用。相关问题建议:1.如何正确使用@MapperScan注解?2.@MapperScan与@Mapper有什么区别?3.MyBatis中其他常用注解是什么?确保回答真实可靠:基于我的知识和提供的引用。最终回答框架:-开头:是的,需要手动导入。-解释:在Java中,使用外部类或注解时,需要导入相应的包。-示例:例如,在配置类中,添加importorg.mybatis.spring.annotation.MapperScan;然后在类上使用@MapperScan("package.name")。-引用:引用[3]或[4]。-生成相关问题。</think>### MyBatis @MapperScan 注解是否需要手动导入? 是的,在使用 MyBatis 的 `@MapperScan` 注解时,**需要手动导入(import)** 该注解到你的 Java 代码中。这是因为 `@MapperScan` 是 MyBatis-Spring 集成库提供的注解,位于 `org.mybatis.spring.annotation` 包下。在 Java 开发中,任何外部类或注解都必须通过 `import` 语句显式引入,以便编译器识别和使用它。如果不手动导入,编译器会报错,提示找不到符号。 #### 原因和背景 - **Java 语言要求**:Java 的包管理机制要求开发者显式导入非当前包下的类或注解。`@MapperScan` 属于 MyBatis-Spring 的特定包,因此必须在代码顶部添加导入语句。 - **Spring Boot 集成**:虽然 Spring Boot 的起步依赖(如 `mybatis-spring-boot-starter`)会自动引入 `@MapperScan` 的库到项目类路径中,但这只解决了依赖问题。你仍需在代码中手动导入注解,类似于其他 Spring 或 MyBatis 注解的使用方式[^3][^4]。 - **实际使用示例**:以下是个典型的配置类代码片段,展示了如何手动导入和使用 `@MapperScan`: ```java import org.mybatis.spring.annotation.MapperScan; // 必须手动导入 import org.springframework.context.annotation.Configuration; @Configuration @MapperScan("com.example.mapper") // 扫描指定包下的 Mapper 接口 public class MyBatisConfig { // 其他配置代码... } ``` 如果不添加 `import org.mybatis.spring.annotation.MapperScan;`,编译器会抛出错误,例如 `cannot find symbol`。 #### 注意事项 - **依赖管理**:确保项目中已添加 MyBatis-Spring 依赖(如 Maven 中的 `mybatis-spring-boot-starter`)。否则,手动导入也无法解析注解。 - **替代方案**:如果不想手动导入,可以使用 Spring 的组件扫描(如 `@ComponentScan`),但它不能直接替代 `@MapperScan` 的 Mapper 接口注册功能。`@MapperScan` 专为简化 MyBatis Mapper 的注册而设计,避免了手动配置每个 Mapper Bean 的繁琐工作[^4][^5]。 总之,手动导入 `@MapperScan` 是必要的步骤,它确保了代码的可编译性和正确性。如果你在 Spring Boot 项目中正确配置了依赖和导入,该注解会自动扫描并注册 Mapper 接口到 Spring 容器,简化数据库操作[^1][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值