文章目录
Spring 静态注入实际案例 Demo
有时候我们想将某个实例 Bean
封装成一个工具类,方便复用,也可以直接放到接口中定义为 default
方法。初衷就是这个。结果封装的工具类引发了 NPE
异常。造成线上重大事故😭😭😭
>>>>>>话不多说,直接看代码案例😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭😭:
为什么这样写有时候 RemoteEBRpcInvoker.getEbFormIdUtil 是一个 NULL???
@Component
public class RemoteEBRpcInvoker {
private static final String DEFAULT_RPC_GROUP = "ebuilderform";
private static PublishKitRuntimeUtil publishKitRuntimeUtil;
private static GetEbFormIdUtil getEbFormIdUtil;
@Autowired
public RemoteEBRpcInvoker(GetEbFormIdUtil getEbFormIdUtil,
PublishKitRuntimeUtil publishKitRuntimeUtil) {
RemoteEBRpcInvoker.getEbFormIdUtil = getEbFormIdUtil;
RemoteEBRpcInvoker.publishKitRuntimeUtil = publishKitRuntimeUtil;
}
/**
* 通过指定 appId 获取 RPC 服务实例
*
* @param tClass 要构建的 RPC 服务类
* @param appId 应用 ID
* @param <T> 服务类的类型
* @return 构建后的 RPC 服务实例
*/
public static <T> T newInstance(Class<T> tClass, Long appId) {
Long appId = getEbFormIdUtil.getAppId(TagConstant.CS_APPID_TAG, tenantKey);
return publishKitRuntimeUtil.buildRpcService(tClass, DEFAULT_RPC_GROUP, String.valueOf(appId));
}
}
就上面这段代码,在我们去调用 newInstance() 方法的时候,方法里面的 getEbFormIdUtil 竟然是个 NULL 对象。明显是我们这种写法 Spring 在静态注入的时候,直接没有引用成功。
所以在这种段代码中,“看似没问题”,实则 RemoteEBRpcInvoker.getEbFormIdUtil
可能为 null
,原因存在两种情况:
原因1: 静态变量初始化顺序问题
我正在使用静态变量 RemoteEBRpcInvoker.getEbFormIdUtil
和 RemoteEBRpcInvoker.publishKitRuntimeUtil
,而静态变量的生命周期和实例变量不同。@Autowired
注入的依赖通常在对象实例化时被注入,但静态变量属于类级别,不依赖于对象实例化。这可能导致在某些情况下(例如,依赖注入还未完成时)静态变量没有被正确赋值,导致它们为 null
。
原因2: Spring 生命周期与静态字段
Spring 依赖注入是基于对象实例的,静态变量不依赖于对象实例。@Autowired
只能保证在构造函数中注入实例变量,而不能确保静态变量的安全初始化。因为静态变量与类的生命周期绑定,而不是与实例的生命周期绑定,Spring 容器可能在静态字段初始化之前或之后管理类的生命周期,从而导致未能正确注入。
接下来是如何解决这个潜在问题方法介绍:
解决方案:
方法1:移除静态字段(违背了我的初衷)
避免将需要通过依赖注入获得的对象定义为静态字段,因为它们不依赖于实例。
你可以修改代码如下:
@Component
public class RemoteEBRpcInvoker {
private static final String DEFAULT_RPC_GROUP = "ebuilderform";
private PublishKitRuntimeUtil publishKitRuntimeUtil;
private GetEbFormIdUtil getEbFormIdUtil;
@Autowired
public RemoteEBRpcInvoker(GetEbFormIdUtil getEbFormIdUtil,
PublishKitRuntimeUtil publishKitRuntimeUtil) {
this.getEbFormIdUtil = getEbFormIdUtil;
this.publishKitRuntimeUtil = publishKitRuntimeUtil;
}
}
这个方法虽然行,但是违背了我的初衷,我需要封装一个工具类,那么就需要将方法弄成 static
状态方法。
方法2:使用 @PostConstruct
如果需要使用静态字段,你可以利用 @PostConstruct
注解,在依赖注入完成后手动初始化静态变量。
@Component
public class RemoteEBRpcInvoker {
private static final String DEFAULT_RPC_GROUP = "ebuilderform";
private static PublishKitRuntimeUtil publishKitRuntimeUtil;
private static GetEbFormIdUtil getEbFormIdUtil;
@Autowired
public RemoteEBRpcInvoker(GetEbFormIdUtil getEbFormIdUtil,
PublishKitRuntimeUtil publishKitRuntimeUtil) {
this.getEbFormIdUtil = getEbFormIdUtil;
this.publishKitRuntimeUtil = publishKitRuntimeUtil;
}
@PostConstruct
public void init() {
RemoteEBRpcInvoker.getEbFormIdUtil = this.getEbFormIdUtil;
RemoteEBRpcInvoker.publishKitRuntimeUtil = this.publishKitRuntimeUtil;
}
}
这种方法可以确保在依赖注入完成后,静态变量被正确赋值。
除了我之前提到的解决方案之外,还有一些其他方法可以避免静态字段注入问题,并确保你能够在 Spring 环境中正确地使用静态字段。以下是其他几种可能的方法:
方法3: 使用 @Autowired 的静态方法
你可以通过编写一个静态的 setter
方法来为静态变量注入依赖。Spring 允许通过 @Autowired
注解静态方法来完成依赖注入。这种方式不会依赖于实例,因此更加符合静态变量的使用场景。
@Component
public class RemoteEBRpcInvoker {
private static final String DEFAULT_RPC_GROUP = "ebuilderform";
private static PublishKitRuntimeUtil publishKitRuntimeUtil;
private static GetEbFormIdUtil getEbFormIdUtil;
@Autowired
public static void setPublishKitRuntimeUtil(PublishKitRuntimeUtil publishKitRuntimeUtil) {
RemoteEBRpcInvoker.publishKitRuntimeUtil = publishKitRuntimeUtil;
}
@Autowired
public static void setGetEbFormIdUtil(GetEbFormIdUtil getEbFormIdUtil) {
RemoteEBRpcInvoker.getEbFormIdUtil = getEbFormIdUtil;
}
}
这种方法通过静态的 setter
方法来完成静态变量的注入,确保它们在类加载时能被正确初始化。
方法4: 使用 ApplicationContext 获取 Bean
另一种方法是使用 Spring 的 ApplicationContext
来手动获取所需的 Bean。这种方法可以在静态上下文中安全地使用依赖注入。
你可以通过以下方式将 ApplicationContext
注入,并在需要时手动获取依赖。
@Component
public class RemoteEBRpcInvoker implements ApplicationContextAware {
private static final String DEFAULT_RPC_GROUP = "ebuilderform";
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
RemoteEBRpcInvoker.applicationContext = applicationContext;
}
public static GetEbFormIdUtil getGetEbFormIdUtil() {
return applicationContext.getBean(GetEbFormIdUtil.class);
}
public static PublishKitRuntimeUtil getPublishKitRuntimeUtil() {
return applicationContext.getBean(PublishKitRuntimeUtil.class);
}
}
在这种方法中,ApplicationContext
被注入到静态字段 applicationContext
中,并通过静态方法从上下文中获取所需的 Bean。这避免了直接使用 @Autowired
注解静态字段的问题。
方法5: 使用单例模式和手动注入
如果你确实需要使用静态方法或静态字段,也可以考虑使用单例模式来管理这些依赖。在这种情况下,你可以手动注入依赖,并在静态上下文中访问它们。
@Component
public class RemoteEBRpcInvoker {
private static final String DEFAULT_RPC_GROUP = "ebuilderform";
private static RemoteEBRpcInvoker instance;
private PublishKitRuntimeUtil publishKitRuntimeUtil;
private GetEbFormIdUtil getEbFormIdUtil;
@Autowired
public RemoteEBRpcInvoker(GetEbFormIdUtil getEbFormIdUtil,
PublishKitRuntimeUtil publishKitRuntimeUtil) {
this.getEbFormIdUtil = getEbFormIdUtil;
this.publishKitRuntimeUtil = publishKitRuntimeUtil;
instance = this;
}
public static RemoteEBRpcInvoker getInstance() {
return instance;
}
public GetEbFormIdUtil getGetEbFormIdUtil() {
return this.getEbFormIdUtil;
}
public PublishKitRuntimeUtil getPublishKitRuntimeUtil() {
return this.publishKitRuntimeUtil;
}
}
这种方式通过一个静态的 instance
来存储当前对象实例,从而可以在静态上下文中访问非静态字段和方法。虽然这并不是典型的 Spring 注入方式,但它可以让你在需要使用静态字段时仍然能访问 Spring 管理的依赖。
方法6: 使用 @Lazy 懒加载
如果某些依赖可能在初始化时无法立即注入,或者你希望推迟静态字段的初始化,可以使用 @Lazy
注解,使依赖注入变为懒加载模式,这样可以确保当依赖确实需要时才会初始化。
@Component
public class RemoteEBRpcInvoker {
private static final String DEFAULT_RPC_GROUP = "ebuilderform";
private static PublishKitRuntimeUtil publishKitRuntimeUtil;
private static GetEbFormIdUtil getEbFormIdUtil;
@Autowired
@Lazy
public RemoteEBRpcInvoker(GetEbFormIdUtil getEbFormIdUtil,
PublishKitRuntimeUtil publishKitRuntimeUtil) {
RemoteEBRpcInvoker.getEbFormIdUtil = getEbFormIdUtil;
RemoteEBRpcInvoker.publishKitRuntimeUtil = publishKitRuntimeUtil;
}
}
懒加载会推迟对依赖的初始化,直到第一次实际使用时才进行初始化。这种方式可以帮助解决静态字段在不适当的时间被初始化的问题。
总结
如果遇到以上问题,可以通过以下几种方式解决静态字段注入问题:
- 避免静态字段,使用实例字段(推荐)
- 使用 @PostConstruct 注解初始化
- 使用 静态 setter 方法 来注入静态依赖
- 使用 ApplicationContext 手动获取依赖
- 通过 单例模式 访问非静态依赖
- 使用
@Lazy
注解 懒加载依赖
以上每种方法都有其适用场景,具体选择可以根据项目的需求和设计模式来决定。ε=(´ο`*)))唉,希望此文可以帮助大家踩坑时避坑吧。。。
推荐阅读文章
- 使用 Spring 框架构建 MVC 应用程序:初学者教程
- 有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误
- 如何理解应用 Java 多线程与并发编程?
- Java Spring 中常用的 @PostConstruct 注解使用总结
- 线程 vs 虚拟线程:深入理解及区别
- 深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别
- 10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!
- “打破重复代码的魔咒:使用 Function 接口在 Java 8 中实现优雅重构!”
- Java 中消除 If-else 技巧总结
- 线程池的核心参数配置(仅供参考)
- 【人工智能】聊聊Transformer,深度学习的一股清流(13)
- Java 枚举的几个常用技巧,你可以试着用用
- 如何理解线程安全这个概念?
- 理解 Java 桥接方法
- Spring 整合嵌入式 Tomcat 容器
- Tomcat 如何加载 SpringMVC 组件