likes
comments
collection
share

Spring中使用自定义注解完成对象的创建

作者站长头像
站长
· 阅读数 12

在Spring中,如果想为某一个类创建一个spring容器管理的对象,我们可以使用 @Service@Repository@Controller 以及 @Component注解,但其实这些归根结底就是一个注解:@Component 注解,因为其他注解也都是基于 @Component 构建的用于业务分层的注解,我们以 @Service 注解为例看下:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component   //TODO:确实是基于@Component注解的
public @interface Service {

   @AliasFor(annotation = Component.class)
   String value() default "";

}

那么这就给了我们一个思路,如果我们想自定义一个注解,并且想让这个注解完成 @Component的功能,那就完成参考 @Servcie 注解:

1.实现方式一

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component  //TODO:在自定义注解上添加 @Component注解
public @interface MyComponent {

}

这种方式非常的简单,就是在自定义注解上添加 @Component注解即可

2. 实现方式二

在使用Spring的时候,我们知道它有一个包扫描的过滤规则(excludeFilters/includeFilters),比如:

@ComponentScan(basePackages = "com.qiuguan.spring", //指定包扫描路
               //排除掉 @Controller注解标注的类
               excludeFilters = {
                    @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class)
               }
)
@Configuration //等价于 applicationContext.xml配置文件
public class MainConfig {
   
}

如果熟悉过滤规则就知道,它有一个自定义的过滤规则FilterType.CUSTOM,那么我们就可以利用它来完成自定义注解的生效。

自定义过滤规则需要实现 TypeFilter接口

public class MyTypeFilter implements TypeFilter {

    /**
     *
     * @param metadataReader: 读取到的当前正在扫描的类的信息
     * @param metadataReaderFactory: 可以获取到其他任何类的信息
     */
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {

        //TODO:获取当前类的所有注解信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        //TODO:获取当前类的基本信息,比如实现了哪个接口,修饰符是什么等等
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        //TODO:获取当前类的资源信息,比如类的磁盘位置
        Resource resource = metadataReader.getResource();

        //TODO:包含了自定义注解的MyComponent2 就返回true, 否则就返回false
        return annotationMetadata.hasAnnotation(MyComponent2.class.getName());
    }
}

使用自定义的过滤规则:

@ComponentScan(basePackages = "com.qiuguan.spring", //指定包扫描路径
               //如果想要"只"包含 includeFilters 的规则,需要 useDefaultFilters = false,禁用掉默认的规则
               includeFilters = {
                     @ComponentScan.Filter(type = FilterType.CUSTOM, classes = MyTypeFilter.class)
               }, useDefaultFilters = true //默认是true, 这样可以在默认的过滤规则的基础上在添加自定义的过滤规则
)
@Configuration //等价于 applicationContext.xml配置文件
public class MainConfig {

}

这样在com.qiuguan.spring 包下的标注了@MyComponent2注解的类将会纳入到Spring的IOC容器中.

测试:

public class MainApplication {

    public static void main(String[] args) {
        //注解版的IOC容器是  AnnotationConfigApplicationContext
        ApplicationContext ctx = new AnnotationConfigApplicationContext(MainConfig.class);
        for (String beanDefinitionName : ctx.getBeanDefinitionNames()) {
            System.out.println("beanDefinitionName = " + beanDefinitionName);
        }
    }
}

3.实现方式三

逐本溯源,还是围绕 @Component 注解展开,我们看下他是如何被注册并生效的,这个是在spring 的 ClassPathBeanDefinitionScanner 的构造器中,跟进去会来到ClassPathScanningCandidateComponentProvider#registerDefaultFilters() 方法中,那么本着模仿的精神,我们也可以实现自定义注解的注册和工作:

  1. 自定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyComponent2 {

}
  1. 定义一个实现了 ImportBeanDefinitionRegistrar 接口的实现类

这个接口就是给容器注册bean, 将类的基本信息放入DefaultListableBeanFactory#beanDefinitionMap 属性中,留着后面去创建对象

public class MyImportBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {

    /**
     *
     * @param importingClassMetadata:
     * @param registry
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry, false);
        //注册自定义扫描的注解
        scanner.addIncludeFilter(new AnnotationTypeFilter(MyComponent2.class));

        //获取包扫描的路径
        Map<String, Object> annMap = importingClassMetadata.getAnnotationAttributes(ComponentScan.class.getName());
        AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap(annMap);
        assert annotationAttributes != null;
        String[] basePackages = annotationAttributes.getStringArray("basePackages");

        //默认取主配置类下的包路径,springboot使用的就是这种方式
        //AutoConfigurationPackages.get(this.beanFactory);
        //ClassUtils.getPackageName("mainClass");

        //扫描的包路径
        scanner.scan(basePackages);
    }

}
  1. 将注册类添加到spring容器中
//TODO: 将注册类导入到容器中
@Import(MyImportBeanDefinitionRegister.class)
@ComponentScan(basePackages = "com.qiuguan.spring.bean")
@Configuration
public class MainConfig {
}
  1. 测试生效
public class MainApplication {

    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(MainConfig.class);
        
        //TODO:给Student类上添加 @MyComponent2  注解
        Student bean = ctx.getBean(Student.class);

        System.out.println("bean = " + bean);
    }
}

目前我知道的方式有这三种,后面发现其他的方式在进行补充。

题外话:学习第三种方式可以参考 @MapperScan 注解的原理,对自定义注解的拓展将会更有帮助,可以完成更高级的功能。

以及 MybatisAutoConfiguration@ServletComponentScan 等等