spring控制bean的实例化顺序
事件由来
事情是这样的老板在群里说了今后不要在那样获取XxxService了,那样会出问题。。。。(此省略好多字)
我们来看出问题的代码,大致是这样的
@Component
public class Login {
//注意查看这里,通常我们会使用@Autowired这个注解获取某个服务但是这里是这样获取的
//获取服务后我们可以去数据库做一些查询操作
User u = (User) ContextUtil.getService("user");//省略user类
public void login(){
System.out.println("Login.login .u :"+ u);
u.getUser();
}
}
//ContextUtil代码
@Component
public class ContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext=null;
//在服务中获取bean
public static Object getService(String component){//3
Object bean = applicationContext.getBean(component);//4
return bean;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;//2
}
}
我看到这个代码后,首先想到的是怎么能这样写呢。。。。但是仔细一想或许别人有别的特殊用法,就不再想了,但是为什么会出问题,这个是我主要钻研的。
初探
其实现在想想是怎么会出问题,就很简单,自己当时想的太多了,反而错过了解决问题的最佳时机,有的问题真的不能想的太难。反而坑自己!
在上面的代码中ContextUtil
和 Login
这两个类,在什么情况下会出问题。经过多次尝试发现
如果Login
在ContextUtil
后实例化将不会出现问题,如果颠倒回来(将ContextUtil变成ZContextUtil就会先实例化Login了)将会在4处报NPE(applicationContext为null了)。因此出现这个问题主要的原因就是取决于spring实例化这两个类的顺序。
Spring是按照怎样的顺序创建bean的?
其实这个是深思问题,之前看过,简单的了解了下,现在忘记了。。。。,
依稀记得的是不同的操作系统读取的顺序会不同,如果在同一包中,且在windows系统下spring是按照类的字母顺序,一个一个放到map中然后初始化的,也就是C在L前,所以就不会出问题了,如果我们将C改成Z,让ContextUtil
在Login
后实例化,这样就出现问题了。这个问题我测试过了,的确是这样。
我们知道了原因,就要知道怎样解决这个问题,问题的关键在于 我们如何让ContextUtil类在实例化我们系统组件的时候先实例化它,保证ContextUtil在实例化所有用户bean之前实例化它,这样再有使用ContextUtil类的地放就可以愉快的使用了
。
为什么要这样想呢?
在一个庞大的系统中,如果很多地方都在使用这个类的这个方法在属性中获取一个组件,我们就没办法把所有的类中方法给修改了,所有我们要做的就是亡羊补牢!
解决办法
经过网上搜寻说的最多就是采用@Order的方法,但是经过反复测试然并卵
。
另外一种方法就是使用@DependsOn的放法,显式声明依赖关系,使得强制改变bean的加载顺序
,但是问题出在@DependsOn上我们不可能将所有类上加上这个注解。
此外我看到还有使用实现PriorityOrdered接口的形式定义优先级,这个方案明显不行。。。题外话
有兴趣的可以去了解下@Order
和 @DependsOn
用法.
直到今天,终于想到了一种方案,也解开了这个心劫,是的!时隔一个多月,我还在想这这件事。
虽然老板当时已经将所有的那种写法改多来了,但是我觉得应该还会有更好的办法去解决。
这个想法来自我对spring源码的理解,查了资料的确有用这种方法来处理bean的加载顺序问题。
首先说方法
1.让 ContextUtil implements InstantiationAwareBeanPostProcessor
接口;
2.让ContextUtil extends InstantiationAwareBeanPostProcessorAdapter
类;
网上的方法是在里面需要调用getBean主动触发bean创建,我的方法是直接让这个类实现或者继承接口即可。如果有多个这样的工具类,我们该怎们办呢?这个使用用@Order
就起作用了。所以了解@Order还是有必要的,
(不推荐,但是这也是一种思路)可以创建出来一个配置类实现或者继承上面的方法,然后主动调用getBean方法,触发bean的创建。
完美解决。
全剧终!
原理分析
Spring为开发人员定制了很多接口和类,这样以至于让开发人员能干扰bean的创建周期,在bean的创建周期中我们应该想到是spring是怎样干扰的,这些定制化功能的类肯定是在bean创建之前创建的。以至于在实例化类的时候可以让他们发挥作用。
之前我一直在想怎么让ContextUtil排成老大,让它先实例化,思维陷入了困境,跳出这个怪圈,我们可以让这个bean不在bean的生命周期中实例化(其实源码还是调用同一个方法实例化的,不同的是这个类相当于处理器类了,它的优先级会更高,spring在准备阶段会更早的去实例化这些类),即可。
源码具体位置在