PostProcessor和FactoryBean过早实例化的分析及解决方案

487 阅读5分钟

背景知识

两个Map

Map<String, BeanDefinition> beanDefinitionMap,声明在DefaultListableBeanFactory,这个属于IOC容器的源头,所有注册的BeanDefinition都在这里,占位符处理器PlaceholderConfigurer也是对这个Map处理.

Map<String, RootBeanDefinition> mergedBeanDefinitions,声明在AbstractBeanFactory,顾名思义里面存放的是"融合"的BeanDefinition,即加工后衍生出来的BeanDefinition

三种BeanDefinition

RootBeanDefinition,GenericBeanDefinitionChildBeanDefinition都继承于AbstractBeanDefinition

  • RootBeanDefinition完善了AbstractBeanDefinition的很多信息,熟悉的final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);也是返回了RootBeanDefinition
  • ChildBeanDefinition只比AbstractBeanDefinition多了个parentName,并且所有ChildBeanDefinition的构造方法都需要指定parentName,可以看出它是专为继承而设计的
  • GenericBeanDefinition也是只多了parentName,但它的构造方法无需指定parentName,在Spring 2.5之后都建议使用GenericBeanDefinition来注册自定义BeanDefinition

特别注意的是getMergedLocalBeanDefinition()返回的都是RootBeanDefinition,即经过父子继承,融合加工后都成为RootBeanDefinition存放在mergedBeanDefinitions

关于BeanDefinition的继承可以参考代码段

    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    // 注册parentBeanDefinition
    GenericBeanDefinition parentBeanDefinition = new GenericBeanDefinition();
    parentBeanDefinition.setBeanClass(ParentChlidBeanDefinition.class);
    MutablePropertyValues parentPropertyValues = new MutablePropertyValues();
    parentPropertyValues.addPropertyValue("name", "ypq");
    parentBeanDefinition.setPropertyValues(parentPropertyValues);
    applicationContext.registerBeanDefinition("parent", parentBeanDefinition);
    // 注册ChildDefinition
    GenericBeanDefinition childBeanDefinition = new GenericBeanDefinition();
    childBeanDefinition.setParentName("parent");
    childBeanDefinition.setBeanClass(ParentChlidBeanDefinition.class);
    MutablePropertyValues childPropertyValues = new MutablePropertyValues();
    childPropertyValues.addPropertyValue("age", 10);
    childBeanDefinition.setPropertyValues(childPropertyValues);
    applicationContext.registerBeanDefinition("child", childBeanDefinition);

    applicationContext.refresh();
    ParentChlidBeanDefinition parent = applicationContext.getBean("parent", ParentChlidBeanDefinition.class);
    ParentChlidBeanDefinition child = applicationContext.getBean("child", ParentChlidBeanDefinition.class);

    // 父BeanDefinition没有age
    Assert.assertEquals("ypq", parent.getName());
    Assert.assertNull(parent.getAge());
    // 子BeanDefinition有age
    Assert.assertEquals("ypq", child.getName());
    Assert.assertEquals(10, child.getAge().intValue());

关于这部分背景知识也可参考cloud.tencent.com/developer/a…

applicationContextrefresh()

三个常用的PostProcessor

  • BeanPostProcessor, 在Bean实例化时执行,如CommonAnnotationBeanPostProcessor负责@PostConstruct@PreDestroy
  • BeanFactoryPostProcessor, 在refresh()中段执行,例如PropertySourcesPlaceholderConfigurer,处理BeanFactory的PropertySources
  • BeanDefinitionRegistryPostProcessor, 继承BeanFactoryPostProcessor, 具有更高优先级, 动态引入更多的BeanDefinition

三者在refresh()中的顺序和关系如下图所示 我们知道,如果不合理使用Spring会产生莫名其妙的问题,例如提早实例化Bean就是一种常见的原因.总结一下这类问题,可以分为若干种场景

问题一 提早至BeanPostProcessor实例化阶段

@Configuration
public class EarlyConfiguration {
    @Bean
    public BeanPostProcessor myBeanPostProcessor() {
        return new MyBeanPostProcessor();
    }
}

现象:打印EarlyConfiguration$$EnhancerBySpringCGLIB$$f48a5b2 is not eligible for getting processed by all BeanPostProcessors

分析:日志提示EarlyConfiguration未被所有BeanPostProcessor处理.注意上图绿色的BeanPostProcessor实例化阶段,Spring为了实例化MyBeanPostProcessor,必须先实例化EarlyConfiguration, 此时至少有一个BeanPostProcessor未实例化,更谈不上被BeanPostProcessor处理,因此打印上述日志.

解决:public static BeanPostProcessor myBeanPostProcessor(),加上static后myBeanPostProcessor()不再依赖EarlyConfiguration,也不会导致EarlyConfiguration提早实例化

问题二 提早至BeanFactoryPostProcessor阶段

@Configuration
public class EarlyConfiguration {
    @PostConstruct
    public void init() {
        System.out.println("EarlyConfiguration init...");
    }
    @Bean
    public BeanFactoryPostProcessor myBeanFactoryPostProcessor() {
        return new MyBeanFactoryPostProcessor();
    }
}

@Bean method EarlyConfiguration.myBeanFactoryPostProcessor is non-static and returns an object assignable to Spring's BeanFactoryPostProcessor interface.This will result in a failure to process annotations such as @Autowired, @Resource and @PostConstruct within the method's declaring @Configuration class.Add the 'static' modifier to this method to avoid these container lifecycle issues; see @Bean javadoc for complete details.

现象:打印上述日志,没有打印EarlyConfiguration init..., 即@PostConstruct没有执行

分析:Spring为了实例化MyBeanFactoryPostProcessor,导致EarlyConfiguration提前实例化(在图中的蓝色阶段),此时BeanPostProcessor(例如CommonAnnotationBeanPostProcessor)尚未实例化,所以他负责的@PostConstruct也不会执行

解决:public static BeanFactoryPostProcessor myStaticBeanFactoryPostProcessor(), static关键字避免了依赖关系.

问题三 提早至BeanDefinitionRegistryPostProcessor阶段

@Configuration
public class EarlyConfiguration {
    @Bean
    public BeanDefinitionRegistryPostProcessor myBeanDefinitionRegistryPostProcessor() {
        return new MyBeanDefinitionRegistryPostProcessor();
    }
}

Cannot enhance @Configuration bean definition 'earlyConfiguration' since its singleton instance has been created too early. The typical cause is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor return type: Consider declaring such methods as 'static'.

现象:打印上述日志, EarlyConfiguration虽然是Full模式但并没有增强(即不是EarlyConfiguration$$EnhancerBySpringCGLIB$$f48a5b2),只是一个Lite模式(普通)的Configuration

分析:Spring为了实例化MyBeanDefinitionRegistryPostProcessor, 导致EarlyConfiguration提前实例化(在图中的紫色阶段),而ConfigurationClassPostProcessor#postProcessBeanFactory(即增强实现方法enhanceConfigurationClasses())在BeanDefinitionRegistryPostProcessor实例化后和BeanFactoryPostProcessor实例化前执行,此时已经生成了Lite模式的EarlyConfiguration实例, 无法再增强了.

解决: public static BeanDefinitionRegistryPostProcessor myStaticBeanDefinitionRegistryPostProcessor(), static关键字避免了依赖关系.

问题四 getObjectType返回null的FactoryBean

FactoryBeangetObjectType依赖@Value("${car-factory.brand}")确定类型

@Configuration
public class EarlyConfiguration {
    @Bean
    public BeanFactoryPostProcessor carBeanFactoryPostProcessor(ConfigurableEnvironment configurableEnvironment) {
        return new MyBeanFactoryPostProcessor();
    }
    @Bean
    public static FactoryBean carFactory() {
        return new CarFactory();
    }
}
public class CarFactory implements FactoryBean, InitializingBean {
    @Value("${car-factory.brand}")
    private String brand;
    // ...
    @Override
    public Class<?> getObjectType() {
        if (brand == null) {
            return null;
        }
        try {
            clazz = this.getClass().getClassLoader().loadClass(brand);
        } catch (ClassNotFoundException e) {
            // ignore
        }
        return clazz;
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        LOGGER.error("the brand is {}", brand);
    }
}

现象:打印"the brand is null, 属于问题二的特殊情况

分析:首先我们了解下getBean流程的BeanDefinition变化,以methodValidationPostProcessor为例,它需要一个构造参数,调用getBeanNamesForType获取匹配这个参数类型的BeanName, 如果FactoryBean#getObjectType无法确定类型,Spring会完全初始化FactoryBean尝试确定类型. 图中getMergedLocalBeanDefinition的逻辑:先查mergedBeanDefinitions,有则直接返回,没有则从根据传入BeanDefinition的parent等关系,生成一个"融合"的BeanDefinition存入mergedBeanDefinitions,有点类似于缓存.

回到我们的案例,propertySourcesPlaceholderConfigurer是用来替代占位符的BeanFactoryPostProcessor,属于PriorityOrderd,它的postProcessBeanFactory会修改BeanDefinitionMap,占位符被替换为实际值.此时通常mergedBeanDefinitions已经缓存了一份BeanDefinitionMap,因此此修改不会即时生效.必须在BeanFactoryPostProcessor实例化和调用阶段结束后(图中蓝色部分),调用clearMetadataCache清理mergedBeanDefinitions才会生效.

但是自定义的carBeanFactoryPostProcessor需要构造参数, 导致carFactorymyBeanFactoryPostProcessor实例化时一起实例化,早于clearMetadataCache,因此carFactory打印出null

总结

概括来说,Spring的提前初始化可以分为三大类,分别是BeanPostProcessor(绿色),BeanFactoryPostProcessor(蓝色),BeanDefinitionRegistryPostProcessor(紫色)三个阶段都有可能提前初始化,特别地如果FactoryBeangetObjectType返回null,也可能导致其提前初始化.掌握好以下两点避坑指南:

  • PostProcessor用static关键字
  • FactoryBean尽早明确Bean类型