【小家Spring】注意BeanPostProcessor启动时对依赖Bean的“误伤”陷阱(is not eligible for getting processed by all...)(下)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 【小家Spring】注意BeanPostProcessor启动时对依赖Bean的“误伤”陷阱(is not eligible for getting processed by all...)(下)

采用@Autowired注入ApplicationContext和实现接口ApplicationContextAware的区别

在绝大多数情况下,这两种使用方式是等价的,都能够方便的获取到Spring容器上下文:ApplicationContext,但是在某些情况下,是有区别的,比如在如下情况下:


// 和上面唯一区别是 此处实现的是`PriorityOrdered`接口,上面就是普通的Ordered接口
@Slf4j
@Component
public class MyBeanPostProcessor implements BeanPostProcessor, PriorityOrdered{
  @Autowired
    private ApplicationContext applicationContext;
    ...
}


这样子注入,最终applicationContext的值为null。为何呢?其实这就和BeanPostProcessor的加载时机以及@Autowired的执行时机有关,下面通过两张截图可以清晰的看到顺序:

此图表示执行到此处,容器内已经存在的BeanPostProcessor实例(注意已经是实例):

image.png


此图表示BeanPostProcessor的定义信息们,他们等待被实例化:


image.png


下面对此几个BeanPostProcessor的Bean定义信息做分析如下:


  • AutowiredAnnotationBeanPostProcessor它实现了PriorityOrdered接口
  • CommonAnnotationBeanPostProcessor它也实现了PriorityOrdered接口
  • AsyncAnnotationBeanPostProcessor没有实现任何Orderd排序接口
  • MyBeanPostProcessor此处我们让它实现了PriorityOrdered接口===========


从上面可以看到,因为实现了PriorityOrdered接口的BeanPostProcessor属于于同一级别,都是先统一调用getBean()实例化后再被统一addBeanPostProcessor。


因此AutowiredAnnotationBeanPostProcessor这个后置处理器并不能作用在我们的MyBeanPostProcessor上面给我们的属性赋值(因为他俩是同一级别的),因为根本就木有生效嘛~~~~但是像我们第一个例子,MyBeanPostProcessor只是实现了普通的Ordered接口,优先级比PriorityOrdered低,所以就实现了正常的注入(因为高优先级的处理器先辈实例化了,所以可以作用于低优先级的bean了)~


由上可知,我们在注册BeanPostProcessor的时候,他们的优先级的层级原则是需要注意的:高优先级的Bean能够作用于低有衔接的,反之不成立。但是同优先级的Bean不能相互作用~


若是实现ApplicationContextAware接口的话,ApplicationContext不管咋样都可以被正常获取到。道理也是一样的,是因为这个接口是被ApplicationContextAwareProcessor来解析的,而它已经早早被放进了Spring容器里面,所以通过实现接口的方式任何时候都是阔仪的


那平时到底使用@Autowired注入还是实现接口ApplicationContextAware呢?

建议平时能够使用@Autowired注入时(我更建议使用构造器注入,这样完全不与Spring容器耦合),就尽量使用注入的方式。而不是去实现实现ApplicationContextAware接口的方式,因为这种方式属于与Spring容器强耦合的方式。(当然非常特殊情况下,只能使用ApplicationContextAware,比如上面那个情况)


@Async异步注解失效得原因


相信到了此处,@Async失效的原因已经不用再详细阐述一遍了。简单的说就是因为HelloService该Bean被提前初始化了,而这个时候AsyncAnnotationBeanPostProcessor根本就还没起作用(因为它仅仅是一个普通的BeanPostProcessor,加载是靠后的),所以肯定也就不能扫描到@Async这种`注解方法,从而就不能生成代理对象,那就自然而然就失效了~


想说明的是,本文说明的是一类问题,而不是@Async这一个问题,请大家能够举一反三


关于BeanPostProcessorChecker

首先它是一个BeanPostProcessor,是PostProcessorRegistrationDelegate的一个private static内部类,它的作用就是在postProcessAfterInitialization里检查作用在此Bean上的BeanPostProcessor够不够数,如果不够数(一般是提前加载了,或者被auto-proxying了这种情况就输出一条info日志~~~)

  private static final class BeanPostProcessorChecker implements BeanPostProcessor {
    private static final Log logger = LogFactory.getLog(BeanPostProcessorChecker.class);
    // 从if条件可以看出哪些是不需要检测的Bean
    // 1、BeanPostProcessor类型不检测
    // 2、ROLE_INFRASTRUCTURE这种类型的Bean不检测 (Spring自己的Bean)
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
      if (!(bean instanceof BeanPostProcessor) && !isInfrastructureBean(beanName) &&
          this.beanFactory.getBeanPostProcessorCount() < this.beanPostProcessorTargetCount) {
        if (logger.isInfoEnabled()) {
          logger.info("Bean '" + beanName + "' of type [" + bean.getClass().getName() +
              "] is not eligible for getting processed by all BeanPostProcessors " +
              "(for example: not eligible for auto-proxying)");
        }
      }
      return bean;
    }
    private boolean isInfrastructureBean(@Nullable String beanName) {
      if (beanName != null && this.beanFactory.containsBeanDefinition(beanName)) {
        BeanDefinition bd = this.beanFactory.getBeanDefinition(beanName);
        return (bd.getRole() == RootBeanDefinition.ROLE_INFRASTRUCTURE);
      }
      return false;
    }
  }


如果你的bean被提前加载了,那就会看到这么一条日志:


Bean 'XXX' of type [XXXX] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)


比如我们项目中使用到了Feign面向接口进行远程调用,每次项目启动的时候,就会输出大量的类似日志,如下图:


image.png

Tips:

一般的如果你的Config类是一个XXXConfigurer的扩展配置类,也会打印类似的消息:

o.s.c.s.PostProcessorRegistrationDelegate$Be



根本原因也是上面说的。具体到代码这里简单分析一下(此处以AsyncConfigurer为例):

// 在对它(AsyncAnnotationBeanPostProcessor)进行实例化的时候(它是个BeanPostProcessor,所以会根据bean定义进行getBean())
// 所以就必须先实例化它@Bean所在的类:
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {
  @Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)
  @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  public AsyncAnnotationBeanPostProcessor asyncAdvisor() { ... }
}
// 然后它的父类`AbstractAsyncConfiguration`里有个@Autowired方法:
@Configuration
public abstract class AbstractAsyncConfiguration implements ImportAware {
  @Autowired(required = false)
  void setConfigurers(Collection<AsyncConfigurer> configurers) { ... }
}
// 这个依赖注入就会去容器内找所有的`AsyncConfigurer`的实现,所以就把`AsyncConfig`给提前实例化了
// 所以打印了那么一句日志,不要感觉到惊奇。这也是为何spring这个checker里使用的日志级别是Info,而不是debug,更不是worn。
// 因为它Spring认为这个debug太轻了,但是warn又太重了,因为绝大部分情况下它都不影响程序的正常work~


注意避免BeanPostProcessor启动时对依赖的Bean造成误伤


BeanPostProcessor实例化时,自动依赖注入根据类型获得需要注入的Bean时,会将某些符合条件的Bean先实例化,如果此FacotryBean又依赖其他普通Bean,会导致该Bean提前启动,造成"误伤"(无法享受部分BeanPostProcessor的后置处理,例如典型的auto-proxy)。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
2月前
|
XML Java 数据格式
Spring5入门到实战------7、IOC容器-Bean管理XML方式(外部属性文件)
这篇文章是Spring5框架的实战教程,主要介绍了如何在Spring的IOC容器中通过XML配置方式使用外部属性文件来管理Bean,特别是数据库连接池的配置。文章详细讲解了创建属性文件、引入属性文件到Spring配置、以及如何使用属性占位符来引用属性文件中的值。
Spring5入门到实战------7、IOC容器-Bean管理XML方式(外部属性文件)
|
20天前
|
缓存 安全 Java
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
从底层源码入手,通过代码示例,追踪AnnotationConfigApplicationContext加载配置类、启动Spring容器的整个流程,并对IOC、BeanDefinition、PostProcesser等相关概念进行解释
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
|
20天前
|
XML Java 数据格式
Spring IOC—基于XML配置Bean的更多内容和细节(通俗易懂)
Spring 第二节内容补充 关于Bean配置的更多内容和细节 万字详解!
116 18
Spring IOC—基于XML配置Bean的更多内容和细节(通俗易懂)
|
7天前
|
XML Java 数据格式
spring复习02,xml配置管理bean
详细讲解了Spring框架中基于XML配置文件管理bean的各种方式,包括获取bean、依赖注入、特殊值处理、属性赋值、集合类型处理、p命名空间、bean作用域及生命周期和自动装配。
spring复习02,xml配置管理bean
|
4天前
|
缓存 Java Spring
手写Spring Ioc 循环依赖底层源码剖析
在Spring框架中,IoC(控制反转)是一个核心特性,它通过依赖注入(DI)实现了对象间的解耦。然而,在实际开发中,循环依赖是一个常见的问题。
13 4
|
7天前
|
XML Java 数据格式
spring复习03,注解配置管理bean
Spring框架中使用注解配置管理bean的方法,包括常用注解的标识组件、扫描组件、基于注解的自动装配以及使用注解后的注意事项,并提供了一个基于注解自动装配的完整示例。
spring复习03,注解配置管理bean
|
2月前
|
XML Java 数据格式
Spring5入门到实战------5、IOC容器-Bean管理(三)
这篇文章深入探讨了Spring5框架中IOC容器的高级Bean管理,包括FactoryBean的使用、Bean作用域的设置、Bean生命周期的详细解释以及Bean后置处理器的实现和应用。
Spring5入门到实战------5、IOC容器-Bean管理(三)
|
2月前
|
XML Java 数据格式
Spring5入门到实战------6、IOC容器-Bean管理XML方式(自动装配)
这篇文章是Spring5框架的入门教程,详细讲解了IOC容器中Bean的自动装配机制,包括手动装配、`byName`和`byType`两种自动装配方式,并通过XML配置文件和Java代码示例展示了如何在Spring中实现自动装配。
Spring5入门到实战------6、IOC容器-Bean管理XML方式(自动装配)
|
2月前
|
XML Java 数据格式
Spring5入门到实战------8、IOC容器-Bean管理注解方式
这篇文章详细介绍了Spring5框架中使用注解进行Bean管理的方法,包括创建Bean的注解、自动装配和属性注入的注解,以及如何用配置类替代XML配置文件实现完全注解开发。
Spring5入门到实战------8、IOC容器-Bean管理注解方式
|
2月前
|
Java Spring
下一篇
无影云桌面