通過@MapperScan源碼瞭解Spring自定義註解掃描器

我們在使用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 . valuebasePackages
	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中添加annotationClassmarkerInterface 參數,所以這裏會使用默認值Annotation.classClass.class,並且不會給scanner掃描器賦值,而之後scanner掃描器也就不會對掃描到的mapper進行過濾。

5.這裏開始獲取我們配置的要掃描的包了,可以看到會從valuebasePackagesbasePackageClasses中獲取,所以我上面說結果是一樣的了。
	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");
      }
    });
  }

從上面可以看到會判斷annotationClassmarkerInterface ,然後通過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;
}

第一眼看上去是不是瞬間臥槽,怎麼源碼有點少,這裏實際上是調用父類ClassPathBeanDefinitionScannerdoScan方法進行掃描(內容有點多=.=)。這裏我們只要知道會對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繼承於AbstractBeanDefinitionAbstractBeanDefinition實現了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實例,beanNameuserInfo2
測試下

	@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容器時。程序會獲取beanDefinitionbeanName,然後調用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.baseIcom.p6spy.demop6spy.baseII包,用來對比參照
(1)在com.p6spy.demop6spy.baseI中新建BaseITestIBaseITestIIBaseITestIII,都實現BaseService 接口,BaseITestI.javaBaseITestII.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);
    }

}

這裏我設置了basePackagesannotationClass 兩個參數,意思爲只掃描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成功注入了,這也符合我們定的掃描策略。

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