前言
顺序:意思是依次而不乱。顺序在生活的方方面面都显得尤为重要,自然的它对程序执行来说也是至关重要的。有了顺序的保证,我们就能对“结果”做出预期,作为coder的我们对应的也就更能“掌控”自己所写代码,心里也就更加踏实。
顺序固然重要,但是不乏有些场景它是不需要顺序保证的。一般来说:无序的效率会比顺序高,毕竟保证顺序是需要花费资源的(人力、物理、时间…)。本文将主要讨论Spring在实例化Bean时的顺序性,以及我们如何才能“控制”这种顺序呢?
正文
Spring容器载入(实例化)Bean的顺序是不确定的,Spring框架没有约定特定顺序逻辑规范。但是这并不代表Spring没有在这方面做“努力”,下面讲主要以代码示例的形式看效果,最后再从源码的角度分析其原因。
为何需要控制Bean的顺序?
问题的提出可以通过需求场景来驱动,举例如下:
- 一个非常典型的场景:事件发布 - 订阅机制。发布者Bean:Publisher,订阅者Bean:Listener。现在有需求:要保证Listener这个Bean能够监听到Publisher发出的所有事件,一个都不能落下,那么这个时候就对Listener这个Bean提出了强制要求:在Publisher初始化之前必须准备好,否则就会错过一些“早期事件”嘛
- 该场景在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的实例化顺序能够解释为何它能迟到所有的鱼。但是,但是,但是此解决方案并不推荐,原因如下:
- 该方案强依赖于这个规则:同一配置类下,Bean的实例化顺序是按照从上至下的顺序实例化的。一旦你的相关配置处在不同配置类内,此顺序是确定不了的
- 这种顺序是由程序员来人工确保的,而非通过结构来固化,因此容错性极低。所以生产上极不推荐这么做