Introduce AOT variant of BeanPostProcessor
This commit introduces an optional interface that a BeanPostProcessor can implement to opt-in for providing a generated code equivalent of what it does with a regular runtime. This commit has a first implementation of this interface with AutowiredAnnotationBeanPostProcessor replacing its processing of autowired annotations by generated code. Closes gh-27921
This commit is contained in:
parent
5bbc7dbce2
commit
2141373c43
|
|
@ -22,10 +22,12 @@ import java.lang.reflect.AccessibleObject;
|
|||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
|
|
@ -37,6 +39,8 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.aot.generator.CodeContribution;
|
||||
import org.springframework.aot.hint.ExecutableMode;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.PropertyValues;
|
||||
|
|
@ -48,9 +52,13 @@ import org.springframework.beans.factory.BeanFactoryUtils;
|
|||
import org.springframework.beans.factory.InjectionPoint;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.UnsatisfiedDependencyException;
|
||||
import org.springframework.beans.factory.annotation.InjectionMetadata.InjectedElement;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.DependencyDescriptor;
|
||||
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
|
||||
import org.springframework.beans.factory.generator.BeanInstanceAotPostProcessor;
|
||||
import org.springframework.beans.factory.generator.BeanInstanceContributor;
|
||||
import org.springframework.beans.factory.generator.InjectionGenerator;
|
||||
import org.springframework.beans.factory.support.LookupOverride;
|
||||
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
|
|
@ -65,6 +73,7 @@ import org.springframework.core.annotation.MergedAnnotations;
|
|||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
|
|
@ -131,7 +140,7 @@ import org.springframework.util.StringUtils;
|
|||
* @see Value
|
||||
*/
|
||||
public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor,
|
||||
MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware {
|
||||
MergedBeanDefinitionPostProcessor, BeanInstanceAotPostProcessor, PriorityOrdered, BeanFactoryAware {
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
|
|
@ -255,8 +264,22 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
|||
|
||||
@Override
|
||||
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
|
||||
findInjectionMetadata(beanName, beanType, beanDefinition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanInstanceContributor buildAotContributor(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
|
||||
InjectionMetadata metadata = findInjectionMetadata(beanName, beanType, beanDefinition);
|
||||
Collection<InjectedElement> injectedElements = metadata.getInjectedElements();
|
||||
return (!ObjectUtils.isEmpty(injectedElements)
|
||||
? new AutowiredAnnotationBeanInstanceContributor(injectedElements)
|
||||
: BeanInstanceContributor.NO_OP);
|
||||
}
|
||||
|
||||
private InjectionMetadata findInjectionMetadata(String beanName, Class<?> beanType, RootBeanDefinition beanDefinition) {
|
||||
InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
|
||||
metadata.checkConfigMembers(beanDefinition);
|
||||
return metadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -798,6 +821,52 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
|||
}
|
||||
}
|
||||
|
||||
private static final class AutowiredAnnotationBeanInstanceContributor implements BeanInstanceContributor {
|
||||
|
||||
private final Collection<InjectedElement> injectedElements;
|
||||
|
||||
private final InjectionGenerator generator;
|
||||
|
||||
AutowiredAnnotationBeanInstanceContributor(Collection<InjectedElement> injectedElements) {
|
||||
this.injectedElements = injectedElements;
|
||||
this.generator = new InjectionGenerator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contribute(CodeContribution contribution) {
|
||||
this.injectedElements.forEach(element -> {
|
||||
boolean isRequired = isRequired(element);
|
||||
Member member = element.getMember();
|
||||
analyzeMember(contribution, member);
|
||||
contribution.statements().addStatement(this.generator.writeInjection(member, isRequired));
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isRequired(InjectedElement element) {
|
||||
if (element instanceof AutowiredMethodElement injectedMethod) {
|
||||
return injectedMethod.required;
|
||||
}
|
||||
else if (element instanceof AutowiredFieldElement injectedField) {
|
||||
return injectedField.required;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void analyzeMember(CodeContribution contribution, Member member) {
|
||||
if (member instanceof Method method) {
|
||||
contribution.runtimeHints().reflection().registerMethod(method,
|
||||
hint -> hint.setModes(ExecutableMode.INTROSPECT));
|
||||
contribution.protectedAccess().analyze(member,
|
||||
this.generator.getProtectedAccessInjectionOptions(member));
|
||||
}
|
||||
else if (member instanceof Field field) {
|
||||
contribution.runtimeHints().reflection().registerField(field);
|
||||
contribution.protectedAccess().analyze(member,
|
||||
this.generator.getProtectedAccessInjectionOptions(member));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* DependencyDescriptor variant with a pre-resolved target bean name.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
|
|
@ -88,6 +88,14 @@ public class InjectionMetadata {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the {@link InjectedElement elements} to inject.
|
||||
* @return the elements to inject
|
||||
*/
|
||||
public Collection<InjectedElement> getInjectedElements() {
|
||||
return Collections.unmodifiableCollection(this.injectedElements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether this metadata instance needs to be refreshed.
|
||||
* @param clazz the current target class
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 2002-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.beans.factory.generator;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
|
||||
/**
|
||||
* Strategy interface to be implemented by a {@link BeanPostProcessor} that
|
||||
* participates in a bean instance setup and can provide an equivalent setup
|
||||
* using generated code.
|
||||
*
|
||||
* <p>Contrary to other bean post processors, implementations of this interface
|
||||
* are instantiated at build-time and should not rely on other beans in the
|
||||
* context.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface BeanInstanceAotPostProcessor extends BeanPostProcessor {
|
||||
|
||||
/**
|
||||
* Build a {@link BeanInstanceContributor} for the given bean definition.
|
||||
* @param beanDefinition the merged bean definition for the bean
|
||||
* @param beanType the actual type of the managed bean instance
|
||||
* @param beanName the name of the bean
|
||||
* @return the contributor to use
|
||||
*/
|
||||
BeanInstanceContributor buildAotContributor(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* Copyright 2002-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.beans.factory.annotation;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.generator.CodeContribution;
|
||||
import org.springframework.aot.generator.DefaultCodeContribution;
|
||||
import org.springframework.aot.hint.ExecutableMode;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.TypeReference;
|
||||
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessorTests.ResourceInjectionBean;
|
||||
import org.springframework.beans.factory.generator.BeanInstanceContributor;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.testfixture.beans.TestBean;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.javapoet.support.CodeSnippet;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for code contribution of {@link AutowiredAnnotationBeanPostProcessor}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class AutowiredAnnotationBeanInstanceContributorTests {
|
||||
|
||||
@Test
|
||||
void buildAotContributorWithPackageProtectedFieldInjection() {
|
||||
CodeContribution contribution = contribute(PackageProtectedFieldInjectionSample.class);
|
||||
assertThat(CodeSnippet.process(contribution.statements().toCodeBlock())).isEqualTo("""
|
||||
instanceContext.field("environment", Environment.class)
|
||||
.invoke(beanFactory, (attributes) -> bean.environment = attributes.get(0))""");
|
||||
assertThat(contribution.runtimeHints().reflection().typeHints()).singleElement().satisfies(typeHint -> {
|
||||
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(PackageProtectedFieldInjectionSample.class));
|
||||
assertThat(typeHint.fields()).singleElement().satisfies(fieldHint -> {
|
||||
assertThat(fieldHint.getName()).isEqualTo("environment");
|
||||
assertThat(fieldHint.isAllowWrite()).isTrue();
|
||||
assertThat(fieldHint.isAllowUnsafeAccess()).isFalse();
|
||||
});
|
||||
});
|
||||
assertThat(contribution.protectedAccess().getPrivilegedPackageName("com.example"))
|
||||
.isEqualTo(PackageProtectedFieldInjectionSample.class.getPackageName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildAotContributorWithPrivateFieldInjection() {
|
||||
CodeContribution contribution = contribute(PrivateFieldInjectionSample.class);
|
||||
assertThat(CodeSnippet.process(contribution.statements().toCodeBlock())).isEqualTo("""
|
||||
instanceContext.field("environment", Environment.class)
|
||||
.invoke(beanFactory, (attributes) -> {
|
||||
Field environmentField = ReflectionUtils.findField(AutowiredAnnotationBeanInstanceContributorTests.PrivateFieldInjectionSample.class, "environment", Environment.class);
|
||||
ReflectionUtils.makeAccessible(environmentField);
|
||||
ReflectionUtils.setField(environmentField, bean, attributes.get(0));
|
||||
})""");
|
||||
assertThat(contribution.runtimeHints().reflection().typeHints()).singleElement().satisfies(typeHint -> {
|
||||
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(PrivateFieldInjectionSample.class));
|
||||
assertThat(typeHint.fields()).singleElement().satisfies(fieldHint -> {
|
||||
assertThat(fieldHint.getName()).isEqualTo("environment");
|
||||
assertThat(fieldHint.isAllowWrite()).isTrue();
|
||||
assertThat(fieldHint.isAllowUnsafeAccess()).isFalse();
|
||||
});
|
||||
});
|
||||
assertThat(contribution.protectedAccess().isAccessible("com.example")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildAotContributorWithPublicMethodInjection() {
|
||||
CodeContribution contribution = contribute(PublicMethodInjectionSample.class);
|
||||
assertThat(CodeSnippet.process(contribution.statements().toCodeBlock())).isEqualTo("""
|
||||
instanceContext.method("setTestBean", TestBean.class)
|
||||
.invoke(beanFactory, (attributes) -> bean.setTestBean(attributes.get(0)))""");
|
||||
assertThat(contribution.runtimeHints().reflection().typeHints()).singleElement().satisfies(typeHint -> {
|
||||
assertThat(typeHint.getType()).isEqualTo(TypeReference.of(PublicMethodInjectionSample.class));
|
||||
assertThat(typeHint.methods()).singleElement().satisfies(methodHint -> {
|
||||
assertThat(methodHint.getName()).isEqualTo("setTestBean");
|
||||
assertThat(methodHint.getModes()).contains(ExecutableMode.INTROSPECT);
|
||||
});
|
||||
});
|
||||
assertThat(contribution.protectedAccess().isAccessible("com.example")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildAotContributorWithInjectionPoints() {
|
||||
CodeContribution contribution = contribute(ResourceInjectionBean.class);
|
||||
assertThat(CodeSnippet.process(contribution.statements().toCodeBlock())).isEqualTo("""
|
||||
instanceContext.field("testBean", TestBean.class)
|
||||
.resolve(beanFactory, false).ifResolved((attributes) -> {
|
||||
Field testBeanField = ReflectionUtils.findField(AutowiredAnnotationBeanPostProcessorTests.ResourceInjectionBean.class, "testBean", TestBean.class);
|
||||
ReflectionUtils.makeAccessible(testBeanField);
|
||||
ReflectionUtils.setField(testBeanField, bean, attributes.get(0));
|
||||
});
|
||||
instanceContext.method("setTestBean2", TestBean.class)
|
||||
.invoke(beanFactory, (attributes) -> bean.setTestBean2(attributes.get(0)));""");
|
||||
assertThat(contribution.runtimeHints().reflection().typeHints()).singleElement().satisfies(typeHint -> {
|
||||
assertThat(typeHint.fields()).singleElement().satisfies(fieldHint ->
|
||||
assertThat(fieldHint.getName()).isEqualTo("testBean"));
|
||||
assertThat(typeHint.methods()).singleElement().satisfies(methodHint ->
|
||||
assertThat(methodHint.getName()).isEqualTo("setTestBean2"));
|
||||
});
|
||||
assertThat(contribution.protectedAccess().isAccessible("com.example")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildAotContributorWithoutInjectionPoints() {
|
||||
BeanInstanceContributor contributor = createAotContributor(String.class);
|
||||
assertThat(contributor).isNotNull().isSameAs(BeanInstanceContributor.NO_OP);
|
||||
}
|
||||
|
||||
private DefaultCodeContribution contribute(Class<?> type) {
|
||||
BeanInstanceContributor contributor = createAotContributor(type);
|
||||
assertThat(contributor).isNotNull();
|
||||
DefaultCodeContribution contribution = new DefaultCodeContribution(new RuntimeHints());
|
||||
contributor.contribute(contribution);
|
||||
return contribution;
|
||||
}
|
||||
|
||||
private BeanInstanceContributor createAotContributor(Class<?> type) {
|
||||
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
|
||||
RootBeanDefinition beanDefinition = new RootBeanDefinition(type);
|
||||
return bpp.buildAotContributor(beanDefinition, type, "test");
|
||||
}
|
||||
|
||||
|
||||
public static class PackageProtectedFieldInjectionSample {
|
||||
|
||||
@Autowired
|
||||
Environment environment;
|
||||
|
||||
}
|
||||
|
||||
public static class PrivateFieldInjectionSample {
|
||||
|
||||
@Autowired
|
||||
private Environment environment;
|
||||
|
||||
}
|
||||
|
||||
public static class PublicMethodInjectionSample {
|
||||
|
||||
@Autowired
|
||||
public void setTestBean(TestBean testBean) {
|
||||
|
||||
}
|
||||
|
||||
public void setUnrelated(String unrelated) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue