|
25 | 25 | import java.lang.reflect.Method;
|
26 | 26 | import java.lang.reflect.Modifier;
|
27 | 27 | import java.util.ArrayList;
|
| 28 | +import java.util.Collection; |
28 | 29 | import java.util.Collections;
|
29 | 30 | import java.util.HashSet;
|
30 | 31 | import java.util.LinkedHashSet;
|
|
35 | 36 |
|
36 | 37 | import org.springframework.aop.TargetSource;
|
37 | 38 | 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; |
38 | 46 | import org.springframework.beans.BeanUtils;
|
39 | 47 | import org.springframework.beans.PropertyValues;
|
40 | 48 | import org.springframework.beans.factory.BeanCreationException;
|
|
43 | 51 | import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
44 | 52 | import org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor;
|
45 | 53 | import org.springframework.beans.factory.annotation.InjectionMetadata;
|
| 54 | +import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; |
| 55 | +import org.springframework.beans.factory.aot.BeanRegistrationCode; |
46 | 56 | import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
47 | 57 | import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
48 | 58 | import org.springframework.beans.factory.config.DependencyDescriptor;
|
49 | 59 | import org.springframework.beans.factory.config.EmbeddedValueResolver;
|
50 | 60 | 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; |
51 | 64 | import org.springframework.beans.factory.support.RootBeanDefinition;
|
| 65 | +import org.springframework.context.aot.ResourceFieldValueResolver; |
| 66 | +import org.springframework.context.aot.ResourceMethodArgumentResolver; |
52 | 67 | import org.springframework.core.BridgeMethodResolver;
|
53 | 68 | import org.springframework.core.MethodParameter;
|
54 | 69 | import org.springframework.core.Ordered;
|
55 | 70 | import org.springframework.core.annotation.AnnotationUtils;
|
| 71 | +import org.springframework.javapoet.ClassName; |
| 72 | +import org.springframework.javapoet.CodeBlock; |
56 | 73 | import org.springframework.jndi.support.SimpleJndiBeanFactory;
|
57 | 74 | import org.springframework.lang.Nullable;
|
58 | 75 | import org.springframework.util.Assert;
|
59 | 76 | import org.springframework.util.ClassUtils;
|
| 77 | +import org.springframework.util.ObjectUtils; |
60 | 78 | import org.springframework.util.ReflectionUtils;
|
61 | 79 | import org.springframework.util.StringUtils;
|
62 | 80 | import org.springframework.util.StringValueResolver;
|
@@ -298,6 +316,37 @@ public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, C
|
298 | 316 | metadata.checkConfigMembers(beanDefinition);
|
299 | 317 | }
|
300 | 318 |
|
| 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 | + |
301 | 350 | @Override
|
302 | 351 | public void resetBeanDefinition(String beanName) {
|
303 | 352 | this.injectionMetadataCache.remove(beanName);
|
@@ -787,4 +836,144 @@ public Class<?> getDependencyType() {
|
787 | 836 | }
|
788 | 837 | }
|
789 | 838 |
|
| 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 | + |
790 | 979 | }
|
0 commit comments