某些时候,我们想要控制bean的加载顺序,比如某些资源配置类的bean需要在其他的bean之前被加载,以便其他bean在创建的时候可以使用。
举个例子:我们有一个bean,叫商品管理器GoodsManager,它在项目启动时,从数据库加载所有商品,并且定时刷新商品数据,并且为了便于使用,它提供了static类型方法供调用者使用。这种场景下,由于GoodsManager对外提供的是static方法,所以其他类可以直接调用它的方法,如果它不是最先加载的话,当有人请求商品列表时,商品还没有加载完成,那就就会导致问题的出现。为了避免出现这样的问题,我们必须要保证GoodsManager这个bean最先创建。
误区
- 期望通过实现Orderd或者Order来控制bean加载顺序
原因:注解@Order或者接口Ordered的作用是定义Spring IOC容器中Bean的执行顺序的优先级,而不是定义Bean的加载顺序,Bean的加载顺序不受@Order或Ordered接口的影响
疑问
spring bean的加载到底有没有顺序呢?有的话是按照什么方式?
其实,要得到答案,无需猜想,我们可以利用spring的扩展点来验证。
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
/**
* Modify the application context's internal bean definition registry after its
* standard initialization. All regular bean definitions will have been loaded,
* but no beans will have been instantiated yet. This allows for adding further
* bean definitions before the next post-processing phase kicks in.
* @param registry the bean definition registry used by the application context
* @throws org.springframework.beans.BeansException in case of errors
*/
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}
实现了这个接口的bean,会在所有的bean注册完成之后被调用。那么,我们就可以通过BeanDefinitionRegistry输出所有的bean的定义信息。通过测试,发现spring加载bean是按照其注册bean时的顺序来加载的。以全注解方式为例,按照包名->类名方式排序的
思路
- 基于@DependsOn注解控制bean加载顺序;控制能力较弱,不作介绍
- 通过实现BeanDefinitionRegistryPostProcessor接口,在postProcessBeanDefinitionRegistry方法中通过BeanDefinitionRegistry获取到所有bean的注册信息,将bean保存到LinkedHashMap中,并从BeanDefinitionRegistry中删除,然后将保存的bean定义排序后,重新再注册到BeanDefinitionRegistry中,即可实现bean加载顺序的控制
注意:BeanDefinitionRegistry中最前面的几个spring自身定义的bean以及你定义的用作配置(@Configuration标注的,并且被context用来当做配置)的bean的顺序不要动
关键代码示例
/**
*配置类
*/
@Configuration
@ComponentScan(basePackages = "demo")
public class AppConfig extends WebMvcConfigurationSupport {
/**
* 注册用于控制bean加载顺序的处理器
*
* @return
*/
@Bean
public static DemoProcessor demoProcessor() {
return new DemoProcessor();
}
}
/**
*
*/
public class DemoProcessor implements BeanDefinitionRegistryPostProcessor{
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
String[] beanDefinitionNames = registry.getBeanDefinitionNames();
int index = 0;
//保留前n个关键bean的顺序
for(;index<beanDefinitionNames.length;index++) {
if(AppConfig.class.getName().equals(registry.getBeanDefinition(beanDefinitionNames[index]).getBeanClassName())) {
break;
}
}
Map<String, BeanDefinition> beans = new LinkedHashMap<>(beanDefinitionNames.length-index);
for(;index<beanDefinitionNames.length;index++) {
BeanDefinition beanDefinition = registry.getBeanDefinition(beanDefinitionNames[index]);
beans.put(beanDefinitionNames[index], beanDefinition);
registry.removeBeanDefinition(beanDefinitionNames[index]);
}
//TODO ...排序逻辑,注意beans中可能还包含有其他spring自身定义的bean
List<String> orderdBeanNames = new ArrayList<>();
//将排好序的bean再次注册到容器中
orderdBeanNames.forEach(beanName->{
registry.registerBeanDefinition(beanName, beans.get(beanName));
});
}
}
public class Run {
public static void main(String[] args) {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(AppConfig.class);
MockServletConfig config = new MockServletConfig(new MockServletContext(AppConfig.class.getResource("/").getFile(),new FileSystemResourceLoader()),"demo");
context.setServletConfig(config);
context.refresh();
//TODO 这里可以写一些校验用的代码
context.close();
}
}