Spring的Bean有序吗?试试用@DependsOn或static来提高优先级(上)

简介: Spring的Bean有序吗?试试用@DependsOn或static来提高优先级(上)

前言


顺序:意思是依次而不乱。顺序在生活的方方面面都显得尤为重要,自然的它对程序执行来说也是至关重要的。有了顺序的保证,我们就能对“结果”做出预期,作为coder的我们对应的也就更能“掌控”自己所写代码,心里也就更加踏实。


顺序固然重要,但是不乏有些场景它是不需要顺序保证的。一般来说:无序的效率会比顺序高,毕竟保证顺序是需要花费资源的(人力、物理、时间…)。本文将主要讨论Spring在实例化Bean时的顺序性,以及我们如何才能“控制”这种顺序呢?


正文


Spring容器载入(实例化)Bean的顺序是不确定的,Spring框架没有约定特定顺序逻辑规范。但是这并不代表Spring没有在这方面做“努力”,下面讲主要以代码示例的形式看效果,最后再从源码的角度分析其原因。

为何需要控制Bean的顺序?


问题的提出可以通过需求场景来驱动,举例如下:


  1. 一个非常典型的场景:事件发布 - 订阅机制。发布者Bean:Publisher,订阅者Bean:Listener。现在有需求:要保证Listener这个Bean能够监听到Publisher发出的所有事件,一个都不能落下,那么这个时候就对Listener这个Bean提出了强制要求:在Publisher初始化之前必须准备好,否则就会错过一些“早期事件”嘛
  2. 该场景在Spring Boot的自动配置中极为常见:此处我拿Feign和Hystrix的整合举例。Feign若不和Hystrix整合使用的是feign.Feign.Builder构建器,若整合使用的是feign.hystrix.HystrixFeign构建器,因此这里存在先后顺序:必须先判断是否能构建起HystrixFeign实例(类路径下是否有相关类),再去考虑原生Builder,这中case也就对顺序有强依赖了。


关于顺序的控制,本文区分出传统Spring环境下和Spring Boot环境下的不同处理(请以前者为主)。


传统Spring环境



场景一示例:


这里我用JDK原生的Observable/Observer机制来写出观察者模式代码(结合Spring):


主人(事件发布者):


// 主人:最终会有小动物观察主人
// 主人有个能力:放鱼
public class Master extends Observable {
    public String name;
    private Master(String name) {
        this.name = name;
    }
    // 给鱼:然后通知所有的观察者过来吃鱼。这样所有观察的猫都会过来了
    public void giveFish() {
        System.out.println(name + "主人放了一条鱼,通知猫过来吃~~~~~~");
        setChanged(); // 这个一定不能忘
        notifyObservers();
    }
    // 单例
    private static final Master MASTER = new Master("YoutBatman");
    public static Master getMaster() {
        return MASTER;
    }
}
// 它作为Master的代理,把它放进容器内,而非Master本身
public class MasterBean implements InitializingBean {
    // 初始化完成后,立马放一条鱼
    @Override
    public void afterPropertiesSet() throws Exception {
        Master.getMaster().giveFish();
    }
}


猫(观察者):


// 观察者:它会观察主人,只要放鱼了它就会去吃(消费)
public class Cat implements Observer {
    public String name;
    public Cat(String name) {
        this.name = name;
    }
    @Override
    public void update(Observable o, Object arg) {
        String masterName = o.toString();
        // 因为该观察者接口没有泛型 所以只能强转
        if (o instanceof Master) {
            masterName = ((Master) o).name;
        }
        System.out.println(name + "吃了主人" + masterName + "放的鱼");
    }
}


主人 + 猫的关系绑定上(通过Spring配置):本文放两只猫


@Configuration(proxyBeanMethods = false)
public class Config {
    @Bean
    public MasterBean master() {
        return new MasterBean();
    }
    @Bean
    public Cat tom() {
        Cat tom = new Cat("Tom");
        Master.getMaster().addObserver(tom);
        return tom;
    }
    @Bean
    public Cat cc() {
        Cat cc = new Cat("Cc");
        Master.getMaster().addObserver(cc);
        return cc;
    }
}


书写测试程序:


public static void main(String[] args) {
    new AnnotationConfigApplicationContext(Config.class);
    Master.getMaster().giveFish();
    Master.getMaster().giveFish();
}


运行程序,控制台输出:


...
09:36:35.096 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'config'
09:36:35.103 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'master'
YoutBatman主人放了一条鱼,通知猫过来吃~~~~~~
09:36:35.106 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'tom'
09:36:35.107 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'cc'
YoutBatman主人放了一条鱼,通知猫过来吃~~~~~~
Cc吃了主人YoutBatman放的鱼
Tom吃了主人YoutBatman放的鱼
YoutBatman主人放了一条鱼,通知猫过来吃~~~~~~
Cc吃了主人YoutBatman放的鱼
Tom吃了主人YoutBatman放的鱼


从debug日志中很明显的看到这些Bean的初始化顺序为:config -> master -> tom -> cc,所以当master初始化完毕后放出鱼时,两只猫都没有监听到,所以错失了首次放的鱼,这也就是错失某些事件的例子,在生产上很多时候是不能容忍的,需要解决。


解决方案


针对此种case,我们的诉求是希望无论如何猫兄都能监听到主人放鱼的动作,从而“吃到所有的鱼”。那么此处我给出三种方案供你参考:


方案一(不推荐):改变@Bean的定义顺序


把上面的Config.java配置文件改为如下顺序:


@Configuration(proxyBeanMethods = false)
public class Config {
    @Bean
    public Cat tom() {
        Cat tom = new Cat("Tom");
        Master.getMaster().addObserver(tom);
        return tom;
    }
    @Bean
    public Cat cc() {
        Cat cc = new Cat("Cc");
        Master.getMaster().addObserver(cc);
        return cc;
  }
    @Bean
    public MasterBean master() {
        return new MasterBean();
    }
}


其它均不变,再次运行程序,控制台输出


...
09:44:03.987 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'config'
09:44:03.994 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'tom'
09:44:04.000 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'cc'
09:44:04.000 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'master'
YoutBatman主人放了一条鱼,通知猫过来吃~~~~~~
Cc吃了主人YoutBatman放的鱼
Tom吃了主人YoutBatman放的鱼
YoutBatman主人放了一条鱼,通知猫过来吃~~~~~~
Cc吃了主人YoutBatman放的鱼
Tom吃了主人YoutBatman放的鱼
YoutBatman主人放了一条鱼,通知猫过来吃~~~~~~
Cc吃了主人YoutBatman放的鱼
Tom吃了主人YoutBatman放的鱼


问题解决:猫兄吃到了所有的鱼,从debug日志中Bean的实例化顺序能够解释为何它能迟到所有的鱼。但是,但是,但是此解决方案并不推荐,原因如下:


  1. 该方案强依赖于这个规则:同一配置类下,Bean的实例化顺序是按照从上至下的顺序实例化的。一旦你的相关配置处在不同配置类内,此顺序是确定不了的
  2. 这种顺序是由程序员来人工确保的,而非通过结构来固化,因此容错性极低。所以生产上极不推荐这么做


相关文章
|
1月前
|
XML 安全 Java
|
11天前
|
存储 Java Spring
【Spring】获取Bean对象需要哪些注解
@Conntroller,@Service,@Repository,@Component,@Configuration,关于Bean对象的五个常用注解
|
11天前
|
存储 Java 应用服务中间件
【Spring】IoC和DI,控制反转,Bean对象的获取方式
IoC,DI,控制反转容器,Bean的基本常识,类注解@Controller,获取Bean对象的常用三种方式
|
17天前
|
XML Java 数据格式
Spring容器Bean之XML配置方式
通过对以上内容的掌握,开发人员可以灵活地使用Spring的XML配置方式来管理应用程序的Bean,提高代码的模块化和可维护性。
53 6
|
18天前
|
XML Java 数据格式
🌱 深入Spring的心脏:Bean配置的艺术与实践 🌟
本文深入探讨了Spring框架中Bean配置的奥秘,从基本概念到XML配置文件的使用,再到静态工厂方式实例化Bean的详细步骤,通过实际代码示例帮助读者更好地理解和应用Spring的Bean配置。希望对你的Spring开发之旅有所助益。
80 3
|
2月前
|
缓存 Java Spring
实战指南:四种调整 Spring Bean 初始化顺序的方案
本文探讨了如何调整 Spring Boot 中 Bean 的初始化顺序,以满足业务需求。文章通过四种方案进行了详细分析: 1. **方案一 (@Order)**:通过 `@Order` 注解设置 Bean 的初始化顺序,但发现 `@PostConstruct` 会影响顺序。 2. **方案二 (SmartInitializingSingleton)**:在所有单例 Bean 初始化后执行额外的初始化工作,但无法精确控制特定 Bean 的顺序。 3. **方案三 (@DependsOn)**:通过 `@DependsOn` 注解指定 Bean 之间的依赖关系,成功实现顺序控制,但耦合性较高。
实战指南:四种调整 Spring Bean 初始化顺序的方案
|
1月前
|
安全 Java 开发者
Spring容器中的bean是线程安全的吗?
Spring容器中的bean默认为单例模式,多线程环境下若操作共享成员变量,易引发线程安全问题。Spring未对单例bean做线程安全处理,需开发者自行解决。通常,Spring bean(如Controller、Service、Dao)无状态变化,故多为线程安全。若涉及线程安全问题,可通过编码或设置bean作用域为prototype解决。
34 1
|
3月前
|
XML Java 数据格式
Spring从入门到入土(bean的一些子标签及注解的使用)
本文详细介绍了Spring框架中Bean的创建和使用,包括使用XML配置文件中的标签和注解来创建和管理Bean,以及如何通过构造器、Setter方法和属性注入来配置Bean。
86 9
Spring从入门到入土(bean的一些子标签及注解的使用)
|
3月前
|
Java 测试技术 Windows
咦!Spring容器里为什么没有我需要的Bean?
【10月更文挑战第11天】项目经理给小菜分配了一个紧急需求,小菜迅速搭建了一个SpringBoot项目并完成了开发。然而,启动测试时发现接口404,原因是控制器包不在默认扫描路径下。通过配置`@ComponentScan`的`basePackages`字段,解决了问题。总结:`@SpringBootApplication`默认只扫描当前包下的组件,需要扫描其他包时需配置`@ComponentScan`。
|
3月前
|
Java 开发者 Spring
Spring bean的生命周期详解!
本文详细解析Spring Bean的生命周期及其核心概念,并深入源码分析。Spring Bean是Spring框架的核心,由容器管理其生命周期。从实例化到销毁,共经历十个阶段,包括属性赋值、接口回调、初始化及销毁等。通过剖析`BeanFactory`、`ApplicationContext`等关键接口与类,帮助你深入了解Spring Bean的管理机制。希望本文能助你更好地掌握Spring Bean生命周期。
152 1