javascript
java 外部覆盖内部配置,Spring 与自定义注解、外部配置化的结合使用
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 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 getExclusionFilter() {
return null;
}
}
這個接口的實現類如果沒有進行@Aware拓展,功能比較單一,因為我們無法參與Spring Bean 的構建過程,只是告訴Spring 要注入的Bean的名字。不再詳述。
六、總結
通過接口和配置類的靈活結合,可以實現自定義注解的配置化設計,歸根到底是Spring Bean的靈活構建,如果你有更好更優雅的方式,歡迎留言指教。
總結
以上是生活随笔為你收集整理的java 外部覆盖内部配置,Spring 与自定义注解、外部配置化的结合使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 中国人寿意外险产品介绍
- 下一篇: 民生银行总部在哪里