想减少代码量,快设置一个有感知的 Aware Spring Bean
摘要:正常情况下,Spring 中的 Bean 对 Spring 是无感知的,Spring 框架提供了这种扩展能力,能让一个 bean 成为有感知的。
本文分享自华为云社区《有感知的 Aware Spring Bean》,作者:陈皮的JavaLib。
有感知能力的 Spring Bean
正常情况下,Spring 中的 Bean 对 Spring 是无感知的,即它感知不到自己是由哪个 Bean 工厂(BeanFactory)创建的;感知不到自己在工厂中的 id,即 beanName;也感知不到 Spring 应用上下文对象(ApplicationContext)等等。
如果要想 Spring Bean 能感知到这些信息,我们可以自己通过某些手段来获取这些信息,然后设置到 bean 实例中。这种方法缺点是需要我们自己获取需要感知的信息,然后主动设置到 bean 中,会写许多冗余代码。但是优点是可以和 Spring 解耦。
当然,Spring 框架提供了这种扩展能力,能让一个 bean 成为有感知的。只要让 bean 类实现特定的接口,重写其中的方法即可。这样 Spring 会在 bean 的生命周期的某个阶段调用这些重写的方法,注入需要感知的信息。这方式的缺点是需要和 Spring 耦合,优点是能减少代码量,简单。
Aware 接口
要让 Spring 中的 bean 获得某些感知能力,需要实现特定的接口,这些接口有个共同的父接口,即 Aware 接口。
package org.springframework.beans.factory; public interface Aware { }
Aware 是一个标记接口,表明一个 bean 可以通过回调方法得到 Spring 容器中特定的对象。具体的方法签名由各个子接口定义,但通常应该只包含一个接受单个参数并返回 void 的方法。
注意,仅实现 Aware 接口不会提供默认功能。我们一般是实现 Aware 的子接口来获得特定的感知能力。
Aware 接口的子接口有很多,例如 BeanNameAware,BeanFactoryAware,ApplicationContextAware,EnvironmentAware,ApplicationEventPublisherAware 等等,以下介绍几个常用的用法。
BeanNameAware 接口
如果 bean 需要知道自己在 bean 工厂中的 beanName,即在 Spring 容器中的名字(标识)。可以实现此 BeanNameAware 接口。BeanNameAware 接口源码如下,只有一个方法,beanName 会通过这个方法设置到 bean 中。
package org.springframework.beans.factory; public interface BeanNameAware extends Aware { void setBeanName(String name); }
其实,我们使用的 bean 一般不需要知道它在 beanFactory 中的名字,意义不大。一般是官方在 Spring 自身框架使用比较多,官方也不推荐我们使用,因为这样会导致 bean 依赖 Spring API。
package com.chenpi; import org.springframework.beans.factory.BeanNameAware; import org.springframework.stereotype.Component; /** * @author 陈皮 * @version 1.0 * @description * @date 2022/4/3 */ @Component public class Person implements BeanNameAware { private String beanName; @Override public void setBeanName(String name) { this.beanName = name; } public String getBeanName() { return beanName; } }
编写测试单元,验证如下:
package com.chenpi; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class ApplicationTests { @Autowired private Person person; @Test public void testValue() { System.out.println("Person BeanName:" + person.getBeanName()); } } // 输出结果如下 Person BeanName:person
BeanFactoryAware 接口
如果 Bean 想感知配置自己的 BeanFactory 对象,可以实现 BeanFactoryAware 接口。如果需要,bean 可以通过 BeanFactory 对象获取其他 bean 对象,进行协作。当然也不推荐这种方式,推荐使用 DI 方式注入依赖的 bean 对象。BeanFactoryAware 接口源码如下:
package org.springframework.beans.factory; import org.springframework.beans.BeansException; public interface BeanFactoryAware extends Aware { // 在bean属性填充之后,但是在初始回调(例如afterPropertiesSet()方法)之前回调此方法 void setBeanFactory(BeanFactory beanFactory) throws BeansException; }
测试代码以及单元测试验证结果如下:
package com.chenpi; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanNameAware; import org.springframework.stereotype.Component; /** * @author 陈皮 * @version 1.0 * @description * @date 2022/4/3 */ @Component public class Person implements BeanNameAware, BeanFactoryAware { private String beanName; private BeanFactory beanFactory; @Override public void setBeanName(String name) { this.beanName = name; } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } public String getBeanName() { return beanName; } public BeanFactory getBeanFactory() { return beanFactory; } } package com.chenpi; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class ApplicationTests { @Autowired private Person person; @Test public void testValue() { System.out.println("Person BeanName:" + person.getBeanName()); System.out.println("Person Bean's BeanFactory:" + person.getBeanFactory().getClass()); System.out.println( person == person.getBeanFactory().getBean(person.getBeanName(), Person.class)); } } // 输出结果如下 Person BeanName:person Person Bean's BeanFactory:class org.springframework.beans.factory.support.DefaultListableBeanFactory true
ApplicationContextAware 接口
如果 Spring Bean 需要感知 Spring 容器,即 ApplicationContext 对象,可以实现 ApplicationContextAware 接口。可以通过 Spring 容器获取其他 Bean 对象,进行协作。
当然,如果一个 bean 对象需要访问文件资源,例如调用applicationContext.getResource()方法,或想要发布一个应用程序事件applicationContext.publishEvent(ApplicationEvent event),或需要访问MessageSource,此种方式可以实现。不过,官方对于这些的特定场景,最好实现更具体的ResourceLoaderAware、ApplicationEventPublisherAware或MessageSourceAware接口更合适。
package org.springframework.context; import org.springframework.beans.BeansException; import org.springframework.beans.factory.Aware; public interface ApplicationContextAware extends Aware { void setApplicationContext(ApplicationContext applicationContext) throws BeansException; }
测试代码以及单元测试验证结果如下:
package com.chenpi; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanNameAware; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; /** * @author 陈皮 * @version 1.0 * @description * @date 2022/4/3 */ @Component public class Person implements BeanNameAware, BeanFactoryAware, ApplicationContextAware { private String beanName; private BeanFactory beanFactory; private ApplicationContext applicationContext; @Override public void setBeanName(String name) { this.beanName = name; } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } public String getBeanName() { return beanName; } public BeanFactory getBeanFactory() { return beanFactory; } public ApplicationContext getApplicationContext() { return applicationContext; } } package com.chenpi; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.ApplicationContext; @SpringBootTest class ApplicationTests { @Autowired private Person person; @Test public void testValue() { System.out.println("Person BeanName:" + person.getBeanName()); System.out.println("Person Bean's BeanFactory:" + person.getBeanFactory().getClass()); System.out.println( person == person.getBeanFactory().getBean(person.getBeanName(), Person.class)); ApplicationContext applicationContext = person.getApplicationContext(); Person person1 = applicationContext.getBean(person.getBeanName(), Person.class); System.out.println(applicationContext.getClass()); System.out.println(person == person1); } } // 输出结果如下 Person BeanName:person Person Bean's BeanFactory:class org.springframework.beans.factory.support.DefaultListableBeanFactory true class org.springframework.web.context.support.GenericWebApplicationContext true
相关文章
- 【分布式技术专题】「LVS负载均衡」全面透析Web基础架构负载均衡LVS机制的原理分析指南
- 数说,世界杯让你印象最深刻的面孔
- 记一次docker虚拟机横向移动渗透测试
- 华为秋招面经分享!
- 面试官:如何保证接口幂等性?一口气说了12种方法!
- 网易秋招高频面试题汇总
- 《前端图形学实战》几何学在前端边界计算中的应用和原理分析
- 前端图形学实战: 从零实现编辑器的图层管理面板和实时缩略图(vue3 + vite版)
- 结构建模设计——Solidworks软件之装配体操作基本总结一(装配体功能界面简介、插入零件操作、基本配合操作)
- 程序员如何准备好一次面试
- 面试问到DCL失效不知所措
- Kafka大厂高频面试题:在保证高性能、高吞吐的同时保证高可用性
- 域渗透-横向移动命令总结
- PHP程序员面试时经常会被考的冒泡排序算法
- Spring框架学习笔记(2)——面向切面编程AOP
- Jsp学习笔记(2)——页面导航、表单、EL表达式
- ArcGIS QGIS学习二:图层如何只显示需要的部分几何面数据(附最新坐标边界下载全国省市区县乡镇)
- 新开源HTML5单文件网页版ACME客户端,可在线申请Let's Encrypt、ZeroSSL免费HTTPS多域名通配符泛域名SSL/TLS证书(RSA/ECC/ECDSA)
- Java开发桌面程序学习(13)——Javafx多线程 下载功能
- 移动端实现HTML5 mp3录音踩坑指南:系统播放音量变小、一些机型录音断断续续 之 MediaRecorder和AudioWorklet的终极对决