文章背景
我们在使用SpringBoot 时,经常需要开启某个功能,比如开启事务注解,需要在配置类上加@EnableTransactionManagement , 开启异步注解需要配置类加上@EnableAsync。看起来很牛逼,本文就简单展示三种方式实现,自定义 @Enable 功能。
温馨提示 :①本文不涉及任何原理
②看了这篇当然不够,远远不够,别指望看一篇就能学到所有
1 导入配置类
实现需要有一个自定义注解, @Import 导入一个配置类(Configuration),配置类通过@Bean 定义我们需要的Bean,我们可以通过配置类定义多个Bean(我这里只是演示简单的例子) 。当然配置内可以加上一些条件注解 ConditionalOnProperty 等,使得需要某些配置才会生效。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MyEnableConfig.class)
@Documented
public @interface MyEnable {
}
@Configuration
public class MyEnableConfig {
@Bean
public MyEnableProcessor enableDirectionProcessor(){
return new MyEnableProcessor();
}
}
测试类
@SpringBootApplication
@MyEnable
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
MyEnableProcessor myEnableProcessor = context.getBean(MyEnableProcessor.class);
myEnableProcessor.testEnableDirection();
}
}
2 导入 ImportSelector
先看看ImportSelector接口,直接看demo
还是那个配置了,只是@Import 导入的是我们实现的Selector,返回的是String[],也就是我们可以“注册”多个我们需要的Bean。入参AnnotationMetadata,我们可以根据注解信息,我们的例子没有用到,返回不同的Bean,
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
//@Import(MyEnableConfig.class)
@Import(MyEnableSelector.class)
@Documented
public @interface MyEnable {
}
public class MyEnableSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{MyEnableProcessor.class.getName()};
}
}
@EnableAsync
来看看我们平常用的@EnableAsync 注解,根据不同条件返回了不同的Bean。
3 ImportBeanDefinitionRegistrar
还是那个配置了,只是@Import 导入的是我们实现的
ImportBeanDefinitionRegistrar。ImportBeanDefinitionRegistrar 更粗暴,可以通过BeanDefinitionRegistry 给Spring容器注册我们的BeanDefinition。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
//@Import(MyEnableConfig.class)
//@Import(MyEnableSelector.class)
@Import(MyEnableRegistrar.class)
@Documented
public @interface MyEnable {
}
public class MyEnableRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
ImportBeanDefinitionRegistrar.super.registerBeanDefinitions(importingClassMetadata, registry, importBeanNameGenerator);
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Class clazz = MyEnableProcessor.class;
GenericBeanDefinition bd = new GenericBeanDefinition();
bd.setBeanClass(clazz);
registry.registerBeanDefinition(clazz.getName(),bd);
}
}
项目结构
下图是实际项目目录结构,启动类和对应的注册,放在不同包下。
总结下各自使用场景
-
导入配置,这种方式推荐我们业务使用,这种我们最熟悉,结合一些条件注解比如ConditionalOnProperty,轻松实现满足一些条件才开启功能的场景。
-
ImportSelector ,自定义注解中可以有一些其他属性,这些属性不同,可能返回不同的Bean。
-
ImportBeanDefinitionRegistrar:通过BeanDefinitionRegistry 给Spring容器注册我们的BeanDefinition。这个感知Spring框架,通常一些中间件框架使用,平常业务不建议使用。
How to 选择
-
如果你的使用场景是,配置文件存在某些配置才开启某些功能, 比如配置文件中包含spring.data.mongodb相关配置,则自动开启Spring data mongo 功能,这种方式建议使用第一种,因为用ConditionalOnProperty 很轻松就实现了。
-
如果是 @EnableXXX 注解包含了一些属性、配置等, 优先选第二种。
-
如果想要更精确的定制化Bean , 使用第三种