如何通过@MapperScan源码了解Spring自定义注解扫描器

 更新时间:2026年09月28日 09:46:00   作者:团子ing  
想知道SpringBoot整合MyBatis时@MapperScan注解是如何将Mapper接口实例化并注入SpringIOC容器的吗?本文深入源码,详细解析@MapperScan注解的工作流程,包括MapperScannerRegistrar注册器和ClassPathMapperScanner扫描器的作用

我们在使用springboot 整合MyBatis时,需要在启动类上添加上@MapperScan注解,并写入mapper接口的包路径,然后我们就能通过从spring IOC容器中取对应的mapper的Bean来进行持久化操作了,那么@MapperScan是如何将mapper接口实例化并注入到Spring IOC容器中的呢?

首先搭建一个spring boot项目,引入mybatis和mysql的相关maven包。在application.properties中配置好连接参数。

这些基本操作 我就不写了

新建mapper,当然我这里取名取成dao了,意思到了就行。

package com.p6spy.demop6spy.dao;

import org.apache.ibatis.annotations.Select;
public interface TestDao {
    @Select("select count(*) from subject_sae_detail")
    String getInfo();
}

启动类上添加注解,指定扫描com.p6spy.demop6spy.dao包

@SpringBootApplication
@MapperScan({"com.p6spy.demop6spy.dao"})
public class Demop6spyApplication {

    public static void main(String[] args) {
        SpringApplication.run(Demop6spyApplication.class, args);
    }

}

就能在service实现类中注入TestDao 来进行持久化操作了。

一.@MapperScan

首先让我们来看下@MapperScan注解源码

(这里为了清晰的了解扫描注入过程,所以我只选取了部分代码,其余的删除了)。

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

  String[] value() default {};

  String[] basePackages() default {};

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

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

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

1.首先是@Import(MapperScannerRegistrar.class)

从名字上看是一个扫描注册器,点击去看下,发现其继承于ImportBeanDefinitionRegistrar。

主要作用是:

ImportBeanDefinitionRegistrar接口的作用是当这个接口的实现类(类A)被@Import接口引入某个被标记了@Configuration的注册类(类B)时,可以得到这个类(类B)的所有注解,然后做一些动态注册Bean的事儿。

我们看下@MapperScan,这也没有@Configuration啊,别急,因为@MapperScan是被启动类引用的,所以还要去看下启动类,虽然启动类看上去是只有@SpringBootApplication和@MapperScan两个注解,实际上是这样的。

@SpringBootApplication如下

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

@SpringBootConfiguration如下

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

找到@Configuration了。

2 . value和basePackages

	String[] value() default {};
  	String[] basePackages() default {};

这两行应该比较好辨认,就是需要我们填写的mapper接口所在的包路径信息。

就像这样写,后面我们会发现这俩填哪个都行,结果是一样的。

或者这样,这样写的话默认是赋值给value的

3.annotationClass

在这里的作用是:

配置了该注解的mapper才会被扫描器扫描,与basePackage是与的作用。

默认值是Annotation.class。

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

4.markerInterface

在这里的作用是:

基于接口的过滤器,实现了该接口的mapper才会被扫描器扫描,与basePackage是与的作用。

默认值是Class.class。

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

5.构造工厂

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

二.MapperScannerRegistrar

在一中我们知道了实现ImportBeanDefinitionRegistrar的大致作用。

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  private ResourceLoader resourceLoader;

  /**
   * {@inheritDoc}
   */
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

    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);
    }

    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"));
    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));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void setResourceLoader(ResourceLoader resourceLoader) {
    this.resourceLoader = resourceLoader;
  }

}

1.首先在registerBeanDefinitions()方法

第一行打个断点,看下是不是真的得到引用类的所有注解信息了

点开看下详细信息:

可以看到只含有两个注解@SpringBootApplication和@MapperScan,然后看下@MapperScan注解的value值,没错,也是我们填的值。

2.这里会通过getAnnotationAttributes方法

获取MapperScan的所有信息,上面我们知道importingClassMetadata包含启动类的所有注解信息,所以这里获取MapperScan.class注解的信息也就很轻松了。

AnnotationAttributes annoAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));

打个断点就能很清晰的看到annoAttrs 的值了:

这里我们能看到value的值就是我上面填的。

3.然后是比较重要的一点,新建扫描器scanner 

对mapper接口的扫描在这里实现。

ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

4.然后是把一些参数赋给scanner扫描器

	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);
    }
    ......

这里annotationClass对应的是只会扫描到带该注解的mapper,而markerInterface对应的是只会扫描到实现该接口的mapper。

因为我们并没有在启动器上的@MapperScan中添加annotationClass 和markerInterface 参数,所以这里会使用默认值Annotation.class和Class.class,并且不会给scanner扫描器赋值,而之后scanner扫描器也就不会对扫描到的mapper进行过滤。

5.这里开始获取我们配置的要扫描的包了

可以看到会从value,basePackages和basePackageClasses中获取,所以我上面说结果是一样的了。

	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));
    }

6.在这里scanner扫描器会开始扫描

并返回Bean定义集合(Set<BeanDefinitionHolder>)

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

在第一句scanner.registerFilters();中会用到上面3中设置的过滤参数,从方法名也可以看出来,这里会对扫描出的mapper设置过滤条件,只留下带有annotationClass 注解和实现了markerInterface接口的mapper,我们知道这里实际并没有给scanner赋这些值,所以相当于没有过滤这俩条件。

在第二句scanner.doScan(StringUtils.toStringArray(basePackages));中,会扫描出所有符合条件的mapper。

7.所有流程走完后会生成一个

Bean定义集合(Set<BeanDefinitionHolder>,内含BeanDefinition),之后

BeanDefinitionRegistry接口(ApplicationContext会实现此接口)会将新的BeanDefinition注册到Spring IOC容器中。

主要实现方法

void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)

三.ClassPathMapperScanner扫描器

因为源码相对有点长,这里就不放所有的了,只截取重点部分说明,请自行查看所有源码。

1.首先看下继承关系,ClassPathMapperScanner 继承于ClassPathBeanDefinitionScanner。

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner

熟悉Spring IOC容器的应该会知道ClassPathBeanDefinitionScanner,我们知道有多种方式将Bean注入IOC容器,可以通过在xml中配置<bean>,也可以通过@Bean注解,两种方式最终都会调用ClassPathBeanDefinitionScanner来实现扫描并生成BeanDefinition。

2.registerFilters(),在上面二.MapperScannerRegistrar中有讲到是用来配置扫描过滤条件的,来看下源码:

  public void registerFilters() {
    boolean acceptAllInterfaces = true;

    // if specified, use the given annotation and / or marker interface
    if (this.annotationClass != null) {
      addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
      acceptAllInterfaces = false;
    }

    // override AssignableTypeFilter to ignore matches on the actual marker interface
    if (this.markerInterface != null) {
      addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
        @Override
        protected boolean matchClassName(String className) {
          return false;
        }
      });
      acceptAllInterfaces = false;
    }

    if (acceptAllInterfaces) {
      // default include filter that accepts all classes
      addIncludeFilter(new TypeFilter() {
        @Override
        public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
          return true;
        }
      });
    }

    // exclude package-info.java
    addExcludeFilter(new TypeFilter() {
      @Override
      public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        String className = metadataReader.getClassMetadata().getClassName();
        return className.endsWith("package-info");
      }
    });
  }

从上面可以看到会判断annotationClass 和markerInterface ,然后通过AnnotationTypeFilter()和AssignableTypeFilter()来设置过滤器。

如果这俩都没设置的话就会默认扫描所有的:

	addIncludeFilter(new TypeFilter() {
        @Override
        public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
          return true;
        }
      });

最后会过滤掉package-info.java。

	// exclude package-info.java
    addExcludeFilter(new TypeFilter() {
      @Override
      public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        String className = metadataReader.getClassMetadata().getClassName();
        return className.endsWith("package-info");
      }
    });

3.doScan(),终于到最重要的doScan方法了。

看下源码:

public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {
      logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
    } else {
      processBeanDefinitions(beanDefinitions);
    }
    return beanDefinitions;
}

第一眼看上去是不是瞬间无语,怎么源码有点少,这里实际上是调用父类ClassPathBeanDefinitionScanner的doScan方法进行扫描(内容有点多=.=)。

这里我们只要知道会对basePackages参数包含的所有包进行扫描,并且会使用之前设置的过滤器。

最后返回扫描到的所有类——的BeanDefinitionHolder集合(当成类定义集合就行)。因为这里扫描的是Mybatis的mapper,我们知道mapper都是接口,而接口是无法实例化的,所以这样直接返回的话是没办法生成对应的实例的,就更不用说注入Spring IOC容器了。

所以需要使用processBeanDefinitions方法再加工一下。

4.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");
      }

      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
      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) {
          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);
      }
	}
}

别看洋洋洒洒写了一大堆东西,核心只有BeanDefinition一点。上面我们知道beanDefinitions集合中的bean定义都是针对mapper接口的,而接口无法实例化。

先来认知下GenericBeanDefinition ,大致讲下作用:类的定义,创建完后,BeanDefinitionRegistry接口(ApplicationContext会实现此接口)会将新的BeanDefinition注册到Spring IOC容器中(GenericBeanDefinition继承于AbstractBeanDefinition,AbstractBeanDefinition实现了BeanDefinition)。

说几个用GenericBeanDefinition注入Bean的例子吧:

新建UserInfo.java类,之后会通过几种方式实例化并注入Spring IOC容器。

public class UserInfo {
    private String userName;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
}

(1)通过实现BeanFactoryPostProcessor(Bean工厂的后置处理器)注入。

该接口在Spring IOC容器注册Bean定义的逻辑都跑完后,但是所有的Bean都还没真正实例化之前调用。

void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)

这个方法的主要是通过注入进来的BeanFactory,在真正初始化Bean之前,再对spring

IOC容器做一些动态修改。增加或者修改某些Bean定义的值,甚至再动态创建一些BeanDefinition。

新建MyBeanFactoryPostProcessor 并实现BeanFactoryPostProcessor 。

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
        genericBeanDefinition.setBeanClass(UserInfo.class);
        genericBeanDefinition.getPropertyValues().add("userName","123");

        ((DefaultListableBeanFactory) configurableListableBeanFactory)
                .registerBeanDefinition("userInfo", genericBeanDefinition);
    }
}

测试下

	@RequestMapping("/testUser")
	public void testUser(){
		try{
			UserInfo s4 = (UserInfo)applicationContext.getBean("userInfo");
			System.out.println(s4.getUserName());
		}catch (Exception ex){
		System.out.println("4.There is something error:"+ex.getMessage());
		}
	}

结果,成功注入。

(2)通过实现BeanDefinitionRegistryPostProcessor接口(Bean注册器的后置处理器)注入。

新建MyBeanDefinitionRegistryPostProcessor实现BeanDefinitionRegistryPostProcessor 接口

@Component
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
        genericBeanDefinition.setBeanClass(UserInfo.class);
        genericBeanDefinition.getPropertyValues().add("userName","456");
        
        beanDefinitionRegistry.registerBeanDefinition("userInfo2",genericBeanDefinition);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

    }
}

我们能看到这里竟然也有postProcessBeanFactory方法。原来BeanDefinitionRegistryPostProcessor 继承了BeanFactoryPostProcessor。然后注入一个UserInfo实例,beanName为userInfo2。测试下

	@RequestMapping("/testUser")
    public void testUser(){
        try{
            UserInfo s4 = (UserInfo)applicationContext.getBean("userInfo");
            System.out.println("userInfo userName:"+s4.getUserName());
        }catch (Exception ex){
            System.out.println("4.There is something error:"+ex.getMessage());
        }

        try{
            UserInfo s5 = (UserInfo)applicationContext.getBean("userInfo2");
            System.out.println("userInfo2 userName:"+s5.getUserName());
        }catch (Exception ex){
            System.out.println("5.There is something error:"+ex.getMessage());
        }
    }

结果如下,也注入进来了。

我们只要知道该步骤是完善BeanDefinition,使之后能够实例化并注入Spring IOC即可。

让我们回到源码中来。打个断点看下,在对beanDefinitions进行加工前,能看到BeanDefinition中的beanClass是我们写我的Mybatis的mapper TestDao,而TestDao是一个接口,是无法实例化的,所以之后会修改该值。

之后会重新设置BeanClass

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

这个mapperFactoryBean又是什么?

private MapperFactoryBean<?> mapperFactoryBean = new MapperFactoryBean<Object>();

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
	//......
}

翻看源码后我们发现该类继承了SqlSessionDaoSupport并实现了FactoryBean<T>,SqlSessionDaoSupport主要是可以提供sqlSessionTemplate,我们可以通过继承其来使用sqlSessionTemplate连接数据库并进行持久化操作,当然这个不是本篇的重点,重点是FactoryBean<T>。

FactoryBean是spring对外提供的对接接口,当向spring对象使用getBean("…")方法时,spring会使用FactoryBean的getObject方法返回对象。所以当一个类实现了 factoryBean接口时,那么每次向spring要这个类时,spring就返回T对象。

MapperFactoryBean<T>中有一个成员变量

private Class<T> mapperInterface;

这个是什么时候传入的呢?

让我们往回一步,在重新设置beanName的上一步。

设置了ConstructorArgumentValues

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

这里会将definition.getBeanClassName()添加进ConstructorArgumentValues中。

之后在实例化并注入Spring IOC容器时。程序会获取beanDefinition的beanName,然后调用beanName对应的构造方法实例化beanName。

用到的参数就是这里添加的ConstructorArgumentValues>

让我们看看MapperFactoryBean<T>的构造方法:

public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
}

是不是整个流程都清晰了呢。

我们知道实现了FactoryBean<T>接口的类,想要从Spring IOC容器中获取该Bean,会调用getObject方法。看下源码

	@Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

从表面上看好像mybatis也自己实现了一个容器,然后会从容器中取出Bean,之后Spring 的IOC容器会缓存该Bean,所以第二次之后就不会再去取了。

以上,就是mapper扫描,实例化到注入容器的整个过程了。

这里我们可以自己写一个demo来验证下。当然我这里就不用接口了,为了简单起见,直接注入类。

四.自动注入带自定义注解的类

1.首先让我们模仿MapperScan写一个自定义注解,作用是启动自动扫描。

MyScanAnnotation.java

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import(MyTestScannerRegistrar.class)
public @interface MyScanAnnotation {
    String[] basePackages() default {};

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

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

可以看到有三个参数,相信看完文章的各位一定都知道意义,就不多讲了。

2.新建MyTestScannerRegistrar,扫描注册器。

MyTestScannerRegistrar.java

public class MyTestScannerRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(annotationMetadata.getAnnotationAttributes(MyScanAnnotation.class.getName()));
        MyTestScanner scanner = new MyTestScanner(beanDefinitionRegistry);

        Class<? extends Annotation> annotationClass = (Class<? extends Annotation>) annoAttrs.getClass("annotationClass");
        if (!Annotation.class.equals(annotationClass)) {
            scanner.setAnnotationClass(annotationClass);
        }

        Class<?> markerInterface = annoAttrs.getClass("markerInterface");
        if (!Class.class.equals(markerInterface)) {
            scanner.setMarkerInterface(markerInterface);
        }

        List<String> basePackages = new ArrayList<String>();
        for (String pkg : annoAttrs.getStringArray("basePackages")) {
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }
        scanner.registerFilters();
        scanner.doScan(StringUtils.toStringArray(basePackages));
    }
}

这里也是很简单,获取注解参数,新建扫描器,然后把注解参数赋值给新建的扫描器。然后运行扫描。获取Set<BeanDefinitionHolder>,内含BeanDefinition,之后spring的上下文applicationContext实现的BeanDefinitionRegistry接口的

void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)

方法实例化和注入这些BeanDefinition。

3.新建MyTestScanner。

MyTestScanner.java

public class MyTestScanner extends ClassPathBeanDefinitionScanner {

    private Class<? extends Annotation> annotationClass;

    private Class<?> MarkerInterface;

    public Class<?> getMarkerInterface() {
        return MarkerInterface;
    }

    public void setMarkerInterface(Class<?> markerInterface) {
        MarkerInterface = markerInterface;
    }

    public Class<? extends Annotation> getAnnotationClass() {
        return annotationClass;
    }

    public void setAnnotationClass(Class<? extends Annotation> annotationClass) {
        this.annotationClass = annotationClass;
    }

    public MyTestScanner(BeanDefinitionRegistry registry) {
        super(registry);
    }

    public void registerFilters(){
        if(!this.annotationClass.isInstance(Annotation.class)){
            addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
        }
    }

    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
        if (beanDefinitions.isEmpty()) {
            logger.info("No MyScanAnnotation class was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
        }

        return beanDefinitions;
    }
}

因为扫描的都是类,可以实例化,所以就直接返回了。

4.新建注解MyTestAnntation,之后会自动注入带有该注解的类。

MyTestAnntation.java

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface MyTestAnntation {
}

没有参数,最简单的方式。

5.新建BaseService接口

BaseService.java

public interface BaseService {
    void getInfo();
}

5.新增包和类

新增com.p6spy.demop6spy.baseI和com.p6spy.demop6spy.baseII包,用来对比参照

(1)在com.p6spy.demop6spy.baseI中新建BaseITestI,BaseITestII和BaseITestIII,都实现BaseService 接口,BaseITestI.java和BaseITestII.java加上@MyTestAnntation注解。

BaseITestI.java

@MyTestAnntation
public class BaseITestI implements BaseService {
    public void getInfo(){
        System.out.println("Hello,BaseITestI");
    }
}

BaseITestII.java

@MyTestAnntation
public class BaseITestII implements BaseService {
    public void getInfo(){
        System.out.println("Hello,BaseITestII");
    }
}

BaseITestIII.java

public class BaseITestIII implements BaseService {
    public void getInfo(){
        System.out.println("Hello,BaseITestIII");
    }
}

(2)在com.p6spy.demop6spy.baseII中新建BaseIITestI,实现BaseService接口,添加@MyTestAnntation注解。

BaseIITestI.java

@MyTestAnntation
public class BaseIITestI implements BaseService {
    public void getInfo(){
        System.out.println("Hello,BaseIITestI");
    }
}

6.在启动类上添加@MyScanAnnotation注解

Demop6spyApplication.java

@SpringBootApplication
@MapperScan({"com.p6spy.demop6spy.dao"})
@MyScanAnnotation(basePackages = {"com.p6spy.demop6spy.baseI"},
annotationClass = MyTestAnntation.class)
public class Demop6spyApplication {

    public static void main(String[] args) {
        SpringApplication.run(Demop6spyApplication.class, args);
    }

}

这里我设置了basePackages 和annotationClass 两个参数,意思为只扫描com.p6spy.demop6spy.baseI包中带MyTestAnntation注解的类。

@MyScanAnnotation(basePackages = {"com.p6spy.demop6spy.baseI"},
annotationClass = MyTestAnntation.class)

7.测试

测试一下

	@RequestMapping("/testAnn")
    public void testAnnotation(){
        try{
            BaseService s1 = (BaseService)applicationContext.getBean("baseITestI");
            s1.getInfo();
        }catch (Exception ex){
            System.out.println("1.There is something error:"+ex.getMessage());
        }

        try{
            BaseService s2 = (BaseService)applicationContext.getBean("baseITestII");
            s2.getInfo();
        }catch (Exception ex){
            System.out.println("2.There is something error :"+ex.getMessage());
        }

        try{
            BaseService s3 = (BaseService)applicationContext.getBean("baseITestIII");
            s3.getInfo();
        }catch (Exception ex){
            System.out.println("3.There is something error:"+ex.getMessage());
        }

        try{
            BaseService s4 = (BaseService)applicationContext.getBean("baseIITestI");
            s4.getInfo();
        }catch (Exception ex){
            System.out.println("4.There is something error:"+ex.getMessage());
        }
    }

结果

可以看到只有I和II成功注入了,这也符合我们定的扫描策略。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Java中使用增强for循环的实例方法

    Java中使用增强for循环的实例方法

    在本篇文章里小编给大家整理是的关于Java中如何使用增强for循环的实例内容以及相关代码,需要的朋友们可以学习下。
    2019-08-08
  • idea中javaweb的jsp页面图片加载不出来问题及解决

    idea中javaweb的jsp页面图片加载不出来问题及解决

    这篇文章主要介绍了idea中javaweb的jsp页面图片加载不出来问题及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-07-07
  • 查找native方法的本地实现函数native_function详解

    查找native方法的本地实现函数native_function详解

    JDK开放给用户的源码中随处可见Native方法,被Native关键字声明的方法说明该方法不是以Java语言实现的,而是以本地语言实现的,Java可以直接拿来用。这里介绍下查找native方法的本地实现函数native_function,感兴趣的朋友跟随小编一起看看吧
    2021-12-12
  • SpringBoot3应用中集成和使用Spring Retry的实践记录

    SpringBoot3应用中集成和使用Spring Retry的实践记录

    SpringRetry为SpringBoot3提供重试机制,支持注解和编程式两种方式,可配置重试策略与监听器,适用于临时性故障场景,需合理设置次数、退避策略并做好监控,本文给大家详细介绍如何在 SpringBoot 3 应用中集成和使用 Spring Retry,感兴趣的朋友一起看看吧
    2025-06-06
  • 深入解读Java三大集合之map list set的用法

    深入解读Java三大集合之map list set的用法

    集合弥补了数组的缺陷,它比数组更灵活更实用,可大大提高软件的开发效率,而且不同的集合适用于不同的场合,这篇文章带你了解Java的三大集合map、list、set用法
    2021-11-11
  • Java实现WGS84/GCJ02/BD09的坐标互转终极方案

    Java实现WGS84/GCJ02/BD09的坐标互转终极方案

    这篇文章主要为大家详细介绍了Java如何利用Geotools实现WGS84/GCJ02/BD09的坐标互转,文中的示例代码讲解详细,需要的小伙伴可以了解下
    2025-09-09
  • Java根据身份证号计算年龄,15位身份证号码转18位原理与操作示例

    Java根据身份证号计算年龄,15位身份证号码转18位原理与操作示例

    这篇文章主要介绍了Java根据身份证号计算年龄,15位身份证号码转18位原理与操作,结合实例形式详细分析了构成身份证号码的各个位的含义,15位身份证号码转18位的方法及Java根据身份证号计算年龄相关操作技巧,需要的朋友可以参考下
    2019-10-10
  • Springboot视图解析器ViewResolver使用实例

    Springboot视图解析器ViewResolver使用实例

    这篇文章主要介绍了Springboot视图解析器ViewResolver使用实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • Java 9中List.of()的使用示例及注意事项

    Java 9中List.of()的使用示例及注意事项

    Java 9引入了一个新的静态工厂方法List.of(),用于创建不可变的列表对象,这篇文章主要介绍了Java 9中List.of()的使用示例及注意事项的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-03-03
  • spring boot线上日志级别动态调整的配置步骤

    spring boot线上日志级别动态调整的配置步骤

    这篇文章主要为大家介绍了spring boot线上日志级别动态调整的配置步骤,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2022-03-03

最新评论