Polish 'Support ConfigurationClassPostProcessor supplier'

See gh-22858
This commit is contained in:
Phillip Webb 2020-08-11 12:22:21 -07:00
parent 06eff45a71
commit 7838c7b072
2 changed files with 96 additions and 21 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -19,10 +19,12 @@ package org.springframework.boot.autoconfigure;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition;
@ -47,6 +49,7 @@ import org.springframework.core.type.classreading.MetadataReaderFactory;
* {@link ConfigurationClassPostProcessor} and Spring Boot. * {@link ConfigurationClassPostProcessor} and Spring Boot.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Dave Syer
*/ */
class SharedMetadataReaderFactoryContextInitializer class SharedMetadataReaderFactoryContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered { implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
@ -56,7 +59,8 @@ class SharedMetadataReaderFactoryContextInitializer
@Override @Override
public void initialize(ConfigurableApplicationContext applicationContext) { public void initialize(ConfigurableApplicationContext applicationContext) {
applicationContext.addBeanFactoryPostProcessor(new CachingMetadataReaderFactoryPostProcessor(applicationContext)); BeanFactoryPostProcessor postProcessor = new CachingMetadataReaderFactoryPostProcessor(applicationContext);
applicationContext.addBeanFactoryPostProcessor(postProcessor);
} }
@Override @Override
@ -69,7 +73,7 @@ class SharedMetadataReaderFactoryContextInitializer
* {@link CachingMetadataReaderFactory} and configure the * {@link CachingMetadataReaderFactory} and configure the
* {@link ConfigurationClassPostProcessor}. * {@link ConfigurationClassPostProcessor}.
*/ */
private static class CachingMetadataReaderFactoryPostProcessor static class CachingMetadataReaderFactoryPostProcessor
implements BeanDefinitionRegistryPostProcessor, PriorityOrdered { implements BeanDefinitionRegistryPostProcessor, PriorityOrdered {
private ConfigurableApplicationContext context; private ConfigurableApplicationContext context;
@ -103,28 +107,64 @@ class SharedMetadataReaderFactoryContextInitializer
private void configureConfigurationClassPostProcessor(BeanDefinitionRegistry registry) { private void configureConfigurationClassPostProcessor(BeanDefinitionRegistry registry) {
try { try {
BeanDefinition definition = registry configureConfigurationClassPostProcessor(
.getBeanDefinition(AnnotationConfigUtils.CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME); registry.getBeanDefinition(AnnotationConfigUtils.CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
if (definition instanceof AbstractBeanDefinition) {
AbstractBeanDefinition bean = (AbstractBeanDefinition) definition;
if (bean.getInstanceSupplier() != null) {
Supplier<?> supplier = bean.getInstanceSupplier();
bean.setInstanceSupplier(() -> modify(supplier));
return;
}
}
definition.getPropertyValues().add("metadataReaderFactory", new RuntimeBeanReference(BEAN_NAME));
} }
catch (NoSuchBeanDefinitionException ex) { catch (NoSuchBeanDefinitionException ex) {
} }
} }
private Object modify(Supplier<?> supplier) { private void configureConfigurationClassPostProcessor(BeanDefinition definition) {
Object object = supplier.get(); if (definition instanceof AbstractBeanDefinition) {
if (object instanceof ConfigurationClassPostProcessor) { configureConfigurationClassPostProcessor((AbstractBeanDefinition) definition);
((ConfigurationClassPostProcessor) object).setMetadataReaderFactory(this.context.getBean(BEAN_NAME, MetadataReaderFactory.class)); return;
} }
return object; configureConfigurationClassPostProcessor(definition.getPropertyValues());
}
private void configureConfigurationClassPostProcessor(AbstractBeanDefinition definition) {
Supplier<?> instanceSupplier = definition.getInstanceSupplier();
if (instanceSupplier != null) {
definition.setInstanceSupplier(
new ConfigurationClassPostProcessorCustomizingSupplier(this.context, instanceSupplier));
return;
}
configureConfigurationClassPostProcessor(definition.getPropertyValues());
}
private void configureConfigurationClassPostProcessor(MutablePropertyValues propertyValues) {
propertyValues.add("metadataReaderFactory", new RuntimeBeanReference(BEAN_NAME));
}
}
/**
* {@link Supplier} used to customize the {@link ConfigurationClassPostProcessor} when
* it's first created.
*/
static class ConfigurationClassPostProcessorCustomizingSupplier implements Supplier<Object> {
private final ConfigurableApplicationContext context;
private final Supplier<?> instanceSupplier;
ConfigurationClassPostProcessorCustomizingSupplier(ConfigurableApplicationContext context,
Supplier<?> instanceSupplier) {
this.context = context;
this.instanceSupplier = instanceSupplier;
}
@Override
public Object get() {
Object instance = this.instanceSupplier.get();
if (instance instanceof ConfigurationClassPostProcessor) {
configureConfigurationClassPostProcessor((ConfigurationClassPostProcessor) instance);
}
return instance;
}
private void configureConfigurationClassPostProcessor(ConfigurationClassPostProcessor instance) {
instance.setMetadataReaderFactory(this.context.getBean(BEAN_NAME, MetadataReaderFactory.class));
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -19,32 +19,43 @@ package org.springframework.boot.autoconfigure;
import java.util.List; import java.util.List;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType; import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer.CachingMetadataReaderFactoryPostProcessor;
import org.springframework.boot.type.classreading.ConcurrentReferenceCachingMetadataReaderFactory;
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.support.GenericApplicationContext; import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/** /**
* Tests for {@link SharedMetadataReaderFactoryContextInitializer}. * Tests for {@link SharedMetadataReaderFactoryContextInitializer}.
* *
* @author Dave Syer * @author Dave Syer
* @author Phillip Webb
*/ */
class SharedMetadataReaderFactoryContextInitializerTests { class SharedMetadataReaderFactoryContextInitializerTests {
@Test @Test
@SuppressWarnings("unchecked")
void checkOrderOfInitializer() { void checkOrderOfInitializer() {
SpringApplication application = new SpringApplication(TestConfig.class); SpringApplication application = new SpringApplication(TestConfig.class);
application.setWebApplicationType(WebApplicationType.NONE); application.setWebApplicationType(WebApplicationType.NONE);
@SuppressWarnings("unchecked")
List<ApplicationContextInitializer<?>> initializers = (List<ApplicationContextInitializer<?>>) ReflectionTestUtils List<ApplicationContextInitializer<?>> initializers = (List<ApplicationContextInitializer<?>>) ReflectionTestUtils
.getField(application, "initializers"); .getField(application, "initializers");
// Simulate what would happen if an initializer was added using spring.factories // Simulate what would happen if an initializer was added using spring.factories
@ -55,6 +66,30 @@ class SharedMetadataReaderFactoryContextInitializerTests {
assertThat(definition.getAttribute("seen")).isEqualTo(true); assertThat(definition.getAttribute("seen")).isEqualTo(true);
} }
@Test
void initializeWhenUsingSupplierDecorates() {
GenericApplicationContext context = new GenericApplicationContext();
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) context.getBeanFactory();
ConfigurationClassPostProcessor configurationAnnotationPostProcessor = mock(
ConfigurationClassPostProcessor.class);
BeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(ConfigurationClassPostProcessor.class).getBeanDefinition();
((AbstractBeanDefinition) beanDefinition).setInstanceSupplier(() -> configurationAnnotationPostProcessor);
registry.registerBeanDefinition(AnnotationConfigUtils.CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME,
beanDefinition);
CachingMetadataReaderFactoryPostProcessor postProcessor = new CachingMetadataReaderFactoryPostProcessor(
context);
postProcessor.postProcessBeanDefinitionRegistry(registry);
context.refresh();
ConfigurationClassPostProcessor bean = context.getBean(ConfigurationClassPostProcessor.class);
assertThat(bean).isSameAs(configurationAnnotationPostProcessor);
ArgumentCaptor<MetadataReaderFactory> metadataReaderFactory = ArgumentCaptor
.forClass(MetadataReaderFactory.class);
verify(configurationAnnotationPostProcessor).setMetadataReaderFactory(metadataReaderFactory.capture());
assertThat(metadataReaderFactory.getValue())
.isInstanceOf(ConcurrentReferenceCachingMetadataReaderFactory.class);
}
static class TestConfig { static class TestConfig {
} }