diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java index 4825563735..499628a113 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -375,6 +375,16 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable } } + /** + * Determine whether this dependency supports lazy resolution, + * e.g. through extra proxying. The default is {@code true}. + * @since 6.1.2 + * @see org.springframework.beans.factory.support.AutowireCandidateResolver#getLazyResolutionProxyIfNecessary + */ + public boolean supportsLazyResolution() { + return true; + } + @Override public boolean equals(@Nullable Object other) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index 65ef5680a4..ded11d6fb1 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -1343,14 +1343,14 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto else if (javaxInjectProviderClass == descriptor.getDependencyType()) { return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName); } - else { + else if (descriptor.supportsLazyResolution()) { Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary( descriptor, requestingBeanName); - if (result == null) { - result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter); + if (result != null) { + return result; } - return result; } + return doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter); } @Nullable diff --git a/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java index 2fb7ac0a2e..3bb8e2d667 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor.java @@ -62,10 +62,7 @@ import org.springframework.beans.factory.support.AutowireCandidateResolver; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RegisteredBean; import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.context.aot.ResourceFieldValueResolver; -import org.springframework.context.aot.ResourceMethodArgumentResolver; import org.springframework.core.BridgeMethodResolver; -import org.springframework.core.MethodParameter; import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.javapoet.ClassName; @@ -501,16 +498,9 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean return element.lookupType; } @Override - public boolean isStatic() { - return false; - } - @Override public Object getTarget() { return getResource(element, requestingBeanName); } - @Override - public void releaseTarget(Object target) { - } }; ProxyFactory pf = new ProxyFactory(); @@ -655,12 +645,23 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean */ public final DependencyDescriptor getDependencyDescriptor() { if (this.isField) { - return new LookupDependencyDescriptor((Field) this.member, this.lookupType); + return new ResourceElementResolver.LookupDependencyDescriptor( + (Field) this.member, this.lookupType, isLazyLookup()); } else { - return new LookupDependencyDescriptor((Method) this.member, this.lookupType); + return new ResourceElementResolver.LookupDependencyDescriptor( + (Method) this.member, this.lookupType, isLazyLookup()); } } + + /** + * Determine whether this dependency is marked for lazy lookup. + * The default is {@code false}. + * @since 6.1.2 + */ + boolean isLazyLookup() { + return false; + } } @@ -707,6 +708,11 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) : getResource(this, requestingBeanName)); } + + @Override + boolean isLazyLookup() { + return this.lazyLookup; + } } @@ -753,6 +759,11 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) : getResource(this, requestingBeanName)); } + + @Override + boolean isLazyLookup() { + return this.lazyLookup; + } } @@ -812,30 +823,6 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean } - /** - * Extension of the DependencyDescriptor class, - * overriding the dependency type with the specified resource type. - */ - private static class LookupDependencyDescriptor extends DependencyDescriptor { - - private final Class lookupType; - - public LookupDependencyDescriptor(Field field, Class lookupType) { - super(field, true); - this.lookupType = lookupType; - } - - public LookupDependencyDescriptor(Method method, Class lookupType) { - super(new MethodParameter(method, 0), true); - this.lookupType = lookupType; - } - - @Override - public Class getDependencyType() { - return this.lookupType; - } - } - /** * {@link BeanRegistrationAotContribution} to inject resources on fields and methods. */ @@ -924,11 +911,11 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean private CodeBlock generateFieldResolverCode(Field field, LookupElement lookupElement) { if (lookupElement.isDefaultName) { - return CodeBlock.of("$T.$L($S)", ResourceFieldValueResolver.class, + return CodeBlock.of("$T.$L($S)", ResourceElementResolver.class, "forField", field.getName()); } else { - return CodeBlock.of("$T.$L($S, $S)", ResourceFieldValueResolver.class, + return CodeBlock.of("$T.$L($S, $S)", ResourceElementResolver.class, "forField", field.getName(), lookupElement.getName()); } } @@ -940,7 +927,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean AccessControl accessControl = AccessControl.forMember(method); if (!accessControl.isAccessibleFrom(targetClassName)) { hints.reflection().registerMethod(method, ExecutableMode.INVOKE); - return CodeBlock.of("$L.resolveAndInvoke($L, $L)", resolver, + return CodeBlock.of("$L.resolveAndSet($L, $L)", resolver, REGISTERED_BEAN_PARAMETER, INSTANCE_PARAMETER); } hints.reflection().registerMethod(method, ExecutableMode.INTROSPECT); @@ -951,11 +938,11 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean private CodeBlock generateMethodResolverCode(Method method, LookupElement lookupElement) { if (lookupElement.isDefaultName) { - return CodeBlock.of("$T.$L($S, $T.class)", ResourceMethodArgumentResolver.class, + return CodeBlock.of("$T.$L($S, $T.class)", ResourceElementResolver.class, "forMethod", method.getName(), lookupElement.getLookupType()); } else { - return CodeBlock.of("$T.$L($S, $T.class, $S)", ResourceMethodArgumentResolver.class, + return CodeBlock.of("$T.$L($S, $T.class, $S)", ResourceElementResolver.class, "forMethod", method.getName(), lookupElement.getLookupType(), lookupElement.getName()); } } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ResourceElementResolver.java b/spring-context/src/main/java/org/springframework/context/annotation/ResourceElementResolver.java new file mode 100644 index 0000000000..d3a78ea91b --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/annotation/ResourceElementResolver.java @@ -0,0 +1,330 @@ +/* + * Copyright 2002-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.context.annotation; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.springframework.aop.TargetSource; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.DependencyDescriptor; +import org.springframework.beans.factory.support.RegisteredBean; +import org.springframework.core.MethodParameter; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +/** + * Resolver for the injection of named beans on a field or method element, + * following the rules of the {@link jakarta.annotation.Resource} annotation + * but without any JNDI support. This is primarily intended for AOT processing. + * + * @author Stephane Nicoll + * @author Juergen Hoeller + * @since 6.1.2 + * @see CommonAnnotationBeanPostProcessor + * @see jakarta.annotation.Resource + */ +public abstract class ResourceElementResolver { + + private final String name; + + private final boolean defaultName; + + + ResourceElementResolver(String name, boolean defaultName) { + this.name = name; + this.defaultName = defaultName; + } + + + /** + * Create a new {@link ResourceFieldResolver} for the specified field. + * @param fieldName the field name + * @return a new {@link ResourceFieldResolver} instance + */ + public static ResourceElementResolver forField(String fieldName) { + return new ResourceFieldResolver(fieldName, true, fieldName); + } + + /** + * Create a new {@link ResourceFieldResolver} for the specified field and resource name. + * @param fieldName the field name + * @param resourceName the resource name + * @return a new {@link ResourceFieldResolver} instance + */ + public static ResourceElementResolver forField(String fieldName, String resourceName) { + return new ResourceFieldResolver(resourceName, false, fieldName); + } + + /** + * Create a new {@link ResourceMethodResolver} for the specified method + * using a resource name that infers from the method name. + * @param methodName the method name + * @param parameterType the parameter type. + * @return a new {@link ResourceMethodResolver} instance + */ + public static ResourceElementResolver forMethod(String methodName, Class parameterType) { + return new ResourceMethodResolver(defaultResourceNameForMethod(methodName), true, + methodName, parameterType); + } + + /** + * Create a new {@link ResourceMethodResolver} for the specified method + * and resource name. + * @param methodName the method name + * @param parameterType the parameter type + * @param resourceName the resource name + * @return a new {@link ResourceMethodResolver} instance + */ + public static ResourceElementResolver forMethod(String methodName, Class parameterType, String resourceName) { + return new ResourceMethodResolver(resourceName, false, methodName, parameterType); + } + + private static String defaultResourceNameForMethod(String methodName) { + if (methodName.startsWith("set") && methodName.length() > 3) { + return StringUtils.uncapitalizeAsProperty(methodName.substring(3)); + } + return methodName; + } + + + /** + * Resolve the value for the specified registered bean. + * @param registeredBean the registered bean + * @return the resolved field or method parameter value + */ + @Nullable + @SuppressWarnings("unchecked") + public T resolve(RegisteredBean registeredBean) { + Assert.notNull(registeredBean, "'registeredBean' must not be null"); + return (T) (isLazyLookup(registeredBean) ? buildLazyResourceProxy(registeredBean) : + resolveValue(registeredBean)); + } + + /** + * Resolve the value for the specified registered bean and set it using reflection. + * @param registeredBean the registered bean + * @param instance the bean instance + */ + public abstract void resolveAndSet(RegisteredBean registeredBean, Object instance); + + /** + * Create a suitable {@link DependencyDescriptor} for the specified bean. + * @param registeredBean the registered bean + * @return a descriptor for that bean + */ + abstract DependencyDescriptor createDependencyDescriptor(RegisteredBean registeredBean); + + abstract Class getLookupType(RegisteredBean registeredBean); + + abstract AnnotatedElement getAnnotatedElement(RegisteredBean registeredBean); + + boolean isLazyLookup(RegisteredBean registeredBean) { + AnnotatedElement ae = getAnnotatedElement(registeredBean); + Lazy lazy = ae.getAnnotation(Lazy.class); + return (lazy != null && lazy.value()); + } + + private Object buildLazyResourceProxy(RegisteredBean registeredBean) { + Class lookupType = getLookupType(registeredBean); + + TargetSource ts = new TargetSource() { + @Override + public Class getTargetClass() { + return lookupType; + } + @Override + public Object getTarget() { + return resolveValue(registeredBean); + } + }; + + ProxyFactory pf = new ProxyFactory(); + pf.setTargetSource(ts); + if (lookupType.isInterface()) { + pf.addInterface(lookupType); + } + return pf.getProxy(registeredBean.getBeanFactory().getBeanClassLoader()); + } + + /** + * Resolve the value to inject for this instance. + * @param registeredBean the bean registration + * @return the value to inject + */ + private Object resolveValue(RegisteredBean registeredBean) { + ConfigurableListableBeanFactory factory = registeredBean.getBeanFactory(); + + Object resource; + Set autowiredBeanNames; + DependencyDescriptor descriptor = createDependencyDescriptor(registeredBean); + if (this.defaultName && !factory.containsBean(this.name)) { + autowiredBeanNames = new LinkedHashSet<>(); + resource = factory.resolveDependency(descriptor, registeredBean.getBeanName(), autowiredBeanNames, null); + if (resource == null) { + throw new NoSuchBeanDefinitionException(descriptor.getDependencyType(), "No resolvable resource object"); + } + } + else { + resource = factory.resolveBeanByName(this.name, descriptor); + autowiredBeanNames = Collections.singleton(this.name); + } + + for (String autowiredBeanName : autowiredBeanNames) { + if (factory.containsBean(autowiredBeanName)) { + factory.registerDependentBean(autowiredBeanName, registeredBean.getBeanName()); + } + } + return resource; + } + + + private static final class ResourceFieldResolver extends ResourceElementResolver { + + private final String fieldName; + + public ResourceFieldResolver(String name, boolean defaultName, String fieldName) { + super(name, defaultName); + this.fieldName = fieldName; + } + + @Override + public void resolveAndSet(RegisteredBean registeredBean, Object instance) { + Assert.notNull(registeredBean, "'registeredBean' must not be null"); + Assert.notNull(instance, "'instance' must not be null"); + Field field = getField(registeredBean); + Object resolved = resolve(registeredBean); + ReflectionUtils.makeAccessible(field); + ReflectionUtils.setField(field, instance, resolved); + } + + @Override + protected DependencyDescriptor createDependencyDescriptor(RegisteredBean registeredBean) { + Field field = getField(registeredBean); + return new LookupDependencyDescriptor(field, field.getType(), isLazyLookup(registeredBean)); + } + + @Override + protected Class getLookupType(RegisteredBean registeredBean) { + return getField(registeredBean).getType(); + } + + @Override + protected AnnotatedElement getAnnotatedElement(RegisteredBean registeredBean) { + return getField(registeredBean); + } + + private Field getField(RegisteredBean registeredBean) { + Field field = ReflectionUtils.findField(registeredBean.getBeanClass(), this.fieldName); + Assert.notNull(field, + () -> "No field '" + this.fieldName + "' found on " + registeredBean.getBeanClass().getName()); + return field; + } + } + + + private static final class ResourceMethodResolver extends ResourceElementResolver { + + private final String methodName; + + private final Class lookupType; + + private ResourceMethodResolver(String name, boolean defaultName, String methodName, Class lookupType) { + super(name, defaultName); + this.methodName = methodName; + this.lookupType = lookupType; + } + + @Override + public void resolveAndSet(RegisteredBean registeredBean, Object instance) { + Assert.notNull(registeredBean, "'registeredBean' must not be null"); + Assert.notNull(instance, "'instance' must not be null"); + Method method = getMethod(registeredBean); + Object resolved = resolve(registeredBean); + ReflectionUtils.makeAccessible(method); + ReflectionUtils.invokeMethod(method, instance, resolved); + } + + @Override + protected DependencyDescriptor createDependencyDescriptor(RegisteredBean registeredBean) { + return new LookupDependencyDescriptor( + getMethod(registeredBean), this.lookupType, isLazyLookup(registeredBean)); + } + + @Override + protected Class getLookupType(RegisteredBean bean) { + return this.lookupType; + } + + @Override + protected AnnotatedElement getAnnotatedElement(RegisteredBean registeredBean) { + return getMethod(registeredBean); + } + + private Method getMethod(RegisteredBean registeredBean) { + Method method = ReflectionUtils.findMethod(registeredBean.getBeanClass(), this.methodName, this.lookupType); + Assert.notNull(method, + () -> "Method '%s' with parameter type '%s' declared on %s could not be found.".formatted( + this.methodName, this.lookupType.getName(), registeredBean.getBeanClass().getName())); + return method; + } + } + + + /** + * Extension of the DependencyDescriptor class, + * overriding the dependency type with the specified resource type. + */ + @SuppressWarnings("serial") + static class LookupDependencyDescriptor extends DependencyDescriptor { + + private final Class lookupType; + + private final boolean lazyLookup; + + public LookupDependencyDescriptor(Field field, Class lookupType, boolean lazyLookup) { + super(field, true); + this.lookupType = lookupType; + this.lazyLookup = lazyLookup; + } + + public LookupDependencyDescriptor(Method method, Class lookupType, boolean lazyLookup) { + super(new MethodParameter(method, 0), true); + this.lookupType = lookupType; + this.lazyLookup = lazyLookup; + } + + @Override + public Class getDependencyType() { + return this.lookupType; + } + + @Override + public boolean supportsLazyResolution() { + return !this.lazyLookup; + } + } + +} diff --git a/spring-context/src/main/java/org/springframework/context/aot/ResourceElementResolver.java b/spring-context/src/main/java/org/springframework/context/aot/ResourceElementResolver.java deleted file mode 100644 index 2b186ad541..0000000000 --- a/spring-context/src/main/java/org/springframework/context/aot/ResourceElementResolver.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2002-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.context.aot; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Set; - -import javax.lang.model.element.Element; - -import jakarta.annotation.Resource; - -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.config.DependencyDescriptor; -import org.springframework.beans.factory.support.RegisteredBean; -import org.springframework.core.MethodParameter; -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; - -/** - * Base class for resolvers that support injection of named beans on - * an {@link Element}. - * - * @author Stephane Nicoll - * @since 6.1 - * @see Resource - */ -public abstract class ResourceElementResolver { - - protected final String name; - - protected final boolean defaultName; - - protected ResourceElementResolver(String name, boolean defaultName) { - this.name = name; - this.defaultName = defaultName; - } - - /** - * Resolve the field value for the specified registered bean. - * @param registeredBean the registered bean - * @return the resolved field value - */ - @Nullable - @SuppressWarnings("unchecked") - public T resolve(RegisteredBean registeredBean) { - return (T) resolveObject(registeredBean); - } - - /** - * Resolve the field value for the specified registered bean. - * @param registeredBean the registered bean - * @return the resolved field value - */ - public Object resolveObject(RegisteredBean registeredBean) { - Assert.notNull(registeredBean, "'registeredBean' must not be null"); - return resolveValue(registeredBean); - } - - - /** - * Create a suitable {@link DependencyDescriptor} for the specified bean. - * @param bean the registered bean - * @return a descriptor for that bean - */ - protected abstract DependencyDescriptor createDependencyDescriptor(RegisteredBean bean); - - /** - * Resolve the value to inject for this instance. - * @param bean the bean registration - * @return the value to inject - */ - protected Object resolveValue(RegisteredBean bean) { - ConfigurableListableBeanFactory factory = bean.getBeanFactory(); - - Object resource; - Set autowiredBeanNames; - DependencyDescriptor descriptor = createDependencyDescriptor(bean); - if (this.defaultName && !factory.containsBean(this.name)) { - autowiredBeanNames = new LinkedHashSet<>(); - resource = factory.resolveDependency(descriptor, bean.getBeanName(), autowiredBeanNames, null); - if (resource == null) { - throw new NoSuchBeanDefinitionException(descriptor.getDependencyType(), "No resolvable resource object"); - } - } - else { - resource = factory.resolveBeanByName(this.name, descriptor); - autowiredBeanNames = Collections.singleton(this.name); - } - - for (String autowiredBeanName : autowiredBeanNames) { - if (factory.containsBean(autowiredBeanName)) { - factory.registerDependentBean(autowiredBeanName, bean.getBeanName()); - } - } - return resource; - } - - - @SuppressWarnings("serial") - protected static class LookupDependencyDescriptor extends DependencyDescriptor { - - private final Class lookupType; - - public LookupDependencyDescriptor(Field field, Class lookupType) { - super(field, true); - this.lookupType = lookupType; - } - - public LookupDependencyDescriptor(Method method, Class lookupType) { - super(new MethodParameter(method, 0), true); - this.lookupType = lookupType; - } - - @Override - public Class getDependencyType() { - return this.lookupType; - } - } - -} diff --git a/spring-context/src/main/java/org/springframework/context/aot/ResourceFieldValueResolver.java b/spring-context/src/main/java/org/springframework/context/aot/ResourceFieldValueResolver.java deleted file mode 100644 index afda73b498..0000000000 --- a/spring-context/src/main/java/org/springframework/context/aot/ResourceFieldValueResolver.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2002-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.context.aot; - -import java.lang.reflect.Field; - -import org.springframework.aot.hint.ExecutableMode; -import org.springframework.beans.factory.config.DependencyDescriptor; -import org.springframework.beans.factory.support.RegisteredBean; -import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; - -/** - * Resolver used to support injection of named beans on fields. Typically used in - * AOT-processed applications as a targeted alternative to the - * {@link org.springframework.context.annotation.CommonAnnotationBeanPostProcessor}. - * - *

When resolving arguments in a native image, the {@link Field} being used must - * be marked with an {@link ExecutableMode#INTROSPECT introspection} hint so - * that field annotations can be read. Full {@link ExecutableMode#INVOKE - * invocation} hints are only required if the - * {@link #resolveAndSet(RegisteredBean, Object)} method of this class is being - * used (typically to support private fields). - * - * @author Stephane Nicoll - * @since 6.1 - */ -public final class ResourceFieldValueResolver extends ResourceElementResolver { - - private final String fieldName; - - public ResourceFieldValueResolver(String name, boolean defaultName, String fieldName) { - super(name, defaultName); - this.fieldName = fieldName; - } - - - /** - * Create a new {@link ResourceFieldValueResolver} for the specified field. - * @param fieldName the field name - * @return a new {@link ResourceFieldValueResolver} instance - */ - public static ResourceFieldValueResolver forField(String fieldName) { - return new ResourceFieldValueResolver(fieldName, true, fieldName); - } - - /** - * Create a new {@link ResourceFieldValueResolver} for the specified field and - * resource name. - * @param fieldName the field name - * @param resourceName the resource name - * @return a new {@link ResourceFieldValueResolver} instance - */ - public static ResourceFieldValueResolver forField(String fieldName, String resourceName) { - return new ResourceFieldValueResolver(resourceName, false, fieldName); - } - - @Override - protected DependencyDescriptor createDependencyDescriptor(RegisteredBean bean) { - Field field = getField(bean); - return new LookupDependencyDescriptor(field, field.getType()); - } - - /** - * Resolve the field value for the specified registered bean and set it - * using reflection. - * @param registeredBean the registered bean - * @param instance the bean instance - */ - public void resolveAndSet(RegisteredBean registeredBean, Object instance) { - Assert.notNull(registeredBean, "'registeredBean' must not be null"); - Assert.notNull(instance, "'instance' must not be null"); - Field field = getField(registeredBean); - Object resolved = resolveValue(registeredBean); - ReflectionUtils.makeAccessible(field); - ReflectionUtils.setField(field, instance, resolved); - } - - private Field getField(RegisteredBean registeredBean) { - Field field = ReflectionUtils.findField(registeredBean.getBeanClass(), - this.fieldName); - Assert.notNull(field, () -> "No field '" + this.fieldName + "' found on " - + registeredBean.getBeanClass().getName()); - return field; - } - -} diff --git a/spring-context/src/main/java/org/springframework/context/aot/ResourceMethodArgumentResolver.java b/spring-context/src/main/java/org/springframework/context/aot/ResourceMethodArgumentResolver.java deleted file mode 100644 index 6abf885373..0000000000 --- a/spring-context/src/main/java/org/springframework/context/aot/ResourceMethodArgumentResolver.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2002-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.context.aot; - -import java.lang.reflect.Method; - -import org.springframework.aot.hint.ExecutableMode; -import org.springframework.beans.factory.config.DependencyDescriptor; -import org.springframework.beans.factory.support.RegisteredBean; -import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; -import org.springframework.util.StringUtils; - -/** - * Resolver used to support injection of named beans to methods. Typically used in - * AOT-processed applications as a targeted alternative to the - * {@link org.springframework.context.annotation.CommonAnnotationBeanPostProcessor}. - * - *

When resolving arguments in a native image, the {@link Method} being used - * must be marked with an {@link ExecutableMode#INTROSPECT introspection} hint - * so that field annotations can be read. Full {@link ExecutableMode#INVOKE - * invocation} hints are only required if the - * {@link #resolveAndInvoke(RegisteredBean, Object)} method of this class is - * being used (typically to support private methods). - * @author Stephane Nicoll - * @since 6.1 - */ -public final class ResourceMethodArgumentResolver extends ResourceElementResolver { - - private final String methodName; - - private final Class lookupType; - - private ResourceMethodArgumentResolver(String name, boolean defaultName, - String methodName, Class lookupType) { - super(name, defaultName); - this.methodName = methodName; - this.lookupType = lookupType; - } - - - /** - * Create a new {@link ResourceMethodArgumentResolver} for the specified method - * using a resource name that infers from the method name. - * @param methodName the method name - * @param parameterType the parameter type. - * @return a new {@link ResourceMethodArgumentResolver} instance - */ - public static ResourceMethodArgumentResolver forMethod(String methodName, Class parameterType) { - return new ResourceMethodArgumentResolver(defaultResourceName(methodName), true, - methodName, parameterType); - } - - /** - * Create a new {@link ResourceMethodArgumentResolver} for the specified method - * and resource name. - * @param methodName the method name - * @param parameterType the parameter type - * @param resourceName the resource name - * @return a new {@link ResourceMethodArgumentResolver} instance - */ - public static ResourceMethodArgumentResolver forMethod(String methodName, Class parameterType, String resourceName) { - return new ResourceMethodArgumentResolver(resourceName, false, methodName, parameterType); - } - - @Override - protected DependencyDescriptor createDependencyDescriptor(RegisteredBean bean) { - return new LookupDependencyDescriptor(getMethod(bean), this.lookupType); - } - - /** - * Resolve the method argument for the specified registered bean and invoke - * the method using reflection. - * @param registeredBean the registered bean - * @param instance the bean instance - */ - public void resolveAndInvoke(RegisteredBean registeredBean, Object instance) { - Assert.notNull(registeredBean, "'registeredBean' must not be null"); - Assert.notNull(instance, "'instance' must not be null"); - Method method = getMethod(registeredBean); - Object resolved = resolveValue(registeredBean); - ReflectionUtils.makeAccessible(method); - ReflectionUtils.invokeMethod(method, instance, resolved); - } - - private Method getMethod(RegisteredBean registeredBean) { - Method method = ReflectionUtils.findMethod(registeredBean.getBeanClass(), - this.methodName, this.lookupType); - Assert.notNull(method, () -> - "Method '%s' with parameter type '%s' declared on %s could not be found.".formatted( - this.methodName, this.lookupType.getName(), - registeredBean.getBeanClass().getName())); - return method; - } - - private static String defaultResourceName(String methodName) { - if (methodName.startsWith("set") && methodName.length() > 3) { - return StringUtils.uncapitalizeAsProperty(methodName.substring(3)); - } - return methodName; - } - -} diff --git a/spring-context/src/test/java/org/springframework/context/annotation/CommonAnnotationBeanRegistrationAotContributionTests.java b/spring-context/src/test/java/org/springframework/context/annotation/CommonAnnotationBeanRegistrationAotContributionTests.java index c9bb47dca6..7f4e591bc4 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/CommonAnnotationBeanRegistrationAotContributionTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/CommonAnnotationBeanRegistrationAotContributionTests.java @@ -142,7 +142,7 @@ class CommonAnnotationBeanRegistrationAotContributionTests { postProcessor.apply(registeredBean, instance); assertThat(instance).extracting("one").isEqualTo("1"); assertThat(getSourceFile(compiled, PrivateMethodResourceSample.class)) - .contains("resolveAndInvoke("); + .contains("resolveAndSet("); }); } @@ -160,7 +160,7 @@ class CommonAnnotationBeanRegistrationAotContributionTests { postProcessor.apply(registeredBean, instance); assertThat(instance).extracting("text").isEqualTo("1"); assertThat(getSourceFile(compiled, PrivateMethodResourceWithCustomNameSample.class)) - .contains("resolveAndInvoke("); + .contains("resolveAndSet("); }); } @@ -198,7 +198,7 @@ class CommonAnnotationBeanRegistrationAotContributionTests { postProcessor.apply(registeredBean, instance); assertThat(instance).extracting("one").isEqualTo("1"); assertThat(getSourceFile(compiled, PackagePrivateMethodResourceFromParentSample.class)) - .contains("resolveAndInvoke("); + .contains("resolveAndSet("); }); } diff --git a/spring-context/src/test/java/org/springframework/context/aot/ResourceFieldValueResolverTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ResourceElementResolverFieldTests.java similarity index 76% rename from spring-context/src/test/java/org/springframework/context/aot/ResourceFieldValueResolverTests.java rename to spring-context/src/test/java/org/springframework/context/annotation/ResourceElementResolverFieldTests.java index a2d425602d..bac2714cad 100644 --- a/spring-context/src/test/java/org/springframework/context/aot/ResourceFieldValueResolverTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ResourceElementResolverFieldTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.context.aot; +package org.springframework.context.annotation; import org.junit.jupiter.api.Test; @@ -30,11 +30,11 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException import static org.assertj.core.api.Assertions.assertThatThrownBy; /** - * Tests for {@link ResourceFieldValueResolver}. + * Tests for {@code ResourceFieldValueResolver}. * * @author Stephane Nicoll */ -class ResourceFieldValueResolverTests { +class ResourceElementResolverFieldTests { private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); @@ -43,8 +43,7 @@ class ResourceFieldValueResolverTests { void resolveWhenFieldIsMissingThrowsException() { RegisteredBean registeredBean = registerTestBean(this.beanFactory); assertThatIllegalArgumentException() - .isThrownBy(() -> ResourceFieldValueResolver.forField("missing") - .resolve(registeredBean)) + .isThrownBy(() -> ResourceElementResolver.forField("missing").resolve(registeredBean)) .withMessage("No field 'missing' found on " + TestBean.class.getName()); } @@ -53,7 +52,7 @@ class ResourceFieldValueResolverTests { this.beanFactory.registerSingleton("one", "1"); this.beanFactory.registerSingleton("two", "2"); RegisteredBean registeredBean = registerTestBean(this.beanFactory); - Object resolved = ResourceFieldValueResolver.forField("one") + Object resolved = ResourceElementResolver.forField("one") .resolve(registeredBean); assertThat(resolved).isEqualTo("1"); } @@ -63,8 +62,7 @@ class ResourceFieldValueResolverTests { this.beanFactory.registerSingleton("one", "1"); this.beanFactory.registerSingleton("two", "2"); RegisteredBean registeredBean = registerTestBean(this.beanFactory); - Object resolved = ResourceFieldValueResolver.forField("test", "two") - .resolve(registeredBean); + Object resolved = ResourceElementResolver.forField("test", "two").resolve(registeredBean); assertThat(resolved).isEqualTo("2"); } @@ -72,8 +70,7 @@ class ResourceFieldValueResolverTests { void resolveWheNoMatchFallbackOnType() { this.beanFactory.registerSingleton("two", "2"); RegisteredBean registeredBean = registerTestBean(this.beanFactory); - Object resolved = ResourceFieldValueResolver.forField("one") - .resolve(registeredBean); + Object resolved = ResourceElementResolver.forField("one").resolve(registeredBean); assertThat(resolved).isEqualTo("2"); } @@ -82,9 +79,8 @@ class ResourceFieldValueResolverTests { this.beanFactory.registerSingleton("one", "1"); this.beanFactory.registerSingleton("two", "2"); RegisteredBean registeredBean = registerTestBean(this.beanFactory); - assertThatThrownBy(() -> ResourceFieldValueResolver.forField("test") - .resolve(registeredBean) - ).isInstanceOf(NoUniqueBeanDefinitionException.class) + assertThatThrownBy(() -> ResourceElementResolver.forField("test").resolve(registeredBean)) + .isInstanceOf(NoUniqueBeanDefinitionException.class) .hasMessageContaining(String.class.getName()) .hasMessageContaining("one").hasMessageContaining("two"); } @@ -92,9 +88,8 @@ class ResourceFieldValueResolverTests { @Test void resolveWhenNoCandidateMatchingTypeThrowsException() { RegisteredBean registeredBean = registerTestBean(this.beanFactory); - assertThatThrownBy(() -> ResourceFieldValueResolver.forField("test") - .resolve(registeredBean) - ).isInstanceOf(NoSuchBeanDefinitionException.class) + assertThatThrownBy(() -> ResourceElementResolver.forField("test").resolve(registeredBean)) + .isInstanceOf(NoSuchBeanDefinitionException.class) .hasMessageContaining(String.class.getName()); } @@ -102,9 +97,8 @@ class ResourceFieldValueResolverTests { void resolveWhenInvalidMatchingTypeThrowsException() { this.beanFactory.registerSingleton("count", "counter"); RegisteredBean registeredBean = registerTestBean(this.beanFactory); - assertThatThrownBy(() -> ResourceFieldValueResolver.forField("count") - .resolve(registeredBean) - ).isInstanceOf(BeanNotOfRequiredTypeException.class) + assertThatThrownBy(() -> ResourceElementResolver.forField("count").resolve(registeredBean)) + .isInstanceOf(BeanNotOfRequiredTypeException.class) .hasMessageContaining(Integer.class.getName()) .hasMessageContaining(String.class.getName()); } @@ -114,8 +108,7 @@ class ResourceFieldValueResolverTests { this.beanFactory.registerSingleton("one", "1"); RegisteredBean registeredBean = registerTestBean(this.beanFactory); TestBean testBean = new TestBean(); - ResourceFieldValueResolver.forField("one").resolveAndSet(registeredBean, - testBean); + ResourceElementResolver.forField("one").resolveAndSet(registeredBean, testBean); assertThat(testBean.one).isEqualTo("1"); } @@ -123,16 +116,16 @@ class ResourceFieldValueResolverTests { void resolveRegistersDependantBeans() { this.beanFactory.registerSingleton("one", "1"); RegisteredBean registeredBean = registerTestBean(this.beanFactory); - ResourceFieldValueResolver.forField("one").resolve(registeredBean); + ResourceElementResolver.forField("one").resolve(registeredBean); assertThat(this.beanFactory.getDependentBeans("one")).containsExactly("testBean"); } private RegisteredBean registerTestBean(DefaultListableBeanFactory beanFactory) { - beanFactory.registerBeanDefinition("testBean", - new RootBeanDefinition(TestBean.class)); + beanFactory.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class)); return RegisteredBean.of(beanFactory, "testBean"); } + static class TestBean { String one; @@ -140,7 +133,6 @@ class ResourceFieldValueResolverTests { String test; Integer count; - } } diff --git a/spring-context/src/test/java/org/springframework/context/aot/ResourceMethodArgumentResolverTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ResourceElementResolverMethodTests.java similarity index 75% rename from spring-context/src/test/java/org/springframework/context/aot/ResourceMethodArgumentResolverTests.java rename to spring-context/src/test/java/org/springframework/context/annotation/ResourceElementResolverMethodTests.java index 50db06e867..d8b689216a 100644 --- a/spring-context/src/test/java/org/springframework/context/aot/ResourceMethodArgumentResolverTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ResourceElementResolverMethodTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.context.aot; +package org.springframework.context.annotation; import java.io.InputStream; @@ -32,18 +32,19 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException import static org.assertj.core.api.Assertions.assertThatThrownBy; /** - * Tests for {@link ResourceMethodArgumentResolver}/ + * Tests for {@code ResourceMethodArgumentResolver}. * * @author Stephane Nicoll */ -class ResourceMethodArgumentResolverTests { +class ResourceElementResolverMethodTests { private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + @Test void resolveWhenMethodIsMissingThrowsException() { RegisteredBean registeredBean = registerTestBean(this.beanFactory); - ResourceMethodArgumentResolver resolver = ResourceMethodArgumentResolver.forMethod("missing", InputStream.class); + ResourceElementResolver resolver = ResourceElementResolver.forMethod("missing", InputStream.class); assertThatIllegalArgumentException() .isThrownBy(() -> resolver.resolve(registeredBean)) .withMessage("Method 'missing' with parameter type 'java.io.InputStream' declared on %s could not be found.", @@ -55,8 +56,7 @@ class ResourceMethodArgumentResolverTests { this.beanFactory.registerSingleton("one", "1"); this.beanFactory.registerSingleton("two", "2"); RegisteredBean registeredBean = registerTestBean(this.beanFactory); - ResourceMethodArgumentResolver resolver = ResourceMethodArgumentResolver - .forMethod("setOne", String.class); + ResourceElementResolver resolver = ResourceElementResolver.forMethod("setOne", String.class); Object resolved = resolver.resolve(registeredBean); assertThat(resolved).isEqualTo("1"); } @@ -66,8 +66,7 @@ class ResourceMethodArgumentResolverTests { this.beanFactory.registerSingleton("one", "1"); this.beanFactory.registerSingleton("two", "2"); RegisteredBean registeredBean = registerTestBean(this.beanFactory); - Object resolved = ResourceMethodArgumentResolver.forMethod("setTest", String.class, "two") - .resolve(registeredBean); + Object resolved = ResourceElementResolver.forMethod("setTest", String.class, "two").resolve(registeredBean); assertThat(resolved).isEqualTo("2"); } @@ -75,8 +74,7 @@ class ResourceMethodArgumentResolverTests { void resolveWheNoMatchFallbackOnType() { this.beanFactory.registerSingleton("two", "2"); RegisteredBean registeredBean = registerTestBean(this.beanFactory); - Object resolved = ResourceMethodArgumentResolver.forMethod("setOne", String.class) - .resolve(registeredBean); + Object resolved = ResourceElementResolver.forMethod("setOne", String.class).resolve(registeredBean); assertThat(resolved).isEqualTo("2"); } @@ -85,9 +83,8 @@ class ResourceMethodArgumentResolverTests { this.beanFactory.registerSingleton("one", "1"); this.beanFactory.registerSingleton("two", "2"); RegisteredBean registeredBean = registerTestBean(this.beanFactory); - assertThatThrownBy(() -> ResourceMethodArgumentResolver.forMethod("setTest", String.class) - .resolve(registeredBean) - ).isInstanceOf(NoUniqueBeanDefinitionException.class) + assertThatThrownBy(() -> ResourceElementResolver.forMethod("setTest", String.class).resolve(registeredBean)) + .isInstanceOf(NoUniqueBeanDefinitionException.class) .hasMessageContaining(String.class.getName()) .hasMessageContaining("one").hasMessageContaining("two"); } @@ -95,9 +92,8 @@ class ResourceMethodArgumentResolverTests { @Test void resolveWhenNoCandidateMatchingTypeThrowsException() { RegisteredBean registeredBean = registerTestBean(this.beanFactory); - assertThatThrownBy(() -> ResourceMethodArgumentResolver.forMethod("setTest", String.class) - .resolve(registeredBean) - ).isInstanceOf(NoSuchBeanDefinitionException.class) + assertThatThrownBy(() -> ResourceElementResolver.forMethod("setTest", String.class).resolve(registeredBean)) + .isInstanceOf(NoSuchBeanDefinitionException.class) .hasMessageContaining(String.class.getName()); } @@ -105,9 +101,8 @@ class ResourceMethodArgumentResolverTests { void resolveWhenInvalidMatchingTypeThrowsException() { this.beanFactory.registerSingleton("count", "counter"); RegisteredBean registeredBean = registerTestBean(this.beanFactory); - assertThatThrownBy(() -> ResourceMethodArgumentResolver.forMethod("setCount", Integer.class) - .resolve(registeredBean) - ).isInstanceOf(BeanNotOfRequiredTypeException.class) + assertThatThrownBy(() -> ResourceElementResolver.forMethod("setCount", Integer.class).resolve(registeredBean)) + .isInstanceOf(BeanNotOfRequiredTypeException.class) .hasMessageContaining(Integer.class.getName()) .hasMessageContaining(String.class.getName()); } @@ -117,8 +112,7 @@ class ResourceMethodArgumentResolverTests { this.beanFactory.registerSingleton("one", "1"); RegisteredBean registeredBean = registerTestBean(this.beanFactory); TestBean testBean = new TestBean(); - ResourceMethodArgumentResolver.forMethod("setOne", String.class) - .resolveAndInvoke(registeredBean, testBean); + ResourceElementResolver.forMethod("setOne", String.class).resolveAndSet(registeredBean, testBean); assertThat(testBean.one).isEqualTo("1"); } @@ -126,13 +120,12 @@ class ResourceMethodArgumentResolverTests { void resolveRegistersDependantBeans() { this.beanFactory.registerSingleton("one", "1"); RegisteredBean registeredBean = registerTestBean(this.beanFactory); - ResourceMethodArgumentResolver.forMethod("setOne", String.class).resolve(registeredBean); + ResourceElementResolver.forMethod("setOne", String.class).resolve(registeredBean); assertThat(this.beanFactory.getDependentBeans("one")).containsExactly("testBean"); } private RegisteredBean registerTestBean(DefaultListableBeanFactory beanFactory) { - beanFactory.registerBeanDefinition("testBean", - new RootBeanDefinition(TestBean.class)); + beanFactory.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class)); return RegisteredBean.of(beanFactory, "testBean"); } @@ -156,7 +149,6 @@ class ResourceMethodArgumentResolverTests { public void setCount(Integer count) { this.count = count; } - } } diff --git a/spring-context/src/test/java/org/springframework/context/aot/ApplicationContextAotGeneratorTests.java b/spring-context/src/test/java/org/springframework/context/aot/ApplicationContextAotGeneratorTests.java index af307a8bc2..23ace1a4f7 100644 --- a/spring-context/src/test/java/org/springframework/context/aot/ApplicationContextAotGeneratorTests.java +++ b/spring-context/src/test/java/org/springframework/context/aot/ApplicationContextAotGeneratorTests.java @@ -24,7 +24,6 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.stream.Stream; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -253,7 +252,6 @@ class ApplicationContextAotGeneratorTests { } @Test - @Disabled("gh-31447") void processAheadOfTimeWhenHasLazyResourceAutowiringOnField() { testResourceAutowiringComponent(LazyResourceFieldComponent.class, (bean, generationContext) -> { Environment environment = bean.getEnvironment();