【Spring错误笔记】如何成功的在静态方法调用被Spring注入的属性?

如何成功的在静态方法调用被Spring注入的属性?


今天在Coding的时候,发现一个问题,在一个静态方法,无法成功的调用一个被属性注入的成员中。既会报空指针一次,Shame

以前的标题是

  • 静态方法注入失败的原因以及如何正确的在静态方法实现注入 后来复盘,发现这个标题有点绕,挺难以理解的,所以换了一个

问题


为什么要在静态方法调用被Spring注入的属性?
  • 让我举个粟子,比如要在我们的静态公共类或则静态工具类要引用到底层的Dao层接口或则是Serive层去实现去做一些逻辑判断,当然这个需求有些奇怪,我们假设需要这么去做。
  • 或者说我要在一个静态方法中调用一个被@Autowired修饰的成员变量

注入为什么不能在静态方法中实现呢?
  • 假设注入到容器中的是一个成员属性,而我们在静态方法中调用该成员属性,就会发现静态方法是不能调用非静态成员的,所以这里会报语法错误,需要声明成员为静态成员。
  • 所以我们将成员属性声明为静态,这样就可以让我们在静态方法中调用一个静态成员(该静态成员被@Autowired修饰)。虽然编译器过关了,那么在调用的时候,得到的是一个null,会报空指针错误

真正的问题是什么?

如何在一个静态方法中,成功调用一个被属性注入的静态成员? 这个问题,其实还是比较绕的,它本质要问的问题应该是:

  • 我们如何对一个静态变量实现Spring注入?

也正因为Spring并不支持属性注入一个静态变量,所以也就造成了我们在静态方法中调用一个被@Autowired修饰的静态成员变量失败的根本原因


为什么Spring不支持属性注入一个静态变量?


问题的原因

首先我们需要了解一点的是,Spring则是基于对象层面上的依赖注入,并不是类层级的注入方式。当然这是一个笼统的概念,很多地方也是这么说的,所以我也就了解一下就好。

解释主要来源于高票回答中的@ Andrea T 的回复:
why can’t we autowire static fields in spring(the correct answer is in reply)

主要有价值的就是这个回复:

The answer is that there isn’t yet Spring library loaded when static
Class are instantiated by class loader.

翻译过来的意思就是,当虚拟机加载我们的Java类时,Spring的类库还没有被类加载器所加载。简而言之就是 Spring比我们所定义的Java类还要晚被虚拟机所加载

这样,原因就非常清晰了,如果我们在静态变量上使用@Autowire以期待我们的静态变量能被Spring容器所加载。但是当我们的静态变量被虚拟机执行初始化时,@Autowire还没有任何意义,所以静态变量在初始化阶段依然指向的是null。

呃呃呃…我好像还是无法解释为什么放在属性上不行,而放在再Set方法或构造方法就可以的原因,一开始我在stackoverflow看到这个答案的时候,我以为恍然大悟了,但是仔细想想好像又有问题啊,Spring容器没有加载就没有加载,这个跟@Autowired注入有关系吗,对应的成员变量被加载时,Spring容器也没有加载呀。所以这个可能需要了解了原理才能做出解释,也有可能是@Autowired属性的实现只对对象层面生效,对类层面不生效(所以可以曲线救国,从对象层面获得实例,从而对类属性进行赋值)


2019.02.25更新
嗯,上面的想法可以是有错误的,stackoverflow的高票答案是有道理的,但是我觉得还是描述的不够准确,正确的描述应该是:

我们在使用@Autowired修改静态成员变量时,该类必然被@Component所修饰,所以该类的类加载必然是因为@Component所引起的(不信,你去掉肯定不会出错);
流程是:

  • Spring容器启动,Bean后置工厂扫描要注入到Spring容器的类(比如被@Component修饰的类)
  • 发现某个类需要被注册到Spring容器中,开始做准备工作,比如实例化操作,但实例化前类先要被虚拟机加载
  • 所以虚拟机现在知道了某个类要被使用了,得先去加载它。而静态域则是该类最先开始被加载的地方,然后它会发现这个类的某个静态成员需要被Spring容器去注入,所以通知Spring去注入。
  • 但现状是Spring可能都还没有收纳这个Bean,或者Spring容器本身都还没准备完毕,所以就造成了冲突

2019.07.12更新
改变观点的时候又到了,没过一段时间,对Spring的理解就有不一样的理解哈。非常感谢 @邬磊 博主的纠正。以下是@邬磊 博主的回复
在这里插入图片描述

通俗的讲,意思就是说,Spring不支持对静态变量进行属性注入的根本原因是

  • 技术上我是支持这么做的,但Spring不想让你这么做而已

测试实验已证明

  • 而且我也亲自的Debug了AutowiredAnnotationBeanPostProcessor类的buildAutowiringMetadata方法。将Modifier.isStatic()方法的返回值,从true修改为false,不触发if条件内部的拒绝策略。最终我是可以成功的通过属性注入的方式对一个静态成员进行注入的,并且成功获得,不再抛出空指针异常

那这是出于什么样的角度考虑呢?我个人的理解是

  • 属性注入区别于设置注入和构造注入,属性注入的原理是利用了Java的反射机制实现的
  • 静态成员是类的成员,属于类的范畴,不属于对象的范畴。而Spring的IOC容器本质应该是对对象实例进行管理。而静态成员属于类的概念。不应该由我去管理一个静态成员对象。

如何解决


属性注入和设置,构造注入的不同

在如下的网站中,它说明了@Autowired在属性上和在方法或构造器上的区别
Field-Based Dependency Injection

While constructing the Store object, if there’s no constructor or setter method to inject the Item bean, the container will use reflection to inject Item into Store.

也就是说,当Spring容器在构建对象时,发现没有构造方法或者Setter方法去注入Bean时,Spring容器就会使用反射的方式去注入。

这也就说明了@Autowired在属性上和构造器/Setter方法上的方式是有很大不同的。 我想这也是为什么@Autowired在属性上不能注入静态变量,而构造器/Setter方法的方式却可以的原因

从Bean的生命周期角度理解:
Bean注入是一般发生在Bean实例化后的填充属性阶段。


方法一

因为属性注入的方式被Spring在代码层面上就限制死了,所以我们可以采用剩余的两种注入方式,比如

  • 设置注入(@Autowired修饰在Setter方法上)
  • 构造注入(@Autowired修饰在构造方法上)

方法二

当然还可以通过对象实例被构建或构建后,在对象的层次通过方法来修改类属性的值,比如

@Component
public class JustAClass{
  private static Service service;

  @Autowired
  private Service tmpService;

  @PostConstruct
  public void init() {
    service = tmpService;
  }

}

参考网站


  • 10
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值