Skip to content

Commit e2c2268

Browse files
committed
Add AOT support for @resource
This commit adds ahead of time support for @resource on fields and methods. Lookup elements are discovered and code is generated to replace that introspection at runtime. Closes gh-29614
1 parent b510bc3 commit e2c2268

19 files changed

+1557
-0
lines changed

spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.lang.reflect.Method;
2626
import java.lang.reflect.Modifier;
2727
import java.util.ArrayList;
28+
import java.util.Collection;
2829
import java.util.Collections;
2930
import java.util.HashSet;
3031
import java.util.LinkedHashSet;
@@ -35,6 +36,13 @@
3536

3637
import org.springframework.aop.TargetSource;
3738
import org.springframework.aop.framework.ProxyFactory;
39+
import org.springframework.aot.generate.AccessControl;
40+
import org.springframework.aot.generate.GeneratedClass;
41+
import org.springframework.aot.generate.GeneratedMethod;
42+
import org.springframework.aot.generate.GenerationContext;
43+
import org.springframework.aot.hint.ExecutableMode;
44+
import org.springframework.aot.hint.RuntimeHints;
45+
import org.springframework.aot.hint.support.ClassHintUtils;
3846
import org.springframework.beans.BeanUtils;
3947
import org.springframework.beans.PropertyValues;
4048
import org.springframework.beans.factory.BeanCreationException;
@@ -43,20 +51,30 @@
4351
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
4452
import org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor;
4553
import org.springframework.beans.factory.annotation.InjectionMetadata;
54+
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
55+
import org.springframework.beans.factory.aot.BeanRegistrationCode;
4656
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
4757
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
4858
import org.springframework.beans.factory.config.DependencyDescriptor;
4959
import org.springframework.beans.factory.config.EmbeddedValueResolver;
5060
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
61+
import org.springframework.beans.factory.support.AutowireCandidateResolver;
62+
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
63+
import org.springframework.beans.factory.support.RegisteredBean;
5164
import org.springframework.beans.factory.support.RootBeanDefinition;
65+
import org.springframework.context.aot.ResourceFieldValueResolver;
66+
import org.springframework.context.aot.ResourceMethodArgumentResolver;
5267
import org.springframework.core.BridgeMethodResolver;
5368
import org.springframework.core.MethodParameter;
5469
import org.springframework.core.Ordered;
5570
import org.springframework.core.annotation.AnnotationUtils;
71+
import org.springframework.javapoet.ClassName;
72+
import org.springframework.javapoet.CodeBlock;
5673
import org.springframework.jndi.support.SimpleJndiBeanFactory;
5774
import org.springframework.lang.Nullable;
5875
import org.springframework.util.Assert;
5976
import org.springframework.util.ClassUtils;
77+
import org.springframework.util.ObjectUtils;
6078
import org.springframework.util.ReflectionUtils;
6179
import org.springframework.util.StringUtils;
6280
import org.springframework.util.StringValueResolver;
@@ -298,6 +316,37 @@ public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, C
298316
metadata.checkConfigMembers(beanDefinition);
299317
}
300318

319+
@Override
320+
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
321+
BeanRegistrationAotContribution parentAotContribution = super.processAheadOfTime(registeredBean);
322+
Class<?> beanClass = registeredBean.getBeanClass();
323+
String beanName = registeredBean.getBeanName();
324+
RootBeanDefinition beanDefinition = registeredBean.getMergedBeanDefinition();
325+
InjectionMetadata metadata = findResourceMetadata(beanName, beanClass,
326+
beanDefinition.getPropertyValues());
327+
Collection<LookupElement> injectedElements = getInjectedElements(metadata,
328+
beanDefinition.getPropertyValues());
329+
if (!ObjectUtils.isEmpty(injectedElements)) {
330+
AotContribution aotContribution = new AotContribution(beanClass, injectedElements,
331+
getAutowireCandidateResolver(registeredBean));
332+
return BeanRegistrationAotContribution.concat(parentAotContribution, aotContribution);
333+
}
334+
return parentAotContribution;
335+
}
336+
337+
@Nullable
338+
private AutowireCandidateResolver getAutowireCandidateResolver(RegisteredBean registeredBean) {
339+
if (registeredBean.getBeanFactory() instanceof DefaultListableBeanFactory lbf) {
340+
return lbf.getAutowireCandidateResolver();
341+
}
342+
return null;
343+
}
344+
345+
@SuppressWarnings({ "rawtypes", "unchecked" })
346+
private Collection<LookupElement> getInjectedElements(InjectionMetadata metadata, PropertyValues propertyValues) {
347+
return (Collection) metadata.getInjectedElements(propertyValues);
348+
}
349+
301350
@Override
302351
public void resetBeanDefinition(String beanName) {
303352
this.injectionMetadataCache.remove(beanName);
@@ -787,4 +836,144 @@ public Class<?> getDependencyType() {
787836
}
788837
}
789838

839+
/**
840+
* {@link BeanRegistrationAotContribution} to inject resources on fields and methods.
841+
*/
842+
private static class AotContribution implements BeanRegistrationAotContribution {
843+
844+
private static final String REGISTERED_BEAN_PARAMETER = "registeredBean";
845+
846+
private static final String INSTANCE_PARAMETER = "instance";
847+
848+
private final Class<?> target;
849+
850+
private final Collection<LookupElement> lookupElements;
851+
852+
@Nullable
853+
private final AutowireCandidateResolver candidateResolver;
854+
855+
AotContribution(Class<?> target, Collection<LookupElement> lookupElements,
856+
@Nullable AutowireCandidateResolver candidateResolver) {
857+
858+
this.target = target;
859+
this.lookupElements = lookupElements;
860+
this.candidateResolver = candidateResolver;
861+
}
862+
863+
@Override
864+
public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) {
865+
GeneratedClass generatedClass = generationContext.getGeneratedClasses()
866+
.addForFeatureComponent("ResourceAutowiring", this.target, type -> {
867+
type.addJavadoc("Resource autowiring for {@link $T}.", this.target);
868+
type.addModifiers(javax.lang.model.element.Modifier.PUBLIC);
869+
});
870+
GeneratedMethod generateMethod = generatedClass.getMethods().add("apply", method -> {
871+
method.addJavadoc("Apply resource autowiring.");
872+
method.addModifiers(javax.lang.model.element.Modifier.PUBLIC,
873+
javax.lang.model.element.Modifier.STATIC);
874+
method.addParameter(RegisteredBean.class, REGISTERED_BEAN_PARAMETER);
875+
method.addParameter(this.target, INSTANCE_PARAMETER);
876+
method.returns(this.target);
877+
method.addCode(generateMethodCode(generatedClass.getName(),
878+
generationContext.getRuntimeHints()));
879+
});
880+
beanRegistrationCode.addInstancePostProcessor(generateMethod.toMethodReference());
881+
882+
registerHints(generationContext.getRuntimeHints());
883+
}
884+
885+
private CodeBlock generateMethodCode(ClassName targetClassName, RuntimeHints hints) {
886+
CodeBlock.Builder code = CodeBlock.builder();
887+
for (LookupElement lookupElement : this.lookupElements) {
888+
code.addStatement(generateMethodStatementForElement(
889+
targetClassName, lookupElement, hints));
890+
}
891+
code.addStatement("return $L", INSTANCE_PARAMETER);
892+
return code.build();
893+
}
894+
895+
private CodeBlock generateMethodStatementForElement(ClassName targetClassName,
896+
LookupElement lookupElement, RuntimeHints hints) {
897+
898+
Member member = lookupElement.getMember();
899+
if (member instanceof Field field) {
900+
return generateMethodStatementForField(
901+
targetClassName, field, lookupElement, hints);
902+
}
903+
if (member instanceof Method method) {
904+
return generateMethodStatementForMethod(
905+
targetClassName, method, lookupElement, hints);
906+
}
907+
throw new IllegalStateException(
908+
"Unsupported member type " + member.getClass().getName());
909+
}
910+
911+
private CodeBlock generateMethodStatementForField(ClassName targetClassName,
912+
Field field, LookupElement lookupElement, RuntimeHints hints) {
913+
914+
hints.reflection().registerField(field);
915+
CodeBlock resolver = generateFieldResolverCode(field, lookupElement);
916+
AccessControl accessControl = AccessControl.forMember(field);
917+
if (!accessControl.isAccessibleFrom(targetClassName)) {
918+
return CodeBlock.of("$L.resolveAndSet($L, $L)", resolver,
919+
REGISTERED_BEAN_PARAMETER, INSTANCE_PARAMETER);
920+
}
921+
return CodeBlock.of("$L.$L = $L.resolve($L)", INSTANCE_PARAMETER,
922+
field.getName(), resolver, REGISTERED_BEAN_PARAMETER);
923+
}
924+
925+
private CodeBlock generateFieldResolverCode(Field field, LookupElement lookupElement) {
926+
if (lookupElement.isDefaultName) {
927+
return CodeBlock.of("$T.$L($S)", ResourceFieldValueResolver.class,
928+
"forField", field.getName());
929+
}
930+
else {
931+
return CodeBlock.of("$T.$L($S, $S)", ResourceFieldValueResolver.class,
932+
"forField", field.getName(), lookupElement.getName());
933+
}
934+
}
935+
936+
private CodeBlock generateMethodStatementForMethod(ClassName targetClassName,
937+
Method method, LookupElement lookupElement, RuntimeHints hints) {
938+
939+
CodeBlock resolver = generateMethodResolverCode(method, lookupElement);
940+
AccessControl accessControl = AccessControl.forMember(method);
941+
if (!accessControl.isAccessibleFrom(targetClassName)) {
942+
hints.reflection().registerMethod(method, ExecutableMode.INVOKE);
943+
return CodeBlock.of("$L.resolveAndInvoke($L, $L)", resolver,
944+
REGISTERED_BEAN_PARAMETER, INSTANCE_PARAMETER);
945+
}
946+
hints.reflection().registerMethod(method, ExecutableMode.INTROSPECT);
947+
return CodeBlock.of("$L.$L($L.resolve($L))", INSTANCE_PARAMETER,
948+
method.getName(), resolver, REGISTERED_BEAN_PARAMETER);
949+
950+
}
951+
952+
private CodeBlock generateMethodResolverCode(Method method, LookupElement lookupElement) {
953+
if (lookupElement.isDefaultName) {
954+
return CodeBlock.of("$T.$L($S, $T.class)", ResourceMethodArgumentResolver.class,
955+
"forMethod", method.getName(), lookupElement.getLookupType());
956+
}
957+
else {
958+
return CodeBlock.of("$T.$L($S, $T.class, $S)", ResourceMethodArgumentResolver.class,
959+
"forMethod", method.getName(), lookupElement.getLookupType(), lookupElement.getName());
960+
}
961+
}
962+
963+
private void registerHints(RuntimeHints runtimeHints) {
964+
this.lookupElements.forEach(lookupElement ->
965+
registerProxyIfNecessary(runtimeHints, lookupElement.getDependencyDescriptor()));
966+
}
967+
968+
private void registerProxyIfNecessary(RuntimeHints runtimeHints, DependencyDescriptor dependencyDescriptor) {
969+
if (this.candidateResolver != null) {
970+
Class<?> proxyClass =
971+
this.candidateResolver.getLazyResolutionProxyClass(dependencyDescriptor, null);
972+
if (proxyClass != null) {
973+
ClassHintUtils.registerProxyIfNecessary(proxyClass, runtimeHints);
974+
}
975+
}
976+
}
977+
}
978+
790979
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
* Copyright 2002-2023 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.aot;
18+
19+
import java.lang.reflect.Field;
20+
import java.lang.reflect.Method;
21+
import java.util.Collections;
22+
import java.util.LinkedHashSet;
23+
import java.util.Set;
24+
25+
import javax.lang.model.element.Element;
26+
27+
import jakarta.annotation.Resource;
28+
29+
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
30+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
31+
import org.springframework.beans.factory.config.DependencyDescriptor;
32+
import org.springframework.beans.factory.support.RegisteredBean;
33+
import org.springframework.core.MethodParameter;
34+
import org.springframework.lang.Nullable;
35+
import org.springframework.util.Assert;
36+
37+
/**
38+
* Base class for resolvers that support injection of named beans on
39+
* an {@link Element}.
40+
*
41+
* @author Stephane Nicoll
42+
* @since 6.1
43+
* @see Resource
44+
*/
45+
public abstract class ResourceElementResolver {
46+
47+
protected final String name;
48+
49+
protected final boolean defaultName;
50+
51+
protected ResourceElementResolver(String name, boolean defaultName) {
52+
this.name = name;
53+
this.defaultName = defaultName;
54+
}
55+
56+
/**
57+
* Resolve the field value for the specified registered bean.
58+
* @param registeredBean the registered bean
59+
* @return the resolved field value
60+
*/
61+
@Nullable
62+
@SuppressWarnings("unchecked")
63+
public <T> T resolve(RegisteredBean registeredBean) {
64+
return (T) resolveObject(registeredBean);
65+
}
66+
67+
/**
68+
* Resolve the field value for the specified registered bean.
69+
* @param registeredBean the registered bean
70+
* @return the resolved field value
71+
*/
72+
public Object resolveObject(RegisteredBean registeredBean) {
73+
Assert.notNull(registeredBean, "'registeredBean' must not be null");
74+
return resolveValue(registeredBean);
75+
}
76+
77+
78+
/**
79+
* Create a suitable {@link DependencyDescriptor} for the specified bean.
80+
* @param bean the registered bean
81+
* @return a descriptor for that bean
82+
*/
83+
protected abstract DependencyDescriptor createDependencyDescriptor(RegisteredBean bean);
84+
85+
/**
86+
* Resolve the value to inject for this instance.
87+
* @param bean the bean registration
88+
* @return the value to inject
89+
*/
90+
protected Object resolveValue(RegisteredBean bean) {
91+
ConfigurableListableBeanFactory factory = bean.getBeanFactory();
92+
93+
Object resource;
94+
Set<String> autowiredBeanNames;
95+
DependencyDescriptor descriptor = createDependencyDescriptor(bean);
96+
if (this.defaultName && !factory.containsBean(this.name)) {
97+
autowiredBeanNames = new LinkedHashSet<>();
98+
resource = factory.resolveDependency(descriptor, bean.getBeanName(), autowiredBeanNames, null);
99+
if (resource == null) {
100+
throw new NoSuchBeanDefinitionException(descriptor.getDependencyType(), "No resolvable resource object");
101+
}
102+
}
103+
else {
104+
resource = factory.resolveBeanByName(this.name, descriptor);
105+
autowiredBeanNames = Collections.singleton(this.name);
106+
}
107+
108+
for (String autowiredBeanName : autowiredBeanNames) {
109+
if (factory.containsBean(autowiredBeanName)) {
110+
factory.registerDependentBean(autowiredBeanName, bean.getBeanName());
111+
}
112+
}
113+
return resource;
114+
}
115+
116+
117+
@SuppressWarnings("serial")
118+
protected static class LookupDependencyDescriptor extends DependencyDescriptor {
119+
120+
private final Class<?> lookupType;
121+
122+
public LookupDependencyDescriptor(Field field, Class<?> lookupType) {
123+
super(field, true);
124+
this.lookupType = lookupType;
125+
}
126+
127+
public LookupDependencyDescriptor(Method method, Class<?> lookupType) {
128+
super(new MethodParameter(method, 0), true);
129+
this.lookupType = lookupType;
130+
}
131+
132+
@Override
133+
public Class<?> getDependencyType() {
134+
return this.lookupType;
135+
}
136+
}
137+
138+
}

0 commit comments

Comments
 (0)