Hutool – SpringUtil

最近在学习框架,同时也在捡 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 容器能够识别并管理这个类,你需要进行如下两个步骤。

  1. 使用 @ComponentScan 注解来指示 Spring 在指定的包下进行组件扫描,寻找带有 @Component 注解(以及其他注解,如 @Service@Repository@Controller)的类,并将它们注册为 Spring 容器中的 Bean。
// 扫描cn.hutool.extra.spring包下所有类并注册之
@ComponentScan(basePackages={"cn.hutool.extra.spring"})
  1. 使用 @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”例子说明:

  1. 定义一个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;
 }
}
  1. 获取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的所有别名。

我们用例子来理解一下:

  1. 获取 Bean 对象:
// 假设有一个名为 "userService" 的 Bean 注册到了 BeanFactory 中
UserService userService = beanFactory.getBean("userService", UserService.class);

这里通过 getBean() 方法从 BeanFactory 中获取名为 “userService” 的 Bean,并将其转换为 UserService 类型的对象。

  1. 判断 Bean 是否存在:
boolean exists = beanFactory.containsBean("userService");

这里使用 containsBean() 方法判断名为 “userService” 的 Bean 是否存在于 BeanFactory 中,返回一个布尔值表示是否存在。

  1. 判断 Bean 是否是单例:
boolean isSingleton = beanFactory.isSingleton("userService");

这里使用 isSingleton() 方法判断名为 “userService” 的 Bean 是否是单例,返回一个布尔值表示是否是单例。

  1. 获取 Bean 的类型:
Class<?> beanType = beanFactory.getType("userService");

这里使用 getType() 方法获取名为 “userService” 的 Bean 的类型,返回一个 Class 对象表示 Bean 的类型。

  1. 获取 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的定义,它实现了BeanFactoryPostProcessorApplicationContextAware两个接口:

@Component
public class SpringUtil implements BeanFactoryPostProcessorApplicationContextAware

在 SpringUtil 类中实现 BeanFactoryPostProcessorApplicationContextAware 接口的目的是为了能够在 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管理的相关能力

Hutool - SpringUtil

正如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;
}

也就是说,这两个接口都在初始化时发挥作用。

延伸方法

实现前面两个接口是为了初始化以获得 beanFactoryapplicationContext 两个对象,所以下面再写几个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 之前的自定义事件

  1. 自定义事件类,继承自ApplicationEvent类,并实现必要的构造方法和其他方法。
public class CustomEvent extends ApplicationEvent {
    public CustomEvent(Object source) {
        super(source);
    }
}
  1. 创建事件发布者,注入ApplicationContext对象,并在需要发布事件的地方调用publishEvent()方法。
@Component
public class CustomEventPublisher {
    @Autowired
    private ApplicationContext applicationContext;

    public void publish() {
        CustomEvent event = new CustomEvent(this);
        applicationContext.publishEvent(event);
    }
}
  1. 创建事件监听器,实现ApplicationListener接口,并在相应的方法中处理事件。
@Component
public class CustomEventListener implements ApplicationListener<CustomEvent{
    @Override
    public void onApplicationEvent(CustomEvent event) {
        // 处理事件逻辑
    }
}

Spring 4.3 之后的自定义事件

在Spring4.2之后,自定义事件的流程得到了简化,可以直接使用@EventListener注解来标记处理事件的方法,无需再实现ApplicationListener接口。

  1. 自定义事件类,不再继承自ApplicationEvent类,只需要定义一个普通的POJO类即可。
public class CustomEvent {
    private Object source;

    public CustomEvent(Object source) {
        this.source = source;
    }

    // getter and setter methods for source
}
  1. 创建事件发布者,注入ApplicationEventPublisher对象,并在需要发布事件的地方调用publishEvent()方法。
@Component
public class CustomEventPublisher {
    @Autowired
    private ApplicationEventPublisher eventPublisher;

    public void publish() {
        CustomEvent event = new CustomEvent(this);
        eventPublisher.publishEvent(event);
    }
}
  1. 创建事件监听器,使用@EventListener注解标记处理事件的方法。
@Component
public class CustomEventListener {
    @EventListener
    public void handleCustomEvent(CustomEvent event) {
        // 处理事件逻辑
    }
}

至此,所有SpringUtil中的方法我们都过了一遍,如果还想要深入,请继续阅读源码。


原文始发于微信公众号(anywareAI):Hutool – SpringUtil

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/186564.html

(1)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!