当前位置: 欣欣网 > 码农

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

2024-05-26码农

来源:juejin.cn/post/7283803914645569536

👉 欢迎 ,你将获得: 专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡 / 每月赠书

新项目: 仿小红书 (微服务架构)正在更新中... , 全栈前后端分离博客项目 2.0 版本完结啦, 演示链接 http://116.62.199.48/ 。全程手摸手,后端 + 前端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,直到项目上线。 目前已更新了261小节,累计43w+字,讲解图:1806张,还在持续爆肝中.. 后续还会上新更多项目,目标是将Java领域典型的项目都整一波,如秒杀系统, 在线商城, IM即时通讯,Spring Cloud Alibaba 等等,

我们都知道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.get class(), 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.isCandidate class(clazz, this.autowiredAnnotationTypes)) {
    return InjectionMetadata.EMPTY;
    }
    List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
    class<?> target class = clazz;
    do {
    final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
    // 处理字段
    ReflectionUtils.doWithLocalFields(target class, 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(target class, 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);
    // 获取父类,继续找
    target class = target class.getSuper class();
    }
    while (target class != null && target class != Object. class);
    return InjectionMetadata.forElements(elements, clazz);
    }


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

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

    ReflectionUtils.doWithLocalFields(target class, 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 doWithLocalFields 方法

    // 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本身就不鼓励我们这么做,这种不鼓励已经深入到代码里面了

    👉 欢迎 ,你将获得: 专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡 / 每月赠书

    新项目: 仿小红书 (微服务架构)正在更新中... , 全栈前后端分离博客项目 2.0 版本完结啦, 演示链接 http://116.62.199.48/ 。全程手摸手,后端 + 前端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,直到项目上线。 目前已更新了261小节,累计43w+字,讲解图:1806张,还在持续爆肝中.. 后续还会上新更多项目,目标是将Java领域典型的项目都整一波,如秒杀系统, 在线商城, IM即时通讯,Spring Cloud Alibaba 等等,


    1. 

    2. 

    3. 

    4. 

    最近面试BAT,整理一份面试资料Java面试BATJ通关手册,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。

    获取方式:点「在看」,关注公众号并回复 Java 领取,更多内容陆续奉上。

    PS:因公众号平台更改了推送规则,如果不想错过内容,记得读完点一下在看,加个星标,这样每次新文章推送才会第一时间出现在你的订阅列表里。

    「在看」支持小哈呀,谢谢啦