文章背景

有一个封装 RocketMq 的 client 的需求,用来提供给各项目收、发消息,但是项目当中常常只使用收或者发消息的单一功能,而且不同的项目 group 等并不相同而且不会变化,可以在项目当中配置,其余的 topic 等配置信息因有变动则迁移到配置中心去,因此萌生了如下想法

提供一个自定义注解来启用收、发消息其中之一或者全部的公共组件

研究之后,决定采用 @Import 来实现该功能

一、Java注解的简单介绍

注解,也叫Annotation、标注,是 Java 5 带来的新特性。

  1. 可使用范围

    类、字段、方法、参数、构造函数、包等,具体可参阅枚举类 java.lang.annotation.ElementType

  2. 生命周期(摘自 刘大飞的博客

    • RetentionPolicy.SOURCE 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃
    • RetentionPolicy.CLASS 注解被保留到class文件,但 jvm 加载class文件时候被遗弃,这是默认的生命周期
    • RetentionPolicy.RUNTIME 注解不仅被保存到class文件中,jvm 加载class文件之后,仍然存在
  3. 使用方式

    可以使用反射获取注解的内容,具体如何使用请自己百度,可参考这篇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引入内外部配置并完成某一功能的启用的更多相关文章

  1. Spring MVC中使用FastJson自定义注解

    最近在做.net转译成Java.其中遇到一个很蛋疼的问题.以前.net属性名都是首字母大写.造成返回给客户端的JSON字符串属性名称都是首字母大写.为了和前端对接我们以前都是如下图所示做法 publi ...

  2. Spring 自定义注解,结合AOP,配置简单日志注解 (转)

    java在jdk1.5中引入了注解,spring框架也正好把java注解发挥得淋漓尽致. 下面会讲解Spring中自定义注解的简单流程,其中会涉及到spring框架中的AOP(面向切面编程)相关概念. ...

  3. JAVA中如何定义自定义注解

    了解注解 注解是Java1.5,JDK5.0引用的技术,与类,接口,枚举处于同一层次 .它可以声明在包.类.字段.方法.局部变量.方法参数等的前面,用来对这些元素进行说明,注释 . 在Java中,自带 ...

  4. 手写SpringBoot自动配置及自定义注解搭配Aop,实现升级版@Value()功能

    背景 项目中为了统一管理项目的配置,比如接口地址,操作类别等信息,需要一个统一的配置管理中心,类似nacos. 我根据项目的需求写了一套分布式配置中心,测试无误后,改为单体应用并耦合到项目中.项目中使 ...

  5. Spring 实现策略模式--自定义注解方式解耦if...else

    策略模式 定义 定义一簇算法类,将每个算法分别封装起来,让他们可以互相替换,策略模式可以使算法的变化独立于使用它们的客户端 场景 使用策略模式,可以避免冗长的if-else 或 switch分支判断 ...

  6. spring中整合ssm框架注解版

    和xml版差不多,只不过创建对象的方式是由spring自动扫描包名,然后命名空间多一行context代码在application.xml中,然后将每个对象通过注解创建和注入: 直接上代码: 1.use ...

  7. Spring启动时获取自定义注解的属性值

    1.自定义注解 @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documen ...

  8. Spring中的@Transactional事务注解

    事务注解方式 @Transactional 当标于类前时, 标示类中所有方法都进行事物处理 , 例子: @Transactional public class TestServiceBean impl ...

  9. spring 接口校验参数(自定义注解)

    1. 注解类 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.l ...

随机推荐

  1. docker swarm外部验证负载均衡时不生效

    问题描述 我在本地创建了3个装了centos7的虚拟机, 并初始化了swarm集群, 即1个manager节点, 2个worker节点; 三台机子的ip分别是 192.168.124.8 - (man ...

  2. zabbix企业级的分布式开源监控解决方案 v5.0 LTS

    目录 zabbix简介 服务模块 客户端守护进程 监控流程 功能拆解 安装 zabbix 5.0 LTS 参考官网 zabbix 5.0.12-1.el7 zabbix-server相关优化 1. 字 ...

  3. 【SpringBoot基础系列】手把手实现国际化支持实例开发

    [SpringBoot基础系列]手把手实现国际化支持实例开发 国际化的支持,对于app开发的小伙伴来说应该比价常见了:作为java后端的小伙伴,一般来讲接触国际化的机会不太多,毕竟业务开展到海外的企业 ...

  4. Jmeter- 笔记5 - 从响应数据提取信息

    JSON提取器 提取 响应体(response body)里的信息 在需要提取数据的请求下添加 JSON提取器,一个JSON提取器可以写多个json提取器 路径:后置处理器 -> JSON提取器 ...

  5. Linux BSP非标准HDMI分辨率

    Linux BSP非标准HDMI分辨率 Intrinsyc公司发布了它的一个新的Linux BSP软件的发布 打开-Q820 开发套件基于Linux内核版本.支持的软件功能包括HDMI输出,可以支持标 ...

  6. CMOS图像传感器理解

    CMOS图像传感器理解 水流方向从左边流向右边,上面有一个开关,拧下去的时候水流停止,拧上去的时候水流打开.左边是水流的源头我们给它起个名字,叫做源端(就是源头的意思嘛),右边是水流出去的地方,也就是 ...

  7. MegEngine计算图、MatMul优化解析

    MegEngine计算图.MatMul优化解析 本文针对天元在推理优化过程中所涉及的计算图优化与 MatMul 优化进行深度解读,希望能够帮助广大开发者在利用天元 MegEngine「深度学习,简单开 ...

  8. 什么是视觉Visual SLAM

    什么是视觉Visual SLAM What Is Visual SLAM? What are the origins of visual SLAM? and what are some other a ...

  9. HAL库|神器cubemx的正确打开方式

    前言 工欲善其事,必先利其器.HAL库的开发不一定必须使用cubemx,但是使用了cubemx,你绝对不会后悔.基于一些小伙伴对cubemx的使用还有一些疑问,本次小飞哥从新建工程到生成工程,编写应用 ...

  10. 五、部署LNMP环境(linux + nginx + mysql + php)

    装包(nginx.数据库.php.php调用)---------起服务-----权限   装包: yum -y install gcc openssl-devel pcre-devel zlib-de ...