Redian新闻
>
面试官:Spring 为什么不支持 static 字段的注入?

面试官:Spring 为什么不支持 static 字段的注入?

公众号新闻

👉 这是一个或许对你有用的社群

🐱 一对一交流/面试小册/简历优化/求职解惑,欢迎加入芋道快速开发平台知识星球。下面是星球提供的部分资料: 

👉这是一个或许对你有用的开源项目

国产 Star 破 10w+ 的开源项目,前端包括管理后台 + 微信小程序,后端支持单体和微服务架构。

功能涵盖 RBAC 权限、SaaS 多租户、数据权限、商城、支付、工作流、大屏报表、微信公众号、CRM 等等功能:

  • Boot 仓库:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • Cloud 仓库:https://gitee.com/zhijiantianya/yudao-cloud
  • 视频教程:https://doc.iocoder.cn
【国内首批】支持 JDK 21 + SpringBoot 3.2.2、JDK 8 + Spring Boot 2.7.18 双版本 

来源:juejin.cn/post/
7283803914645569536


我们都知道Spring在创建一个bean的时候,还要去填充bean的属性

大致流程如下:

  • 反射创建bean——createBeanInstance
  • 填充bean——populateBean
  • 初始化bean——initializeBean(包括前后置增强)
  • 注册bean的销毁方法——registerDisposableBeanIfNecessary

这个填充bean的逻辑是在populateBean

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
   // ...

   PropertyDescriptor[] filteredPds = null;
   if (hasInstAwareBpps) {
      if (pvs == null) {
         pvs = mbd.getPropertyValues();
      }
      for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
         // here
         PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
         if (pvsToUse == null) {
            if (filteredPds == null) {
               filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
            }
            pvsToUse = bp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
            if (pvsToUse == null) {
               return;
            }
         }
         pvs = pvsToUse;
      }
   }
   // ...

   if (pvs != null) {
      applyPropertyValues(beanName, mbd, bw, pvs);
   }
}

而除了applyPropertyValues可以填充bean的属性外

更多的填充逻辑(字段注入)应该是在InstantiationAwareBeanPostProcessor中的postProcessProperties里面,字段注入就是常用的@Autowired@Resource注解

InstantiationAwareBeanPostProcessor是一个接口

它的子类中实现Autowired注入的是AutowiredAnnotationBeanPostProcessor,实现Resource注入的是CommonAnnotationBeanPostProcessor

接下来分析一下AutowiredAnnotationBeanPostProcessor是怎么进行字段注入的

// AutowiredAnnotationBeanPostProcessor.postProcessProperties
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
   // 找到需要Autowired的元数据(字段、方法) 
   InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
   try {
      // 注入
      metadata.inject(bean, beanName, pvs);
   }
   catch (BeanCreationException ex) {
      throw ex;
   }
   catch (Throwable ex) {
      throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
   }
   return pvs;
}

以上这段代码是AutowiredAnnotationBeanPostProcessor实现的postProcessProperties

流程就是先找到需要通过findAutowiringMetadata找到需要Autowired的元数据(字段、方法) ,然后再inject

先看看findAutowiringMetadata

// AutowiredAnnotationBeanPostProcessor.findAutowiringMetadata
private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
   // Fall back to class name as cache key, for backwards compatibility with custom callers.
   String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
   // Quick check on the concurrent map first, with minimal locking.
   InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
   if (InjectionMetadata.needsRefresh(metadata, clazz)) {
      synchronized (this.injectionMetadataCache) {
         metadata = this.injectionMetadataCache.get(cacheKey);
         if (InjectionMetadata.needsRefresh(metadata, clazz)) {
            if (metadata != null) {
               metadata.clear(pvs);
            }
            metadata = buildAutowiringMetadata(clazz);
            this.injectionMetadataCache.put(cacheKey, metadata);
         }
      }
   }
   return metadata;
}

这个needsRefresh简单看看就好,我们是第一次进入这个方法,所以这个metadata是null,那么这个方法返回的是true

public static boolean needsRefresh(@Nullable InjectionMetadata metadata, Class<?> clazz) {
   return (metadata == null || metadata.needsRefresh(clazz));
}

那么会进入到这个方法buildAutowiringMetadata

private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
   if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) {
      return InjectionMetadata.EMPTY;
   }

   List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
   Class<?> targetClass = clazz;

   do {
      final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
      // 处理字段
      ReflectionUtils.doWithLocalFields(targetClass, field -> {
         MergedAnnotation<?> ann = findAutowiredAnnotation(field);
         if (ann != null) {
            if (Modifier.isStatic(field.getModifiers())) {
               if (logger.isInfoEnabled()) {
                  logger.info("Autowired annotation is not supported on static fields: " + field);
               }
               return;
            }
            boolean required = determineRequiredStatus(ann);
            currElements.add(new AutowiredFieldElement(field, required));
         }
      });
      // 处理方法
      ReflectionUtils.doWithLocalMethods(targetClass, method -> {
         Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
         if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
            return;
         }
         MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);
         if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
            if (Modifier.isStatic(method.getModifiers())) {
               if (logger.isInfoEnabled()) {
                  logger.info("Autowired annotation is not supported on static methods: " + method);
               }
               return;
            }
            if (method.getParameterCount() == 0) {
               if (logger.isInfoEnabled()) {
                  logger.info("Autowired annotation should only be used on methods with parameters: " +
                        method);
               }
            }
            boolean required = determineRequiredStatus(ann);
            PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
            currElements.add(new AutowiredMethodElement(method, required, pd));
         }
      });

      elements.addAll(0, currElements);
      // 获取父类,继续找
      targetClass = targetClass.getSuperclass();
   }
   while (targetClass != null && targetClass != Object.class);

   return InjectionMetadata.forElements(elements, clazz);
}

这边传入的clazz就是bean的Class,忘记了可以找上面的代码看一下

这里源码写了很多,我们暂时只关心注入字段的那一块

ReflectionUtils.doWithLocalFields(targetClass, field -> {
   MergedAnnotation<?> ann = findAutowiredAnnotation(field);
   if (ann != null) {
      if (Modifier.isStatic(field.getModifiers())) {
         if (logger.isInfoEnabled()) {
            logger.info("Autowired annotation is not supported on static fields: " + field);
         }
         return;
      }
      boolean required = determineRequiredStatus(ann);
      currElements.add(new AutowiredFieldElement(field, required));
   }
});

处理字段的时候进入了ReflectionUtilsdoWithLocalFields方法

// ReflectionUtils.doWithLocalFields
public static void doWithLocalFields(Class<?> clazz, FieldCallback fc) {
   for (Field field : getDeclaredFields(clazz)) {
      try {
         fc.doWith(field);
      }
      catch (IllegalAccessException ex) {
         throw new IllegalStateException("Not allowed to access field '" + field.getName() + "': " + ex);
      }
   }
}

继续追溯一下可以得知,这里是获取clazz的所有字段并进行处理,这个FieldCallback是一个函数式接口,它的实现就是外面传进来的这段代码

field -> {
   MergedAnnotation<?> ann = findAutowiredAnnotation(field);
   if (ann != null) {
      if (Modifier.isStatic(field.getModifiers())) {
         if (logger.isInfoEnabled()) {
            logger.info("Autowired annotation is not supported on static fields: " + field);
         }
         return;
      }
      boolean required = determineRequiredStatus(ann);
      currElements.add(new AutowiredFieldElement(field, required));
   }
}

那么在这段代码里面,又去找这个字段有没有被@Autowired修饰

// AutowiredAnnotationBeanPostProcessor.findAutowiredAnnotation
@Nullable
private MergedAnnotation<?> findAutowiredAnnotation(AccessibleObject ao) {
   MergedAnnotations annotations = MergedAnnotations.from(ao);
   // autowiredAnnotationTypes包含 @Autowired,@Value,@Inject
   for (Class<? extends Annotation> type : this.autowiredAnnotationTypes) {
      MergedAnnotation<?> annotation = annotations.get(type);
      if (annotation.isPresent()) {
         return annotation;
      }
   }
   return null;
}

如果有@Autowired修饰,那么lambda中的ann不为null

最关键的地方来了,接下来会判断这个字段是不是static的,如果是,那么会发出警告,并且直接返回,不进行注入了

field -> {
   MergedAnnotation<?> ann = findAutowiredAnnotation(field);
   if (ann != null) {
      // 判断是否被static修饰
      if (Modifier.isStatic(field.getModifiers())) {
         if (logger.isInfoEnabled()) {
            logger.info("Autowired annotation is not supported on static fields: " + field);
         }
         return;
      }
      boolean required = determineRequiredStatus(ann);
      currElements.add(new AutowiredFieldElement(field, required));
   }
}

最终注入的逻辑在InjectionMetadata的inject中

// InjectionMetadata.inject
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
   Collection<InjectedElement> checkedElements = this.checkedElements;
   Collection<InjectedElement> elementsToIterate =
         (checkedElements != null ? checkedElements : this.injectedElements);
   if (!elementsToIterate.isEmpty()) {
      for (InjectedElement element : elementsToIterate) {
         element.inject(target, beanName, pvs);
      }
   }
}

element的逻辑就暂时省略了,大概就是如果是字段,那么通过反射去注入,如果是方法,也通过反射去执行

@Resource与以上大致同理

总结

总的来说,就一句话,spring在使用字段注入对静态字段进行注入时,会忽略掉这个字段,不去注入

也就是说Spring是有能力去注入静态字段的,但是Spring没有选择注入,为什么呢?

可能是因为Spring的设计理念是管理bean对象,只有属于对象的字段Spring才去进行管理,如果是static的话,那么这个字段属于类了,这个时候Spring去进行管理貌似不符合它的设计理念,所以Spring直接忽略掉了;另外如果一个bean修改了这个字段,那么所有bean的这个字段都会受到影响,因为这个字段是属于类的,这个时候可能就会问题

那么有没有办法实现静态字段注入呢?

可以的,在方法中打上@Autowired注解,在方法里面去对静态字段进行赋值,当然这个方法也不能是静态的,否则也会被spring会忽略掉

不过如果能够不对静态字段注入就尽量不要注入,因为spring本身就不鼓励我们这么做,这种不鼓励已经深入到代码里面了


欢迎加入我的知识星球,全面提升技术能力。

👉 加入方式,长按”或“扫描”下方二维码噢

星球的内容包括:项目实战、面试招聘、源码解析、学习路线。

文章有帮助的话,在看,转发吧。

谢谢支持哟 (*^__^*)

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
俩前国安顾问提交俄乌和平计划 特朗普无意反普京也不支持民主美股基本面 - 2024_03_23 * 晨报 * 51Talk去年净亏损缩窄至1400万美元,预计今年一季度净收入增逾四成。大炮专家布尔spring是“春天”,chicken是“鸡”,那spring chicken什么意思?我心目中最美的爱彼皇橡,全新“霜金”为什么是此生必入?OpenAI封杀不支持地区API:违规封号,7月9日生效又有大厂不支持sponsor了!专家称高铁涨价是必要的,花钱可解锁特斯拉续航,索尼绑定PSN惹众怒,谷歌新验证系统不支持火狐,这就是今天的其他大新闻!不是,你还在随便设计数据库字段类型和长度?OpenAI将停止中国等不支持的国家和地区的API使用Melodies of Spring in Jiangnan哈佛留学生被“野鸡大学”学生抢走offer! 硅谷面试官揭秘求职真相......一个麻省招生面试官的亲述:3个录取MIT本科的故事,告诉你什么才是打动名校的重要原因!回国杂记(2307)高中毕业六十年校友相聚在武昌南湖马斯克:SpaceX基本不用AI,没看到有什么用作为面试官,我在面试产品经理时,重点考察的5个能力马斯克不支持拜登给中国电动车加关税对亿万富豪开征全球富人税?叶伦不支持哈佛金牌面试官:锁定这10步黄金实操法则,孩子随时名校ready!突发!大量开发者收到OpenAI警告,7月9日起封杀不支持地区API熵泱——第五十一章面试攻略 | 面试现场反问面试官,这14个问题模板最受欢迎!谷歌裁员继续!面试当天,面试官没了……Last Stop: Looking Past the Stigma Facing China’s Morticians知道strong姐为什么不讨喜了吧?Rust 修复了 std 库中 Windows 10/11 的关键命令注入缺陷面试官:String 能存储多少个字符?Spring 全家桶版本更新:Spring Boot、Spring Security 和 Spring Modulith为什么建议你替换掉 SpringBoot 框架中的 Tomcat ?仅剩3席|MBB导师教研组倾力研发,全面覆盖面试常见题型,并采取课堂模拟面试,三周搞定案例面试!一张长图透彻理解 SpringBoot 启动原理,架构师必备知识,不为应付面试!新雨湖南大学聂舟团队Angew |工程化Anti-CRISPR蛋白创建CRISPR-Cas蛋白开关用于激活型基因编辑和病毒蛋白酶检测面试官:“领导给你100块买咖啡,发现是假钞怎么办?”这样回答,当场录取!Spring Boot 3.2 和 Spring Framework 6.1添加对 Java 21、虚拟线程和 CRaC 的支持心眼子训练 | 面试官:如果收到了更好的OFFER,你还会选择我们吗?
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。