Skip to content

Commit c3c5eaf

Browse files
committed
Align AOT contributions for injection with the runtime behavior
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 gh-30476
1 parent a9b9424 commit c3c5eaf

File tree

5 files changed

+79
-14
lines changed

5 files changed

+79
-14
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,8 @@ public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registe
286286
String beanName = registeredBean.getBeanName();
287287
RootBeanDefinition beanDefinition = registeredBean.getMergedBeanDefinition();
288288
InjectionMetadata metadata = findInjectionMetadata(beanName, beanClass, beanDefinition);
289-
Collection<AutowiredElement> autowiredElements = getAutowiredElements(metadata);
289+
Collection<AutowiredElement> autowiredElements = getAutowiredElements(metadata,
290+
registeredBean.getMergedBeanDefinition().getPropertyValues());
290291
if (!ObjectUtils.isEmpty(autowiredElements)) {
291292
return new AotContribution(beanClass, autowiredElements, getAutowireCandidateResolver());
292293
}
@@ -295,8 +296,8 @@ public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registe
295296

296297

297298
@SuppressWarnings({ "rawtypes", "unchecked" })
298-
private Collection<AutowiredElement> getAutowiredElements(InjectionMetadata metadata) {
299-
return (Collection) metadata.getInjectedElements();
299+
private Collection<AutowiredElement> getAutowiredElements(InjectionMetadata metadata, PropertyValues propertyValues) {
300+
return (Collection) metadata.getInjectedElements(propertyValues);
300301
}
301302

302303
@Nullable
@@ -752,7 +753,7 @@ public AutowiredMethodElement(Method method, boolean required, @Nullable Propert
752753

753754
@Override
754755
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
755-
if (checkPropertySkipping(pvs)) {
756+
if (!shouldInject(pvs)) {
756757
return;
757758
}
758759
Method method = (Method) this.member;

spring-beans/src/main/java/org/springframework/beans/factory/annotation/InjectionMetadata.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,19 @@ public Collection<InjectedElement> getInjectedElements() {
9797
return Collections.unmodifiableCollection(this.injectedElements);
9898
}
9999

100+
/**
101+
* Return the {@link InjectedElement elements} to inject based on the
102+
* specified {@link PropertyValues}. If a property is already defined
103+
* for an {@link InjectedElement}, it is excluded.
104+
* @param pvs the property values to consider
105+
* @return the elements to inject
106+
* @since 6.0.10
107+
*/
108+
public Collection<InjectedElement> getInjectedElements(@Nullable PropertyValues pvs) {
109+
return this.injectedElements.stream()
110+
.filter(candidate -> candidate.shouldInject(pvs)).toList();
111+
}
112+
100113
/**
101114
* Determine whether this metadata instance needs to be refreshed.
102115
* @param clazz the current target class
@@ -230,21 +243,28 @@ protected final void checkResourceType(Class<?> resourceType) {
230243
}
231244
}
232245

246+
protected boolean shouldInject(@Nullable PropertyValues pvs) {
247+
if (this.isField) {
248+
return true;
249+
}
250+
return !checkPropertySkipping(pvs);
251+
}
252+
233253
/**
234254
* Either this or {@link #getResourceToInject} needs to be overridden.
235255
*/
236256
protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs)
237257
throws Throwable {
238258

259+
if (!shouldInject(pvs)) {
260+
return;
261+
}
239262
if (this.isField) {
240263
Field field = (Field) this.member;
241264
ReflectionUtils.makeAccessible(field);
242265
field.set(target, getResourceToInject(target, requestingBeanName));
243266
}
244267
else {
245-
if (checkPropertySkipping(pvs)) {
246-
return;
247-
}
248268
try {
249269
Method method = (Method) this.member;
250270
ReflectionUtils.makeAccessible(method);

spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanRegistrationAotContributionTests.java

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -65,11 +65,15 @@ class AutowiredAnnotationBeanRegistrationAotContributionTests {
6565

6666
private final DefaultListableBeanFactory beanFactory;
6767

68+
private final AutowiredAnnotationBeanPostProcessor beanPostProcessor;
69+
6870

6971
AutowiredAnnotationBeanRegistrationAotContributionTests() {
7072
this.generationContext = new TestGenerationContext();
7173
this.beanRegistrationCode = new MockBeanRegistrationCode(this.generationContext);
7274
this.beanFactory = new DefaultListableBeanFactory();
75+
this.beanPostProcessor = new AutowiredAnnotationBeanPostProcessor();
76+
this.beanPostProcessor.setBeanFactory(this.beanFactory);
7377
}
7478

7579

@@ -185,10 +189,19 @@ void contributeWhenPackagePrivateMethodInjectionOnParentClassInjectsUsingReflect
185189
});
186190
}
187191

192+
@Test
193+
void contributeWhenMethodInjectionHasMatchingPropertyValue() {
194+
RootBeanDefinition beanDefinition = new RootBeanDefinition(InjectionBean.class);
195+
beanDefinition.getPropertyValues().addPropertyValue("counter", 42);
196+
this.beanFactory.registerBeanDefinition("test", beanDefinition);
197+
BeanRegistrationAotContribution contribution = this.beanPostProcessor
198+
.processAheadOfTime(RegisteredBean.of(this.beanFactory, "test"));
199+
assertThat(contribution).isNull();
200+
}
201+
188202
private RegisteredBean getAndApplyContribution(Class<?> beanClass) {
189203
RegisteredBean registeredBean = registerBean(beanClass);
190-
BeanRegistrationAotContribution contribution = new AutowiredAnnotationBeanPostProcessor()
191-
.processAheadOfTime(registeredBean);
204+
BeanRegistrationAotContribution contribution = this.beanPostProcessor.processAheadOfTime(registeredBean);
192205
assertThat(contribution).isNotNull();
193206
contribution.applyTo(this.generationContext, this.beanRegistrationCode);
194207
return registeredBean;
@@ -229,4 +242,15 @@ private void compile(RegisteredBean registeredBean,
229242
result.accept(compiled.getInstance(BiFunction.class), compiled));
230243
}
231244

245+
static class InjectionBean {
246+
247+
private Integer counter;
248+
249+
@Autowired
250+
public void setCounter(Integer counter) {
251+
this.counter = counter;
252+
}
253+
254+
}
255+
232256
}

spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,8 @@ public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registe
359359
String beanName = registeredBean.getBeanName();
360360
RootBeanDefinition beanDefinition = registeredBean.getMergedBeanDefinition();
361361
InjectionMetadata metadata = findInjectionMetadata(beanDefinition, beanClass, beanName);
362-
Collection<InjectedElement> injectedElements = metadata.getInjectedElements();
362+
Collection<InjectedElement> injectedElements = metadata.getInjectedElements(
363+
registeredBean.getMergedBeanDefinition().getPropertyValues());
363364
if (!CollectionUtils.isEmpty(injectedElements)) {
364365
return new AotContribution(beanClass, injectedElements);
365366
}

spring-orm/src/test/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessorAotContributionTests.java

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import org.springframework.core.test.tools.CompileWithForkedClassLoader;
4444
import org.springframework.core.test.tools.Compiled;
4545
import org.springframework.core.test.tools.TestCompiler;
46+
import org.springframework.lang.Nullable;
4647
import org.springframework.util.ReflectionUtils;
4748

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

71+
@Test
72+
void processAheadOfTimeWhenPersistenceUnitOnFieldAndPropertyValueSet() {
73+
RegisteredBean registeredBean = registerBean(DefaultPersistenceUnitField.class);
74+
registeredBean.getMergedBeanDefinition().getPropertyValues().add("emf", "myEntityManagerFactory");
75+
assertThat(processAheadOfTime(registeredBean)).isNotNull(); // Field not handled by property values
76+
}
77+
78+
@Test
79+
void processAheadOfTimeWhenPersistenceUnitOnMethodAndPropertyValueSet() {
80+
RegisteredBean registeredBean = registerBean(DefaultPersistenceUnitMethod.class);
81+
registeredBean.getMergedBeanDefinition().getPropertyValues().add("emf", "myEntityManagerFactory");
82+
assertThat(processAheadOfTime(registeredBean)).isNull();
83+
}
84+
7085
@Test
7186
void processAheadOfTimeWhenPersistenceUnitOnPublicField() {
7287
RegisteredBean registeredBean = registerBean(DefaultPersistenceUnitField.class);
@@ -192,16 +207,20 @@ private RegisteredBean registerBean(Class<?> beanClass) {
192207

193208
private void testCompile(RegisteredBean registeredBean,
194209
BiConsumer<BiConsumer<RegisteredBean, Object>, Compiled> result) {
195-
PersistenceAnnotationBeanPostProcessor postProcessor = new PersistenceAnnotationBeanPostProcessor();
196-
BeanRegistrationAotContribution contribution = postProcessor
197-
.processAheadOfTime(registeredBean);
210+
BeanRegistrationAotContribution contribution = processAheadOfTime(registeredBean);
198211
BeanRegistrationCode beanRegistrationCode = mock();
199212
contribution.applyTo(generationContext, beanRegistrationCode);
200213
generationContext.writeGeneratedContent();
201214
TestCompiler.forSystem().with(generationContext)
202215
.compile(compiled -> result.accept(new Invoker(compiled), compiled));
203216
}
204217

218+
@Nullable
219+
private BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
220+
PersistenceAnnotationBeanPostProcessor postProcessor = new PersistenceAnnotationBeanPostProcessor();
221+
return postProcessor.processAheadOfTime(registeredBean);
222+
}
223+
205224
static class Invoker implements BiConsumer<RegisteredBean, Object> {
206225

207226
private Compiled compiled;

0 commit comments

Comments
 (0)