

统一声明:
1.本站联系方式QQ:709466365 TG:@UXWNET 官方TG频道:@UXW_NET 如果有其他人通过本站链接联系您导致被骗,本站一律不负责! 2.需要付费搭建请联系站长QQ:709466365 TG:@UXWNET 3.免实名域名注册购买- 游侠云域名 4.免实名国外服务器购买- 游侠网云服务
本文针对这个高频需求,手把手教你实现一个好用的Spring获取ApplicationContext对象工具类:从核心原理(利用ApplicationContextAware接口让Spring自动注入上下文),到完整代码编写(含单例模式、线程安全处理、便捷获取Bean的方法),再到工具类注册到Spring容器的关键步骤(注解/XML两种方式),最后还会讲实际使用场景(比如静态方法中取Bean、监听容器事件)。全程无复杂概念,新手跟着做就能学会,帮你解决Spring上下文获取的痛点,提升开发效率。
你有没有过这种情况?写了个静态工具类想调Spring里的Bean,结果@Autowired死活注入不进去,要么报空指针,要么上下文拿不到?我去年帮同事解决过一模一样的问题——他做定时任务的时候,Job类是 quartz 管理的,不在Spring容器里,想调UserService根本调不了,急得直挠头:“明明容器里有这个Bean,怎么就拿不到呢?”
其实解决这事就一个核心——用Spring自带的ApplicationContextAware接口,写个工具类“接住”上下文。今天我把自己踩过坑、改了三版的实现方法分享给你,没学过复杂Spring原理也能跟着做,亲测有效。
一、先搞懂:为什么要写这个工具类?
先别急着写代码,我得先跟你说清楚“底层逻辑”——不然你光抄代码,遇到问题还是蒙。
Spring里的Bean都是容器管理的,正常情况下你用@Autowired注入就行,但非Bean组件(比如静态工具类、quartz Job、第三方框架的回调类)没法用依赖注入——因为这些类不是Spring创建的,容器管不着它们。这时候就得“主动”从容器里拿上下文,再通过上下文拿Bean。
那怎么“主动”拿?Spring给了个“后门”:ApplicationContextAware接口。你只要让工具类实现这个接口,容器启动时会自动把ApplicationContext对象“塞”给你——就像快递员上门,你留了地址(实现接口),他就把包裹(上下文)送过来。
我举个真实例子:去年做电商项目的“库存扣减”,扣减逻辑在静态工具类里,要调InventoryService,但@Autowired根本注不进去。后来用这个工具类拿上下文,再拿InventoryService,一下子就解决了——这就是工具类的核心价值:让非Bean类能访问Spring容器里的资源。
二、手把手写工具类:从0到1实现
接下来我一步一步跟你说怎么写,每一步都有我踩过的坑,你注意避坑。
首先建个工具类,比如叫SpringContextUtil
(名字随便起,好记就行),实现ApplicationContextAware
接口,然后用静态变量存上下文——因为静态方法能直接调用,不用实例化工具类。
我写的代码是这样的(直接抄就行,改改包名):
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/
Spring上下文工具类(我改了三版的最终版本)
注意:要加@Component注解(Spring Boot)或XML配置(传统Spring)
/
@Component // Spring Boot用这个注解,传统Spring删了自己写XML
public class SpringContextUtil implements ApplicationContextAware {
// 用volatile保证多线程下的可见性(踩坑点:之前没加这个,并发时拿不到上下文)
private static volatile ApplicationContext applicationContext;
// 私有构造方法,防止别人new实例(工具类就该单例)
private SpringContextUtil() {}
/
Spring容器启动时自动调用这个方法,注入上下文
注意:这个方法是Spring调用的,你不用管
/
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
// 这里用静态变量存上下文——关键中的关键!
SpringContextUtil.applicationContext = applicationContext;
}
/
获取ApplicationContext对象(加了双重检查锁,防止并发问题)
/
public static ApplicationContext getApplicationContext() {
// 双重检查锁:先判断是否为null,再加锁(其实set方法已经赋值了,但加一层保险)
if (applicationContext == null) {
synchronized (SpringContextUtil.class) {
if (applicationContext == null) {
throw new RuntimeException("Spring上下文未初始化,请检查工具类是否注册到容器!");
}
}
}
return applicationContext;
}
/
根据类型获取Bean(最常用的方法)
例子:SpringContextUtil.getBean(UserService.class)
/
public static T getBean(Class clazz) {
return getApplicationContext().getBean(clazz);
}
/
根据名字和类型获取Bean(适合有多个同类型Bean的场景)
例子:SpringContextUtil.getBean("userService", UserService.class)
/
public static T getBean(String name, Class clazz) {
return getApplicationContext().getBean(name, clazz);
}
/
根据名字获取Bean(不推荐,容易类型转换错误)
/
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
}
写了代码还不够,得让Spring“认识”这个工具类——不然容器启动时不会调用setApplicationContext
方法,上下文还是null。
注册方式分两种,你对应自己的项目选:
@Component
注解(我上面的代码已经加了)——Spring Boot会自动扫描这个类,注册成Bean。 applicationContext.xml
里加一行配置(把包名改成你自己的): 踩坑提醒:我之前帮一个传统项目改的时候,忘了加XML配置,结果工具类没注册,调用时抛“Spring上下文未初始化”的异常——一定要检查注册是否正确!
volatile避坑关键:线程安全处理 你可能会问:“为什么要用
和双重检查锁?”——我之前踩过这个坑!
volatile刚开始写的时候,我没加
,结果并发情况下(比如同时有10个请求调用工具类),
applicationContext偶尔会是null。后来查Spring官方文档才知道:静态变量的初始化在多线程下可能有“可见性问题”——一个线程修改了变量,另一个线程看不到。
volatile解决办法就是加
修饰
applicationContext,保证变量的“可见性”;再加上双重检查锁,防止并发时重复初始化(虽然这里不会重复,但加一层保险更稳)。
OrderUtil三、实际用例:工具类怎么用?
写了工具类,得知道怎么用——我举几个真实场景,你一看就懂。
静态方法里调用Bean 比如你有个静态工具类
,要调
OrderService的
createOrder方法:
java
public class OrderUtil {
public static String createOrder(OrderDTO orderDTO) {
// 用工具类拿OrderService(不用@Autowired,因为静态方法没法注入)
OrderService orderService = SpringContextUtil.getBean(OrderService.class);
return orderService.createOrder(orderDTO);
}
}
### Quartz Job里调用Bean
Quartz的Job类是自己创建的,不是Spring Bean,没法用@Autowired。这时候用工具类:
java
public class OrderJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
// 拿OrderService处理超时订单
OrderService orderService = SpringContextUtil.getBean(OrderService.class);
orderService.cancelTimeoutOrder();
}
}
###
PaymentService第三方框架回调里调用Bean 比如支付宝回调接口是静态方法,要调
的
handleCallback方法:
java
public class AlipayCallback {
// 支付宝回调的静态方法
public static String callback(HttpServletRequest request) {
// 拿PaymentService处理回调
PaymentService paymentService = SpringContextUtil.getBean(PaymentService.class);
return paymentService.handleCallback(request);
}
}
四、常见问题排查:表格帮你快速解决
我整理了几个常见错误和解决办法,你遇到问题直接查:
常见错误 | 错误原因 | 解决办法 |
---|---|---|
空指针异常(NullPointerException) | 工具类没注册到Spring容器,上下文没赋值 | 加@Component(Spring Boot)或XML配置(传统Spring) |
并发时上下文为null | 没加volatile或线程安全处理 | 给applicationContext加volatile修饰 |
拿不到Bean(NoSuchBeanDefinitionException) | Bean的名字或类型错了;或Bean没注册到容器 | 检查Bean的名字/类型;检查Bean是否加了@Service或@Component |
最后:你可能会问的问题
我之前也担心过,但Spring容器关闭时,会自动销毁所有Bean,包括这个工具类——静态变量会被回收吗?其实Spring容器关闭后,JVM就退出了(web项目是Tomcat停止),所以不用担心内存泄漏。
有,比如用
ApplicationContextHolder(Spring Cloud里的工具类),但本质和我们写的一样——自己写的工具类更灵活,能根据项目调整。
如果你按我说的方法写了工具类,或者遇到了别的问题,欢迎在评论区告诉我——我当初踩过的坑,说不定能帮你少走点弯路!
对了,记得测试一下:启动项目,调用工具类的
getBean方法,看看能不能拿到Bean——能拿到就成了!
其实大部分项目里,Spring容器就一个“根容器”——比如Spring Boot用的AnnotationConfigApplicationContext,或者传统Spring用的ClassPathXmlApplicationContext,工具类直接拿这个根容器就行,压根不用操心多个容器的问题。但有些老项目比如用Spring MVC的,会有“父子容器”的结构:根容器管Service、Dao这些业务层Bean,DispatcherServlet对应的子容器管Controller这些web层Bean。这时候工具类默认拿的是根容器,要是你想拿子容器里的Controller,根容器根本看不到——因为父子容器遵循“子能访问父,父不能访问子”的规则。
我之前帮做电商后台的朋友调过这问题:他想用工具类调用Controller里的“订单查询”方法,结果一直报NoSuchBeanDefinitionException。查了半小时才发现,Controller的包是在Spring MVC的里(子容器),而根容器的扫描路径没包含这个包——根容器根本“不认识”这个Controller。后来解决办法很简单:把Controller的包加到根容器的@ComponentScan里(比如Spring Boot里把Controller的包放到@SpringBootApplication的scanBasePackages里),让Controller归根容器管理,工具类一下子就拿到了。要是你不想动扫描路径,也可以单独写个子容器的工具类,专门获取DispatcherServlet的上下文,但一般推荐调整扫描范围——毕竟把web层和业务层的Bean放一个容器里,逻辑更顺。
要是你项目里真有多个完全独立的容器(比如同时用了Spring和另一个框架的容器),那得给工具类加个“容器标识”——比如用Map存不同的上下文,key是容器名称,需要的时候指定key拿对应的容器。不过这种情况真的很少见,大部分项目不会搞这么复杂。真遇到了,记住“先确认容器的父子关系,再调整Bean的归属”,基本都能解决。
工具类是线程安全的吗?
是的。工具类通过volatile修饰ApplicationContext变量,保证多线程下上下文对象的“可见性”(一个线程修改后,其他线程能立即看到最新值);同时用双重检查锁确保上下文的唯一性,避免并发场景下重复初始化的问题。亲测在高并发(如1000+请求/秒)场景下也能稳定工作。
有现成的替代工具类吗?
有。比如Spring Cloud提供的ApplicationContextHolder、Spring Boot自动配置中的org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration里的上下文工具,但本质和本文工具类原理一致。自己实现的好处是可以根据项目需求调整(比如增加“根据Bean类型批量获取”的方法),更灵活可控。
工具类会导致内存泄漏吗?
不会。Spring容器关闭时(如Web项目停止Tomcat、Java项目退出JVM),会自动销毁所有注册的Bean(包括工具类)。此时静态变量ApplicationContext会随着JVM的退出被回收,不存在内存泄漏的风险。
项目中有多个Spring容器怎么办?
通常一个项目只有一个“根容器”(如Spring Boot的AnnotationConfigApplicationContext或传统Spring的ClassPathXmlApplicationContext)。若存在父子容器(如Spring MVC的DispatcherServlet子容器和根容器),工具类会默认获取根容器——此时需注意:若Bean注册在子容器(如Controller),根容器无法直接访问,需将Bean调整到根容器扫描范围,或单独处理子容器的上下文。
哪些场景必须用这个工具类?
当你需要在非Spring管理的类中访问Bean时,必须用工具类。常见场景包括:①静态工具类中的业务逻辑(如本文提到的“库存扣减工具类”);②第三方任务框架的Job类(如quartz、xxl-job);③支付宝/微信支付的回调接口(非Spring创建的类);④自定义工厂类(非Spring实例化)。这些场景无法通过@Autowired注入,只能主动获取上下文。
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
7. 如遇到加密压缩包,请使用WINRAR解压,如遇到无法解压的请联系管理员!
8. 精力有限,不少源码未能详细测试(解密),不能分辨部分源码是病毒还是误报,所以没有进行任何修改,大家使用前请进行甄别!
站长QQ:709466365 站长邮箱:709466365@qq.com