Spring 与自定义注解、内部配置化的联合应用
一、Java注解的简略介绍
注解,也叫Annotation
、标注,是 Java 5 带来的新个性。
- 可应用范畴
类、字段、办法、参数、构造函数、包等,具体可参阅枚举类
java.lang.annotation.ElementType
生命周期(摘自 刘大飞的博客 )
RetentionPolicy.SOURCE
注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃RetentionPolicy.CLASS
注解被保留到class文件,但 jvm 加载class文件时候被遗弃,这是默认的生命周期RetentionPolicy.RUNTIME
注解不仅被保留到class文件中,jvm 加载class文件之后,依然存在
- 应用形式
能够应用反射获取注解的内容,具体如何应用请本人百度,可参考这篇Java注解齐全解析,这里不是重点,不多做介绍
二、Spring的 @Import
注解
@Import
注解是Spring用来注入 Spring Bean 的一种形式,能够用来润饰别的注解,也能够间接在Springboot配置类上应用。
它只有一个value属性须要设置,来看一下源码
public @interface Import { Class<?>[] value();}
这里的 value属性只承受三种类型的Class:
- 被
@Configuration
润饰的配置类 - 接口
org.springframework.context.annotation.ImportBeanDefinitionRegistrar
的实现类 - 接口
org.springframework.context.annotation.ImportSelector
的实现类
上面针对三种类型的Class别离做简略介绍,两头交叉自定义注解与内部配置的联合应用形式。
三、被@Configuration
润饰的配置类
像 Springboot 中的配置类一样失常应用,须要留神的是,如果该类的包门路已在Springboot启动类上配置的扫描门路下,则不须要再从新应用@Import
导入了,因为@Import
的目标是注入bean,然而Springboot启动类主动扫描曾经能够注入你想通过@Import
导入的bean了。
这种Class能够进行如下拓展
- 继承各种
Aware
接口, 获取对应的信息(如果不分明Aware
接口在Spring当中的作用,请自行百度),如,继承EnviromentAware
,能够拿到Spring的环境配置信息,进而从中拿到@Value
所须要的值,如environment.getProperty("user.username")
- 应用
@Autowire
、@Resource
、@Value
注入各种所需Spring 资源 - 像一般Spring Bean 一样应用该类
更多应用形式,请自行百度。
四、接口org.springframework.context.annotation.ImportBeanDefinitionRegistrar
的实现类
当@Import
润饰自定义注解时候,通常会导入这品种。
来看一下接口定义
public interface ImportBeanDefinitionRegistrar { default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) { registerBeanDefinitions(importingClassMetadata, registry); } /** * importingClassMetadata 被@Import润饰的自定义注解的元信息,能够取得属性汇合 * registry Spring bean注册核心 **/ default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { }
通过这种形式,咱们能够依据自定义注解配置的属性值来注入Spring Bean 信息。
来看如下案例,咱们通过一个注解,启动RocketMq的音讯发送器:
@SpringBootApplication@EnableMqProducer(group="xxx")public class App { public static void main(String[] args) { SpringApplication.run(App.class); }}
这是一个服务项目的启动类,这个服务开启了RocketMq的一个发送器,并且分到xxx组里。
来下一下@EnableMqProducer
注解
@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE})@Documented@Import({XXXRegistrar.class,XXXConfig.class})public @interface EnableMqProducer { String group() default "DEFAULT_PRODUCER_GROUP"; String instanceName() default "defaultProducer"; boolean retryAnotherBrokerWhenNotStoreOK() default true;}
这里应用@Import
导入了两个配置类,第一个是接口org.springframework.context.annotation.ImportBeanDefinitionRegistrar
的实现类,第二个是被@Configuration
润饰的配置类
咱们看第一个类,这个类注入了一个DefaultMQProducer
到Spring 容器中,使业务方能够间接通过@Autowired
注入应用
public class XXXRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AnnotationAttributes attributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(EnableMqProducer.class.getName())); registerBeanDefinitions(attributes, registry); } private void registerBeanDefinitions(AnnotationAttributes attributes, BeanDefinitionRegistry registry) { //获取配置 String group = attributes.getString("group"); //省略局部代码... //增加要注入的类的字段值 Map<String, Object> values = new HashMap<>(); //这里有的同学可能不分明为什么key是这个 //这里的key就是DefaultMQProducer的字段名 values.put("producerGroup", group); //省略局部代码 //注册到Spring中 BeanRegistrationUtil.registerBeanDefinitionIfNotExists(registry, DefaultMQProducer.class.getName(), DefaultMQProducer.class, values); }
到这里,咱们曾经注入了一个DefaultMQProducer
的实例到Spring容器中,然而这个实例,还不残缺,比方,还没有启动,nameServer地址还没有配置,可内部配置的属性还没有笼罩实例已有的值(nameServer地址倡议内部配置)。好消息是,咱们曾经能够通过注入来应用这个实例了。
下面遗留的问题,就是第二个类接下来要做的事。
来看第二个配置类
@Configuration@Role(BeanDefinition.ROLE_INFRASTRUCTURE)@EnableConfigurationProperties(XxxProperties.class) //Spring提供的配置主动映射性能,配置后可间接注入 public class XXXConfig { @Resource //间接注入 private XxxProperties XxxProperties; @Autowired //注入上一步生成的实例 private DefaultMQProducer producer; @PostConstruct public void init() { //省略局部代码 //获取内部配置的值 String nameServer = XxxProperties.getNameServer(); //批改实例 producer.setNamesrvAddr(nameServer); //启动实例 try { this.producer.start(); } catch (MQClientException e) { throw new RocketMqException("mq音讯发送实例启动失败", e); } } @PreDestroy public void destroy() { producer.shutdown(); }
到这里,通过自定义注解和内部配置的联合,一个残缺的音讯发送器就能够应用了,但形式有取巧之嫌,因为在音讯发送器启动之前,不晓得还有没有别的类应用了这个实例,这是不平安的。
五、接口org.springframework.context.annotation.ImportSelector
的实现类
首先看一下接口
public interface ImportSelector { /** * importingClassMetadata 注解元信息,可获取自定义注解的属性汇合 * 依据自定义注解的属性,或者没有属性,返回要注入Spring的Class全限定类名汇合 如:XXX.class.getName(),Spring会主动注入XXX的一个实例 */ String[] selectImports(AnnotationMetadata importingClassMetadata); @Nullable default Predicate<String> getExclusionFilter() { return null; }}
这个接口的实现类如果没有进行@Aware
拓展,性能比拟繁多,因为咱们无奈参加Spring Bean 的构建过程,只是通知Spring 要注入的Bean的名字。不再详述。
六、总结
通过接口和配置类的灵便联合,能够实现自定义注解的配置化设计,归根到底是Spring Bean的灵便构建,如果你有更好更优雅的形式,欢送留言指教。