想用@Autowired注入static静态成员?官方不推荐你却还偏要这么做(中)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 想用@Autowired注入static静态成员?官方不推荐你却还偏要这么做(中)

帮你猜猜你为何有如此需求?


从上面示例类的命名中,我或许能猜出你的用意。UserHelper它被命名为一个工具类,而一般我们对工具类的理解是:


  1. 方法均为static工具方法
  2. 使用越便捷越好
  3. 很明显,static方法使用是最便捷的嘛


现状是:使用UserHelper去处理用户信息还得先@Autowired注入它的实例,实属不便。因此你想方设法的想把getAndFilterTest()这个方法变为静态方法,这样通过类名便可直接调用而并不再依赖于注入UserHelper实例了,so你想当然的这么“优化”:


@Component
public class UserHelper {
    @Autowired
    static UCClient ucClient;
  public static List<User> getAndFilterTest(List<Long> userIds) {
    ... // 处理逻辑完全同上
  }
}

属性和方法都添加上static修饰,这样使用方通过类名便可直接访问(无需注入):


@Service
public class RoomService {
  public void create(List<Long> studentIds, Long teacherId) {
    ...
    // 通过类名直接调用其静态方法
    List<User> users = UserHelper.getAndFilterTest(userIds);
    ...
  }
}


运行程序,结果输出:


07:22:49.359 [main] INFO org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor - Autowired annotation is not supported on static fields: static cn.yourbatman.temp.component.UCClient cn.yourbatman.temp.component.UserHelper.ucClient
07:22:49.359 [main] INFO org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor - Autowired annotation is not supported on static fields: static cn.yourbatman.temp.component.UCClient cn.yourbatman.temp.component.UserHelper.ucClient
...
Exception in thread "main" java.lang.NullPointerException
  at cn.yourbatman.temp.component.UserHelper.getAndFilterTest(UserHelper.java:23)
  at cn.yourbatman.temp.component.RoomService.create(RoomService.java:26)
  at cn.yourbatman.temp.DemoTest.main(DemoTest.java:19)


以为天衣无缝,可结果并不完美,抛异常了。我特意多粘贴了两句info日志,它们告诉了你为何抛出NPE异常的原因:@Autowired不支持标注在static字段/属性上


为什么@Autowired不能注入static成员属性


静态变量是属于类本身的信息,当类加载器加载静态变量时,Spring的上下文环境还没有被加载,所以不可能为静态变量绑定值(这只是最表象原因,并不准确)。同时,Spring也不鼓励为静态变量注入值(言外之意:并不是不能注入),因为它认为这会增加了耦合度,对测试不友好。


这些都是表象,那么实际上Spring是如何“操作”的呢?我们沿着AutowiredAnnotationBeanPostProcessor输出的这句info日志,倒着找原因,这句日志的输出在这:


AutowiredAnnotationBeanPostProcessor:
// 构建@Autowired注入元数据方法
// 简单的说就是找到该Class类下有哪些是需要做依赖注入的
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
  ...
  // 循环递归,因为父类的也要管上
  do {
    // 遍历所有的字段(包括静态字段)
    ReflectionUtils.doWithLocalFields(targetClass, field -> {
      if (Modifier.isStatic(field.getModifiers())) {
        logger.info("Autowired annotation is not supported on static fields: " + field);
      }
      return;
      ...
    });
    // 遍历所有的方法(包括静态方法)
    ReflectionUtils.doWithLocalMethods(targetClass, method -> {
      if (Modifier.isStatic(method.getModifiers())) {
        logger.info("Autowired annotation is not supported on static methods: " + method);
      }
      return;
      ...
    });
    ...
    targetClass = targetClass.getSuperclass();
  } while (targetClass != null && targetClass != Object.class);
  ...
}


这几句代码道出了Spring为何不给static静态字段/静态方法执行@Autowired注入的最真实原因:扫描Class类需要注入的元数据的时候,直接选择忽略掉了static成员(包括属性和方法)。


那么这个处理的入口在哪儿呢?是否在这个阶段时Spring真的无法给static成员完成赋值而选择忽略掉它呢,我们继续最终此方法的调用处。此方法唯一调用处是findAutowiringMetadata()方法,而它被调用的地方有三个:


调用处一:执行时机较早,在MergedBeanDefinitionPostProcessor处理bd合并期间就会解析出需要注入的元数据,然后做check。它会作用于每个bd身上,所以上例中的2句info日志第一句就是从这输出的

AutowiredAnnotationBeanPostProcessor:
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
  InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
  metadata.checkConfigMembers(beanDefinition);
}

调用处二:在InstantiationAwareBeanPostProcessor也就是实例创建好后,给属性赋值阶段(也就是populateBean()阶段)执行。所以它也是会作用于每个bd的,上例中2句info日志的第二句就是从这输出的


AutowiredAnnotationBeanPostProcessor:
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
  InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
  try {
    metadata.inject(bean, beanName, pvs);
  }
  ...
  return pvs;
}



调用处三:这个方法比较特殊,它表示对于带有任意目标实例(已经不仅是Class,而是实例本身)直接调用的“本地”处理方法实行注入。这是Spring提供给“外部”使用/注入的一个public公共方法,比如给容器外的实例注入属性,还是比较实用的,本文下面会介绍它的使用办法


说明:此方法Spring自己并不会主动调用,所以不会自动输出日志(这也是为何调用处有3处,但日志只有2条的原因)


AutowiredAnnotationBeanPostProcessor:
public void processInjection(Object bean) throws BeanCreationException {
  Class<?> clazz = bean.getClass();
  InjectionMetadata metadata = findAutowiringMetadata(clazz.getName(), clazz, null);
  try {
    metadata.inject(bean, null, null);
  }
  ...
}


通过这部分源码,从底层诠释了Spring为何不让你@Autowired注入static成员的原因。既然这样,难道就没有办法满足我的“诉求”了吗?答案是有的,接着往下看。


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
设计模式 Java
Java反射(Class、反射实例化、反射与单例、获取类结构)附带相关面试题
1.了解反射,2.Class类的三种实例化方法,3.反射机制与对象实例化,4.反射与单例设计模式,5.通过反射获取类结构的信息
305 0
|
5月前
|
Java
java反射-获取类的Class对象方式
java反射-获取类的Class对象方式
|
6月前
|
存储 Java 编译器
实例化&&构造方法&&static统统都学会
实例化&&构造方法&&static统统都学会
43 0
|
Java 容器
Java实现Autowired自动注入
Test2正常 Test3空指针 因为不在容器里
126 0
|
druid Java 编译器
Java的第七篇文章——面向对象接口(包含了接口、static修饰符、final修饰符、main方法、内部类等知识点)
Java的第七篇文章——面向对象接口(包含了接口、static修饰符、final修饰符、main方法、内部类等知识点)
java一个文件只能有一个公有类的解决方法。 用公有静态内部类。 public static。 类似于C++的命令空间。
java一个文件只能有一个公有类的解决方法。 用公有静态内部类。 public static。 类似于C++的命令空间。
|
Java
SpringBoot static静态变量使用@Value注入方式
SpringBoot static静态变量使用@Value注入方式
321 0
|
Java Spring 容器
通过反射获得并调用类的方法导致@Autowired注入失效的解决方案
通过反射获得并调用类的方法导致@Autowired注入失效的解决方案
530 0
|
Java Spring 容器
SpringBoot (走读源码)静态方法中调用spring注入的对象,注入对象为null?
SpringBoot (走读源码)静态方法中调用spring注入的对象,注入对象为null?
438 0
SpringBoot (走读源码)静态方法中调用spring注入的对象,注入对象为null?
注解与反射6得到Class类的几种方式
注解与反射6得到Class类的几种方式