首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

超实用的Spring bean工具类

超实用的spring bean工具类1、背景

我们在项目开发过程中,可能会遇到下面的场景:

(1)想在工具类中调用一个http接口请求数据,然后再调用spring容器中托管的service将请求结果保存到数据库

(2)想在工具类中使用spring容器中的环境变量(属性)信息

(3)在spring容器托管的service中只有一个方法需要调用另一个被spring容器托管的service

针对上面的场景,大伙一般会怎么处理呢?是不是也会像下面这样去处理?

上面的方法虽然能解决问题,但是还是有些弊端的。比如:

(1)针对前两种场景,如果在工具类中使用spring容器中托管的信息(bean实例或属性配置信息),那么就要把工具类本身也要被spring容器托管,这样才能在工具类中注入bean实例或者一些属性配置信息。如果有一个工具类还好,那如果有多个呢?而且作为一个工具类,我们使用该工具类时都是直接调用工具类中的静态方法,虽然它(工具类)被托管在了spring容器中,但是并不会有别的bean实例来调用这个工具类对应的bean实例,因此这些工具类对应的bean实例们只会占用内存资源。

(2)针对第三种场景,AppService要依赖RecordService,将RecordService作为AppService类的成员变量而存在,会让人误以为这个变量的重要性很大,虽然它在AppService中只被调用了一次。当然这个也并不是重点。但是如果AppService和RecordService互相依赖,那么这就涉及到spring bean对象的循环依赖问题。考虑到springboot官方自spring boot 2.6.x起,就默认不支持循环依赖了,如果我们再通过这种方式互相注入依赖,也就并不合适了。

其实针对上面的问题,有更合适更优雅的处理办法~

2、功能实现

我们可以利用spring自身的特性,编写一个工具类,来帮我们处理以上的场景。实现代码如下:

如以上代码所述,我们用工具类实现了ApplicationContextAware,实现这个接口是方便我们操作spring容器中的bean实例。而实现EnvironmentAware接口,是为了方便获取spring容器中的环境变量信息。最后,我们在工具类上面还加了@Component注解,让我们工具类由spring容器托管。针对我们之前的描述场景,我们就可以这么使用:

虽然我们将SpringApplicationUtil工具类托管到了spring容器中,但是我们只需要采用SpringApplicationUtil.getBean()的方式直接调用工具类的静态方法,来获取我们想要的数据。

3、实现原理

为啥这么写SpringApplicationUtil工具类,就能满足我们以上场景的需要了呢?这就得结合spring bean的生命周期和spring BeanPostProcessor的特性来说了。

首先我们要理解,如果我们想编码获取spring容器中的bean对象和环境配置信息,就需要拿到application对象和environment对象。我在之前写的“浅谈spring bean的生命周期”的文章中的2.3部分,提到了“初始化bean”的这一操作。在这一步有如下的描述:

另外Spring容器中有一个很重要的BeanPostProcessor对象,它就是ApplicationContextAwareProcessor对象,它专门用来为那些实现了EnvironmentAware接口和ApplicationContextAware接口的bean服务,能往这些bean中注入application对象和environment对象。它的实现如下(以spring 5.3.20版本为例):

可以看到,当初始化bean的时候,ApplicationContextAwareProcessor的postProcessBeforeInitialization方法就会被调用。

如果bean实现了EnvironmentAware接口,就会调用bean的setEnvironment方法,那么就会执行下面这行:

bean对象中的Environment类型的成员变量就有值了。

而如果bean实现了ApplicationContextAware接口,就会调用bean的setApplicationContext方法,也就会执行下面这行,

此时bean对象中的ApplicationContext类型的成员变量就有值了。

当然,前提是我们的bean对象(所属的类)中定义了ApplicationContext类型的成员变量和Environment类型的成员变量,而且要分别在setApplicationContext和setEnvironment方法中对它们赋值才行。另外,最重要的一点是:我们讲的是spring bean的初始化,那我们的SpringApplicationUtil工具类就要被托管(注册)到spring容器中才行,这也就是为什么在SpringApplicationUtil类上面加上@Component注解的原因,就是为了将工具类标记为spring的一个组件(记得要确保该组件能被spring识别)。当然啦,我们在配置类中使用@Bean的方式将工具类托管(注册)到spring容器中也是可以的。

4、知识补充

前面我们提到了ApplicationContextAwareProcessor这个后置处理器,spring容器初始化bean的时候,会从容器中拿到它来执行它内部的方法,那么它是什么时候被注入到spring容器中的呢?

我们再来看下spring容器启动过程中会调用到的AbstractApplicationContext.refresh方法,实现如下:

里面会调用prepareBeanFactory方法,我们来看下它内部实现:

另外,refresh方法内部还调用了一个registerBeanPostProcessors(beanFactory);方法,其实这个方法就是对容器中的各种BeanPostProcessor进行一个优先级排序。

结束语

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20230510A06FG400?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券