static关键字解除Bean对象间的依赖

前言

本文基本上是借鉴于YourBatman的这篇文章 static关键字真能提高Bean的优先级吗?答:真能,只是在原理分析部分加入了自己的理解

我认为static关键字是解除Bean对象间的依赖,而不是提高Bean的优先级

警告一:来自BeanPostProcessorChecker

这是最为常见的一种警告,特别当你的工程使用了shiro做鉴权框架的时候。在我记忆中这一年来有N多位小伙伴问过我此问题,可见一斑。

@Configuration
class RootConfig {

    RootConfig() {
        System.out.println("RootConfig init...");
    }

    @Bean
    BeanPostProcessor postProcessor() {
        return new MyBeanPostProcessor();
    }
}

class MyBeanPostProcessor implements BeanPostProcessor {

    MyBeanPostProcessor() {
        System.out.println("MyBeanPostProcessor init...");
    }
}

启动类

public class App {
	public static void main( String[] args ) {
		AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(RootConfig.class);
		RootConfig rootConfig = (RootConfig) annotationConfigApplicationContext.getBean("rootConfig");
		System.out.println(rootConfig.getClass());
	}
}

运行程序,输出结果:

RootConfig init
六月 14, 2020 3:32:42 下午 org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker postProcessAfterInitialization
信息: Bean 'rootConfig' of type [config.RootConfig$$EnhancerBySpringCGLIB$$2177d46b] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
MyBeanPostProcessor init...
class config.RootConfig$$EnhancerBySpringCGLIB$$2177d46b

结果分析(问题点/冲突点):
AppConfig优先于MyBeanPostProcessor进行实例化
常识是:MyBeanPostProcessor作为一个后置处理器理应是先被初始化的,而AppConfig仅仅是个普通Bean而已,初始化理应靠后
出现了BeanPostProcessorChecker日志:表示AppConfig这个Bena不能被所有的BeanPostProcessors处理,所以有可能会让它“错过”容器对Bean的某些生命周期管理,因此可能损失某些能力(比如不能被自动代理),存在隐患
但凡只要你工程里出现了BeanPostProcessorChecker输出日志,理应都得引起你的注意,因为这属于Spring的警告日志(虽然新版本已下调为了info级别)
说明:这是一个Info日志,并非warn/error级别。绝大多数情况下你确实无需关注,但是如果你是一个容器开发者,建议请务必解决此问题(毕竟貌似大多数中间件开发者都有一定代码洁癖😄)

解决方案:static关键字提升优先级
基于上例,我们仅需做如下小改动:

@Configuration
public class RootConfig {
	RootConfig(){
		System.out.println("RootConfig init");
	}

	@Bean
	public static BeanPostProcessor postProcessor() {
		return new MyBeanPostProcessor();
	}
}

运行程序,结果输出:

MyBeanPostProcessor init...
RootConfig init
class config.RootConfig$$EnhancerBySpringCGLIB$$2177d46b

那个烦人的BeanPostProcessorChecker日志就不见了,清爽了很多。同时亦可发现AppConfig是在MyBeanPostProcessor之后实例化的,这才符合我们所想的“正常”逻辑嘛。

原因分析
非静态方法是属于对象的,所以方法是非静态方法依赖于RootConfig对象的创建。
如果是静态方法,那么其初始化与RootConfig对象的创建,所以遵循Spring一个后置处理器理先被初始化的尝试

警告二:Configuration配置类增强失败

这个“警告”就比上一个严重得多了,它有极大的可能导致你程序错误,并且你还很难定位问题所在。

@Configuration
class AppConfig {

    AppConfig() {
        System.out.println("AppConfig init...");
    }

    @Bean
    BeanDefinitionRegistryPostProcessor postProcessor() {
        return new MyBeanDefinitionRegistryPostProcessor();
    }

    @Bean
    Son son(){
        return new Son();
    }
    @Bean
    Parent parent(){
        return new Parent(son());
    }

}
class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    MyBeanDefinitionRegistryPostProcessor() {
        System.out.println("MyBeanDefinitionRegistryPostProcessor init...");
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    }
}

运行程序,结果输出:

AppConfig init...
MyBeanDefinitionRegistryPostProcessor init...
2020-05-31 07:59:06.363  INFO 37512 --- [           main] o.s.c.a.ConfigurationClassPostProcessor  : Cannot enhance
	 @Configuration bean definition 'appConfig' 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'.
...
son init...hashCode() = 1300528434
son init...hashCode() = 1598434875
Parent init...

果分析(问题点/冲突点):

AppConfig竟然比MyBeanDefinitionRegistryPostProcessor的初始化时机还早,这本就不合理
从ConfigurationClassPostProcessor的日志中可看到:AppConfig配置类enhance增强失败
Son对象竟然被创建了两个不同的实例,这将会直接导致功能性错误
这三步结果环环相扣,因为1导致了2的增强失败,因为2的增强失败导致了3的创建多个实例,真可谓一步错,步步错。需要注意的是:这里ConfigurationClassPostProcessor输出的依旧是info日志(我个人认为,Spring把这个输出调整为warn级别是更为合理的,因为它影响较大)。

原因分析
在BeanFactoryPostProcessor实例化阶段。实例化MyBeanDefinitionRegistryPostProcessor对象时

currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));

因为MyBeanDefinitionRegistryPostProcessor在AppConfig是个非静态方法,所以MyBeanDefinitionRegistryPostProcessor的实话化依赖与AppConfig。所以在BeanFactoryPostProcessor实例化阶段就会把AppConfig实例化
而AppConfig配置类的enhance增强在BeanPostProcessor阶段,所以就会出现AppConfig配置类enhance增强失败的情况

解决方案:static解除MyBeanDefinitionRegistryPostProcessor和AppConfig对象间的依赖关系

@Configuration
class AppConfig {

    AppConfig() {
        System.out.println("AppConfig init...");
    }

    @Bean
    static BeanDefinitionRegistryPostProcessor postProcessor() {
        return new MyBeanDefinitionRegistryPostProcessor();
    }

    @Bean
    Son son(){
        return new Son();
    }
    @Bean
    Parent parent(){
        return new Parent(son());
    }

}

运行程序,结果输出:

MyBeanDefinitionRegistryPostProcessor init...
...
AppConfig init...
son init...hashCode() = 2090289474
Parent init...
警告三:非静态@Bean方法导致@Autowired等注解失效
@Configuration
class AppConfig {

    @Autowired
    private Parent parent;
    
    @PostConstruct
    void init() {
        System.out.println("AppConfig.parent = " + parent);
    }


    AppConfig() {
        System.out.println("AppConfig init...");
    }

    @Bean
    BeanFactoryPostProcessor postProcessor() {
        return new MyBeanFactoryPostProcessor();
    }

    @Bean
    Son son() {
        return new Son();
    }
    @Bean
    Parent parent() {
        return new Parent(son());
    }
}

class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    MyBeanFactoryPostProcessor() {
        System.out.println("MyBeanFactoryPostProcessor init...");
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }
}

运行程序,结果输出:

AppConfig init...
2020-05-31 08:28:06.550  INFO 1464 --- [           main] o.s.c.a.ConfigurationClassEnhancer       : @Bean method
	 AppConfig.postProcessor 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.
MyBeanFactoryPostProcessor init...
...
son init...hashCode() = 882706486
Parent init...

结果分析(问题点/冲突点):

@Autowired/@PostConstruct等注解没有生效,这个问题很大
需要强调的是:此时的AppConfig是被enhance增强成功了的,这样才有可能进入到BeanMethodInterceptor拦截里面,才有可能输出这句日志(该拦截器会拦截Full模式配置列的所有的@Bean方法的执行)

原因分析:MyBeanFactoryPostProcessor的初始化依赖与AppConfig

解决方案:static解除和AppConfig对象间的依赖关系

static静态方法一定优先执行吗?

看完本文,有些小伙伴就忍不住跃跃欲试了,甚至很武断的得出结论:static标注的@Bean方法优先级更高,其实这是错误的,比如你看如下示例:

@Configuration
class AppConfig2 {

    AppConfig2(){
        System.out.println("AppConfig2 init...");
    }

    @Bean
    Son son() {
        return new Son();
    }
    @Bean
    Daughter daughter() {
        return new Daughter();
    }
    @Bean
    Parent Parent() {
        return new Parent();
    }
}

运行程序,结果输出:

AppConfig2 init...
son init...
Daughter init...
Parent init...

这时候你想让Parent在Son之前初始化,因此你想着在用static关键字来提升优先级,这么做:

AppConfig2:

@Bean
static Parent Parent() {
    return new Parent();
}

结果:你徒劳了,static貌似并没有生效,怎么回事?

原因浅析

为了满足你的好奇心,这里给个浅析,道出关键因素。我们知道@Bean方法(不管是静态方法还是实例方法)最终都会被封装进ConfigurationClass实例里面,使用Set beanMethods存储着,关键点在于它是个LinkedHashSet所以是有序的(存放顺序),而存入的顺序底层是由clazz.getDeclaredMethods()来决定的,由此可知@Bean方法执行顺序和有无static没有半毛钱关系。

static关键字使用注意事项

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值