Spring中使用自定义注解完成对象的创建
在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() 方法中,那么本着模仿的精神,我们也可以实现自定义注解的注册和工作:
- 自定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyComponent2 {
}
- 定义一个实现了
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);
}
}
- 将注册类添加到spring容器中
//TODO: 将注册类导入到容器中
@Import(MyImportBeanDefinitionRegister.class)
@ComponentScan(basePackages = "com.qiuguan.spring.bean")
@Configuration
public class MainConfig {
}
- 测试生效
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
等等
转载自:https://juejin.cn/post/7130594445380026405