为什么自定义的BeanDefinitionRegistryPostProcessor不能在Spring加载过程中第一阶段初始化

Spring boot:2.2.1

jdk:1.8

 

所谓的Spring加载过程的第一阶段是指下面这个地方:

AbstractApplicationContext的refrsh方法的registerBeanPostProcessors(beanFactory);会进入到下面到方法里面:PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors

public static void invokeBeanFactoryPostProcessors(
			ConfigurableListableBeanFactory beanFactory, List beanFactoryPostProcessors) {

		// Invoke BeanDefinitionRegistryPostProcessors first, if any.
		Set processedBeans = new HashSet<>();

		if (beanFactory instanceof BeanDefinitionRegistry) {
			BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
			List regularPostProcessors = new ArrayList<>();
			List registryProcessors = new ArrayList<>();

			// 省略

			// Do not initialize FactoryBeans here: We need to leave all regular beans
			// uninitialized to let the bean factory post-processors apply to them!
			// Separate between BeanDefinitionRegistryPostProcessors that implement
			// PriorityOrdered, Ordered, and the rest.
			List currentRegistryProcessors = new ArrayList<>();

			// First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
			String[] postProcessorNames =
					beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
			for (String ppName : postProcessorNames) {
				if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
					currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
					processedBeans.add(ppName);
				}
			}
			sortPostProcessors(currentRegistryProcessors, beanFactory);
			registryProcessors.addAll(currentRegistryProcessors);
			invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); //第一次调用
			currentRegistryProcessors.clear();

			// Next, invoke the BeanDefinitionRegistryPostProcessors that implement Ordered.
			postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
			for (String ppName : postProcessorNames) {
				if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {
					currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
					processedBeans.add(ppName);
				}
			}
			sortPostProcessors(currentRegistryProcessors, beanFactory);
			registryProcessors.addAll(currentRegistryProcessors);
			invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); //第二次调用
// 后续代码省略

在注释的第一次调用的地方,虽然我们自己的实现类实现了PriorityOrdered接口,但是依然拿不到。

检查一下beanFactory的beanDefinitionNames,这里面只有7个。而且都是内部使用的一些bean。

我们自己的bean还没有被加载。所以拿不到。那这几个内部使用的bean是在哪里加载的呢。

还得看SpringApplication.run方法。

	public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
// 省略其他
}

在createApplicationContext方法里面,进去debug,会通过反射的方式创建AnnotationConfigApplicationContext,

在创建它的过程中,先先初始化其父类里面的DefaultListableBeanFactory,这时候,这个beanFactory里面没有管理任何bean。

然后进入到构造方法。

	public AnnotationConfigApplicationContext() {
		this.reader = new AnnotatedBeanDefinitionReader(this);
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}

实例化reader的过程中,就会开始加载内部使用的bean了。具体进入到AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);里面查看。

 

那么如果我想要在比较早的阶段就要初始化bean,或者初始化一些其他资源怎么搞?

方案一:事件监听

我们可以监听ApplicationContextInitializedEvent事件,而这个事件又是在如下过程中发布的。

SpringApplication.run
->
SpringApplicationRunListeners listeners = getRunListeners(args);
->
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
->
applyInitializers(context);
listeners.contextPrepared(context);

而这个事件在Spring的refresh之前发生,所以达到提前初始化资源的目的。

同时需要在spring.factory里面进行如下配置

# Application Listeners
org.springframework.context.ApplicationListener=\
com.demo.InitResourceListener

方案二:使用Initializers

还是上面方案1的流程,看applyInitializers方法,他会调用实现了ApplicationContextInitializer接口的具体实现类。

那么现在需要找一下这些initializers是如何被赋值的?

依然在SpringApplication的类里面

/**
	 * Sets the {@link ApplicationContextInitializer} that will be applied to the Spring
	 * {@link ApplicationContext}.
	 * @param initializers the initializers to set
	 */
	public void setInitializers(Collection> initializers) {
		this.initializers = new ArrayList<>();
		this.initializers.addAll(initializers);
	}

	/**
	 * Add {@link ApplicationContextInitializer}s to be applied to the Spring
	 * {@link ApplicationContext}.
	 * @param initializers the initializers to add
	 */
	public void addInitializers(ApplicationContextInitializer... initializers) {
		this.initializers.addAll(Arrays.asList(initializers));
	}

然后在setInitializers方法里面debug,看哪里调用了这个方法。结果发现入口是在构造SpringApplication

@SuppressWarnings({ "unchecked", "rawtypes" })
	public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
        // 从spring.factory里面加载ApplicationContextInitializer的实现类
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

所以同时需要在spring.factory里面添加如下配置。

org.springframework.context.ApplicationContextInitializer=\
com.demo.InitResourceInitializer

你可能感兴趣的:(spring,spring,spring,boot)