Consistent UnsatisfiedDependencyException exposure with injection point metadata
Issue: SPR-13968
This commit is contained in:
parent
4c964473b1
commit
b6dd8a9233
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2016 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.
|
||||
|
@ -63,7 +63,7 @@ public class BeanCreationException extends FatalBeanException {
|
|||
* @param msg the detail message
|
||||
*/
|
||||
public BeanCreationException(String beanName, String msg) {
|
||||
super("Error creating bean with name '" + beanName + "': " + msg);
|
||||
super("Error creating bean" + (beanName != null ? " with name '" + beanName + "'" : "") + ": " + msg);
|
||||
this.beanName = beanName;
|
||||
}
|
||||
|
||||
|
@ -86,7 +86,7 @@ public class BeanCreationException extends FatalBeanException {
|
|||
* @param msg the detail message
|
||||
*/
|
||||
public BeanCreationException(String resourceDescription, String beanName, String msg) {
|
||||
super("Error creating bean with name '" + beanName + "'" +
|
||||
super("Error creating bean" + (beanName != null ? " with name '" + beanName + "'" : "") +
|
||||
(resourceDescription != null ? " defined in " + resourceDescription : "") + ": " + msg);
|
||||
this.resourceDescription = resourceDescription;
|
||||
this.beanName = beanName;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2012 the original author or authors.
|
||||
* Copyright 2002-2016 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.
|
||||
|
@ -23,8 +23,8 @@ import org.springframework.beans.FatalBeanException;
|
|||
* factory-aware initialization code fails. BeansExceptions thrown by
|
||||
* bean factory methods themselves should simply be propagated as-is.
|
||||
*
|
||||
* <p>Note that non-factory-aware initialization methods like afterPropertiesSet()
|
||||
* or a custom "init-method" can throw any exception.
|
||||
* <p>Note that {@code afterPropertiesSet()} or a custom "init-method"
|
||||
* can throw any exception.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 13.11.2003
|
||||
|
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* Copyright 2002-2016 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
|
||||
*
|
||||
* http://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.beans.factory;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Member;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A simple descriptor for an injection point, pointing to a method/constructor
|
||||
* parameter or a field. Exposed by {@link UnsatisfiedDependencyException}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 4.3
|
||||
* @see UnsatisfiedDependencyException#getInjectionPoint()
|
||||
* @see org.springframework.beans.factory.config.DependencyDescriptor
|
||||
*/
|
||||
public class InjectionPoint {
|
||||
|
||||
protected MethodParameter methodParameter;
|
||||
|
||||
protected Field field;
|
||||
|
||||
private volatile Annotation[] fieldAnnotations;
|
||||
|
||||
|
||||
/**
|
||||
* Create an injection point descriptor for a method or constructor parameter.
|
||||
* @param methodParameter the MethodParameter to wrap
|
||||
*/
|
||||
public InjectionPoint(MethodParameter methodParameter) {
|
||||
Assert.notNull(methodParameter, "MethodParameter must not be null");
|
||||
this.methodParameter = methodParameter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an injection point descriptor for a field.
|
||||
* @param field the field to wrap
|
||||
*/
|
||||
public InjectionPoint(Field field) {
|
||||
Assert.notNull(field, "Field must not be null");
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy constructor.
|
||||
* @param original the original descriptor to create a copy from
|
||||
*/
|
||||
protected InjectionPoint(InjectionPoint original) {
|
||||
this.methodParameter = (original.methodParameter != null ?
|
||||
new MethodParameter(original.methodParameter) : null);
|
||||
this.field = original.field;
|
||||
this.fieldAnnotations = original.fieldAnnotations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Just available for serialization purposes in subclasses.
|
||||
*/
|
||||
protected InjectionPoint() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the wrapped MethodParameter, if any.
|
||||
* <p>Note: Either MethodParameter or Field is available.
|
||||
* @return the MethodParameter, or {@code null} if none
|
||||
*/
|
||||
public MethodParameter getMethodParameter() {
|
||||
return this.methodParameter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the wrapped Field, if any.
|
||||
* <p>Note: Either MethodParameter or Field is available.
|
||||
* @return the Field, or {@code null} if none
|
||||
*/
|
||||
public Field getField() {
|
||||
return this.field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the annotations associated with the wrapped field or method/constructor parameter.
|
||||
*/
|
||||
public Annotation[] getAnnotations() {
|
||||
if (this.field != null) {
|
||||
if (this.fieldAnnotations == null) {
|
||||
this.fieldAnnotations = this.field.getAnnotations();
|
||||
}
|
||||
return this.fieldAnnotations;
|
||||
}
|
||||
else {
|
||||
return this.methodParameter.getParameterAnnotations();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the type declared by the underlying field or method/constructor parameter,
|
||||
* indicating the injection type.
|
||||
*/
|
||||
public Class<?> getDeclaredType() {
|
||||
return (this.field != null ? this.field.getType() : this.methodParameter.getParameterType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the wrapped member, containing the injection point.
|
||||
* @return the Field / Method / Constructor as Member
|
||||
*/
|
||||
public Member getMember() {
|
||||
return (this.field != null ? this.field : this.methodParameter.getMember());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the wrapped annotated element.
|
||||
* <p>Note: In case of a method/constructor parameter, this exposes
|
||||
* the annotations declared on the method or constructor itself
|
||||
* (i.e. at the method/constructor level, not at the parameter level).
|
||||
* Use {@link #getAnnotations()} to obtain parameter-level annotations in
|
||||
* such a scenario, transparently with corresponding field annotations.
|
||||
* @return the Field / Method / Constructor as AnnotatedElement
|
||||
*/
|
||||
public AnnotatedElement getAnnotatedElement() {
|
||||
return (this.field != null ? this.field : this.methodParameter.getAnnotatedElement());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (getClass() != other.getClass()) {
|
||||
return false;
|
||||
}
|
||||
InjectionPoint otherPoint = (InjectionPoint) other;
|
||||
return (this.field != null ? this.field.equals(otherPoint.field) :
|
||||
this.methodParameter.equals(otherPoint.methodParameter));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (this.field != null ? this.field.hashCode() : this.methodParameter.hashCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return (this.field != null ? "field '" + this.field.getName() + "'" : this.methodParameter.toString());
|
||||
}
|
||||
|
||||
}
|
|
@ -31,6 +31,9 @@ import org.springframework.util.ClassUtils;
|
|||
@SuppressWarnings("serial")
|
||||
public class UnsatisfiedDependencyException extends BeanCreationException {
|
||||
|
||||
private InjectionPoint injectionPoint;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new UnsatisfiedDependencyException.
|
||||
* @param resourceDescription description of the resource that the bean definition came from
|
||||
|
@ -60,6 +63,36 @@ public class UnsatisfiedDependencyException extends BeanCreationException {
|
|||
initCause(ex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new UnsatisfiedDependencyException.
|
||||
* @param resourceDescription description of the resource that the bean definition came from
|
||||
* @param beanName the name of the bean requested
|
||||
* @param injectionPoint the injection point (field or method/constructor parameter)
|
||||
* @param msg the detail message
|
||||
* @since 4.3
|
||||
*/
|
||||
public UnsatisfiedDependencyException(
|
||||
String resourceDescription, String beanName, InjectionPoint injectionPoint, String msg) {
|
||||
|
||||
super(resourceDescription, beanName, "Unsatisfied dependency expressed through " + injectionPoint + ": " + msg);
|
||||
this.injectionPoint = injectionPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new UnsatisfiedDependencyException.
|
||||
* @param resourceDescription description of the resource that the bean definition came from
|
||||
* @param beanName the name of the bean requested
|
||||
* @param injectionPoint the injection point (field or method/constructor parameter)
|
||||
* @param ex the bean creation exception that indicated the unsatisfied dependency
|
||||
* @since 4.3
|
||||
*/
|
||||
public UnsatisfiedDependencyException(
|
||||
String resourceDescription, String beanName, InjectionPoint injectionPoint, BeansException ex) {
|
||||
|
||||
this(resourceDescription, beanName, injectionPoint, (ex != null ? ex.getMessage() : ""));
|
||||
initCause(ex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new UnsatisfiedDependencyException.
|
||||
* @param resourceDescription description of the resource that the bean definition came from
|
||||
|
@ -67,7 +100,9 @@ public class UnsatisfiedDependencyException extends BeanCreationException {
|
|||
* @param ctorArgIndex the index of the constructor argument that couldn't be satisfied
|
||||
* @param ctorArgType the type of the constructor argument that couldn't be satisfied
|
||||
* @param msg the detail message
|
||||
* @deprecated in favor of {@link #UnsatisfiedDependencyException(String, String, InjectionPoint, String)}
|
||||
*/
|
||||
@Deprecated
|
||||
public UnsatisfiedDependencyException(
|
||||
String resourceDescription, String beanName, int ctorArgIndex, Class<?> ctorArgType, String msg) {
|
||||
|
||||
|
@ -84,7 +119,9 @@ public class UnsatisfiedDependencyException extends BeanCreationException {
|
|||
* @param ctorArgIndex the index of the constructor argument that couldn't be satisfied
|
||||
* @param ctorArgType the type of the constructor argument that couldn't be satisfied
|
||||
* @param ex the bean creation exception that indicated the unsatisfied dependency
|
||||
* @deprecated in favor of {@link #UnsatisfiedDependencyException(String, String, InjectionPoint, BeansException)}
|
||||
*/
|
||||
@Deprecated
|
||||
public UnsatisfiedDependencyException(
|
||||
String resourceDescription, String beanName, int ctorArgIndex, Class<?> ctorArgType, BeansException ex) {
|
||||
|
||||
|
@ -92,4 +129,13 @@ public class UnsatisfiedDependencyException extends BeanCreationException {
|
|||
initCause(ex);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the injection point (field or method/constructor parameter), if known.
|
||||
* @since 4.3
|
||||
*/
|
||||
public InjectionPoint getInjectionPoint() {
|
||||
return this.injectionPoint;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2016 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.
|
||||
|
@ -45,7 +45,9 @@ import org.springframework.beans.factory.BeanCreationException;
|
|||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.InjectionPoint;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.UnsatisfiedDependencyException;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.DependencyDescriptor;
|
||||
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
|
||||
|
@ -347,6 +349,9 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
|||
try {
|
||||
metadata.inject(bean, beanName, pvs);
|
||||
}
|
||||
catch (BeanCreationException ex) {
|
||||
throw ex;
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
|
||||
}
|
||||
|
@ -365,6 +370,9 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
|||
try {
|
||||
metadata.inject(bean, null, null);
|
||||
}
|
||||
catch (BeanCreationException ex) {
|
||||
throw ex;
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new BeanCreationException("Injection of autowired dependencies failed for class [" + clazz + "]", ex);
|
||||
}
|
||||
|
@ -549,7 +557,6 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
|||
@Override
|
||||
protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
|
||||
Field field = (Field) this.member;
|
||||
try {
|
||||
Object value;
|
||||
if (this.cached) {
|
||||
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
|
||||
|
@ -559,7 +566,12 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
|||
desc.setContainingClass(bean.getClass());
|
||||
Set<String> autowiredBeanNames = new LinkedHashSet<String>(1);
|
||||
TypeConverter typeConverter = beanFactory.getTypeConverter();
|
||||
try {
|
||||
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
|
||||
}
|
||||
catch (BeansException ex) {
|
||||
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
|
||||
}
|
||||
synchronized (this) {
|
||||
if (!this.cached) {
|
||||
if (value != null || this.required) {
|
||||
|
@ -586,10 +598,6 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
|||
field.set(bean, value);
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new BeanCreationException("Could not autowire field: " + field, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -615,7 +623,6 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
|||
return;
|
||||
}
|
||||
Method method = (Method) this.member;
|
||||
try {
|
||||
Object[] arguments;
|
||||
if (this.cached) {
|
||||
// Shortcut for avoiding synchronization...
|
||||
|
@ -629,20 +636,25 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
|||
TypeConverter typeConverter = beanFactory.getTypeConverter();
|
||||
for (int i = 0; i < arguments.length; i++) {
|
||||
MethodParameter methodParam = new MethodParameter(method, i);
|
||||
DependencyDescriptor desc = new DependencyDescriptor(methodParam, this.required);
|
||||
desc.setContainingClass(bean.getClass());
|
||||
descriptors[i] = desc;
|
||||
Object arg = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
|
||||
DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required);
|
||||
currDesc.setContainingClass(bean.getClass());
|
||||
descriptors[i] = currDesc;
|
||||
try {
|
||||
Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeanNames, typeConverter);
|
||||
if (arg == null && !this.required) {
|
||||
arguments = null;
|
||||
break;
|
||||
}
|
||||
arguments[i] = arg;
|
||||
}
|
||||
catch (BeansException ex) {
|
||||
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex);
|
||||
}
|
||||
}
|
||||
synchronized (this) {
|
||||
if (!this.cached) {
|
||||
if (arguments != null) {
|
||||
this.cachedMethodArguments = new Object[arguments.length];
|
||||
this.cachedMethodArguments = new Object[paramTypes.length];
|
||||
for (int i = 0; i < arguments.length; i++) {
|
||||
this.cachedMethodArguments[i] = descriptors[i];
|
||||
}
|
||||
|
@ -667,15 +679,13 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
|||
}
|
||||
}
|
||||
if (arguments != null) {
|
||||
try {
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
method.invoke(bean, arguments);
|
||||
}
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
catch (InvocationTargetException ex){
|
||||
throw ex.getTargetException();
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
throw new BeanCreationException("Could not autowire method: " + method, ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ package org.springframework.beans.factory.config;
|
|||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.Serializable;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
|
@ -27,13 +26,13 @@ import java.util.Map;
|
|||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.InjectionPoint;
|
||||
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
|
||||
import org.springframework.core.GenericCollectionTypeResolver;
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Descriptor for a specific dependency that is about to be injected.
|
||||
|
@ -44,15 +43,9 @@ import org.springframework.util.Assert;
|
|||
* @since 2.5
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class DependencyDescriptor implements Serializable {
|
||||
public class DependencyDescriptor extends InjectionPoint implements Serializable {
|
||||
|
||||
private transient MethodParameter methodParameter;
|
||||
|
||||
private transient Field field;
|
||||
|
||||
private Class<?> declaringClass;
|
||||
|
||||
private Class<?> containingClass;
|
||||
private final Class<?> declaringClass;
|
||||
|
||||
private String methodName;
|
||||
|
||||
|
@ -68,7 +61,7 @@ public class DependencyDescriptor implements Serializable {
|
|||
|
||||
private int nestingLevel = 1;
|
||||
|
||||
private transient Annotation[] fieldAnnotations;
|
||||
private Class<?> containingClass;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -89,10 +82,8 @@ public class DependencyDescriptor implements Serializable {
|
|||
* eagerly resolving potential target beans for type matching
|
||||
*/
|
||||
public DependencyDescriptor(MethodParameter methodParameter, boolean required, boolean eager) {
|
||||
Assert.notNull(methodParameter, "MethodParameter must not be null");
|
||||
this.methodParameter = methodParameter;
|
||||
super(methodParameter);
|
||||
this.declaringClass = methodParameter.getDeclaringClass();
|
||||
this.containingClass = methodParameter.getContainingClass();
|
||||
if (this.methodParameter.getMethod() != null) {
|
||||
this.methodName = methodParameter.getMethod().getName();
|
||||
this.parameterTypes = methodParameter.getMethod().getParameterTypes();
|
||||
|
@ -101,6 +92,7 @@ public class DependencyDescriptor implements Serializable {
|
|||
this.parameterTypes = methodParameter.getConstructor().getParameterTypes();
|
||||
}
|
||||
this.parameterIndex = methodParameter.getParameterIndex();
|
||||
this.containingClass = methodParameter.getContainingClass();
|
||||
this.required = required;
|
||||
this.eager = eager;
|
||||
}
|
||||
|
@ -123,8 +115,7 @@ public class DependencyDescriptor implements Serializable {
|
|||
* eagerly resolving potential target beans for type matching
|
||||
*/
|
||||
public DependencyDescriptor(Field field, boolean required, boolean eager) {
|
||||
Assert.notNull(field, "Field must not be null");
|
||||
this.field = field;
|
||||
super(field);
|
||||
this.declaringClass = field.getDeclaringClass();
|
||||
this.fieldName = field.getName();
|
||||
this.required = required;
|
||||
|
@ -136,39 +127,19 @@ public class DependencyDescriptor implements Serializable {
|
|||
* @param original the original descriptor to create a copy from
|
||||
*/
|
||||
public DependencyDescriptor(DependencyDescriptor original) {
|
||||
this.methodParameter = (original.methodParameter != null ? new MethodParameter(original.methodParameter) : null);
|
||||
this.field = original.field;
|
||||
super(original);
|
||||
this.declaringClass = original.declaringClass;
|
||||
this.containingClass = original.containingClass;
|
||||
this.methodName = original.methodName;
|
||||
this.parameterTypes = original.parameterTypes;
|
||||
this.parameterIndex = original.parameterIndex;
|
||||
this.fieldName = original.fieldName;
|
||||
this.containingClass = original.containingClass;
|
||||
this.required = original.required;
|
||||
this.eager = original.eager;
|
||||
this.nestingLevel = original.nestingLevel;
|
||||
this.fieldAnnotations = original.fieldAnnotations;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the wrapped MethodParameter, if any.
|
||||
* <p>Note: Either MethodParameter or Field is available.
|
||||
* @return the MethodParameter, or {@code null} if none
|
||||
*/
|
||||
public MethodParameter getMethodParameter() {
|
||||
return this.methodParameter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the wrapped Field, if any.
|
||||
* <p>Note: Either MethodParameter or Field is available.
|
||||
* @return the Field, or {@code null} if none
|
||||
*/
|
||||
public Field getField() {
|
||||
return this.field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this dependency is required.
|
||||
*/
|
||||
|
@ -358,19 +329,18 @@ public class DependencyDescriptor implements Serializable {
|
|||
GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter));
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the annotations associated with the wrapped parameter/field, if any.
|
||||
*/
|
||||
public Annotation[] getAnnotations() {
|
||||
if (this.field != null) {
|
||||
if (this.fieldAnnotations == null) {
|
||||
this.fieldAnnotations = this.field.getAnnotations();
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
return this.fieldAnnotations;
|
||||
}
|
||||
else {
|
||||
return this.methodParameter.getParameterAnnotations();
|
||||
if (!super.equals(other)) {
|
||||
return false;
|
||||
}
|
||||
DependencyDescriptor otherDesc = (DependencyDescriptor) other;
|
||||
return (this.required == otherDesc.required && this.eager == otherDesc.eager &&
|
||||
this.nestingLevel == otherDesc.nestingLevel && this.containingClass == otherDesc.containingClass);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2016 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.
|
||||
|
@ -40,6 +40,7 @@ import org.springframework.beans.TypeConverter;
|
|||
import org.springframework.beans.TypeMismatchException;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.InjectionPoint;
|
||||
import org.springframework.beans.factory.UnsatisfiedDependencyException;
|
||||
import org.springframework.beans.factory.config.ConstructorArgumentValues;
|
||||
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
|
||||
|
@ -159,8 +160,7 @@ class ConstructorResolver {
|
|||
Set<Constructor<?>> ambiguousConstructors = null;
|
||||
LinkedList<UnsatisfiedDependencyException> causes = null;
|
||||
|
||||
for (int i = 0; i < candidates.length; i++) {
|
||||
Constructor<?> candidate = candidates[i];
|
||||
for (Constructor<?> candidate : candidates) {
|
||||
Class<?>[] paramTypes = candidate.getParameterTypes();
|
||||
|
||||
if (constructorToUse != null && argsToUse.length > paramTypes.length) {
|
||||
|
@ -665,7 +665,6 @@ class ConstructorResolver {
|
|||
BeanWrapper bw, Class<?>[] paramTypes, String[] paramNames, Object methodOrCtor,
|
||||
boolean autowiring) throws UnsatisfiedDependencyException {
|
||||
|
||||
String methodType = (methodOrCtor instanceof Constructor ? "constructor" : "factory method");
|
||||
TypeConverter converter = (this.beanFactory.getCustomTypeConverter() != null ?
|
||||
this.beanFactory.getCustomTypeConverter() : bw);
|
||||
|
||||
|
@ -700,9 +699,9 @@ class ConstructorResolver {
|
|||
ConstructorArgumentValues.ValueHolder sourceHolder =
|
||||
(ConstructorArgumentValues.ValueHolder) valueHolder.getSource();
|
||||
Object sourceValue = sourceHolder.getValue();
|
||||
MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex);
|
||||
try {
|
||||
convertedValue = converter.convertIfNecessary(originalValue, paramType,
|
||||
MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex));
|
||||
convertedValue = converter.convertIfNecessary(originalValue, paramType, methodParam);
|
||||
// TODO re-enable once race condition has been found (SPR-7423)
|
||||
/*
|
||||
if (originalValue == sourceValue || sourceValue instanceof TypedStringValue) {
|
||||
|
@ -718,8 +717,8 @@ class ConstructorResolver {
|
|||
}
|
||||
catch (TypeMismatchException ex) {
|
||||
throw new UnsatisfiedDependencyException(
|
||||
mbd.getResourceDescription(), beanName, paramIndex, paramType,
|
||||
"Could not convert " + methodType + " argument value of type [" +
|
||||
mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam),
|
||||
"Could not convert argument value of type [" +
|
||||
ObjectUtils.nullSafeClassName(valueHolder.getValue()) +
|
||||
"] to required type [" + paramType.getName() + "]: " + ex.getMessage());
|
||||
}
|
||||
|
@ -728,17 +727,18 @@ class ConstructorResolver {
|
|||
args.rawArguments[paramIndex] = originalValue;
|
||||
}
|
||||
else {
|
||||
MethodParameter methodParam = MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex);
|
||||
// No explicit match found: we're either supposed to autowire or
|
||||
// have to fail creating an argument array for the given constructor.
|
||||
if (!autowiring) {
|
||||
throw new UnsatisfiedDependencyException(
|
||||
mbd.getResourceDescription(), beanName, paramIndex, paramType,
|
||||
"Ambiguous " + methodType + " argument types - " +
|
||||
"did you specify the correct bean references as " + methodType + " arguments?");
|
||||
mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam),
|
||||
"Ambiguous argument values for parameter of type [" + paramType.getName() +
|
||||
"] - did you specify the correct bean references as arguments?");
|
||||
}
|
||||
try {
|
||||
MethodParameter param = MethodParameter.forMethodOrConstructor(methodOrCtor, paramIndex);
|
||||
Object autowiredArgument = resolveAutowiredArgument(param, beanName, autowiredBeanNames, converter);
|
||||
Object autowiredArgument =
|
||||
resolveAutowiredArgument(methodParam, beanName, autowiredBeanNames, converter);
|
||||
args.rawArguments[paramIndex] = autowiredArgument;
|
||||
args.arguments[paramIndex] = autowiredArgument;
|
||||
args.preparedArguments[paramIndex] = new AutowiredArgumentMarker();
|
||||
|
@ -746,7 +746,7 @@ class ConstructorResolver {
|
|||
}
|
||||
catch (BeansException ex) {
|
||||
throw new UnsatisfiedDependencyException(
|
||||
mbd.getResourceDescription(), beanName, paramIndex, paramType, ex);
|
||||
mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -755,7 +755,8 @@ class ConstructorResolver {
|
|||
this.beanFactory.registerDependentBean(autowiredBeanName, beanName);
|
||||
if (this.beanFactory.logger.isDebugEnabled()) {
|
||||
this.beanFactory.logger.debug("Autowiring by type from bean name '" + beanName +
|
||||
"' via " + methodType + " to bean named '" + autowiredBeanName + "'");
|
||||
"' via " + (methodOrCtor instanceof Constructor ? "constructor" : "factory method") +
|
||||
" to bean named '" + autowiredBeanName + "'");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -793,11 +794,9 @@ class ConstructorResolver {
|
|||
resolvedArgs[argIndex] = converter.convertIfNecessary(argValue, paramType, methodParam);
|
||||
}
|
||||
catch (TypeMismatchException ex) {
|
||||
String methodType = (methodOrCtor instanceof Constructor ? "constructor" : "factory method");
|
||||
throw new UnsatisfiedDependencyException(
|
||||
mbd.getResourceDescription(), beanName, argIndex, paramType,
|
||||
"Could not convert " + methodType + " argument value of type [" +
|
||||
ObjectUtils.nullSafeClassName(argValue) +
|
||||
mbd.getResourceDescription(), beanName, new InjectionPoint(methodParam),
|
||||
"Could not convert argument value of type [" + ObjectUtils.nullSafeClassName(argValue) +
|
||||
"] to required type [" + paramType.getName() + "]: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/*
|
||||
* Copyright 2002-2016 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
|
||||
*
|
||||
* http://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.beans.factory;
|
||||
|
||||
import org.junit.Test;
|
||||
|
@ -49,8 +65,7 @@ public class Spr5475Tests {
|
|||
cav.addIndexedArgumentValue(1, "bogusArg2".getBytes());
|
||||
def.setConstructorArgumentValues(cav);
|
||||
|
||||
assertExceptionMessageForMisconfiguredFactoryMethod(
|
||||
def,
|
||||
assertExceptionMessageForMisconfiguredFactoryMethod(def,
|
||||
"Error creating bean with name 'foo': No matching factory method found: factory method 'noArgFactory(CharSequence,byte[])'. " +
|
||||
"Check that a method with the specified name and arguments exists and that it is static.");
|
||||
}
|
||||
|
@ -62,7 +77,8 @@ public class Spr5475Tests {
|
|||
try {
|
||||
factory.preInstantiateSingletons();
|
||||
fail("should have failed with BeanCreationException due to incorrectly invoked factory method");
|
||||
} catch (BeanCreationException ex) {
|
||||
}
|
||||
catch (BeanCreationException ex) {
|
||||
assertThat(ex.getMessage(), equalTo(expectedMessage));
|
||||
}
|
||||
}
|
||||
|
@ -72,15 +88,17 @@ public class Spr5475Tests {
|
|||
// calling a factory method that accepts arguments without any arguments emits an exception unlike cases
|
||||
// where a no-arg factory method is called with arguments. Adding this test just to document the difference
|
||||
assertExceptionMessageForMisconfiguredFactoryMethod(
|
||||
rootBeanDefinition(Foo.class)
|
||||
.setFactoryMethod("singleArgFactory").getBeanDefinition(),
|
||||
rootBeanDefinition(Foo.class).
|
||||
setFactoryMethod("singleArgFactory").getBeanDefinition(),
|
||||
"Error creating bean with name 'foo': " +
|
||||
"Unsatisfied dependency expressed through constructor argument with index 0 of type [java.lang.String]: " +
|
||||
"Ambiguous factory method argument types - did you specify the correct bean references as factory method arguments?");
|
||||
"Unsatisfied dependency expressed through method 'singleArgFactory' parameter 0: " +
|
||||
"Ambiguous argument values for parameter of type [java.lang.String] - " +
|
||||
"did you specify the correct bean references as arguments?");
|
||||
}
|
||||
|
||||
|
||||
static class Foo {
|
||||
|
||||
static Foo noArgFactory() {
|
||||
return new Foo();
|
||||
}
|
||||
|
|
|
@ -83,6 +83,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
|||
bf.registerBeanDefinition("testBean", new GenericBeanDefinition());
|
||||
try {
|
||||
bf.getBean("testBean");
|
||||
fail("Should have thrown BeanCreationException");
|
||||
}
|
||||
catch (BeanCreationException ex) {
|
||||
assertTrue(ex.getRootCause() instanceof IllegalStateException);
|
||||
|
@ -635,6 +636,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
|||
}
|
||||
catch (UnsatisfiedDependencyException ex) {
|
||||
// expected
|
||||
assertSame(ConstructorWithoutFallbackBean.class, ex.getInjectionPoint().getMethodParameter().getDeclaringClass());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -838,8 +840,9 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
|||
bf.getBean("annotatedBean");
|
||||
fail("should have failed, more than one bean of type");
|
||||
}
|
||||
catch (BeanCreationException ex) {
|
||||
catch (UnsatisfiedDependencyException ex) {
|
||||
// expected
|
||||
assertSame(MapMethodInjectionBean.class, ex.getInjectionPoint().getMethodParameter().getDeclaringClass());
|
||||
}
|
||||
bf.destroySingletons();
|
||||
}
|
||||
|
@ -1164,7 +1167,8 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
|||
TestBean tb = new TestBean();
|
||||
bf.registerSingleton("testBean", tb);
|
||||
|
||||
CustomAnnotationRequiredFieldResourceInjectionBean bean = (CustomAnnotationRequiredFieldResourceInjectionBean) bf.getBean("customBean");
|
||||
CustomAnnotationRequiredFieldResourceInjectionBean bean =
|
||||
(CustomAnnotationRequiredFieldResourceInjectionBean) bf.getBean("customBean");
|
||||
assertSame(tb, bean.getTestBean());
|
||||
bf.destroySingletons();
|
||||
}
|
||||
|
@ -1183,10 +1187,12 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
|||
|
||||
try {
|
||||
bf.getBean("customBean");
|
||||
fail("expected BeanCreationException; no dependency available for required field");
|
||||
fail("Should have thrown UnsatisfiedDependencyException");
|
||||
}
|
||||
catch (BeanCreationException ex) {
|
||||
catch (UnsatisfiedDependencyException ex) {
|
||||
// expected
|
||||
assertSame(CustomAnnotationRequiredFieldResourceInjectionBean.class,
|
||||
ex.getInjectionPoint().getField().getDeclaringClass());
|
||||
}
|
||||
bf.destroySingletons();
|
||||
}
|
||||
|
@ -1209,10 +1215,13 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
|||
|
||||
try {
|
||||
bf.getBean("customBean");
|
||||
fail("expected BeanCreationException; multiple beans of dependency type available");
|
||||
fail("Should have thrown UnsatisfiedDependencyException");
|
||||
}
|
||||
catch (BeanCreationException ex) {
|
||||
catch (UnsatisfiedDependencyException ex) {
|
||||
// expected
|
||||
ex.printStackTrace();
|
||||
assertSame(CustomAnnotationRequiredFieldResourceInjectionBean.class,
|
||||
ex.getInjectionPoint().getField().getDeclaringClass());
|
||||
}
|
||||
bf.destroySingletons();
|
||||
}
|
||||
|
@ -1231,7 +1240,8 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
|||
TestBean tb = new TestBean();
|
||||
bf.registerSingleton("testBean", tb);
|
||||
|
||||
CustomAnnotationRequiredMethodResourceInjectionBean bean = (CustomAnnotationRequiredMethodResourceInjectionBean) bf.getBean("customBean");
|
||||
CustomAnnotationRequiredMethodResourceInjectionBean bean =
|
||||
(CustomAnnotationRequiredMethodResourceInjectionBean) bf.getBean("customBean");
|
||||
assertSame(tb, bean.getTestBean());
|
||||
bf.destroySingletons();
|
||||
}
|
||||
|
@ -1250,10 +1260,12 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
|||
|
||||
try {
|
||||
bf.getBean("customBean");
|
||||
fail("expected BeanCreationException; no dependency available for required method");
|
||||
fail("Should have thrown UnsatisfiedDependencyException");
|
||||
}
|
||||
catch (BeanCreationException e) {
|
||||
catch (UnsatisfiedDependencyException ex) {
|
||||
// expected
|
||||
assertSame(CustomAnnotationRequiredMethodResourceInjectionBean.class,
|
||||
ex.getInjectionPoint().getMethodParameter().getDeclaringClass());
|
||||
}
|
||||
bf.destroySingletons();
|
||||
}
|
||||
|
@ -1276,10 +1288,12 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
|||
|
||||
try {
|
||||
bf.getBean("customBean");
|
||||
fail("expected BeanCreationException; multiple beans of dependency type available");
|
||||
fail("Should have thrown UnsatisfiedDependencyException");
|
||||
}
|
||||
catch (BeanCreationException ex) {
|
||||
catch (UnsatisfiedDependencyException ex) {
|
||||
// expected
|
||||
assertSame(CustomAnnotationRequiredMethodResourceInjectionBean.class,
|
||||
ex.getInjectionPoint().getMethodParameter().getDeclaringClass());
|
||||
}
|
||||
bf.destroySingletons();
|
||||
}
|
||||
|
@ -1298,7 +1312,8 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
|||
TestBean tb = new TestBean();
|
||||
bf.registerSingleton("testBean", tb);
|
||||
|
||||
CustomAnnotationOptionalFieldResourceInjectionBean bean = (CustomAnnotationOptionalFieldResourceInjectionBean) bf.getBean("customBean");
|
||||
CustomAnnotationOptionalFieldResourceInjectionBean bean =
|
||||
(CustomAnnotationOptionalFieldResourceInjectionBean) bf.getBean("customBean");
|
||||
assertSame(tb, bean.getTestBean3());
|
||||
assertNull(bean.getTestBean());
|
||||
assertNull(bean.getTestBean2());
|
||||
|
@ -1317,7 +1332,8 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
|||
bf.registerBeanDefinition("customBean", new RootBeanDefinition(
|
||||
CustomAnnotationOptionalFieldResourceInjectionBean.class));
|
||||
|
||||
CustomAnnotationOptionalFieldResourceInjectionBean bean = (CustomAnnotationOptionalFieldResourceInjectionBean) bf.getBean("customBean");
|
||||
CustomAnnotationOptionalFieldResourceInjectionBean bean =
|
||||
(CustomAnnotationOptionalFieldResourceInjectionBean) bf.getBean("customBean");
|
||||
assertNull(bean.getTestBean3());
|
||||
assertNull(bean.getTestBean());
|
||||
assertNull(bean.getTestBean2());
|
||||
|
@ -1342,10 +1358,12 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
|||
|
||||
try {
|
||||
bf.getBean("customBean");
|
||||
fail("expected BeanCreationException; multiple beans of dependency type available");
|
||||
fail("Should have thrown UnsatisfiedDependencyException");
|
||||
}
|
||||
catch (BeanCreationException ex) {
|
||||
catch (UnsatisfiedDependencyException ex) {
|
||||
// expected
|
||||
assertSame(CustomAnnotationOptionalFieldResourceInjectionBean.class,
|
||||
ex.getInjectionPoint().getField().getDeclaringClass());
|
||||
}
|
||||
bf.destroySingletons();
|
||||
}
|
||||
|
@ -1364,7 +1382,8 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
|||
TestBean tb = new TestBean();
|
||||
bf.registerSingleton("testBean", tb);
|
||||
|
||||
CustomAnnotationOptionalMethodResourceInjectionBean bean = (CustomAnnotationOptionalMethodResourceInjectionBean) bf.getBean("customBean");
|
||||
CustomAnnotationOptionalMethodResourceInjectionBean bean =
|
||||
(CustomAnnotationOptionalMethodResourceInjectionBean) bf.getBean("customBean");
|
||||
assertSame(tb, bean.getTestBean3());
|
||||
assertNull(bean.getTestBean());
|
||||
assertNull(bean.getTestBean2());
|
||||
|
@ -1383,7 +1402,8 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
|||
bf.registerBeanDefinition("customBean", new RootBeanDefinition(
|
||||
CustomAnnotationOptionalMethodResourceInjectionBean.class));
|
||||
|
||||
CustomAnnotationOptionalMethodResourceInjectionBean bean = (CustomAnnotationOptionalMethodResourceInjectionBean) bf.getBean("customBean");
|
||||
CustomAnnotationOptionalMethodResourceInjectionBean bean =
|
||||
(CustomAnnotationOptionalMethodResourceInjectionBean) bf.getBean("customBean");
|
||||
assertNull(bean.getTestBean3());
|
||||
assertNull(bean.getTestBean());
|
||||
assertNull(bean.getTestBean2());
|
||||
|
@ -1408,10 +1428,12 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
|||
|
||||
try {
|
||||
bf.getBean("customBean");
|
||||
fail("expected BeanCreationException; multiple beans of dependency type available");
|
||||
fail("Should have thrown UnsatisfiedDependencyException");
|
||||
}
|
||||
catch (BeanCreationException ex) {
|
||||
catch (UnsatisfiedDependencyException ex) {
|
||||
// expected
|
||||
assertSame(CustomAnnotationOptionalMethodResourceInjectionBean.class,
|
||||
ex.getInjectionPoint().getMethodParameter().getDeclaringClass());
|
||||
}
|
||||
bf.destroySingletons();
|
||||
}
|
||||
|
@ -2644,7 +2666,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
|||
|
||||
@Target({ElementType.METHOD, ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public static @interface MyAutowired {
|
||||
public @interface MyAutowired {
|
||||
|
||||
boolean optional() default false;
|
||||
}
|
||||
|
|
|
@ -180,7 +180,14 @@ public class MethodParameter {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the wrapped member.
|
||||
* Return the class that declares the underlying Method or Constructor.
|
||||
*/
|
||||
public Class<?> getDeclaringClass() {
|
||||
return getMember().getDeclaringClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the wrapped member.
|
||||
* @return the Method or Constructor as Member
|
||||
*/
|
||||
public Member getMember() {
|
||||
|
@ -196,7 +203,9 @@ public class MethodParameter {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the wrapped annotated element.
|
||||
* Return the wrapped annotated element.
|
||||
* <p>Note: This method exposes the annotations declared on the method/constructor
|
||||
* itself (i.e. at the method/constructor level, not at the parameter level).
|
||||
* @return the Method or Constructor as AnnotatedElement
|
||||
*/
|
||||
public AnnotatedElement getAnnotatedElement() {
|
||||
|
@ -211,13 +220,6 @@ public class MethodParameter {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the class that declares the underlying Method or Constructor.
|
||||
*/
|
||||
public Class<?> getDeclaringClass() {
|
||||
return getMember().getDeclaringClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the index of the method/constructor parameter.
|
||||
* @return the parameter index (-1 in case of the return type)
|
||||
|
@ -577,6 +579,12 @@ public class MethodParameter {
|
|||
return (getMember().hashCode() * 31 + this.parameterIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return (this.method != null ? "method '" + this.method.getName() + "'" : "constructor") +
|
||||
" parameter " + this.parameterIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodParameter clone() {
|
||||
return new MethodParameter(this);
|
||||
|
|
Loading…
Reference in New Issue