Avoid double proxying for @Resource @Lazy fallback autowiring

Includes refactored @Resource resolver for AOT with lazy resolution support.

Closes gh-31447
See gh-29614
This commit is contained in:
Juergen Hoeller
2023-12-12 12:39:59 +01:00
parent 6bb9775309
commit 6dcba4de2c
11 changed files with 410 additions and 457 deletions

View File

@@ -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());
}
}

View File

@@ -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> 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<String> 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;
}
}
}

View File

@@ -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> 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<String> 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;
}
}
}

View File

@@ -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}.
*
* <p>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;
}
}

View File

@@ -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}.
*
* <p>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;
}
}