Add AOT support for immutable ConfigurationProperties bean definitions
This commit introduces a dedicated AotProcessors for immutable configuration properties beans as their bean definition use an instance supplier that needs special handling. If such a bean definition is detected, dedicated code is generated that replicates the behavior of the instance supplier. Closes gh-31247
This commit is contained in:
parent
3f0c14187a
commit
ac16432fad
|
@ -93,20 +93,10 @@ final class ConfigurationPropertiesBeanRegistrar {
|
||||||
RootBeanDefinition definition = new RootBeanDefinition(type);
|
RootBeanDefinition definition = new RootBeanDefinition(type);
|
||||||
definition.setAttribute(BindMethod.class.getName(), bindMethod);
|
definition.setAttribute(BindMethod.class.getName(), bindMethod);
|
||||||
if (bindMethod == BindMethod.VALUE_OBJECT) {
|
if (bindMethod == BindMethod.VALUE_OBJECT) {
|
||||||
definition.setInstanceSupplier(() -> createValueObject(beanName, type));
|
definition.setInstanceSupplier(
|
||||||
|
() -> ConstructorBindingValueSupplier.createValueObject(this.beanFactory, beanName, type));
|
||||||
}
|
}
|
||||||
return definition;
|
return definition;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object createValueObject(String beanName, Class<?> beanType) {
|
|
||||||
ConfigurationPropertiesBean bean = ConfigurationPropertiesBean.forValueObject(beanType, beanName);
|
|
||||||
ConfigurationPropertiesBinder binder = ConfigurationPropertiesBinder.get(this.beanFactory);
|
|
||||||
try {
|
|
||||||
return binder.bindOrCreate(bean);
|
|
||||||
}
|
|
||||||
catch (Exception ex) {
|
|
||||||
throw new ConfigurationPropertiesBindException(bean, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.context.properties;
|
||||||
|
|
||||||
|
import java.lang.reflect.Executable;
|
||||||
|
|
||||||
|
import javax.lang.model.element.Modifier;
|
||||||
|
|
||||||
|
import org.springframework.aot.generate.GeneratedMethod;
|
||||||
|
import org.springframework.aot.generate.GenerationContext;
|
||||||
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
|
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
|
||||||
|
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
|
||||||
|
import org.springframework.beans.factory.aot.BeanRegistrationCode;
|
||||||
|
import org.springframework.beans.factory.aot.BeanRegistrationCodeFragments;
|
||||||
|
import org.springframework.beans.factory.config.BeanDefinition;
|
||||||
|
import org.springframework.beans.factory.support.InstanceSupplier;
|
||||||
|
import org.springframework.beans.factory.support.RegisteredBean;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationPropertiesBean.BindMethod;
|
||||||
|
import org.springframework.javapoet.CodeBlock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link BeanRegistrationAotProcessor} for immutable configuration properties.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
* @see ConstructorBindingValueSupplier
|
||||||
|
*/
|
||||||
|
class ConfigurationPropertiesBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
|
||||||
|
if (!isImmutableConfigurationPropertiesBeanDefinition(registeredBean.getMergedBeanDefinition())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return BeanRegistrationAotContribution.ofBeanRegistrationCodeFragmentsCustomizer(
|
||||||
|
(codeFragments) -> new ConfigurationPropertiesBeanRegistrationCodeFragments(codeFragments,
|
||||||
|
registeredBean));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isImmutableConfigurationPropertiesBeanDefinition(BeanDefinition beanDefinition) {
|
||||||
|
return beanDefinition.hasAttribute(BindMethod.class.getName())
|
||||||
|
&& BindMethod.VALUE_OBJECT.equals(beanDefinition.getAttribute(BindMethod.class.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ConfigurationPropertiesBeanRegistrationCodeFragments extends BeanRegistrationCodeFragments {
|
||||||
|
|
||||||
|
private static final String REGISTERED_BEAN_PARAMETER_NAME = "registeredBean";
|
||||||
|
|
||||||
|
private final RegisteredBean registeredBean;
|
||||||
|
|
||||||
|
ConfigurationPropertiesBeanRegistrationCodeFragments(BeanRegistrationCodeFragments codeFragments,
|
||||||
|
RegisteredBean registeredBean) {
|
||||||
|
super(codeFragments);
|
||||||
|
this.registeredBean = registeredBean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext,
|
||||||
|
BeanRegistrationCode beanRegistrationCode, Executable constructorOrFactoryMethod,
|
||||||
|
boolean allowDirectSupplierShortcut) {
|
||||||
|
GeneratedMethod method = beanRegistrationCode.getMethodGenerator().generateMethod("get", "instance")
|
||||||
|
.using((builder) -> {
|
||||||
|
Class<?> beanClass = this.registeredBean.getBeanClass();
|
||||||
|
builder.addJavadoc("Get the bean instance for '$L'.", this.registeredBean.getBeanName());
|
||||||
|
builder.addModifiers(Modifier.PRIVATE, Modifier.STATIC);
|
||||||
|
builder.returns(beanClass);
|
||||||
|
builder.addParameter(RegisteredBean.class, REGISTERED_BEAN_PARAMETER_NAME);
|
||||||
|
builder.addStatement("$T beanFactory = registeredBean.getBeanFactory()", BeanFactory.class);
|
||||||
|
builder.addStatement("$T beanName = registeredBean.getBeanName()", String.class);
|
||||||
|
builder.addStatement("$T<?> beanClass = registeredBean.getBeanClass()", Class.class);
|
||||||
|
builder.addStatement("return ($T) $T.createValueObject(beanFactory, beanName, beanClass)",
|
||||||
|
beanClass, ConstructorBindingValueSupplier.class);
|
||||||
|
});
|
||||||
|
return CodeBlock.of("$T.of($T::$L)", InstanceSupplier.class, beanRegistrationCode.getClassName(),
|
||||||
|
method.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019-2021 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.context.properties;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class to programmatically bind configuration properties that use constructor
|
||||||
|
* injection.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
* @since 6.0
|
||||||
|
* @see ConstructorBinding
|
||||||
|
*/
|
||||||
|
public abstract class ConstructorBindingValueSupplier {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an immutable {@link ConfigurationProperties} instance for the specified
|
||||||
|
* {@code beanType}.
|
||||||
|
* @param beanFactory the bean factory to use
|
||||||
|
* @param beanName the name of the bean
|
||||||
|
* @param beanType the type of the bean
|
||||||
|
* @return a new instance
|
||||||
|
*/
|
||||||
|
public static Object createValueObject(BeanFactory beanFactory, String beanName, Class<?> beanType) {
|
||||||
|
ConfigurationPropertiesBean bean = ConfigurationPropertiesBean.forValueObject(beanType, beanName);
|
||||||
|
ConfigurationPropertiesBinder binder = ConfigurationPropertiesBinder.get(beanFactory);
|
||||||
|
try {
|
||||||
|
return binder.bindOrCreate(bean);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
throw new ConfigurationPropertiesBindException(bean, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -2,4 +2,7 @@ org.springframework.aot.hint.RuntimeHintsRegistrar=\
|
||||||
org.springframework.boot.logging.logback.LogbackRuntimeHintsRegistrar,\
|
org.springframework.boot.logging.logback.LogbackRuntimeHintsRegistrar,\
|
||||||
org.springframework.boot.WebApplicationType.WebApplicationTypeRuntimeHintsRegistrar
|
org.springframework.boot.WebApplicationType.WebApplicationTypeRuntimeHintsRegistrar
|
||||||
org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\
|
org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\
|
||||||
org.springframework.boot.context.properties.ConfigurationPropertiesBeanFactoryInitializationAotProcessor
|
org.springframework.boot.context.properties.ConfigurationPropertiesBeanFactoryInitializationAotProcessor
|
||||||
|
|
||||||
|
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
|
||||||
|
org.springframework.boot.context.properties.ConfigurationPropertiesBeanRegistrationAotProcessor
|
|
@ -0,0 +1,102 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.boot.context.properties;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.aot.AotFactoriesLoader;
|
||||||
|
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
|
||||||
|
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
|
||||||
|
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||||
|
import org.springframework.beans.factory.support.RegisteredBean;
|
||||||
|
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link ConfigurationPropertiesBeanRegistrationAotProcessor}.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
class ConfigurationPropertiesBeanRegistrationAotProcessorTests {
|
||||||
|
|
||||||
|
private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||||
|
|
||||||
|
private final ConfigurationPropertiesBeanRegistrationAotProcessor processor = new ConfigurationPropertiesBeanRegistrationAotProcessor();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void configurationPropertiesBeanRegistrationAotProcessorIsRegistered() {
|
||||||
|
assertThat(new AotFactoriesLoader(new DefaultListableBeanFactory()).load(BeanRegistrationAotProcessor.class))
|
||||||
|
.anyMatch(ConfigurationPropertiesBeanRegistrationAotProcessor.class::isInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void processAheadOfTimeWithNoConfigurationPropertiesBean() {
|
||||||
|
RootBeanDefinition beanDefinition = new RootBeanDefinition(String.class);
|
||||||
|
this.beanFactory.registerBeanDefinition("test", beanDefinition);
|
||||||
|
BeanRegistrationAotContribution contribution = this.processor
|
||||||
|
.processAheadOfTime(RegisteredBean.of(this.beanFactory, "test"));
|
||||||
|
assertThat(contribution).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void processAheadOfTimeWithJavaBeanConfigurationPropertiesBean() {
|
||||||
|
BeanRegistrationAotContribution contribution = process(JavaBeanSampleBean.class);
|
||||||
|
assertThat(contribution).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void processAheadOfTimeWithValueObjectConfigurationPropertiesBean() {
|
||||||
|
BeanRegistrationAotContribution contribution = process(ValueObjectSampleBean.class);
|
||||||
|
assertThat(contribution).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
private BeanRegistrationAotContribution process(Class<?> type) {
|
||||||
|
ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(this.beanFactory);
|
||||||
|
beanRegistrar.register(type);
|
||||||
|
RegisteredBean registeredBean = RegisteredBean.of(this.beanFactory,
|
||||||
|
this.beanFactory.getBeanDefinitionNames()[0]);
|
||||||
|
return this.processor.processAheadOfTime(registeredBean);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConfigurationProperties("test")
|
||||||
|
public static class JavaBeanSampleBean {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConfigurationProperties("test")
|
||||||
|
public static class ValueObjectSampleBean {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
ValueObjectSampleBean(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue