Spring 中 Configuration 的顺序及 ConditionalOnBean 的注意事项

微信群里有网友发了一张图并说道: 这个代码 注释掉能跑  不注释掉 不能跑啥问题 来个大佬

Spring 中 Configuration 的顺序及 ConditionalOnBean 的注意事项

经过一翻讨论,我们知道了答案:

@ConditionalOnBean 依赖于 bean 初始化的顺序。

上图中,DfsProcessor.class 对应的 bean 的初始化时间晚于 MediaConfiguration 类 ,由于在初始化 MediaConfiguration 时,DfsProcessor.class 对应的 bean 还不存在,所以条件就不成立,导致 VideoProcessor 这个 bean 没被初始化。

这在 @ConditionalOnBean 的文档中也有特别提及:

The condition can only match the bean definitions that have been processed by the application context so far and, as such, it is strongly recommended to use this condition on auto-configuration classes only. If a candidate bean may be created by another auto-configuration, make sure that the one using this condition runs after.

文档给的建议是,@ConditionalOnBean 应该用在 auto-configuration 类中,并在需要时指定顺序。

为了加深理解,我们写一个可复现的 demo 项目


demo 项目包含如下 beanconfiguration 类:

public class Tom {
    {
        System.out.println("Init: " + this.getClass());
    }
}

public class Jerry {
    {
        System.out.println("Init: " + this.getClass());
    }
}

@Configuration
public class TomConfig {

    {
        System.out.println("Init: " + this.getClass());
    }

    @Bean
    Tom tom() {
        return new Tom();
    }
}

@Configuration
public class JerryConfig {

    {
        System.out.println("Init: " + this.getClass());
    }
    
    @Bean
    Jerry jerry() {
        return new Jerry();
    }
}

为了便于观察 bean 的初始化顺序,我们在各自的构造函数中都加了一条 System.out.println() 的输出。

项目启动类是一个典型的 Spring Boot 主类:

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.classargs);
    }
}

运行该启动类,可以看到如下打印:

Init: class com.example.demo.config.JerryConfig$$EnhancerBySpringCGLIB$$41c83f4c
Init: class com.example.demo.config.TomConfig$$EnhancerBySpringCGLIB$$c009d080
Init: class com.example.demo.bean.Jerry
Init: class com.example.demo.bean.Tom

(注意 $$EnhancerBySpringCGLIBxxx 部分,这是因为 Spring 会对 configuration 类进行特殊操作(比如防止 bean method 执行多次返回不同的实例),需要 cglib 库对它们进行字节码增加(enhance))

日志可以看出,Jerry 先于 Tom 被初始化。如果(重要!),我们想让 Jerry 依赖 Tom,即,当不存在 Tom 对应的 bean 时,就不初始化 Jerry。我们可能会这么做:

// 有 Tom, 才有 Jerry
@ConditionalOnBean(Tom.class)
@Bean
Jerry jerry() 
{
    return new Jerry();
}

再次运行项目,我们看到了如下输出:

Init: class com.example.demo.config.JerryConfig$$EnhancerBySpringCGLIB$$53ca8447
Init: class com.example.demo.config.TomConfig$$EnhancerBySpringCGLIB$$d20c157b
Init: class com.example.demo.bean.Tom

什么情况?明明有 Tom 这个 beanJerry 却不见了?

如文章开头所说,@ConditionalOnBeanbean 的注册顺序有要求,由于 JerryConfig 先于 TomConfig 被发现和注册, 导致在运行 JerryConfig 内的方法时,Tom 还不存在,因此 Jerry 不再被初始化。

按照官方文本的指示,我们要做如下调整,以指定正确的加载顺序:

  1. resources/META-INF 下新建一个 spring.factories 的空白文件,并加入以下内容:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.demo.config.TomConfig,
com.example.demo.config.JerryConfig

它用来配置项目中的 auto-configuration 类,该文件会被 SpringFactoriesLoader 类加载解析。

  1. 指定正确的顺序
// 指定当前 config 要在 TomConfig 之后执行
@AutoConfigureAfter(TomConfig.class)
@Configuration
public class JerryConfig 
{
  // 不变,省略
}

这里我们用了 AutoConfigureAfter 注解,另外还有 AutoConfigureBeforeAutoConfigureOrder 可供使用。

有了以上2步,我们再运行启动类,其日志是:

Init: class com.example.demo.config.TomConfig$$EnhancerBySpringCGLIB$$d9c9a644
Init: class com.example.demo.bean.Tom
Init: class com.example.demo.config.JerryConfig$$EnhancerBySpringCGLIB$$5b881510
Init: class com.example.demo.bean.Jerry

符合预期了。

再试试把 Tom 注释掉:

//    @Bean
//    Tom tom() {
//        return new Tom();
//    }

重新运行程序,得到的日志是:

Init: class com.example.demo.config.TomConfig$$EnhancerBySpringCGLIB$$c1e02b9
Init: class com.example.demo.config.JerryConfig$$EnhancerBySpringCGLIB$$8ddc7185

也没问题,因为没了 Tom,所以依赖它的 Jerry 也没了。


额外的话,其实我们使用 auto-configuration 类和各种Conditional 类,主要是为了满足这样的场景:

库的设计者提供一些默认的 bean,使用了该库的项目,一是会自动注册这些 bean,二是可以用自定义 bean 替代库中默认的 bean,以达到个性化使用的目的。这也是 Spring 框架的强大之处,既提供了默认的实现,又支持扩展、定制。

我们在编写自己的库时,也可借鉴 —— 不可把实现写死!


原文始发于微信公众号(背井):Spring 中 Configuration 的顺序及 ConditionalOnBean 的注意事项

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/246601.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!