Skip to content

Commit ebf6de8

Browse files
committed
Infer JDK dynamic proxies for Spring beans
See spring-projectsgh-28980
1 parent a946643 commit ebf6de8

File tree

4 files changed

+60
-21
lines changed

4 files changed

+60
-21
lines changed

spring-context/src/main/java/org/springframework/context/aot/ApplicationContextAotGenerator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public class ApplicationContextAotGenerator {
5151
public ClassName processAheadOfTime(GenericApplicationContext applicationContext,
5252
GenerationContext generationContext) {
5353
return withGeneratedClassHandler(new GeneratedClassHandler(generationContext), () -> {
54-
applicationContext.refreshForAotProcessing();
54+
applicationContext.refreshForAotProcessing(generationContext.getRuntimeHints());
5555
DefaultListableBeanFactory beanFactory = applicationContext.getDefaultListableBeanFactory();
5656
ApplicationContextInitializationCodeGenerator codeGenerator =
5757
new ApplicationContextInitializationCodeGenerator(generationContext);

spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@
1818

1919
import java.io.IOException;
2020
import java.lang.reflect.Constructor;
21+
import java.lang.reflect.Proxy;
2122
import java.util.List;
2223
import java.util.concurrent.atomic.AtomicBoolean;
2324
import java.util.function.Supplier;
2425

26+
import org.springframework.aot.hint.RuntimeHints;
2527
import org.springframework.beans.BeanUtils;
2628
import org.springframework.beans.BeansException;
2729
import org.springframework.beans.factory.BeanDefinitionStoreException;
@@ -394,12 +396,13 @@ public boolean isAlias(String beanName) {
394396
* processing that optimizes the application context, typically at build time.
395397
* <p>In this mode, only {@link BeanDefinitionRegistryPostProcessor} and
396398
* {@link MergedBeanDefinitionPostProcessor} are invoked.
399+
* @param runtimeHints the runtime hints
397400
* @throws BeansException if the bean factory could not be initialized
398401
* @throws IllegalStateException if already initialized and multiple refresh
399402
* attempts are not supported
400403
* @since 6.0
401404
*/
402-
public void refreshForAotProcessing() {
405+
public void refreshForAotProcessing(RuntimeHints runtimeHints) {
403406
if (logger.isDebugEnabled()) {
404407
logger.debug("Preparing bean factory for AOT processing");
405408
}
@@ -410,15 +413,15 @@ public void refreshForAotProcessing() {
410413
invokeBeanFactoryPostProcessors(this.beanFactory);
411414
this.beanFactory.freezeConfiguration();
412415
PostProcessorRegistrationDelegate.invokeMergedBeanDefinitionPostProcessors(this.beanFactory);
413-
preDetermineBeanTypes();
416+
preDetermineBeanTypes(runtimeHints);
414417
}
415418

416419
/**
417420
* Pre-determine bean types in order to trigger early proxy class creation.
418421
* @see org.springframework.beans.factory.BeanFactory#getType
419422
* @see SmartInstantiationAwareBeanPostProcessor#determineBeanType
420423
*/
421-
private void preDetermineBeanTypes() {
424+
private void preDetermineBeanTypes(RuntimeHints runtimeHints) {
422425
List<SmartInstantiationAwareBeanPostProcessor> bpps =
423426
PostProcessorRegistrationDelegate.loadBeanPostProcessors(
424427
this.beanFactory, SmartInstantiationAwareBeanPostProcessor.class);
@@ -427,6 +430,9 @@ private void preDetermineBeanTypes() {
427430
if (beanType != null) {
428431
for (SmartInstantiationAwareBeanPostProcessor bpp : bpps) {
429432
beanType = bpp.determineBeanType(beanType, beanName);
433+
if (Proxy.isProxyClass(beanType)) {
434+
runtimeHints.proxies().registerJdkProxy(beanType.getInterfaces());
435+
}
430436
}
431437
}
432438
}

spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import org.junit.jupiter.api.Test;
2323

24+
import org.springframework.aot.hint.RuntimeHints;
2425
import org.springframework.beans.factory.FactoryBean;
2526
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
2627
import org.springframework.beans.factory.annotation.Autowired;
@@ -426,7 +427,7 @@ void individualBeanWithFactoryBeanObjectTypeAsTargetTypeAndLazy() {
426427
void refreshForAotProcessingWithConfiguration() {
427428
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
428429
context.register(Config.class);
429-
context.refreshForAotProcessing();
430+
context.refreshForAotProcessing(new RuntimeHints());
430431
assertThat(context.getBeanFactory().getBeanDefinitionNames()).contains(
431432
"annotationConfigApplicationContextTests.Config", "testBean");
432433
}
@@ -435,7 +436,7 @@ void refreshForAotProcessingWithConfiguration() {
435436
void refreshForAotCanInstantiateBeanWithAutowiredApplicationContext() {
436437
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
437438
context.register(BeanD.class);
438-
context.refreshForAotProcessing();
439+
context.refreshForAotProcessing(new RuntimeHints());
439440
BeanD bean = context.getBean(BeanD.class);
440441
assertThat(bean.applicationContext).isSameAs(context);
441442
}
@@ -444,7 +445,7 @@ void refreshForAotCanInstantiateBeanWithAutowiredApplicationContext() {
444445
void refreshForAotCanInstantiateBeanWithFieldAutowiredApplicationContext() {
445446
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
446447
context.register(BeanB.class);
447-
context.refreshForAotProcessing();
448+
context.refreshForAotProcessing(new RuntimeHints());
448449
BeanB bean = context.getBean(BeanB.class);
449450
assertThat(bean.applicationContext).isSameAs(context);
450451
}

spring-context/src/test/java/org/springframework/context/support/GenericApplicationContextTests.java

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,32 @@
1616

1717
package org.springframework.context.support;
1818

19+
import java.lang.reflect.Proxy;
1920
import java.nio.file.InvalidPathException;
21+
import java.util.HashMap;
22+
import java.util.Map;
2023

2124
import org.junit.jupiter.api.AfterEach;
2225
import org.junit.jupiter.api.Test;
2326
import org.junit.jupiter.api.condition.OS;
2427
import org.mockito.ArgumentCaptor;
2528

29+
import org.springframework.aot.hint.RuntimeHints;
30+
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
2631
import org.springframework.beans.BeansException;
2732
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
2833
import org.springframework.beans.factory.config.AbstractFactoryBean;
2934
import org.springframework.beans.factory.config.BeanDefinition;
3035
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
36+
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
3137
import org.springframework.beans.factory.support.AbstractBeanDefinition;
3238
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
3339
import org.springframework.beans.factory.support.GenericBeanDefinition;
3440
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
3541
import org.springframework.beans.factory.support.RootBeanDefinition;
3642
import org.springframework.context.ApplicationContext;
3743
import org.springframework.context.ApplicationContextAware;
44+
import org.springframework.core.DecoratingProxy;
3845
import org.springframework.core.env.ConfigurableEnvironment;
3946
import org.springframework.core.env.Environment;
4047
import org.springframework.core.io.ByteArrayResource;
@@ -296,7 +303,7 @@ private void assertGetResourceSemantics(ResourceLoader resourceLoader, Class<? e
296303
void refreshForAotSetsContextActive() {
297304
GenericApplicationContext context = new GenericApplicationContext();
298305
assertThat(context.isActive()).isFalse();
299-
context.refreshForAotProcessing();
306+
context.refreshForAotProcessing(new RuntimeHints());
300307
assertThat(context.isActive()).isTrue();
301308
context.close();
302309
}
@@ -306,7 +313,7 @@ void refreshForAotRegistersEnvironment() {
306313
ConfigurableEnvironment environment = mock(ConfigurableEnvironment.class);
307314
GenericApplicationContext context = new GenericApplicationContext();
308315
context.setEnvironment(environment);
309-
context.refreshForAotProcessing();
316+
context.refreshForAotProcessing(new RuntimeHints());
310317
assertThat(context.getBean(Environment.class)).isEqualTo(environment);
311318
context.close();
312319
}
@@ -315,7 +322,7 @@ void refreshForAotRegistersEnvironment() {
315322
void refreshForAotLoadsBeanClassName() {
316323
GenericApplicationContext context = new GenericApplicationContext();
317324
context.registerBeanDefinition("number", new RootBeanDefinition("java.lang.Integer"));
318-
context.refreshForAotProcessing();
325+
context.refreshForAotProcessing(new RuntimeHints());
319326
assertThat(getBeanDefinition(context, "number").getBeanClass()).isEqualTo(Integer.class);
320327
context.close();
321328
}
@@ -328,7 +335,7 @@ void refreshForAotLoadsBeanClassNameOfConstructorArgumentInnerBeanDefinition() {
328335
innerBeanDefinition.setBeanClassName("java.lang.Integer");
329336
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, innerBeanDefinition);
330337
context.registerBeanDefinition("test",beanDefinition);
331-
context.refreshForAotProcessing();
338+
context.refreshForAotProcessing(new RuntimeHints());
332339
RootBeanDefinition bd = getBeanDefinition(context, "test");
333340
GenericBeanDefinition value = (GenericBeanDefinition) bd.getConstructorArgumentValues()
334341
.getIndexedArgumentValue(0, GenericBeanDefinition.class).getValue();
@@ -345,7 +352,7 @@ void refreshForAotLoadsBeanClassNameOfPropertyValueInnerBeanDefinition() {
345352
innerBeanDefinition.setBeanClassName("java.lang.Integer");
346353
beanDefinition.getPropertyValues().add("inner", innerBeanDefinition);
347354
context.registerBeanDefinition("test",beanDefinition);
348-
context.refreshForAotProcessing();
355+
context.refreshForAotProcessing(new RuntimeHints());
349356
RootBeanDefinition bd = getBeanDefinition(context, "test");
350357
GenericBeanDefinition value = (GenericBeanDefinition) bd.getPropertyValues().get("inner");
351358
assertThat(value.hasBeanClass()).isTrue();
@@ -358,7 +365,7 @@ void refreshForAotInvokesBeanFactoryPostProcessors() {
358365
GenericApplicationContext context = new GenericApplicationContext();
359366
BeanFactoryPostProcessor bfpp = mock(BeanFactoryPostProcessor.class);
360367
context.addBeanFactoryPostProcessor(bfpp);
361-
context.refreshForAotProcessing();
368+
context.refreshForAotProcessing(new RuntimeHints());
362369
verify(bfpp).postProcessBeanFactory(context.getBeanFactory());
363370
context.close();
364371
}
@@ -369,7 +376,7 @@ void refreshForAotInvokesMergedBeanDefinitionPostProcessors() {
369376
context.registerBeanDefinition("test", new RootBeanDefinition(String.class));
370377
context.registerBeanDefinition("number", new RootBeanDefinition("java.lang.Integer"));
371378
MergedBeanDefinitionPostProcessor bpp = registerMockMergedBeanDefinitionPostProcessor(context);
372-
context.refreshForAotProcessing();
379+
context.refreshForAotProcessing(new RuntimeHints());
373380
verify(bpp).postProcessMergedBeanDefinition(getBeanDefinition(context, "test"), String.class, "test");
374381
verify(bpp).postProcessMergedBeanDefinition(getBeanDefinition(context, "number"), Integer.class, "number");
375382
context.close();
@@ -384,7 +391,7 @@ void refreshForAotInvokesMergedBeanDefinitionPostProcessorsOnConstructorArgument
384391
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, innerBeanDefinition);
385392
context.registerBeanDefinition("test", beanDefinition);
386393
MergedBeanDefinitionPostProcessor bpp = registerMockMergedBeanDefinitionPostProcessor(context);
387-
context.refreshForAotProcessing();
394+
context.refreshForAotProcessing(new RuntimeHints());
388395
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
389396
verify(bpp).postProcessMergedBeanDefinition(getBeanDefinition(context, "test"), BeanD.class, "test");
390397
verify(bpp).postProcessMergedBeanDefinition(any(RootBeanDefinition.class), eq(Integer.class), captor.capture());
@@ -401,7 +408,7 @@ void refreshForAotInvokesMergedBeanDefinitionPostProcessorsOnPropertyValue() {
401408
beanDefinition.getPropertyValues().add("counter", innerBeanDefinition);
402409
context.registerBeanDefinition("test", beanDefinition);
403410
MergedBeanDefinitionPostProcessor bpp = registerMockMergedBeanDefinitionPostProcessor(context);
404-
context.refreshForAotProcessing();
411+
context.refreshForAotProcessing(new RuntimeHints());
405412
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
406413
verify(bpp).postProcessMergedBeanDefinition(getBeanDefinition(context, "test"), BeanD.class, "test");
407414
verify(bpp).postProcessMergedBeanDefinition(any(RootBeanDefinition.class), eq(Integer.class), captor.capture());
@@ -414,7 +421,7 @@ void refreshForAotFreezeConfiguration() {
414421
GenericApplicationContext context = new GenericApplicationContext();
415422
context.registerBeanDefinition("test", new RootBeanDefinition(String.class));
416423
MergedBeanDefinitionPostProcessor bpp = registerMockMergedBeanDefinitionPostProcessor(context);
417-
context.refreshForAotProcessing();
424+
context.refreshForAotProcessing(new RuntimeHints());
418425
RootBeanDefinition mergedBeanDefinition = getBeanDefinition(context, "test");
419426
verify(bpp).postProcessMergedBeanDefinition(mergedBeanDefinition, String.class, "test");
420427
context.getBeanFactory().clearMetadataCache();
@@ -442,7 +449,7 @@ public Object postProcessBeforeInitialization(Object bean, String beanName) thro
442449
AbstractBeanDefinition bd = BeanDefinitionBuilder.rootBeanDefinition(String.class)
443450
.addConstructorArgValue("value").getBeanDefinition();
444451
context.registerBeanDefinition("test", bd);
445-
context.refreshForAotProcessing();
452+
context.refreshForAotProcessing(new RuntimeHints());
446453
assertThat(context.getBeanFactory().getMergedBeanDefinition("test")
447454
.hasAttribute("mbdppCalled")).isTrue();
448455
assertThat(context.getBean("test")).isEqualTo("42");
@@ -453,7 +460,7 @@ public Object postProcessBeforeInitialization(Object bean, String beanName) thro
453460
void refreshForAotFailsOnAnActiveContext() {
454461
GenericApplicationContext context = new GenericApplicationContext();
455462
context.refresh();
456-
assertThatIllegalStateException().isThrownBy(context::refreshForAotProcessing)
463+
assertThatIllegalStateException().isThrownBy(() -> context.refreshForAotProcessing(new RuntimeHints()))
457464
.withMessageContaining("does not support multiple refresh attempts");
458465
context.close();
459466
}
@@ -463,7 +470,7 @@ void refreshForAotDoesNotInitializeFactoryBeansEarly() {
463470
GenericApplicationContext context = new GenericApplicationContext();
464471
context.registerBeanDefinition("genericFactoryBean",
465472
new RootBeanDefinition(TestAotFactoryBean.class));
466-
context.refreshForAotProcessing();
473+
context.refreshForAotProcessing(new RuntimeHints());
467474
context.close();
468475
}
469476

@@ -473,7 +480,32 @@ void refreshForAotDoesNotInstantiateBean() {
473480
context.registerBeanDefinition("test", BeanDefinitionBuilder.rootBeanDefinition(String.class, () -> {
474481
throw new IllegalStateException("Should not be invoked");
475482
}).getBeanDefinition());
476-
context.refreshForAotProcessing();
483+
context.refreshForAotProcessing(new RuntimeHints());
484+
context.close();
485+
}
486+
487+
@Test
488+
void refreshForAotRegisterProxyHint() {
489+
GenericApplicationContext context = new GenericApplicationContext();
490+
context.registerBeanDefinition("bpp", BeanDefinitionBuilder.rootBeanDefinition(
491+
SmartInstantiationAwareBeanPostProcessor.class, () -> new SmartInstantiationAwareBeanPostProcessor() {
492+
@Override
493+
public Class<?> determineBeanType(Class<?> beanClass, String beanName) throws BeansException {
494+
if (beanClass.isInterface()) {
495+
return Proxy.newProxyInstance(GenericApplicationContextTests.class.getClassLoader(),
496+
new Class[] { Map.class, DecoratingProxy.class }, (proxy, method, args) -> null).getClass();
497+
}
498+
else {
499+
return beanClass;
500+
}
501+
}
502+
})
503+
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition());
504+
context.registerBeanDefinition("map", BeanDefinitionBuilder.rootBeanDefinition(Map.class,
505+
HashMap::new).getBeanDefinition());
506+
RuntimeHints runtimeHints = new RuntimeHints();
507+
context.refreshForAotProcessing(runtimeHints);
508+
assertThat(RuntimeHintsPredicates.proxies().forInterfaces(Map.class, DecoratingProxy.class)).accepts(runtimeHints);
477509
context.close();
478510
}
479511

0 commit comments

Comments
 (0)