如何让 Bean 深度感知 Spring 容器

Spring 有一个特点,就是创建出来的 Bean 对容器是无感的,一个 Bean 是怎么样被容器从一个 Class 整成一个 Bean 的,对于 Bean 本身来说是不知道的,当然也不需要知道,也就是 Bean 对容器的存在是无感的。

但是有时候我们可能会遇到一些场景,这些场景让我们去感知容器的存在,松哥举几个例子:

  1. Spring 容器提供的功能不止 IoC、AOP 这些,常见的 I18N 也是 Spring 的能力之一,如果我们想要在自己的 Bean 中去使用 I18N,那就得去找 Spring,这样就感知到了 Spring 容器的存在了。
  2. Spring 提供了资源加载器,如果我们想要使用这个资源加载器去加载配置,那就得去找 Spring 要,这样就感知到了 Spring 容器的存在了。
  3. 想根据 beanName 去 Spring 容器中查找 Bean,那不用多说,肯定得知道 Spring 容器的存在。

也就是说,虽然 Spring 中的 Bean 可以不用去感知 Spring 容器的存在,但是在实际开发中,我们往往还是需要 Spring 容器提供的各种能力,这样就迫使我们的 Bean 不得不去感知到 Spring 容器的存在。

那么 Spring 中的 Bean 如何感知到 Spring 容器的存在呢?

1. Aware

Aware 本身就有感知的意思。

Spring Aware 是 Spring 框架中的一个特性,它允许我们的应用程序或组件与 Spring 容器进行交互。当一个类实现了 Spring Aware 接口并注册到 Spring 容器中时,该类就能够感知到 Spring 容器的存在,并且可以获取容器的一些资源或进行一些特定的操作。

Spring Aware 接口包括了多个子接口,每个子接口对应于不同的 Spring 容器资源或功能。

Aware 的实现有很多,大的方向来说主要有如下一些:

每一个 Aware 的作用如下:

  • ApplicationEventPublisherAware:实现该接口的对象可以获取事件发布的能力。
  • ServletContextAware:实现该接口的对象可以获取到 ServletContext 对象。
  • MessageSourceAware:实现该接口的对象可以获取到 MessageSource 对象,MessageSource 支持多消息源,主要用于主要用于国际化。
  • ResourceLoaderAware:实现该接口的对象可以获取到一个 ResourceLoader,Spring ResourceLoader 则为我们提供了一个统一的 getResource() 方法来通过资源路径检索外部资源,例如文本文件、XML 文件、属性文件或图像文件等。
  • ApplicationStartupAware:实现该接口的对象可以获取到一个 ApplicationStartup 对象,这个比较新,是 Spring 5.3 中新推出的,通过 ApplicationStartup 可以标记应用程序启动期间的步骤,并收集有关执行上下文或其处理时间的数据。
  • NotificationPublisherAware:实现该接的对象可以获取到一个 NotificationPublisher 对象,通过该对象可以实现通知的发送。
  • EnvironmentAware:实现该接口的对象可以获取到一个 Environment 对象,通过 Environment 可以获取到容器的环境信息。
  • BeanFactoryAware:实现该接口的对象可以获取到一个 BeanFactory 对象,通过 BeanFactory 可以完成 Bean 的查询等操作。
  • ImportAware:实现该接口的对象可以获取到一个 AnnotationMetadata 对象,ImportAware 接口是需要和 @Import 注解一起使用的。在 @Import 作为元注解使用时,通过 @Import 导入的配置类如果实现了 ImportAware 接口就可以获取到导入该配置类接口的数据配置。
  • EmbeddedValueResolverAware:实现该接口的对象可以获取到一个 StringValueResolver 对象,通过 StringValueResolver 对象,可以读取到 Spring 容器中的 properties 配置的值(YAML 配置也可以)。
  • ServletConfigAware:实现该接口的对象可以获取到一个 ServletConfig 对象,不过这个似乎没什么用,我们很少自己去配置 ServletConfig。
  • LoadTimeWeaverAware:实现该接口的对象可以获取到一个 LoadTimeWeaver 对象,通过该对象可以获取加载 Spring Bean 时织入的第三方模块,如 AspectJ 等。
  • BeanClassLoaderAware:实现该接口的对象可以获取到一个 ClassLoader 对象,ClassLoader 能干嘛不需要我多说了吧。
  • BeanNameAware:实现该接口的对象可以获取到一个当前 Bean 的名称。
  • ApplicationContextAware:实现该接口的对象可以获取到一个 ApplicationContext 对象,通过 ApplicationContext 可以获取容器中的 Bean、环境等信息。

通过实现这些接口,我们可以在应用程序中获取 Spring 容器提供的各种资源,并与容器进行交互,以实现更灵活和可扩展的功能。

2. 实践

举两个例子小伙伴们来感受下 Aware 的具体用法。

2.1 案例

例如我想在 Bean 中感知到当前 Bean 的名字,那么我们可以按照如下方式来使用:

@Service
public class UserService implements BeanNameAware {private String beanName;@Overridepublic void setBeanName(String name) {this.beanName = name;}@Overridepublic String toString() {return "UserService{" +"beanName='" + beanName + '\'' +'}';}
}

让当前 bean 实现 BeanNameAware 接口,并重写 setBeanName 方法,这个方法会在 Spring 容器初始化 Bean 的时候自动被调用,我们就可以据此获取到 bean 的名称了。

再比如我想做一个工具 Bean,用来查找其他 Bean,那么我可以使用如下方式:

@Component
public class BeanUtils implements BeanFactoryAware {private static BeanFactory beanFactory;@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {this.beanFactory = beanFactory;}public static  <T> T getBean(Class<T> clazz) {return (T) beanFactory.getBean(clazz);}
}

让当前 Bean 实现 BeanFactoryAware 接口并重写 setBeanFactory 方法,在系统初始化当前 Bean 的时候,会自动调用 setBeanFactory 方法,进而将 beanFactory 变量传进来。

2.2 原理

当 Spring 容器创建一个 Bean 的时候,大致的流程是创建实例对象 -> 属性填充 -> Bean 初始化

最后这个 Bean 的初始化,就是调用 init 方法、afterPropertiesSet 方法以及 BeanPostProcessor 中的方法的,如下:

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {invokeAwareMethods(beanName, bean);Object wrappedBean = bean;if (mbd == null || !mbd.isSynthetic()) {wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);}try {invokeInitMethods(beanName, wrappedBean, mbd);}catch (Throwable ex) {throw new BeanCreationException((mbd != null ? mbd.getResourceDescription() : null), beanName, ex.getMessage(), ex);}if (mbd == null || !mbd.isSynthetic()) {wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);}return wrappedBean;
}

在这个方法一进来,首先有一个 invokeAwareMethods,这个就是用来触发 Aware 的,来看下:

private void invokeAwareMethods(String beanName, Object bean) {if (bean instanceof Aware) {if (bean instanceof BeanNameAware beanNameAware) {beanNameAware.setBeanName(beanName);}if (bean instanceof BeanClassLoaderAware beanClassLoaderAware) {ClassLoader bcl = getBeanClassLoader();if (bcl != null) {beanClassLoaderAware.setBeanClassLoader(bcl);}}if (bean instanceof BeanFactoryAware beanFactoryAware) {beanFactoryAware.setBeanFactory(AbstractAutowireCapableBeanFactory.this);}}
}

小伙伴们可以看到,BeanNameAware、BeanClassLoaderAware 以及 BeanFactoryAware 这三种类型的 Aware 是在这里触发的。

每种 Aware 因为功能不同,因此作用的时机也不同。

invokeAwareMethods 方法执行完毕之后,接下来是执行 applyBeanPostProcessorsBeforeInitialization 方法,这个我们之前分析过,这个方法最终会触发 BeanPostProcessor#postProcessBeforeInitialization 方法的执行,而 BeanPostProcessor 有一个子类专门处理 Aware 的,就是 ApplicationContextAwareProcessor:

@Override
@Nullable
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {if (!(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware ||bean instanceof ApplicationStartupAware)) {return bean;}invokeAwareInterfaces(bean);return bean;
}
private void invokeAwareInterfaces(Object bean) {if (bean instanceof Aware) {if (bean instanceof EnvironmentAware environmentAware) {environmentAware.setEnvironment(this.applicationContext.getEnvironment());}if (bean instanceof EmbeddedValueResolverAware embeddedValueResolverAware) {embeddedValueResolverAware.setEmbeddedValueResolver(this.embeddedValueResolver);}if (bean instanceof ResourceLoaderAware resourceLoaderAware) {resourceLoaderAware.setResourceLoader(this.applicationContext);}if (bean instanceof ApplicationEventPublisherAware applicationEventPublisherAware) {applicationEventPublisherAware.setApplicationEventPublisher(this.applicationContext);}if (bean instanceof MessageSourceAware messageSourceAware) {messageSourceAware.setMessageSource(this.applicationContext);}if (bean instanceof ApplicationStartupAware applicationStartupAware) {applicationStartupAware.setApplicationStartup(this.applicationContext.getApplicationStartup());}if (bean instanceof ApplicationContextAware applicationContextAware) {applicationContextAware.setApplicationContext(this.applicationContext);}}
}

大家看下,这七种类型的 Aware 是在这里被触发的。

另外像 ImportAware 是在 ImportAwareBeanPostProcessor#postProcessBeforeInitialization 方法中处理的;LoadTimeWeaverAware 是在 、LoadTimeWeaverAwareProcessor#postProcessBeforeInitialization 方法中处理的。

基本上,大部分的 Aware 接口都是在 BeanPostProcessor 中处理的。

好啦,现在小伙伴们理解 Aware 了吧~

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

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

相关文章

【PHP】对称加密算法 AES-256-GCM 代码示例

前言 下面介绍在PHP代码中&#xff0c;如何对数据进行 AES-256-GCM 加密和解密。我们可以使用 openssl 和 sodium 扩展来实现加密&#xff0c;它们都支持 AES-256-GCM 算法&#xff0c;下面将给出两种扩展的代码示例。 注&#xff1a;在对称加密算法中&#xff0c;除了应用广泛…

【ChatGLM2-6B】P-Tuning训练微调

机器配置 阿里云GPU规格ecs.gn6i-c4g1.xlargeNVIDIA T4显卡*1GPU显存16G*1 准备训练数据 进入/ChatGLM-6B/ptuningmkdir AdvertiseGencd AdvertiseGen上传 dev.json 和 train.json内容都是 {"content": "你是谁", "summary": "你好&…

【代码随想录】算法训练计划11

1、20. 有效的括号 题目&#xff1a; 给定一个只包括 ‘(’&#xff0c;‘)’&#xff0c;‘{’&#xff0c;‘}’&#xff0c;‘[’&#xff0c;‘]’ 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。 左括号…

AI:53-基于机器学习的字母识别

🚀 本文选自专栏:AI领域专栏 从基础到实践,深入了解算法、案例和最新趋势。无论你是初学者还是经验丰富的数据科学家,通过案例和项目实践,掌握核心概念和实用技能。每篇案例都包含代码实例,详细讲解供大家学习。 📌📌📌本专栏包含以下学习方向: 机器学习、深度学…

推荐PHP付费进群源码

PHP付费进群源码带自动定位基于ThinkPHP框架开发的&#xff0c;可以快速搭建知识付费粉丝进群。 更新&#xff1a; 1.首页付款轮播 2.城市定位功能 3.更新及优化域名库及支付设置 4.新增一张图模板设置模式&#xff0c;简化后台模板设置 5.前后台其他优化 演示地址&#xff1a…

使用navicat操纵数据库

<1>连接数据库 打开Navicat&#xff0c;点击“连接”&#xff0c;选择“MySQL”&#xff0c;这边是本机安装的mysql,主机为localhost&#xff0c;输入root密码。 使用Navicat创建数据库并导入SQL文件 SQL查询 普通SQL查询 USE demo; SELECT * FROM t_emp;SELECT emp…

动态内存malloc,calloc,realloc如何使用,使用场景及使用free释放内存时崩溃的原因

目录 1.内存区域 2.void与void* 3.应用场景 4.malloc 5.calloc 6.realloc 7.free崩溃的原因 7.1引入 7.2具体原因 7.2.1越界 7.2.2指针移动 7.2.3重复释放同一段内存 1.内存区域 局部变量:定义在函数内部的变量,包括形参,在栈(stack)中,作用域在函数内部有效,生存周…

【缓存】Spring全家桶中@CacheEvict无效情况共有以下几种

Spring全家桶中CacheEvict无效情况共有以下几种 一、背景介绍二、原因分析三、解决方案 一、背景介绍 SpringBoot中使用Cacheable注解缓存数据&#xff0c;使用CacheEvict注解删除缓存。但是在项目使用过程中&#xff0c;发现使用CacheEvict注解删除缓存无效。 拓展&#xff…

Redis那些事儿(一)

说到redis大家都不陌生&#xff0c;其中包括&#xff1a;共有16个数据库&#xff0c;默认为第0个数据库&#xff1b;数据以key-value键值的形式存储&#xff1b;数据类型包括String、List、Hash、Set等&#xff0c;其中最常用的是字符串&#xff1b;是单线程的、基于内存的&…

腾讯云优惠券是什么?腾讯云优惠券怎么领取?

腾讯云是腾讯集团倾力打造的云计算品牌&#xff0c;为了吸引用户上云&#xff0c;经常推出各种优惠活动&#xff0c;其中就包括腾讯云优惠券。 1、腾讯云优惠券解释说明 腾讯云优惠券是腾讯云的一种优惠凭证&#xff0c;包括代金券和折扣券&#xff0c;领券之后新购、续费、升…

AD9371 官方例程裸机SW 和 HDL配置概述(三)

AD9371 系列快速入口 AD9371ZCU102 移植到 ZCU106 &#xff1a; AD9371 官方例程构建及单音信号收发 ad9371_tx_jesd -->util_ad9371_xcvr接口映射&#xff1a; AD9371 官方例程之 tx_jesd 与 xcvr接口映射 AD9371 官方例程 时钟间的关系与生成 &#xff1a; AD9371 官方…

前端之jQuery

目录 一、jQuery jQuery内容 jQuery版本 jQuery对象 jQuery基础语法 二、查找标签 基本选择器 层级选择器 基本筛选器 属性选择器 表单筛选器 三、筛选器方法 四、操作标签 一、jQuery jQuery是一个轻量级的、兼容多浏览器的JavaScript库。jQuery使用户能够更方便…

管理如何实现制度流程化 流程表单化 表单信息化 信息标准化?

业务化、流程化、信息化、数字化、自动化、智能化&#xff0c;是企业业务管理发展路径的六个必经阶段 制度业务化&#xff0c;业务表单化&#xff0c;表单流程化、流程信息化、信息标准化、标准制度华。 制度流程化、流程表单化、表单信息化、信息标准化、标准制度华。 管理…

Windows安装WinDbg调试工具

一.下载 微软官网下载SDK的地址&#xff0c;有win11&#xff0c;win10&#xff0c;win8&#xff0c;win7&#xff0c;其他 https://developer.microsoft.com/en-us/windows/downloads/sdk-archive/ 二.安装 打开windbg\Installers\X64 Debuggers And Tools-x64_en-us.msi 要安…

《Effective C++》知识点(6)--继承与面向对象设计

32. 确定你的public继承模式是is-a关系 重要规则&#xff1a;public继承就意味is-a的关系。适用于基类身上的每一件事情一定也适用于继承类身 上&#xff0c;因为每一个继承类对象也都是一个基类对象。 另外两种关系是has-a(有一个)和is-implemented-in-terms-of(根据某物实现出…

[揭秘]splunk 背后running 的job

1: 背景: 最近客户总是会收到一些dashboard 的PDF 文件,有些内容不是想要的,想要求更新一下,但是问题来了,这些dashboard 的自动运行的job 在哪里呢? 2: 查找原因: 2.1 如果知道这个dashboard 发到那个email 的,那么可以查找: abc@163.com 去: /opt/splunk/etc/use…

【Redis】String字符串类型-常用命令

文章目录 String字符串类型常用命令setgetMGETMSET 计数命令INCRINCRBYDECRDECRBYINCRBYFLOAT 其它命令APPENDGETRANGESETRANGESTRLEN 命令总结 String字符串类型 1&#xff09;⾸先Redis中所有的键的类型都是字符串类型 2&#xff09;字符串类型的值&#xff08;value&#…

数据结构详细笔记——并查集

文章目录 逻辑结构存储结构并、查代码实现Union 操作的优化Find 操作的优化&#xff08;压缩路径&#xff09; 逻辑结构 集合&#xff1a;将各个元素划分为若干个互不相交的子集的集合 森林是m(m>0)棵互不相交的树的集合 存储结构 #define SIZE 13 int UFSets[SIZE]; …

linux环境下编译,安卓平台使用的luajit库

一、下载luajit源码 1、linux下直接下载&#xff1a; a、使用curl下载&#xff1a;https://luajit.org/download/LuaJIT-2.1.0-beta3.tar.gz b、git下载地址&#xff1b;https://github.com/LuaJIT/LuaJIT.git 2、Windows下载好zip文件&#xff0c;下载地址&#xff1a;https…

云计算、大数据、人工智能、物联网、虚拟现实技术、区块链技术(新一代信息技术)学习这一篇够了!

目录 云计算 一、云计算的基本概念 二、云计算的分类 (一) IaaS (二) SaaS (三) PaaS 三、云环境的分类、云计算的四种部署模式 (一)公有云 (二)私有云 (三)社区云 (四)混合云 四、云计算的特点 (一)虚拟化技术 (二)动态可扩展 (三)按需部署 (四)灵活性高 (五…