Spring揭秘:ImportBeanDefinitionRegistrar应用场景及实现原理!

Spring揭秘:ImportBeanDefinitionRegistrar应用场景及实现原理! - 程序员古德

内容概念

ImportBeanDefinitionRegistrar接口提供了强大的动态注册Bean的能力,它允许开发者在Spring容器初始化时,灵活地根据特定条件或需求来添加或修改Bean定义,从而实现更为精细的控制和扩展性。这是构建可扩展框架、插件系统或处理复杂配置场景的利器。

核心概念

ImportBeanDefinitionRegistrar是Spring框架中一个非常强大的接口,它允许在运行时动态地向Spring容器中注册Bean定义,这特性在一些需要动态扩展、插件化或者编程式配置Spring应用的场景中特别有用。

模拟一个业务案例,假如,有一个电商平台,平台支持多种支付方式,比如支付宝、微信支付、银联支付等,每种支付方式都有自己的配置参数和实现逻辑,而且这些支付方式可能会随着业务的发展不断增加或变更。

传统的做法可能是为每种支付方式编写一个配置类,然后在主配置类中使用@Import注解将这些配置类静态地导入到Spring容器中,但这种方式不够灵活,每次增加新的支付方式时都需要修改主配置类,并且需要重启应用才能生效。

类似这样的场景,就非常适合用ImportBeanDefinitionRegistrar来解决,可以创建一个实现了ImportBeanDefinitionRegistrar接口的类,比如叫做PaymentRegistrar,在这个类中,可以编写逻辑来动态地扫描和识别所有可用的支付方式,并为每种支付方式创建一个对应的Bean定义,然后注册到Spring容器中。

可以在PaymentRegistrarregisterBeanDefinitions方法中编写逻辑,实现思路大概如下:

  1. 扫描指定路径下的支付方式实现类。
  2. 对于每个找到的支付方式实现类,创建一个对应的Bean定义,并设置必要的属性,比如支付URL、密钥等。
  3. 将这些Bean定义注册到传入的BeanDefinitionRegistry中。

最后,在主配置类中使用@Import注解将PaymentRegistrar导入到Spring容器中,这样,当应用启动时,Spring会自动调用PaymentRegistrarregisterBeanDefinitions方法,从而动态地加载和注册所有可用的支付方式。

这种方式,可以在不修改主配置类和重启应用的情况下,灵活地添加、删除或修改支付方式。这对于快速响应业务需求变化和降低维护成本非常有帮助。

核心案例

下面是一个简单的Java例子,演示了如何使用ImportBeanDefinitionRegistrar来动态注册Bean定义,在例子中,创建一个简单的服务接口GreetingService,并提供两个实现类EnglishGreetingServiceSpanishGreetingService,然后使用ImportBeanDefinitionRegistrar来动态地注册这些服务,并通过客户端代码来调用它们,如下代码:

首先,定义服务接口和实现类:

// GreetingService.java  
public interface GreetingService {  String sayGreeting();  
}  // EnglishGreetingService.java  
public class EnglishGreetingService implements GreetingService {  @Override  public String sayGreeting() {  return "Hello!";  }  
}  // SpanishGreetingService.java  
public class SpanishGreetingService implements GreetingService {  @Override  public String sayGreeting() {  return "¡Hola!";  }  
}

接下来,创建实现了ImportBeanDefinitionRegistrar接口的类,用于动态注册Bean定义:

// GreetingServiceRegistrar.java  
import org.springframework.beans.factory.support.BeanDefinitionRegistry;  
import org.springframework.beans.factory.support.GenericBeanDefinition;  
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;  
import org.springframework.core.type.AnnotationMetadata;  public class GreetingServiceRegistrar implements ImportBeanDefinitionRegistrar {  @Override  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {  // 动态注册 EnglishGreetingService  GenericBeanDefinition englishGreetingServiceDefinition = new GenericBeanDefinition();  englishGreetingServiceDefinition.setBeanClassName(EnglishGreetingService.class.getName());  registry.registerBeanDefinition("englishGreetingService", englishGreetingServiceDefinition);  // 动态注册 SpanishGreetingService  GenericBeanDefinition spanishGreetingServiceDefinition = new GenericBeanDefinition();  spanishGreetingServiceDefinition.setBeanClassName(SpanishGreetingService.class.getName());  registry.registerBeanDefinition("spanishGreetingService", spanishGreetingServiceDefinition);  }  
}

现在,需要在Spring配置中使用@Import注解来导入GreetingServiceRegistrar

// AppConfig.java  
import org.springframework.context.annotation.Configuration;  
import org.springframework.context.annotation.Import;  @Configuration  
@Import(GreetingServiceRegistrar.class)  
public class AppConfig {  // 其他配置...  
}

最后,编写客户端代码来调用动态注册的Bean:

// Application.java  
import org.springframework.context.ApplicationContext;  
import org.springframework.context.annotation.AnnotationConfigApplicationContext;  public class Application {  public static void main(String[] args) {  ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);  // 获取并调用 EnglishGreetingService  GreetingService englishService = context.getBean("englishGreetingService", GreetingService.class);  System.out.println(englishService.sayGreeting()); // 输出: Hello!  // 获取并调用 SpanishGreetingService  GreetingService spanishService = context.getBean("spanishGreetingService", GreetingService.class);  System.out.println(spanishService.sayGreeting()); // 输出: ¡Hola!  }  
}

在上面的代码中,使用了AnnotationConfigApplicationContext来创建一个Spring应用上下文,并指定了配置类AppConfig,然后,使用context.getBean()方法来获取动态注册的Bean,并调用它们的方法来输出问候语,输出结果应该是分别打印出"Hello!“和”¡Hola!"。

核心API

ImportBeanDefinitionRegistrar 接口允许开发者在运行时动态地向 Spring 应用程序上下文中注册 Bean 定义,这个接口通常与 @Import 注解结合使用,当 Spring 容器扫描到带有 @Import 注解的类时,会调用实现了 ImportBeanDefinitionRegistrar 接口的类的相关方法,ImportBeanDefinitionRegistrar 接口中只有一个方法,它的核心方法以及含义如下:
registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry),参数:

1、importingClassMetadata:提供有关正在导入的类的元数据信息,如类名、注解等。

2、registry:用于注册 Bean 定义的 BeanDefinitionRegistry,可以使用这个注册中心来添加、移除或修改 Bean 定义。

该方法的用途:

  1. 该方法是 ImportBeanDefinitionRegistrar 接口的核心,它允许开发者在 Spring 容器初始化过程中动态地添加或修改 Bean 定义。
  2. 通过 BeanDefinitionRegistry,可以创建新的 BeanDefinition 对象,并使用 registerBeanDefinition 方法将其注册到容器中。
  3. 还可以使用其他 BeanDefinitionRegistry 的方法来修改已存在的 Bean 定义或查询容器中的 Bean 定义。

技术原理

ImportBeanDefinitionRegistrar接口的实现类会在Spring容器解析到带有@Import注解的配置类时被调用,从而允许开发者在容器初始化过程中动态地添加、修改或删除Bean定义。

实现原理

  1. @Import注解的解析
    当Spring容器解析到带有@Import注解的类时,它会查看该注解所引用的类,如果这些类实现了ImportBeanDefinitionRegistrar接口,Spring容器就会创建这些类的实例,并调用它们的registerBeanDefinitions方法。
  2. registerBeanDefinitions方法的调用
    registerBeanDefinitions方法接收两个参数:一个是AnnotationMetadata,它包含了关于正在被处理的注解类的元数据(如类名、方法、其他注解等);另一个是BeanDefinitionRegistry,它是一个允许操作容器中Bean定义的注册表。
  3. 动态注册Bean定义
    registerBeanDefinitions方法内部,开发者可以编写自定义逻辑来创建BeanDefinition对象(这些对象描述了如何创建Bean实例),并使用BeanDefinitionRegistry将它们注册到Spring容器中,注册过程可以基于传入的AnnotationMetadata来做出决策。

工作流程

  1. 扫描和解析注解
    Spring容器在启动时会扫描指定的包路径,查找并解析带有特定注解(如@Component, @Service, @Repository, @Controller, @Configuration等)的类,当遇到@Import注解时,它会特别处理。
  2. 处理@Import注解
    对于每个@Import注解,Spring会查看其值(即要导入的类),并检查这些类是否实现了ImportBeanDefinitionRegistrar接口,如果实现了,就会实例化这些类,并准备调用它们的registerBeanDefinitions方法。
  3. 执行自定义注册逻辑
    对于每个实现了ImportBeanDefinitionRegistrar的类,Spring会调用其registerBeanDefinitions方法,在这个方法中,开发者可以编写任意逻辑来创建和注册Bean定义,这通常涉及到创建BeanDefinition对象(如GenericBeanDefinition),设置其属性(如bean类名、作用域、依赖等),然后使用BeanDefinitionRegistryregisterBeanDefinition方法将其注册到容器中。
  4. 完成容器初始化
    在调用了所有ImportBeanDefinitionRegistrar实现类的registerBeanDefinitions方法后,Spring容器会继续其初始化过程,包括创建和初始化所有已注册的Bean实例。

核心总结

Spring揭秘:ImportBeanDefinitionRegistrar应用场景及实现原理! - 程序员古德

优点在于灵活性高,允许开发者在Spring容器初始化时,根据特定条件或逻辑动态地添加、修改Bean定义,实现更细粒度的控制,对于编写框架代码或需要动态扩展功能的应用来说非常有用。

但是,由于是在运行时动态注册Bean,可能会增加容器的启动时间和复杂性,推荐,在确实需要动态注册Bean的场景下使用,如插件系统、动态数据源等。

关注我,每天学习互联网编程技术 - 程序员古德

END!
END!
END!

往期回顾

精品文章

Spring揭秘:@import注解应用场景及实现原理!

Java并发基础:原子类之AtomicMarkableReference全面解析!

Java并发基础:concurrent Flow API全面解析

Java并发基础:CopyOnWriteArraySet全面解析

Java并发基础:ConcurrentSkipListMap全面解析

精彩视频

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/725186.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024.03.05作业

select实现tcp并发服务器 #include "test.h"#define SER_IP "192.168.42.106" #define SER_PORT 8888int create_socket() {int sfd socket(AF_INET, SOCK_STREAM, 0);if(sfd -1){perror("socket error");exit(-1);}printf("sfd %d\n&q…

安装VMWare+创建Linux虚拟机

点击VMware官网进入官网,下载VMware安装包。 一、安装VMware 一、安装VMware软件 (1)点击下一步 (2)勾选“我接受许可协议中的条款”,再点 ”击下一步“。 (3)选择下一步&#xf…

【Flink网络数据传输(4)】RecordWriter(下)封装数据并发送到网络的过程

文章目录 一. RecordWriter封装数据并发送到网络1. 数据发送到网络的具体流程2. 源码层面2.1. Serializer的实现逻辑a. SpanningRecordSerializer的实现b. SpanningRecordSerializer中如何对数据元素进行序列化 2.2. 将ByteBuffer中间数据写入BufferBuilder 二. BufferBuilder申…

java 中 string常用方法及相关的例子

我将为您详细讲解 Java 中 String 类的常用方法及其相关例子。String 类是 Java 中最常用的类之一,它代表字符串,提供了许多用于操作字符串的方法。 1. 字符串比较 - equals(Object obj): 比较字符串的内容是否相等。 - equalsIgnoreCase(String str): 比…

code: 500 ] This subject is anonymous - it does not have any identifying

项目场景: 相关背景: 使用idea 开发java 项目,前端页面请求 页面中相关的接口时,idea 控制台有报错信息出现,前端请求失败。 问题描述 问题: 使用idea 开发java 项目,前端页面请求 页面中相…

【three.js】22. Imported Models导入模型

22. Imported Models导入模型 介绍 Three.js 可以让你创建很多原始几何体,但是当涉及到更复杂的形状时,我们最好使用专用的 3D 软件建模。 在本课中,我们将使用已经制作好的模型,但我们将在以后的课程中学习如何完全在 3D 软件中…

【详识C语言】自定义类型之二:枚举

本章重点 枚举 枚举类型的定义 枚举的优点 枚举的使用 枚举 枚举顾名思义就是一一列举。 把可能的取值一一列举。 比如我们现实生活中: 一周的星期一到星期日是有限的7天,可以一一列举。 性别有:男、女、保密,也可以一一列举。…

vscode c/c++ 检测到 #include 错误。请更新 includePath。

问题背景 使用vscode打开项目后,头文件显示红色波浪线,没有引入。 检测到 #include 错误。请更新 includePath。已为此翻译单元(xxx)禁用波形曲线。 解决方法 gcc -v -E -x c - 显示所有头文件路径。 打开c_cpp_properties.json文件,粘贴…

【C++】类与对象(上篇)

一.类的引入 C与C语言比较起来,C引入了一个新的概念,叫做类。那么在C中,类又是什么呢? 在C中,类与C语言中的结构体相似,但不同的是,C中的类中,不仅可以定义变量,还能定义…

【pve】创建虚拟机

创建虚拟机 1.创建虚拟机-常规2.操作系统3. 系统4.磁盘5. cpu6.内存7.网络8.确认9.安装完成 1.创建虚拟机-常规 2.操作系统 这里选择自己的操作系统和系统的类别。 3. 系统 EFI存储选择自己磁盘 4.磁盘 调整适当的磁盘空间。 5. cpu 6.内存 根据自己的内存大小进行配…

最近开发中遇到的一些问题

puppeteer下载失败问题 使用的淘宝镜像,但执行命令npm i puppeteer之后,报错: npm ERR! code 1 npm ERR! path E:\项目-临时\test_install_puppeteer\node_modules\puppeteer npm ERR! command failed npm ERR! command C:\WINDOWS\system3…

cuda WSL2 无需单独安装

https://docs.nvidia.com/cuda/wsl-user-guide/index.html 这个写的很详细

TypeScript 哲学 - everyday Type

1、 2、TypeScript a structurally typed type system. 3、 type vs interface 3、literal reference 4、non-null assertion operator

微信小程序云开发教程——墨刀原型工具入门(Axure导入)

引言 作为一个小白,小北要怎么在短时间内快速学会微信小程序原型设计? “时间紧,任务重”,这意味着学习时必须把握微信小程序原型设计中的重点、难点,而非面面俱到。 要在短时间内理解、掌握一个工具的使用&#xf…

快速上手:剧本杀dm预约平台小程序的制作流程

在当今的娱乐市场中,剧本杀已经成为一种备受欢迎的娱乐方式。为了给玩家提供更好的服务和体验,开发一个剧本杀DM预约平台小程序是至关重要的。下面,我们将详细介绍如何使用乔拓云第三方平台开发这样一个预约平台。 首先,打开乔拓云…

软件测试需求分析如何编写?为什么要进行测试需求分析?

在软件开发的过程中,软件测试需求分析是至关重要的一个环节。测试需求分析是指对待测软件的需求进行全面细致的分析,明确软件测试的目标和范围,为测试活动的进行提供指导。通过对软件需求的详细分析,可以确保测试人员清楚了解软件…

动手学深度学习-现代循环神经网络(GRU、LSTM、编码器-解码器等)

现代循环神经网络 上一章节(循环神经网络)介绍了循环神经网络的基础知识,这种网络可以更好的处理序列数据。我们在文本数据上实现了基于循环神经网络的语言模型,但是对于当今各种各样的序列学习问题,这些技术可能不够…

网络学习:Vlan间路由

目录 一、vlan间路由实现的方法 二、精确匹配转发(交换机)流程 三、最长匹配转发(路由器) 四、交换机最长匹配转发 五、总结 一、vlan间路由实现的方法 方法1:使用路由器的物理接口 特点:在路由器上…

spring 事务失效的 12 种场景

文章目录 spring 事务失效的 12 种场景一、事务不生效1.访问权限问题2. 方法用 final 修饰3.方法内部调用(自己玩自己)3.1 新加一个 Service 方法3.2 在该 Service 类中注入自己3.3 通过 AopContent 类 4.Bean没有纳入Spring IOC容器管理5.多线程调用&am…

图像超分辨率:Fast Nearest Convolution for Real-Time Efficient Image Super-Resolution

9.Fast Nearest Convolution for Real-Time Efficient Image Super-Resolution 提出一种适用移动端的超分网络 一些tensor op 的推理时间 一些卷积结构的推理时间 网络结构NCNet 主干网络预测的是 残差,什么的残差? 是最近邻插值图像与 ground-truth的…