最近在学习框架,同时也在捡 Spring 的东西。正好看见了 hutool 工具里面封装了 SpringUtil,特此来学习一下。
脉络如下:
-
Hutool 工具
-
SpringUtil 解决什么问题
-
关于 Bean
-
使用 SpringUtil
-
注册SpringUtil
-
使用SpringUtil
-
SpringUtil 分析
-
getBeanFactory()
-
BeanFactory
-
ListableBeanFactory
-
SpringUtil 的定义
-
publishEvent()
Hutool 工具
Hutool是一个Java工具类库,提供了一系列简化开发的工具方法和封装,旨在提高Java开发人员的开发效率。Hutool工具包括但不限于以下功能:
-
高效且易用:Hutool致力于提供简洁、高效且易用的工具方法,让开发人员可以快速完成常见的开发任务。 -
字符串处理:Hutool提供了字符串相关的工具方法,包括字符串判空、截取、拼接、格式化、转换等操作,方便处理各种字符串操作需求。 -
集合操作:Hutool提供了丰富而灵活的集合操作工具,包括集合判空、遍历、过滤、转换、排序等,能够简化集合操作的编码。 -
时间日期处理:Hutool提供了强大的时间日期处理工具,包括日期格式化、解析、计算、偏移、格式化输出等,能够简化时间日期相关操作。 -
文件操作:Hutool提供了便捷的文件操作工具,包括文件读写、创建、复制、移动、删除、压缩等,方便处理文件相关操作。 -
加密解密:Hutool提供了多种常用的加密解密算法,包括MD5、SHA1、Base64、AES、RSA等,方便进行数据的加密和解密。 -
HTTP请求:Hutool提供了简单易用的HTTP请求工具,可以发送GET、POST等请求,并支持自定义请求头、参数、代理等配置。 -
JSON处理:Hutool提供了对JSON格式数据的操作工具,包括JSON对象的创建、解析、转换、格式化等,方便处理JSON数据。 -
XML处理:Hutool提供了对XML格式数据的操作工具,包括XML的解析、生成、转换等,能够方便地处理XML数据。 -
编码转换:Hutool提供了各种编码之间的转换工具,包括字符串与十六进制、Base64、URL编码之间的转换,方便进行不同编码之间的互相转换。 -
正则表达式:Hutool提供了强大的正则表达式工具,可以进行正则匹配、提取、替换等操作,方便处理复杂的字符串匹配需求。 -
其他实用工具:Hutool还提供了其他一些实用的工具方法,包括身份证号码验证、随机数生成、系统信息获取、网络相关操作等。
今天要研究的是其中关于Spring框架的一个工具。
SpringUtil 解决什么问题
在使用Spring Boot时,通过依赖注入来获取Bean非常方便。但是,在一些工具化的应用场景中,需要在运行时动态地获取Bean,这会变得比较困难。因此,Hutool库提供了一个工具类——SpringUtil,它封装了Spring框架中用于获取Bean的方法,使得在工具化的应用中可以更加方便地动态获取Bean。通过使用SpringUtil工具类,开发者可以简化获取Bean的过程,提高代码的可读性和易用性。
当使用依赖注入方式获取Bean
时,我们可以通过在类的字段上使用@Autowired
注解来自动将相应的Bean
注入进来。例如,假设有一个UserService
类需要依赖一个UserRepository
接口,并且在Spring
容器中已经定义了一个UserRepository
的实现类,那么可以这样使用依赖注入获取Bean
:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// ...
}
在这个例子中,UserService
类通过@Autowired
注解将UserRepository
的实例注入到了userRepository
字段中,从而实现了依赖注入。
而在工具化的应用场景下,可能需要在运行时根据一些条件或者配置来动态获取Bean
。例如,假设有一个统一的配置管理类ConfigManager
,其中包含了一些配置项和对应的Bean
名称,我们希望能够根据配置项的值来动态获取相应的Bean
实例,就可以使用工具类如SpringUtil
来实现。具体代码可能如下所示:
public class ConfigManager {
public static Object getBeanByConfig(String configName) {
// 根据配置项名称获取相应的Bean名称
String beanName = getConfiguredBeanName(configName);
// 使用SpringUtil工具类获取对应的Bean实例
return SpringUtil.getBean(beanName);
}
// ...
}
在这个例子中,ConfigManager类定义了一个静态方法getBeanByConfig,该方法根据配置项的名称获取相应的Bean名称,并通过SpringUtil.getBean方法来动态获取对应的Bean实例。这样就可以在运行时根据配置项来获取相应的Bean,实现了动态获取Bean的功能。
关于 Bean
在Spring框架中,几乎所有的Java对象都可以定义为Bean,并交由Spring容器进行管理。通常情况下,我们定义为Bean的类都是具有一定特殊用途或者需要在不同类之间共享的对象。以下是一些通俗易懂的例子:
Service类:Service类通常是业务逻辑处理的核心代码,包括了各种数据处理、计算、判断等操作。因为Service类通常需要在不同的Controller、Filter、Job等类中被共享,因此将Service类定义为Bean,并交由Spring容器管理,可以更加方便地进行依赖注入,提高代码的可读性和可维护性。
DAO类:DAO(Data Access Object)类通常是对数据访问的封装,包括了各种数据查询、更新、删除等操作。与Service类类似,DAO类也需要在不同的Service、Controller等类中被共享,因此定义为Bean并交由Spring容器管理也是比较好的选择。
工具类:工具类通常是一些静态方法集合,用于提供一些常用的工具方法,比如日期处理、字符串处理、文件处理等。虽然工具类不需要被实例化,但有时候需要在其他类中使用工具类的方法。因此,将工具类定义为Bean并交由Spring容器管理,可以更方便地在其他类中进行使用。
第三方组件:有些第三方组件,比如数据库连接池、缓存框架等,通常需要在整个应用程序中被共享。将这些组件定义为Bean并交由Spring容器管理,可以更方便地进行依赖注入和配置管理。
任何需要在不同类之间共享的对象,或者需要进行依赖注入和配置管理的对象,都可以定义为Bean
,并交由Spring
容器进行管理。
使用 SpringUtil
注册SpringUtil
在使用 Spring 框架时,我们需要让 Spring 容器意识到特定的类并将其纳入管理。为了让 Spring 容器能够识别并管理这个类,你需要进行如下两个步骤。
-
使用 @ComponentScan
注解来指示 Spring 在指定的包下进行组件扫描,寻找带有@Component
注解(以及其他注解,如@Service
、@Repository
和@Controller
)的类,并将它们注册为 Spring 容器中的 Bean。
// 扫描cn.hutool.extra.spring包下所有类并注册之
@ComponentScan(basePackages={"cn.hutool.extra.spring"})
-
使用 @Import
注解来直接导入指定的类,这样做可以让 Spring 容器意识到这个类的存在并将其纳入管理。在这种情况下,cn.hutool.extra.spring.SpringUtil.class
类会被导入到 Spring 容器中,并成为一个受 Spring 管理的 Bean。
@Import(cn.hutool.extra.spring.SpringUtil.class)
这两个步骤是有一定的联系和依赖关系的,缺少其中任何一步都可能导致注册失败或无法正确使用 SpringUtil 类。
第一个步骤使用 @ComponentScan
注解告诉 Spring 容器去指定的包下进行组件扫描,将符合条件的类注册为 Spring 容器中的 Bean。这样做是为了让 Spring 容器能够自动扫描并注册 Hutool 框架中的相关类。
然而,仅仅通过第一个步骤进行组件扫描并不能保证 SpringUtil
类会被正确地注册为 Bean。因为 @ComponentScan
注解只能扫描到带有 @Component
及其他特定注解的类,并不能直接将一个普通的类纳入 Spring 容器管理。
这就需要第二个步骤,使用 @Import
注解来将指定的类直接导入到 Spring 容器中,并成为一个被 Spring 管理的 Bean。通过这个步骤,Spring 容器能够正确地识别和管理 Hutool 框架中的 SpringUtil 类。
使用SpringUtil
以文档中“获取指定Bean”例子说明:
-
定义一个Bean
@Data
public static class Demo2{
private long id;
private String name;
@Bean(name="testDemo")
public Demo2 generateDemo() {
Demo2 demo = new Demo2();
demo.setId(12345);
demo.setName("test");
return demo;
}
}
-
获取Bean
final Demo2 testDemo = SpringUtil.getBean("testDemo");
确实是很方便。
SpringUtil 分析
getBeanFactory()
为了更加深入了解这个工具,我们从getBeanFactory()
方法入手。
SpringUtil中getBeanFactory
方法是这样的:
public static ListableBeanFactory getBeanFactory() {
// 如果 beanFactory 为 null,则使用 applicationContext 赋值给 factory
final ListableBeanFactory factory = null == beanFactory ? applicationContext : beanFactory;
// 如果 factory 仍然为 null,则抛出 UtilException 异常,提示可能不在 Spring 环境中
if (null == factory) {
throw new UtilException("No ConfigurableListableBeanFactory or ApplicationContext injected, maybe not in the Spring environment?");
}
// 返回 factory
return factory;
}
它会先获取beanFactory,再是applicationContext,用ListableBeanFactory
的类型承接上。
BeanFactory
ListableBeanFactory
接口扩展了 BeanFactory
接口,提供了更多用于获取和检索 Bean 的方法。我们都知道,Spring的根基就是IoC容器,控制反转,核心就是Spring来管理了这些 Bean。而 BeanFactory
是Spring框架中定义bean工厂的核心接口,它提供了对bean的创建、获取、管理等功能的抽象定义。而在实际开发中,我们通常使用的是ApplicationContext
接口,它是BeanFactory
接口的子接口,提供了更多高级的功能和特性。
BeanFactory
接口包括了一些常用管理 Bean 的方法,例如:
-
getBean(String name)
:根据给定的bean名称返回在容器中对应的bean实例。 -
containsBean(String name)
:判断容器中是否包含指定名称的bean。 -
isSingleton(String name)
:判断指定名称的bean是否是单例。 -
getType(String name)
:返回指定名称的bean的类型。 -
getAliases(String name)
:返回指定名称的bean的所有别名。
我们用例子来理解一下:
-
获取 Bean 对象:
// 假设有一个名为 "userService" 的 Bean 注册到了 BeanFactory 中
UserService userService = beanFactory.getBean("userService", UserService.class);
这里通过 getBean() 方法从 BeanFactory 中获取名为 “userService” 的 Bean,并将其转换为 UserService 类型的对象。
-
判断 Bean 是否存在:
boolean exists = beanFactory.containsBean("userService");
这里使用 containsBean() 方法判断名为 “userService” 的 Bean 是否存在于 BeanFactory 中,返回一个布尔值表示是否存在。
-
判断 Bean 是否是单例:
boolean isSingleton = beanFactory.isSingleton("userService");
这里使用 isSingleton() 方法判断名为 “userService” 的 Bean 是否是单例,返回一个布尔值表示是否是单例。
-
获取 Bean 的类型:
Class<?> beanType = beanFactory.getType("userService");
这里使用 getType() 方法获取名为 “userService” 的 Bean 的类型,返回一个 Class 对象表示 Bean 的类型。
-
获取 Bean 的所有名称:
String[] beanNames = beanFactory.getBeanNamesForType(UserService.class);
这里使用 getBeanNamesForType() 方法获取所有类型为 UserService 的 Bean 的名称,返回一个字符串数组表示所有匹配的 Bean 名称。
ListableBeanFactory
ListableBeanFactory
接口是 Spring 框架中的一个子接口,继承自 BeanFactory
接口。它定义了一组用于获取和管理 Bean 的方法,并且提供了更多的功能,可以列举和搜索 Bean。
下面是 ListableBeanFactory 接口的一些常用方法:
-
getBeanDefinitionCount()
:返回容器中注册的 Bean 定义的数量。 -
getBeanDefinitionNames()
:返回容器中所有 Bean 的名称。 -
getBeanDefinition(String name)
:根据给定的 Bean 名称获取相应的 Bean 定义。 -
containsBeanDefinition(String name)
:判断容器中是否存在指定名称的 Bean 定义。 -
getBeansOfType(Class<T> type)
:通过类型获取容器中所有匹配的 Bean 实例,以 Map 的形式返回。 -
getBeansOfType(Class<T> type, boolean includeNonSingletons, boolean allowEagerInit)
:通过类型获取容器中所有匹配的 Bean 实例,以 Map 的形式返回。可以选择是否包括非单例 Bean 和是否允许初始化非懒加载的 Bean。 -
getBeanNamesForType(Class<T> type)
:通过类型获取容器中所有匹配的 Bean 的名称。 -
getBeanNamesForAnnotation(Class<? extends Annotation> annotationType)
:获取所有标注了指定注解的 Bean 的名称。 -
getBeansWithAnnotation(Class<? extends Annotation> annotationType)
:获取所有标注了指定注解的 Bean 实例,以 Map 的形式返回。 -
isPrototype(String name)
:判断指定名称的 Bean 是否是原型(非单例)。 -
isSingleton(String name)
:判断指定名称的 Bean 是否是单例。 -
isTypeMatch(String name, Class<?> targetType)
:判断指定名称的 Bean 是否与给定类型兼容。 -
getType(String name)
:返回指定名称的 Bean 的类型。
ListableBeanFactory 接口扩展了 BeanFactory 接口,提供了更多用于获取和检索 Bean 的方法。
SpringUtil 的定义
我们再从头看SpringUtil的定义,它实现了BeanFactoryPostProcessor
和ApplicationContextAware
两个接口:
@Component
public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextAware
在 SpringUtil 类中实现 BeanFactoryPostProcessor
和 ApplicationContextAware
接口的目的是为了能够在 Spring 容器启动过程中获取到 ApplicationContext
对象,并在容器加载完毕后对 BeanFactory
进行定制操作。
-
实现 BeanFactoryPostProcessor
接口:
通过实现 BeanFactoryPostProcessor 接口,可以在容器加载 BeanDefinition 之后、实例化 Bean 之前对容器进行定制操作。通过重写 postProcessBeanFactory 方法,可以修改或操作 BeanDefinition,例如添加新的 BeanDefinition、修改属性值等。在 SpringUtil 类中,可能会使用这个方法来注册自定义的 BeanDefinition 或者修改已有的 BeanDefinition。
-
实现 ApplicationContextAware
接口:
通过实现 ApplicationContextAware 接口,可以在 Spring 容器启动过程中获取到 ApplicationContext 对象。ApplicationContext 是 Spring 框架中的核心接口,它代表了整个应用的上下文环境。通过实现 ApplicationContextAware 接口并重写 setApplicationContext 方法,Spring 在启动时会将 ApplicationContext 对象注入到 SpringUtil 类中,从而使得 SpringUtil 类可以获取到容器中的 Bean 实例和其他资源。
实现这两个接口的目的,也是为了能够注入下面两个对象:
/**
* "@PostConstruct"注解标记的类中,由于ApplicationContext还未加载,导致空指针<br>
* 因此实现BeanFactoryPostProcessor注入ConfigurableListableBeanFactory实现bean的操作
*/
private static ConfigurableListableBeanFactory beanFactory;
/**
* Spring应用上下文环境
*/
private static ApplicationContext applicationContext;
BeanFactoryPostProcessor
实现BeanFactoryPostProcessor
接口,是为了实现其中的postProcessBeanFactory
函数。其目的是@PostConstruct
注解标记的类中,ApplicationContext还未加载,此时使用 ApplicationContext
的父辈 BeanFactory
使其拥有bean管理的相关能力:

正如SpringUtil中所做的,重写了postProcessBeanFactory
方法,将beanFactory
作为SpringUtil.beanFactory
的初始化值:
@SuppressWarnings("NullableProblems")
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
SpringUtil.beanFactory = beanFactory;
}
ApplicationContextAware
与实现BeanFactoryPostProcessor
接口的目的类似,实现ApplicationContextAware
接口目的在于将ApplicationContext
赋值给SpringUtil.applicationContext
:
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
SpringUtil.applicationContext = applicationContext;
}
也就是说,这两个接口都在初始化时发挥作用。
延伸方法
实现前面两个接口是为了初始化以获得 beanFactory
和applicationContext
两个对象,所以下面再写几个getXXX
的方法也是理所应当吧:
/**
* 获取{@link ApplicationContext}
*
* @return {@link ApplicationContext}
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 获取{@link ListableBeanFactory},可能为{@link ConfigurableListableBeanFactory} 或 {@link ApplicationContextAware}
*
* @return {@link ListableBeanFactory}
* @since 5.7.0
*/
public static ListableBeanFactory getBeanFactory() {
final ListableBeanFactory factory = null == beanFactory ? applicationContext : beanFactory;
if(null == factory){
throw new UtilException("No ConfigurableListableBeanFactory or ApplicationContext injected, maybe not in the Spring environment?");
}
return factory;
}
/**
* 获取{@link ConfigurableListableBeanFactory}
*
* @return {@link ConfigurableListableBeanFactory}
* @throws UtilException 当上下文非ConfigurableListableBeanFactory抛出异常
* @since 5.7.7
*/
public static ConfigurableListableBeanFactory getConfigurableBeanFactory() throws UtilException {
final ConfigurableListableBeanFactory factory;
if (null != beanFactory) {
factory = beanFactory;
} else if (applicationContext instanceof ConfigurableApplicationContext) {
factory = ((ConfigurableApplicationContext) applicationContext).getBeanFactory();
} else {
throw new UtilException("No ConfigurableListableBeanFactory from context!");
}
return factory;
}
再后面,封装了一些常用场景需要获取到的二级对象:
-
getBean(String name)
: 通过给定的名称获取对应的Bean。 -
getBean(Class<T> clazz)
: 通过给定的类获取对应的Bean。 -
getBean(String name, Class<T> clazz)
: 通过给定的名称和类获取对应的Bean。 -
getBean(TypeReference<T> reference)
: 通过给定的类型参考获取带泛型参数的Bean。 -
getBeansOfType(Class<T> type)
: 获取指定类型及其子类对应的所有Bean,并以Map形式返回,其中key为Bean的名称,value为Bean对象。 -
getBeanNamesForType(Class<?> type)
: 获取指定类型及其子类对应的所有Bean的名称。 -
getProperty(String key)
: 获取配置文件中指定配置项的值。 -
getApplicationName()
: 获取应用程序的名称。 -
getActiveProfiles()
: 获取当前的环境配置,如果没有配置则返回null。 -
getActiveProfile()
: 获取当前的环境配置,当有多个环境配置时,只获取第一个。 -
registerBean(String beanName, T bean)
: 动态将Bean注册到Spring容器中。 -
unregisterBean(String beanName)
: 注销在Spring容器中注册的Bean。
至此,SpringUtil就只有一个方法未提及了,那就是publishEvent
。
publishEvent()
applicationContext.publishEvent(event)
方法是Spring框架中的一个核心方法,用于发布事件到应用程序的事件系统。通过该方法,可以将自定义事件发送给注册的监听器,以便它们能够接收并做出相应的响应。
通过applicationContext.publishEvent(event)
方法,可以实现以下功能:
-
实现应用程序内部的模块间通信:不同模块之间可以通过发布和监听事件的方式进行通信,避免直接依赖和耦合。 -
实现事件驱动的编程模型:通过事件触发的方式,可以实现基于事件的编程模型,提高代码的可扩展性和灵活性。 -
实现解耦:事件机制可以将事件源和事件处理逻辑解耦,使得应用程序的各个组件更加独立和可维护。
我们看看SpringUtil
中的定义:
/**
* 发布事件
*
* @param event 待发布的事件,事件必须是{@link ApplicationEvent}的子类
* @since 5.7.12
*/
public static void publishEvent(ApplicationEvent event) {
if (null != applicationContext) {
applicationContext.publishEvent(event);
}
}
/**
* 发布事件
* Spring 4.2+ 版本事件可以不再是{@link ApplicationEvent}的子类
*
* @param event 待发布的事件
* @since 5.7.21
*/
public static void publishEvent(Object event) {
if (null != applicationContext) {
applicationContext.publishEvent(event);
}
}
这个方法因 Spring 4.2 作为分水岭呈现多态,那么这个Spring 4.2 发生了什么?让自定义事件可以不再是ApplicationEvent
的子类了呢。
从Spring 4.2开始,事件基础架构得到了极大的改进,它提供了基于注解的模型 以及发布任何任意事件的能力(也就是不一定是从 ApplicationEvent 继承出来的对象)。
自定义事件过于深入的就不再展开讨论,就以Spring 4.2 前后的自定义事件流程为例,来说它的工作流程如何以及有如何的改动:
Spring 4.2 之前的自定义事件
-
自定义事件类,继承自 ApplicationEvent
类,并实现必要的构造方法和其他方法。
public class CustomEvent extends ApplicationEvent {
public CustomEvent(Object source) {
super(source);
}
}
-
创建事件发布者,注入 ApplicationContext
对象,并在需要发布事件的地方调用publishEvent()
方法。
@Component
public class CustomEventPublisher {
@Autowired
private ApplicationContext applicationContext;
public void publish() {
CustomEvent event = new CustomEvent(this);
applicationContext.publishEvent(event);
}
}
-
创建事件监听器,实现 ApplicationListener
接口,并在相应的方法中处理事件。
@Component
public class CustomEventListener implements ApplicationListener<CustomEvent> {
@Override
public void onApplicationEvent(CustomEvent event) {
// 处理事件逻辑
}
}
Spring 4.3 之后的自定义事件
在Spring4.2之后,自定义事件的流程得到了简化,可以直接使用@EventListener注解来标记处理事件的方法,无需再实现ApplicationListener接口。
-
自定义事件类,不再继承自ApplicationEvent类,只需要定义一个普通的POJO类即可。
public class CustomEvent {
private Object source;
public CustomEvent(Object source) {
this.source = source;
}
// getter and setter methods for source
}
-
创建事件发布者,注入ApplicationEventPublisher对象,并在需要发布事件的地方调用publishEvent()方法。
@Component
public class CustomEventPublisher {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void publish() {
CustomEvent event = new CustomEvent(this);
eventPublisher.publishEvent(event);
}
}
-
创建事件监听器,使用@EventListener注解标记处理事件的方法。
@Component
public class CustomEventListener {
@EventListener
public void handleCustomEvent(CustomEvent event) {
// 处理事件逻辑
}
}
至此,所有SpringUtil中的方法我们都过了一遍,如果还想要深入,请继续阅读源码。
原文始发于微信公众号(anywareAI):Hutool – SpringUtil
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/186564.html