Spring中如何使用自定义注解搭配@Import引入内外部配置并完成某一功能的启用

有些网站第一时间爬取了我的原创文章,并且没有注明出处,不得已在这里加上说明。

文章背景

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

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

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

一、Java注解的简单介绍

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

二、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 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 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的灵活构建,如果你有更好更优雅的方式,欢迎留言指教。

Original: https://www.cnblogs.com/qnlcy/p/15012443.html
Author: 去哪里吃鱼
Title: Spring中如何使用自定义注解搭配@Import引入内外部配置并完成某一功能的启用

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/577910/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 如何提高团队开发质量

    年轻的时候去面过一个相对于当时我的比较高端的管理岗位,当时的我情况是,开发经验相对丰富, 但管理经验还欠缺。对方当时面临一个具体的问题。 “我们最近生产上,出现了一个比…

    Linux 2023年6月13日
    087
  • 阿拉德之怒手游超详细图文架设教程

    写在前面 你是否还记得DNF,一天你不小心救了赛丽亚,从此变成了拯救阿拉德大陆的勇士,从此开始冒险之旅,不管你的职业是亲儿子还是下水道,你一直对你玩的角色情有独钟,在一次次刷图PK…

    Linux 2023年6月7日
    087
  • sql注入

    一.原理 SQL注入攻击指的是通过构建特殊的输入作为参数传入Web应用程序,而这些输入大都是SQL语法里的一些组合,通过执行SQL语句进而执行攻击者所要的操作,其主要原因是程序没有…

    Linux 2023年6月6日
    097
  • Linux 网络分析必备技能:tcpdump 实战详解

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Linux 2023年6月7日
    090
  • pyQt的基本使用

    1. 基本窗口 import sys from PyQt5.QtWidgets import QApplication, QWidget if __name__ == ‘__mai…

    Linux 2023年6月7日
    0123
  • 【C++基础】数据类型

    C++规定在创建一个变量或者产量时,必须要指定相应的数据类型,否则无法给变量分配内存空间 数据类型的存在意义:给变量分配合适的内存空间 整型 作用:整型变量表示的是整数类型的数据 …

    Linux 2023年6月13日
    0105
  • 编写一个简单的linux kernel rootkit

    一、前言 linux kernel rootkit跟普通的应用层rootkit个人感觉不大,个人感觉区别在于一个运行在用户空间中,一个运行在内核空间中;另一个则是编写时调用的API…

    Linux 2023年6月8日
    0118
  • Java秒杀系统一:环境搭建和DAO层设计

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Linux 2023年6月11日
    0130
  • VirtualAlloc加载shellcode免杀一点记录

    一个很好的学习网站 推荐一下: https://docs.microsoft.com/zh-cn/windows/win32/api/ 0x01 VirtualAlloc Virt…

    Linux 2023年5月28日
    085
  • 高速USB转8串口产品设计-RS232串口

    基于480Mbps 高速USB转8路串口芯片CH348,可以为各类主机扩展出8个独立的串口。使用厂商提供的VCP串口驱动程序,可支持Windows、Linux、Android、ma…

    Linux 2023年6月7日
    099
  • Windows关闭135/137/139/445 端口

    通过IP安全策略(以关闭135端口为例) (1) 依次打开”控制面板–>系统和安全–>管理工具–>本地安全策略&#…

    Linux 2023年6月8日
    0243
  • Arrays.binarySearch方法

    Arrays .binarySearch(int[] arr,int b) 1,数组arr必须排序后调用查找b在arr数组中的下标是多少。 2,存在:返回在数组中的下标 不存在:返…

    Linux 2023年6月8日
    077
  • 010 Linux 文本统计与去重 (wc 和 uniq)

    wc 命令一般是作为组合命令的一员与其他命令一同起到统计的作用。而一般情况下使用wc -l 命令较多。uniq 可检查文本文件中重复出现的行,一般与 sort 命令结合使用。一起组…

    Linux 2023年5月27日
    092
  • 配置git环境与项目创建

    主要用于记录上课笔记,方便以后复习 acgit的地址:https://git.acwing.com/wyw/kob1/ 1. 项目模块的包含 1.1 采用前后端分离 Web端大概框…

    Linux 2023年6月6日
    0123
  • Shell脚本8种字符串截取方法总结

    Linux 的字符串截取很有用。有八种方法。 假设有变量 var=http://www.aaa.com/123.htm. 1. # 号截取,删除左边字符,保留右边字符。 echo …

    Linux 2023年5月28日
    0153
  • clang 分四步编译main.c

    这里用的clang/clang++ 分四步编译main.c/main.cpp文件 1.1 C++源文件 #include int main() { std::cout <&l…

    Linux 2023年6月13日
    079
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球