文章目录
- Spring 静态注入实际案例 Demo
- 为什么这样写有时候 RemoteEBRpcInvoker.getEbFormIdUtil 是一个 NULL???
- 原因1: 静态变量初始化顺序问题
- 原因2: Spring 生命周期与静态字段
- 解决方案:
- 方法1:移除静态字段(违背了我的初衷)
- 方法2:使用 @PostConstruct
- 方法3: 使用 @Autowired 的静态方法
- 方法4: 使用 ApplicationContext 获取 Bean
- 方法5: 使用单例模式和手动注入
- 方法6: 使用 @Lazy 懒加载
- 总结
- 推荐阅读文章
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;@Autowiredpublic 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;@Autowiredpublic 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;@Autowiredpublic RemoteEBRpcInvoker(GetEbFormIdUtil getEbFormIdUtil,PublishKitRuntimeUtil publishKitRuntimeUtil) {this.getEbFormIdUtil = getEbFormIdUtil;this.publishKitRuntimeUtil = publishKitRuntimeUtil;}@PostConstructpublic 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;@Autowiredpublic static void setPublishKitRuntimeUtil(PublishKitRuntimeUtil publishKitRuntimeUtil) {RemoteEBRpcInvoker.publishKitRuntimeUtil = publishKitRuntimeUtil;}@Autowiredpublic 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;@Overridepublic 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;@Autowiredpublic 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@Lazypublic 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 组件