工具类如何获取到 Spring 容器中的 Bean?
1. Aware 接口
小伙伴们知道,Spring 容器最大的特点在于所有的 Bean 对于 Spring 容器的存在是没有意识的,因此我们常说理论上你可以无缝将 Spring 容器切换为其他容器(然而在现实世界中,我们其实没有这样的选择,除了 Spring 容器,难道还有更好用的?)。
当然这只是一个理论,在实际开发中,我们往往要用到 Spring 容器为我们提供的诸多资源,例如想要获取到容器中的配置、获取到容器中的 Bean 等等。在这种情况下,就需要 Spring 容器中的 Bean 真正的意识到 Spring 容器的存在,才能要到这些东西,那么如何让一个 Bean 意识到 Spring 容器的存在呢?
这就依赖于 Spring 容器给我们提供的各种 Aware 接口了。
/** * A marker superinterface indicating that a bean is eligible to be notified by the * Spring container of a particular framework object through a callback-style method. * The actual method signature is determined by individual subinterfaces but should * typically consist of just one void-returning method that accepts a single argument. * * <p>Note that merely implementing {@link Aware} provides no default functionality. * Rather, processing must be done explicitly, for example in a * {@link org.springframework.beans.factory.config.BeanPostProcessor}. * Refer to {@link org.springframework.context.support.ApplicationContextAwareProcessor} * for an example of processing specific {@code *Aware} interface callbacks. * * @author Chris Beams * @author Juergen Hoeller * @since 3.1 */ public interface Aware { }
从这个接口的注释中,我们也能大概看出来,这个接口的子类,主要是提供了一些只有一个参数的 set 方法,通过这些方法可以让 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 中提供的一堆 Aware。
接下来松哥随便写个例子大家来看下 Aware 的用法。
2. BeanFactoryAware
实现该接口的对象可以获取到一个 BeanFactory 对象,通过 BeanFactory 可以完成 Bean 的查询等操作。这算是一个比较常见的 Aware 了,我们一起来看下。
这里为了省事,我就在 Spring Boot 中来和大家演示。
首先我们来定义一个简单的 UserService:
@Service public class UserService { public void hello() { System.out.println("hello javaboy!"); } }
然后提供一个工具类:
@Component public class BeanUtils implements BeanFactoryAware { private static BeanFactory beanFactory = null; @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { BeanUtils.beanFactory = beanFactory; } public static <T> T getBean(String beanName) { return (T) beanFactory.getBean(beanName); } }
有了这个工具类,接下来我们就可以在一个非 Spring 管理的 Bean 中,随时随地的查询 Bean 了,像下面这样:
UserService userService = BeanUtils.getBean("userService"); userService.hello();
3. TienChin 项目实践
为什么会有今天这篇文章呢?主要是在松哥最近做的 TienChin 项目中,有一个地方涉及到这块知识点了,但是有的小伙伴不熟悉,因此就拎出来和大家梳理下。
在 TienChin 项目中,在记录日志的时候,因为日志是一个延迟任务,所以提前准备好了相关的 Bean 已经注册到 Spring 容器中了,像下面这样:
@Configuration public class ThreadPoolConfig { /** * 执行周期性或定时任务 */ @Bean(name = "scheduledExecutorService") protected ScheduledExecutorService scheduledExecutorService() { return new ScheduledThreadPoolExecutor(corePoolSize, new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(), new ThreadPoolExecutor.CallerRunsPolicy()) { @Override protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); Threads.printException(r, t); } }; } } @Component public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware { /** * Spring应用上下文环境 */ private static ConfigurableListableBeanFactory beanFactory; private static ApplicationContext applicationContext; @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { SpringUtils.beanFactory = beanFactory; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringUtils.applicationContext = applicationContext; } /** * 获取对象 * * @param name * @return Object 一个以所给名字注册的bean的实例 * @throws org.springframework.beans.BeansException */ @SuppressWarnings("unchecked") public static <T> T getBean(String name) throws BeansException { return (T) beanFactory.getBean(name); } /** * 获取类型为requiredType的对象 * * @param clz * @return * @throws org.springframework.beans.BeansException */ public static <T> T getBean(Class<T> clz) throws BeansException { T result = (T) beanFactory.getBean(clz); return result; } }
而写日志的异步任务工具类,并非一个容器,所以要通过这个工具类获取相应的 Bean,如下:
public class AsyncManager { /** * 操作延迟10毫秒 */ private final int OPERATE_DELAY_TIME = 10; /** * 异步操作任务调度线程池 */ private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService"); /** * 单例模式 */ private AsyncManager() { } private static AsyncManager me = new AsyncManager(); public static AsyncManager me() { return me; } /** * 执行任务 * * @param task 任务 */ public void execute(TimerTask task) { executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS); } }
有了 SpringUtils 我们就可以在一个非 Spring 容器所管理的 Bean 中,获取到 Spring 容器中的 Bean 了。
- HarmonyOS应用开发:鸿蒙JS实战,计算器功能开发!
- Vitest:替代 Jest 的前端测试工具新选择
- Vue的递归组件:渲染嵌套评论
- 在 Web 应用的运行时实现多分支并存和切换
- Umd 的包如何导出 TS 类型
- Volatile:JVM 我警告你,我的人你别乱动
- “整洁架构” 和商家前端的重构之路
- 中文文本智能纠错知多少?
- 写给 Java 程序员的前端 Promise 教程,你学会了吗?
- 什么是服务网格?在微服务体系中又是如何使用的?
- JavaScript大神:我们能对 JavaScript 做的最好事情就是让它退役!
- 掌握 TypeScript 中的映射类型
- 搞清楚 Go Mod的版本和伪版本,下次别乱用了
- 如何最简单、通俗地理解Python模块?
- GitHub 添加工具以简化软件开发管理
- 测试自动化的六大原则
- Google 的 C 实验性继任者 Carbon 是否值得学习
- 一文了解字节跳动“埋点验证平台”
- 京东APP OpenHarmony 化的跨端开发探索
- C语言库函数-printf()