spring技巧之bean加载顺序控制

某些时候,我们想要控制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时的顺序来加载的。以全注解方式为例,按照包名->类名方式排序的

思路

  1. 基于@DependsOn注解控制bean加载顺序;控制能力较弱,不作介绍
  2. 通过实现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();
	}
}

原文:spring技巧之bean加载顺序控制_疯狂熊猫人的博客-CSDN博客_bean加载顺序
作者: 疯狂熊猫人