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-31247pull/31277/head
parent
3f0c14187a
commit
ac16432fad
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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