Browse Source
Includes refactored @Resource resolver for AOT with lazy resolution support. Closes gh-31447 See gh-29614pull/31822/head
11 changed files with 410 additions and 457 deletions
@ -0,0 +1,330 @@
@@ -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; |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -1,138 +0,0 @@
@@ -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; |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -1,101 +0,0 @@
@@ -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; |
||||
} |
||||
|
||||
} |
||||
@ -1,117 +0,0 @@
@@ -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; |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue