Skip to content

Commit 4557158

Browse files
committed
Infer proxy on @Lazy-annotated injection points
This commit makes use of the new `getLazyResolutionProxyClass` on `AutowireCandidateResolver` to detect if a injection point requires a proxy. Closes gh-28980
1 parent e5f9bb7 commit 4557158

File tree

7 files changed

+387
-4
lines changed

7 files changed

+387
-4
lines changed

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

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.lang.reflect.Member;
2626
import java.lang.reflect.Method;
2727
import java.lang.reflect.Modifier;
28+
import java.lang.reflect.Proxy;
2829
import java.util.ArrayList;
2930
import java.util.Arrays;
3031
import java.util.Collection;
@@ -70,6 +71,8 @@
7071
import org.springframework.beans.factory.config.DependencyDescriptor;
7172
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
7273
import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;
74+
import org.springframework.beans.factory.support.AutowireCandidateResolver;
75+
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
7376
import org.springframework.beans.factory.support.LookupOverride;
7477
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
7578
import org.springframework.beans.factory.support.RegisteredBean;
@@ -289,7 +292,7 @@ public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registe
289292
InjectionMetadata metadata = findInjectionMetadata(beanName, beanClass, beanDefinition);
290293
Collection<AutowiredElement> autowiredElements = getAutowiredElements(metadata);
291294
if (!ObjectUtils.isEmpty(autowiredElements)) {
292-
return new AotContribution(beanClass, autowiredElements);
295+
return new AotContribution(beanClass, autowiredElements, getAutowireCandidateResolver());
293296
}
294297
return null;
295298
}
@@ -300,6 +303,14 @@ private Collection<AutowiredElement> getAutowiredElements(InjectionMetadata meta
300303
return (Collection) metadata.getInjectedElements();
301304
}
302305

306+
@Nullable
307+
private AutowireCandidateResolver getAutowireCandidateResolver() {
308+
if (this.beanFactory instanceof DefaultListableBeanFactory lbf) {
309+
return lbf.getAutowireCandidateResolver();
310+
}
311+
return null;
312+
}
313+
303314
private InjectionMetadata findInjectionMetadata(String beanName, Class<?> beanType, RootBeanDefinition beanDefinition) {
304315
InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
305316
metadata.checkConfigMembers(beanDefinition);
@@ -914,10 +925,15 @@ private static class AotContribution implements BeanRegistrationAotContribution
914925

915926
private final Collection<AutowiredElement> autowiredElements;
916927

928+
@Nullable
929+
private final AutowireCandidateResolver candidateResolver;
930+
931+
AotContribution(Class<?> target, Collection<AutowiredElement> autowiredElements,
932+
@Nullable AutowireCandidateResolver candidateResolver) {
917933

918-
AotContribution(Class<?> target, Collection<AutowiredElement> autowiredElements) {
919934
this.target = target;
920935
this.autowiredElements = autowiredElements;
936+
this.candidateResolver = candidateResolver;
921937
}
922938

923939

@@ -940,6 +956,10 @@ public void applyTo(GenerationContext generationContext,
940956
});
941957
beanRegistrationCode.addInstancePostProcessor(
942958
MethodReference.ofStatic(generatedClass.getName(), generateMethod.getName()));
959+
960+
if (this.candidateResolver != null) {
961+
registerHints(generationContext.getRuntimeHints());
962+
}
943963
}
944964

945965
private CodeBlock generateMethodCode(RuntimeHints hints) {
@@ -1023,6 +1043,35 @@ private CodeBlock generateParameterTypesCode(Class<?>[] parameterTypes) {
10231043
return code.build();
10241044
}
10251045

1046+
private void registerHints(RuntimeHints runtimeHints) {
1047+
this.autowiredElements.forEach(autowiredElement -> {
1048+
boolean required = autowiredElement.required;
1049+
Member member = autowiredElement.getMember();
1050+
if (member instanceof Field field) {
1051+
DependencyDescriptor dependencyDescriptor = new DependencyDescriptor(
1052+
field, required);
1053+
registerProxyIfNecessary(runtimeHints, dependencyDescriptor);
1054+
}
1055+
if (member instanceof Method method) {
1056+
Class<?>[] parameterTypes = method.getParameterTypes();
1057+
for (int i = 0; i < parameterTypes.length; i++) {
1058+
MethodParameter methodParam = new MethodParameter(method, i);
1059+
DependencyDescriptor dependencyDescriptor = new DependencyDescriptor(
1060+
methodParam, required);
1061+
registerProxyIfNecessary(runtimeHints, dependencyDescriptor);
1062+
}
1063+
}
1064+
});
1065+
}
1066+
1067+
private void registerProxyIfNecessary(RuntimeHints runtimeHints, DependencyDescriptor dependencyDescriptor) {
1068+
Class<?> proxyType = this.candidateResolver
1069+
.getLazyResolutionProxyClass(dependencyDescriptor, null);
1070+
if (proxyType != null && Proxy.isProxyClass(proxyType)) {
1071+
runtimeHints.proxies().registerJdkProxy(proxyType.getInterfaces());
1072+
}
1073+
}
1074+
10261075
}
10271076

10281077
}

spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionMethodGenerator.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616

1717
package org.springframework.beans.factory.aot;
1818

19+
import java.lang.reflect.Constructor;
1920
import java.lang.reflect.Executable;
21+
import java.lang.reflect.Method;
22+
import java.lang.reflect.Proxy;
2023
import java.util.List;
2124

2225
import javax.lang.model.element.Modifier;
@@ -26,8 +29,13 @@
2629
import org.springframework.aot.generate.GeneratedMethods;
2730
import org.springframework.aot.generate.GenerationContext;
2831
import org.springframework.aot.generate.MethodReference;
32+
import org.springframework.aot.hint.RuntimeHints;
2933
import org.springframework.beans.factory.config.BeanDefinition;
34+
import org.springframework.beans.factory.config.DependencyDescriptor;
35+
import org.springframework.beans.factory.support.AutowireCandidateResolver;
36+
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
3037
import org.springframework.beans.factory.support.RegisteredBean;
38+
import org.springframework.core.MethodParameter;
3139
import org.springframework.javapoet.ClassName;
3240
import org.springframework.lang.Nullable;
3341
import org.springframework.util.StringUtils;
@@ -83,6 +91,7 @@ class BeanDefinitionMethodGenerator {
8391
MethodReference generateBeanDefinitionMethod(GenerationContext generationContext,
8492
BeanRegistrationsCode beanRegistrationsCode) {
8593

94+
registerRuntimeHintsIfNecessary(generationContext.getRuntimeHints());
8695
BeanRegistrationCodeFragments codeFragments = getCodeFragments(generationContext,
8796
beanRegistrationsCode);
8897
Class<?> target = codeFragments.getTarget(this.registeredBean,
@@ -166,4 +175,54 @@ private String getSimpleBeanName(String beanName) {
166175
return StringUtils.uncapitalize(beanName);
167176
}
168177

178+
private void registerRuntimeHintsIfNecessary(RuntimeHints runtimeHints) {
179+
if (this.registeredBean.getBeanFactory() instanceof DefaultListableBeanFactory dlbf) {
180+
ProxyRuntimeHintsRegistrar registrar = new ProxyRuntimeHintsRegistrar(dlbf.getAutowireCandidateResolver());
181+
if (this.constructorOrFactoryMethod instanceof Method method) {
182+
registrar.registerRuntimeHints(runtimeHints, method);
183+
}
184+
else if (this.constructorOrFactoryMethod instanceof Constructor<?> constructor) {
185+
registrar.registerRuntimeHints(runtimeHints, constructor);
186+
}
187+
}
188+
}
189+
190+
private static class ProxyRuntimeHintsRegistrar {
191+
192+
private final AutowireCandidateResolver candidateResolver;
193+
194+
public ProxyRuntimeHintsRegistrar(AutowireCandidateResolver candidateResolver) {
195+
this.candidateResolver = candidateResolver;
196+
}
197+
198+
public void registerRuntimeHints(RuntimeHints runtimeHints, Method method) {
199+
Class<?>[] parameterTypes = method.getParameterTypes();
200+
for (int i = 0; i < parameterTypes.length; i++) {
201+
MethodParameter methodParam = new MethodParameter(method, i);
202+
DependencyDescriptor dependencyDescriptor = new DependencyDescriptor(
203+
methodParam, true);
204+
registerProxyIfNecessary(runtimeHints, dependencyDescriptor);
205+
}
206+
}
207+
208+
public void registerRuntimeHints(RuntimeHints runtimeHints, Constructor<?> constructor) {
209+
Class<?>[] parameterTypes = constructor.getParameterTypes();
210+
for (int i = 0; i < parameterTypes.length; i++) {
211+
MethodParameter methodParam = new MethodParameter(constructor, i);
212+
DependencyDescriptor dependencyDescriptor = new DependencyDescriptor(
213+
methodParam, true);
214+
registerProxyIfNecessary(runtimeHints, dependencyDescriptor);
215+
}
216+
}
217+
218+
private void registerProxyIfNecessary(RuntimeHints runtimeHints, DependencyDescriptor dependencyDescriptor) {
219+
Class<?> proxyType = this.candidateResolver
220+
.getLazyResolutionProxyClass(dependencyDescriptor, null);
221+
if (proxyType != null && Proxy.isProxyClass(proxyType)) {
222+
runtimeHints.proxies().registerJdkProxy(proxyType.getInterfaces());
223+
}
224+
}
225+
226+
}
227+
169228
}

spring-context/src/test/java/org/springframework/context/aot/ApplicationContextAotGeneratorTests.java

Lines changed: 97 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,15 @@
1717
package org.springframework.context.aot;
1818

1919
import java.io.IOException;
20+
import java.lang.reflect.Proxy;
2021
import java.util.function.BiConsumer;
2122

2223
import org.junit.jupiter.api.Test;
2324

2425
import org.springframework.aot.generate.GeneratedFiles.Kind;
26+
import org.springframework.aot.generate.GenerationContext;
2527
import org.springframework.aot.hint.MemberCategory;
28+
import org.springframework.aot.hint.RuntimeHints;
2629
import org.springframework.aot.hint.TypeReference;
2730
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
2831
import org.springframework.aot.test.generator.compile.Compiled;
@@ -44,11 +47,18 @@
4447
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
4548
import org.springframework.context.annotation.AnnotationConfigUtils;
4649
import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor;
50+
import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver;
4751
import org.springframework.context.support.GenericApplicationContext;
4852
import org.springframework.context.testfixture.context.generator.SimpleComponent;
4953
import org.springframework.context.testfixture.context.generator.annotation.AutowiredComponent;
5054
import org.springframework.context.testfixture.context.generator.annotation.CglibConfiguration;
5155
import org.springframework.context.testfixture.context.generator.annotation.InitDestroyComponent;
56+
import org.springframework.context.testfixture.context.generator.annotation.LazyAutowiredFieldComponent;
57+
import org.springframework.context.testfixture.context.generator.annotation.LazyAutowiredMethodComponent;
58+
import org.springframework.context.testfixture.context.generator.annotation.LazyConstructorArgumentComponent;
59+
import org.springframework.context.testfixture.context.generator.annotation.LazyFactoryMethodArgumentComponent;
60+
import org.springframework.core.env.Environment;
61+
import org.springframework.core.io.ResourceLoader;
5262
import org.springframework.core.testfixture.aot.generate.TestGenerationContext;
5363

5464
import static org.assertj.core.api.Assertions.assertThat;
@@ -93,6 +103,87 @@ void processAheadOfTimeWhenHasAutowiring() {
93103
});
94104
}
95105

106+
@Test
107+
void processAheadOfTimeWhenHasLazyAutowiringOnField() {
108+
testAutowiredComponent(LazyAutowiredFieldComponent.class, (bean, generationContext) -> {
109+
Environment environment = bean.getEnvironment();
110+
assertThat(environment).isInstanceOf(Proxy.class);
111+
ResourceLoader resourceLoader = bean.getResourceLoader();
112+
assertThat(resourceLoader).isNotInstanceOf(Proxy.class);
113+
RuntimeHints runtimeHints = generationContext.getRuntimeHints();
114+
assertThat(runtimeHints.proxies().jdkProxies()).singleElement().satisfies(proxyHint ->
115+
assertThat(proxyHint.getProxiedInterfaces()).isEqualTo(TypeReference.listOf(
116+
environment.getClass().getInterfaces())));
117+
118+
});
119+
}
120+
121+
@Test
122+
void processAheadOfTimeWhenHasLazyAutowiringOnMethod() {
123+
testAutowiredComponent(LazyAutowiredMethodComponent.class, (bean, generationContext) -> {
124+
Environment environment = bean.getEnvironment();
125+
assertThat(environment).isNotInstanceOf(Proxy.class);
126+
ResourceLoader resourceLoader = bean.getResourceLoader();
127+
assertThat(resourceLoader).isInstanceOf(Proxy.class);
128+
RuntimeHints runtimeHints = generationContext.getRuntimeHints();
129+
assertThat(runtimeHints.proxies().jdkProxies()).singleElement().satisfies(proxyHint ->
130+
assertThat(proxyHint.getProxiedInterfaces()).isEqualTo(TypeReference.listOf(
131+
resourceLoader.getClass().getInterfaces())));
132+
});
133+
}
134+
135+
@Test
136+
void processAheadOfTimeWhenHasLazyAutowiringOnConstructor() {
137+
testAutowiredComponent(LazyConstructorArgumentComponent.class, (bean, generationContext) -> {
138+
Environment environment = bean.getEnvironment();
139+
assertThat(environment).isInstanceOf(Proxy.class);
140+
ResourceLoader resourceLoader = bean.getResourceLoader();
141+
assertThat(resourceLoader).isNotInstanceOf(Proxy.class);
142+
RuntimeHints runtimeHints = generationContext.getRuntimeHints();
143+
assertThat(runtimeHints.proxies().jdkProxies()).singleElement().satisfies(proxyHint ->
144+
assertThat(proxyHint.getProxiedInterfaces()).isEqualTo(TypeReference.listOf(
145+
environment.getClass().getInterfaces())));
146+
});
147+
}
148+
149+
@Test
150+
void processAheadOfTimeWhenHasLazyAutowiringOnFactoryMethod() {
151+
RootBeanDefinition bd = new RootBeanDefinition(LazyFactoryMethodArgumentComponent.class);
152+
bd.setFactoryMethodName("of");
153+
testAutowiredComponent(LazyFactoryMethodArgumentComponent.class, bd, (bean, generationContext) -> {
154+
Environment environment = bean.getEnvironment();
155+
assertThat(environment).isInstanceOf(Proxy.class);
156+
ResourceLoader resourceLoader = bean.getResourceLoader();
157+
assertThat(resourceLoader).isNotInstanceOf(Proxy.class);
158+
RuntimeHints runtimeHints = generationContext.getRuntimeHints();
159+
assertThat(runtimeHints.proxies().jdkProxies()).singleElement().satisfies(proxyHint ->
160+
assertThat(proxyHint.getProxiedInterfaces()).isEqualTo(TypeReference.listOf(
161+
environment.getClass().getInterfaces())));
162+
});
163+
}
164+
165+
private <T> void testAutowiredComponent(Class<T> type, BiConsumer<T, GenerationContext> assertions) {
166+
testAutowiredComponent(type, new RootBeanDefinition(type), assertions);
167+
}
168+
169+
private <T> void testAutowiredComponent(Class<T> type, RootBeanDefinition beanDefinition,
170+
BiConsumer<T, GenerationContext> assertions) {
171+
GenericApplicationContext applicationContext = new GenericApplicationContext();
172+
applicationContext.getDefaultListableBeanFactory().setAutowireCandidateResolver(
173+
new ContextAnnotationAutowireCandidateResolver());
174+
applicationContext.registerBeanDefinition(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME,
175+
BeanDefinitionBuilder
176+
.rootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class)
177+
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition());
178+
applicationContext.registerBeanDefinition("testComponent", beanDefinition);
179+
TestGenerationContext generationContext = processAheadOfTime(applicationContext);
180+
testCompiledResult(generationContext, (initializer, compiled) -> {
181+
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(initializer);
182+
assertThat(freshApplicationContext.getBeanDefinitionNames()).containsOnly("testComponent");
183+
assertions.accept(freshApplicationContext.getBean("testComponent", type), generationContext);
184+
});
185+
}
186+
96187
@Test
97188
void processAheadOfTimeWhenHasInitDestroyMethods() {
98189
GenericApplicationContext applicationContext = new GenericApplicationContext();
@@ -189,10 +280,14 @@ private static TestGenerationContext processAheadOfTime(GenericApplicationContex
189280
return generationContext;
190281
}
191282

192-
@SuppressWarnings({ "rawtypes", "unchecked" })
193283
private void testCompiledResult(GenericApplicationContext applicationContext,
194284
BiConsumer<ApplicationContextInitializer<GenericApplicationContext>, Compiled> result) {
195-
TestGenerationContext generationContext = processAheadOfTime(applicationContext);
285+
testCompiledResult(processAheadOfTime(applicationContext), result);
286+
}
287+
288+
@SuppressWarnings({ "rawtypes", "unchecked" })
289+
private void testCompiledResult(TestGenerationContext generationContext,
290+
BiConsumer<ApplicationContextInitializer<GenericApplicationContext>, Compiled> result) {
196291
TestCompiler.forSystem().withFiles(generationContext.getGeneratedFiles()).compile(compiled ->
197292
result.accept(compiled.getInstance(ApplicationContextInitializer.class), compiled));
198293
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.context.testfixture.context.generator.annotation;
18+
19+
import org.springframework.beans.factory.annotation.Autowired;
20+
import org.springframework.context.annotation.Lazy;
21+
import org.springframework.core.env.Environment;
22+
import org.springframework.core.io.ResourceLoader;
23+
24+
public class LazyAutowiredFieldComponent {
25+
26+
@Lazy
27+
@Autowired
28+
private Environment environment;
29+
30+
@Autowired
31+
private ResourceLoader resourceLoader;
32+
33+
public Environment getEnvironment() {
34+
return this.environment;
35+
}
36+
37+
38+
public ResourceLoader getResourceLoader() {
39+
return this.resourceLoader;
40+
}
41+
}

0 commit comments

Comments
 (0)