Spring中如何使用自定义注解搭配@Import引入内外部配置并完成某一功能的启用
文章背景
有一个封装 RocketMq 的 client 的需求,用来提供给各项目收、发消息,但是项目当中常常只使用收或者发消息的单一功能,而且不同的项目 group
等并不相同而且不会变化,可以在项目当中配置,其余的 topic
等配置信息因有变动则迁移到配置中心去,因此萌生了如下想法
提供一个自定义注解来启用收、发消息其中之一或者全部的公共组件
研究之后,决定采用 @Import
来实现该功能
一、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 启动类上的 @SpringBootApplication
注解已经自动扫描、注入你想通过@Import
导入的bean了。
这种Class可以进行如下拓展
- 继承各种
Aware
接口, 获取对应的信息(如果不清楚Aware
接口在Spring当中的作用,请自行百度),如,继承EnviromentAware
,可以拿到Spring的环境配置信息,进而从中拿到@Value
所需要的值,如environment.getProperty("user.username")
- 使用
@Autowire
、@Resource
、@Value
注入各种所需 Spring 资源 - 使用
@Bean
声明各种 Spring 资源 - 像普通 Spring Bean 一样使用该类
更多使用方式,请自行百度。
本案例当中,使用这种配置类用来导入外部配置(使用 @Value
的形式)。
四、接口org.springframework.context.annotation.ImportBeanDefinitionRegistrar
的实现类
当实现类的 Class
传入 @Import
注解的时候,就会调用该类对应的方法注入相应的 BeanDefinition
信息,方便后面获取 bean 时候使用。我们可以在此定义我们要注入 Spring 的 bean 的属性,这里的属性信息参数来源于自定义注解当中传来的值。
来看一下接口定义
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 信息。
五、接口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的名字。不再详述。
六、案例
来看如下案例,我们通过一个注解,启动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();
}
到这里,通过自定义注解和外部配置的结合,一个完整的消息发送器就可以使用了,但方式有取巧之嫌,因为在消息发送器启动之前,不知道还有没有别的类使用了这个实例,这是不安全的。
七、总结
通过接口和配置类的灵活结合,可以实现基于自定义注解结合内外配置化的设计,归根到底是Spring Bean的灵活构建,如果你有更好更优雅的方式,欢迎留言指教。
Spring中如何使用自定义注解搭配@Import引入内外部配置并完成某一功能的启用的更多相关文章
- Spring MVC中使用FastJson自定义注解
最近在做.net转译成Java.其中遇到一个很蛋疼的问题.以前.net属性名都是首字母大写.造成返回给客户端的JSON字符串属性名称都是首字母大写.为了和前端对接我们以前都是如下图所示做法 publi ...
- Spring 自定义注解,结合AOP,配置简单日志注解 (转)
java在jdk1.5中引入了注解,spring框架也正好把java注解发挥得淋漓尽致. 下面会讲解Spring中自定义注解的简单流程,其中会涉及到spring框架中的AOP(面向切面编程)相关概念. ...
- JAVA中如何定义自定义注解
了解注解 注解是Java1.5,JDK5.0引用的技术,与类,接口,枚举处于同一层次 .它可以声明在包.类.字段.方法.局部变量.方法参数等的前面,用来对这些元素进行说明,注释 . 在Java中,自带 ...
- 手写SpringBoot自动配置及自定义注解搭配Aop,实现升级版@Value()功能
背景 项目中为了统一管理项目的配置,比如接口地址,操作类别等信息,需要一个统一的配置管理中心,类似nacos. 我根据项目的需求写了一套分布式配置中心,测试无误后,改为单体应用并耦合到项目中.项目中使 ...
- Spring 实现策略模式--自定义注解方式解耦if...else
策略模式 定义 定义一簇算法类,将每个算法分别封装起来,让他们可以互相替换,策略模式可以使算法的变化独立于使用它们的客户端 场景 使用策略模式,可以避免冗长的if-else 或 switch分支判断 ...
- spring中整合ssm框架注解版
和xml版差不多,只不过创建对象的方式是由spring自动扫描包名,然后命名空间多一行context代码在application.xml中,然后将每个对象通过注解创建和注入: 直接上代码: 1.use ...
- Spring启动时获取自定义注解的属性值
1.自定义注解 @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documen ...
- Spring中的@Transactional事务注解
事务注解方式 @Transactional 当标于类前时, 标示类中所有方法都进行事物处理 , 例子: @Transactional public class TestServiceBean impl ...
- spring 接口校验参数(自定义注解)
1. 注解类 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.l ...
随机推荐
- Jmeter(四十七) - 从入门到精通高级篇 - 分布式压测部署之负载机的设置(详解教程)
1.简介 千呼万唤始出来,这一篇感觉写了好久,总想写的清楚明白简洁,但是还是洋洋洒洒写了好多,希望大家喜欢吧!本来打算将这一篇文章是放在性能测试中讲解和分享的,但是有的童鞋或者小伙伴们私下问的太多了, ...
- Linux 查看实时网卡流量的方法 网速 nload sar iftop dstat
1.使用nload yum install -y gcc gcc-c++ ncurses-devel make wgetwget http://www.roland-riegel.de/nload/n ...
- Jmeter- 笔记9 - CLI(无图形界面)
使用CLI模式,减少资源占用 用GUI调试好脚本 在jmeter的bin文件夹运行cmd,然后输入命令:jmeter -n -t [jmx file] -l [results file] -e -o ...
- TVM适配NN编译Compiler缺陷
TVM适配NN编译Compiler缺陷 内容纲要 前言 TVM针对VTA的编译流程 自定义VTA架构:TVM的缺陷与性能瓶颈 TVM缺陷与瓶颈 缺陷一:SRAM配置灵活性差 缺陷二:计算阵列配置僵硬 ...
- Tvm一些基本技术
Tvm一些基本技术 一.总体流程: TVM的工作流程:首先,将网络表示成统一的表示形式(Intermediate Representation),并进行一些可重用的图优化:然后,利用不同的后端生成对应 ...
- ARM NEON指令集优化理论与实践
ARM NEON指令集优化理论与实践 一.简介 NEON就是一种基于SIMD思想的ARM技术,相比于ARMv6或之前的架构,NEON结合了64-bit和128-bit的SIMD指令集,提供128-bi ...
- NVIDIA空中导航SDK改造5G通信
NVIDIA空中导航SDK改造5G通信 Transforming Next-Generation Wireless with 5T for 5G and the NVIDIA Aerial SDK N ...
- Yolov4性能分析(上)
Yolov4性能分析(上) 一.目录 实验测试 1) 测试介绍 2) Test 3) Train 二. 分析 1.实验测试 1. 1 实验测试方法 Yolov4训练train实验方法(Darkn ...
- Nucleus 实时操作系统中断(上)
Nucleus 实时操作系统中断(上) Interrupts in the Nucleus SE RTOS 所有现代微处理器和微控制器都有某种中断设施.这种能力对于提供许多应用程序所需的响应能力是必不 ...
- JUC 并发编程--08,线程池,三大方法,七大参数,4种拒绝策略,代码演示
三大方法: //线程池核心线程数为n, 最大线程数为 n ExecutorService fixedThreadPool = Executors.newFixedThreadPool(n); 源码: ...