Skip to content

Commit

Permalink
Align AOT contributions for injection with the runtime behavior
Browse files Browse the repository at this point in the history
Bean post processors that use InjectionMetadata checks if a property
value for the element it is about to inject is set and skip it, so
that the property value is used. Previously, the AOT contribution for
the same behavior did not check if a matching property value is set
and therefore override the user-defined value.

This commit introduces an additional method that filters the injected
element list so that only the elements that should be processed are
defined.

Closes spring-projectsgh-30476
  • Loading branch information
snicoll authored and mdeinum committed Jun 29, 2023
1 parent b32aa90 commit a07cee2
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,8 @@ public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registe
String beanName = registeredBean.getBeanName();
RootBeanDefinition beanDefinition = registeredBean.getMergedBeanDefinition();
InjectionMetadata metadata = findInjectionMetadata(beanName, beanClass, beanDefinition);
Collection<AutowiredElement> autowiredElements = getAutowiredElements(metadata);
Collection<AutowiredElement> autowiredElements = getAutowiredElements(metadata,
registeredBean.getMergedBeanDefinition().getPropertyValues());
if (!ObjectUtils.isEmpty(autowiredElements)) {
return new AotContribution(beanClass, autowiredElements, getAutowireCandidateResolver());
}
Expand All @@ -295,8 +296,8 @@ public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registe


@SuppressWarnings({ "rawtypes", "unchecked" })
private Collection<AutowiredElement> getAutowiredElements(InjectionMetadata metadata) {
return (Collection) metadata.getInjectedElements();
private Collection<AutowiredElement> getAutowiredElements(InjectionMetadata metadata, PropertyValues propertyValues) {
return (Collection) metadata.getInjectedElements(propertyValues);
}

@Nullable
Expand Down Expand Up @@ -752,7 +753,7 @@ public AutowiredMethodElement(Method method, boolean required, @Nullable Propert

@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
if (checkPropertySkipping(pvs)) {
if (!shouldInject(pvs)) {
return;
}
Method method = (Method) this.member;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,19 @@ public Collection<InjectedElement> getInjectedElements() {
return Collections.unmodifiableCollection(this.injectedElements);
}

/**
* Return the {@link InjectedElement elements} to inject based on the
* specified {@link PropertyValues}. If a property is already defined
* for an {@link InjectedElement}, it is excluded.
* @param pvs the property values to consider
* @return the elements to inject
* @since 6.0.10
*/
public Collection<InjectedElement> getInjectedElements(@Nullable PropertyValues pvs) {
return this.injectedElements.stream()
.filter(candidate -> candidate.shouldInject(pvs)).toList();
}

/**
* Determine whether this metadata instance needs to be refreshed.
* @param clazz the current target class
Expand Down Expand Up @@ -230,21 +243,28 @@ protected final void checkResourceType(Class<?> resourceType) {
}
}

protected boolean shouldInject(@Nullable PropertyValues pvs) {
if (this.isField) {
return true;
}
return !checkPropertySkipping(pvs);
}

/**
* Either this or {@link #getResourceToInject} needs to be overridden.
*/
protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs)
throws Throwable {

if (!shouldInject(pvs)) {
return;
}
if (this.isField) {
Field field = (Field) this.member;
ReflectionUtils.makeAccessible(field);
field.set(target, getResourceToInject(target, requestingBeanName));
}
else {
if (checkPropertySkipping(pvs)) {
return;
}
try {
Method method = (Method) this.member;
ReflectionUtils.makeAccessible(method);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
Expand Down Expand Up @@ -65,11 +65,15 @@ class AutowiredAnnotationBeanRegistrationAotContributionTests {

private final DefaultListableBeanFactory beanFactory;

private final AutowiredAnnotationBeanPostProcessor beanPostProcessor;


AutowiredAnnotationBeanRegistrationAotContributionTests() {
this.generationContext = new TestGenerationContext();
this.beanRegistrationCode = new MockBeanRegistrationCode(this.generationContext);
this.beanFactory = new DefaultListableBeanFactory();
this.beanPostProcessor = new AutowiredAnnotationBeanPostProcessor();
this.beanPostProcessor.setBeanFactory(this.beanFactory);
}


Expand Down Expand Up @@ -185,10 +189,19 @@ void contributeWhenPackagePrivateMethodInjectionOnParentClassInjectsUsingReflect
});
}

@Test
void contributeWhenMethodInjectionHasMatchingPropertyValue() {
RootBeanDefinition beanDefinition = new RootBeanDefinition(InjectionBean.class);
beanDefinition.getPropertyValues().addPropertyValue("counter", 42);
this.beanFactory.registerBeanDefinition("test", beanDefinition);
BeanRegistrationAotContribution contribution = this.beanPostProcessor
.processAheadOfTime(RegisteredBean.of(this.beanFactory, "test"));
assertThat(contribution).isNull();
}

private RegisteredBean getAndApplyContribution(Class<?> beanClass) {
RegisteredBean registeredBean = registerBean(beanClass);
BeanRegistrationAotContribution contribution = new AutowiredAnnotationBeanPostProcessor()
.processAheadOfTime(registeredBean);
BeanRegistrationAotContribution contribution = this.beanPostProcessor.processAheadOfTime(registeredBean);
assertThat(contribution).isNotNull();
contribution.applyTo(this.generationContext, this.beanRegistrationCode);
return registeredBean;
Expand Down Expand Up @@ -229,4 +242,15 @@ private void compile(RegisteredBean registeredBean,
result.accept(compiled.getInstance(BiFunction.class), compiled));
}

static class InjectionBean {

private Integer counter;

@Autowired
public void setCounter(Integer counter) {
this.counter = counter;
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,8 @@ public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registe
String beanName = registeredBean.getBeanName();
RootBeanDefinition beanDefinition = registeredBean.getMergedBeanDefinition();
InjectionMetadata metadata = findInjectionMetadata(beanDefinition, beanClass, beanName);
Collection<InjectedElement> injectedElements = metadata.getInjectedElements();
Collection<InjectedElement> injectedElements = metadata.getInjectedElements(
registeredBean.getMergedBeanDefinition().getPropertyValues());
if (!CollectionUtils.isEmpty(injectedElements)) {
return new AotContribution(beanClass, injectedElements);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import org.springframework.core.test.tools.CompileWithForkedClassLoader;
import org.springframework.core.test.tools.Compiled;
import org.springframework.core.test.tools.TestCompiler;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;

import static org.assertj.core.api.Assertions.assertThat;
Expand All @@ -67,6 +68,20 @@ void setup() {
this.generationContext = new TestGenerationContext();
}

@Test
void processAheadOfTimeWhenPersistenceUnitOnFieldAndPropertyValueSet() {
RegisteredBean registeredBean = registerBean(DefaultPersistenceUnitField.class);
registeredBean.getMergedBeanDefinition().getPropertyValues().add("emf", "myEntityManagerFactory");
assertThat(processAheadOfTime(registeredBean)).isNotNull(); // Field not handled by property values
}

@Test
void processAheadOfTimeWhenPersistenceUnitOnMethodAndPropertyValueSet() {
RegisteredBean registeredBean = registerBean(DefaultPersistenceUnitMethod.class);
registeredBean.getMergedBeanDefinition().getPropertyValues().add("emf", "myEntityManagerFactory");
assertThat(processAheadOfTime(registeredBean)).isNull();
}

@Test
void processAheadOfTimeWhenPersistenceUnitOnPublicField() {
RegisteredBean registeredBean = registerBean(DefaultPersistenceUnitField.class);
Expand Down Expand Up @@ -192,16 +207,20 @@ private RegisteredBean registerBean(Class<?> beanClass) {

private void testCompile(RegisteredBean registeredBean,
BiConsumer<BiConsumer<RegisteredBean, Object>, Compiled> result) {
PersistenceAnnotationBeanPostProcessor postProcessor = new PersistenceAnnotationBeanPostProcessor();
BeanRegistrationAotContribution contribution = postProcessor
.processAheadOfTime(registeredBean);
BeanRegistrationAotContribution contribution = processAheadOfTime(registeredBean);
BeanRegistrationCode beanRegistrationCode = mock();
contribution.applyTo(generationContext, beanRegistrationCode);
generationContext.writeGeneratedContent();
TestCompiler.forSystem().with(generationContext)
.compile(compiled -> result.accept(new Invoker(compiled), compiled));
}

@Nullable
private BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
PersistenceAnnotationBeanPostProcessor postProcessor = new PersistenceAnnotationBeanPostProcessor();
return postProcessor.processAheadOfTime(registeredBean);
}

static class Invoker implements BiConsumer<RegisteredBean, Object> {

private Compiled compiled;
Expand Down

0 comments on commit a07cee2

Please sign in to comment.