diff options
Diffstat (limited to 'spring-beans/src/main/java/org/springframework/beans')
58 files changed, 1367 insertions, 436 deletions
diff --git a/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java b/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java index cf3ebd9a..f3e3bd14 100644 --- a/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java @@ -90,11 +90,11 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA private int autoGrowCollectionLimit = Integer.MAX_VALUE; - private Object wrappedObject; + Object wrappedObject; private String nestedPath = ""; - private Object rootObject; + Object rootObject; /** * Map with cached nested Accessors: nested path -> Accessor instance. @@ -914,11 +914,9 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA return BeanUtils.instantiate(type); } } - catch (Exception ex) { - // TODO: Root cause exception context is lost here; just exception message preserved. - // Should we throw another exception type that preserves context instead? + catch (Throwable ex) { throw new NullValueInNestedPathException(getRootClass(), this.nestedPath + name, - "Could not instantiate property type [" + type.getName() + "] to auto-grow nested property path: " + ex); + "Could not instantiate property type [" + type.getName() + "] to auto-grow nested property path", ex); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/AbstractPropertyAccessor.java b/spring-beans/src/main/java/org/springframework/beans/AbstractPropertyAccessor.java index 07871217..216e5a4a 100644 --- a/spring-beans/src/main/java/org/springframework/beans/AbstractPropertyAccessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/AbstractPropertyAccessor.java @@ -148,7 +148,7 @@ public abstract class AbstractPropertyAccessor extends TypeConverterSupport impl * @throws InvalidPropertyException if there is no such property or * if the property isn't writable * @throws PropertyAccessException if the property was valid but the - * accessor method failed or a type mismatch occured + * accessor method failed or a type mismatch occurred */ @Override public abstract void setPropertyValue(String propertyName, Object value) throws BeansException; diff --git a/spring-beans/src/main/java/org/springframework/beans/BeanInfoFactory.java b/spring-beans/src/main/java/org/springframework/beans/BeanInfoFactory.java index 1906fbfd..4c5fd27c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/BeanInfoFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/BeanInfoFactory.java @@ -24,7 +24,7 @@ import java.beans.IntrospectionException; * Can be used to plug in custom bean property resolution strategies (e.g. for other * languages on the JVM) or more efficient {@link BeanInfo} retrieval algorithms. * - * <p>BeanInfoFactories are are instantiated by the {@link CachedIntrospectionResults}, + * <p>BeanInfoFactories are instantiated by the {@link CachedIntrospectionResults}, * by using the {@link org.springframework.core.io.support.SpringFactoriesLoader} * utility class. * diff --git a/spring-beans/src/main/java/org/springframework/beans/BeanInstantiationException.java b/spring-beans/src/main/java/org/springframework/beans/BeanInstantiationException.java index 09190f6f..d8f7d246 100644 --- a/spring-beans/src/main/java/org/springframework/beans/BeanInstantiationException.java +++ b/spring-beans/src/main/java/org/springframework/beans/BeanInstantiationException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 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. @@ -16,6 +16,9 @@ package org.springframework.beans; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + /** * Exception thrown when instantiation of a bean failed. * Carries the offending bean class. @@ -28,6 +31,10 @@ public class BeanInstantiationException extends FatalBeanException { private Class<?> beanClass; + private Constructor<?> constructor; + + private Method constructingMethod; + /** * Create a new BeanInstantiationException. @@ -49,12 +56,60 @@ public class BeanInstantiationException extends FatalBeanException { this.beanClass = beanClass; } + /** + * Create a new BeanInstantiationException. + * @param constructor the offending constructor + * @param msg the detail message + * @param cause the root cause + * @since 4.3 + */ + public BeanInstantiationException(Constructor<?> constructor, String msg, Throwable cause) { + super("Failed to instantiate [" + constructor.getDeclaringClass().getName() + "]: " + msg, cause); + this.beanClass = constructor.getDeclaringClass(); + this.constructor = constructor; + } /** - * Return the offending bean class. + * Create a new BeanInstantiationException. + * @param constructingMethod the delegate for bean construction purposes + * (typically, but not necessarily, a static factory method) + * @param msg the detail message + * @param cause the root cause + * @since 4.3 + */ + public BeanInstantiationException(Method constructingMethod, String msg, Throwable cause) { + super("Failed to instantiate [" + constructingMethod.getReturnType().getName() + "]: " + msg, cause); + this.beanClass = constructingMethod.getReturnType(); + this.constructingMethod = constructingMethod; + } + + + /** + * Return the offending bean class (never {@code null}). + * @return the class that was to be instantiated */ public Class<?> getBeanClass() { return this.beanClass; } + /** + * Return the offending constructor, if known. + * @return the constructor in use, or {@code null} in case of a + * factory method or in case of default instantiation + * @since 4.3 + */ + public Constructor<?> getConstructor() { + return this.constructor; + } + + /** + * Return the delegate for bean construction purposes, if known. + * @return the method in use (typically a static factory method), + * or {@code null} in case of constructor-based instantiation + * @since 4.3 + */ + public Method getConstructingMethod() { + return this.constructingMethod; + } + } diff --git a/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java b/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java index 5c735f16..50c04c3b 100644 --- a/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java +++ b/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java @@ -63,11 +63,10 @@ public abstract class BeanUtils { /** * Convenience method to instantiate a class using its no-arg constructor. - * As this method doesn't try to load classes by name, it should avoid - * class-loading issues. * @param clazz class to instantiate * @return the new instance * @throws BeanInstantiationException if the bean cannot be instantiated + * @see Class#newInstance() */ public static <T> T instantiate(Class<T> clazz) throws BeanInstantiationException { Assert.notNull(clazz, "Class must not be null"); @@ -87,13 +86,12 @@ public abstract class BeanUtils { /** * Instantiate a class using its no-arg constructor. - * As this method doesn't try to load classes by name, it should avoid - * class-loading issues. * <p>Note that this method tries to set the constructor accessible * if given a non-accessible (that is, non-public) constructor. * @param clazz class to instantiate * @return the new instance * @throws BeanInstantiationException if the bean cannot be instantiated + * @see Constructor#newInstance */ public static <T> T instantiateClass(Class<T> clazz) throws BeanInstantiationException { Assert.notNull(clazz, "Class must not be null"); @@ -110,18 +108,16 @@ public abstract class BeanUtils { /** * Instantiate a class using its no-arg constructor and return the new instance - * as the the specified assignable type. - * <p>Useful in cases where - * the type of the class to instantiate (clazz) is not available, but the type - * desired (assignableTo) is known. - * <p>As this method doesn't try to load classes by name, it should avoid - * class-loading issues. - * <p>Note that this method tries to set the constructor accessible - * if given a non-accessible (that is, non-public) constructor. + * as the specified assignable type. + * <p>Useful in cases where the type of the class to instantiate (clazz) is not + * available, but the type desired (assignableTo) is known. + * <p>Note that this method tries to set the constructor accessible if given a + * non-accessible (that is, non-public) constructor. * @param clazz class to instantiate * @param assignableTo type that clazz must be assignableTo * @return the new instance * @throws BeanInstantiationException if the bean cannot be instantiated + * @see Constructor#newInstance */ @SuppressWarnings("unchecked") public static <T> T instantiateClass(Class<?> clazz, Class<T> assignableTo) throws BeanInstantiationException { @@ -131,14 +127,13 @@ public abstract class BeanUtils { /** * Convenience method to instantiate a class using the given constructor. - * As this method doesn't try to load classes by name, it should avoid - * class-loading issues. - * <p>Note that this method tries to set the constructor accessible - * if given a non-accessible (that is, non-public) constructor. + * <p>Note that this method tries to set the constructor accessible if given a + * non-accessible (that is, non-public) constructor. * @param ctor the constructor to instantiate * @param args the constructor arguments to apply * @return the new instance * @throws BeanInstantiationException if the bean cannot be instantiated + * @see Constructor#newInstance */ public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException { Assert.notNull(ctor, "Constructor must not be null"); @@ -147,20 +142,16 @@ public abstract class BeanUtils { return ctor.newInstance(args); } catch (InstantiationException ex) { - throw new BeanInstantiationException(ctor.getDeclaringClass(), - "Is it an abstract class?", ex); + throw new BeanInstantiationException(ctor, "Is it an abstract class?", ex); } catch (IllegalAccessException ex) { - throw new BeanInstantiationException(ctor.getDeclaringClass(), - "Is the constructor accessible?", ex); + throw new BeanInstantiationException(ctor, "Is the constructor accessible?", ex); } catch (IllegalArgumentException ex) { - throw new BeanInstantiationException(ctor.getDeclaringClass(), - "Illegal arguments for constructor", ex); + throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", ex); } catch (InvocationTargetException ex) { - throw new BeanInstantiationException(ctor.getDeclaringClass(), - "Constructor threw exception", ex.getTargetException()); + throw new BeanInstantiationException(ctor, "Constructor threw exception", ex.getTargetException()); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java b/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java index 6615661d..4d10dc0c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java +++ b/spring-beans/src/main/java/org/springframework/beans/BeanWrapperImpl.java @@ -133,6 +133,19 @@ public class BeanWrapperImpl extends AbstractNestablePropertyAccessor implements } + /** + * Set a bean instance to hold, without any unwrapping of {@link java.util.Optional}. + * @param object the actual target object + * @since 4.3 + * @see #setWrappedInstance(Object) + */ + public void setBeanInstance(Object object) { + this.wrappedObject = object; + this.rootObject = object; + this.typeConverterDelegate = new TypeConverterDelegate(this, this.wrappedObject); + setIntrospectionClass(object.getClass()); + } + @Override public void setWrappedInstance(Object object, String nestedPath, Object rootObject) { super.setWrappedInstance(object, nestedPath, rootObject); diff --git a/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java b/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java index 41c2f4c5..2efe9dab 100644 --- a/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java +++ b/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 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. @@ -303,6 +303,24 @@ public class CachedIntrospectionResults { this.propertyDescriptorCache.put(pd.getName(), pd); } + // Explicitly check implemented interfaces for setter/getter methods as well, + // in particular for Java 8 default methods... + Class<?> clazz = beanClass; + while (clazz != null) { + Class<?>[] ifcs = clazz.getInterfaces(); + for (Class<?> ifc : ifcs) { + BeanInfo ifcInfo = Introspector.getBeanInfo(ifc, Introspector.IGNORE_ALL_BEANINFO); + PropertyDescriptor[] ifcPds = ifcInfo.getPropertyDescriptors(); + for (PropertyDescriptor pd : ifcPds) { + if (!this.propertyDescriptorCache.containsKey(pd.getName())) { + pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd); + this.propertyDescriptorCache.put(pd.getName(), pd); + } + } + } + clazz = clazz.getSuperclass(); + } + this.typeDescriptorCache = new ConcurrentReferenceHashMap<PropertyDescriptor, TypeDescriptor>(); } catch (IntrospectionException ex) { diff --git a/spring-beans/src/main/java/org/springframework/beans/NullValueInNestedPathException.java b/spring-beans/src/main/java/org/springframework/beans/NullValueInNestedPathException.java index e01fafbf..e15885bc 100644 --- a/spring-beans/src/main/java/org/springframework/beans/NullValueInNestedPathException.java +++ b/spring-beans/src/main/java/org/springframework/beans/NullValueInNestedPathException.java @@ -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. @@ -24,6 +24,7 @@ package org.springframework.beans; * spouse property of the target object has a null value. * * @author Rod Johnson + * @author Juergen Hoeller */ @SuppressWarnings("serial") public class NullValueInNestedPathException extends InvalidPropertyException { @@ -47,4 +48,16 @@ public class NullValueInNestedPathException extends InvalidPropertyException { super(beanClass, propertyName, msg); } + /** + * Create a new NullValueInNestedPathException. + * @param beanClass the offending bean class + * @param propertyName the offending property + * @param msg the detail message + * @param cause the root cause + * @since 4.3.2 + */ + public NullValueInNestedPathException(Class<?> beanClass, String propertyName, String msg, Throwable cause) { + super(beanClass, propertyName, msg, cause); + } + } diff --git a/spring-beans/src/main/java/org/springframework/beans/PropertyAccessor.java b/spring-beans/src/main/java/org/springframework/beans/PropertyAccessor.java index 00068ea4..c7faf355 100644 --- a/spring-beans/src/main/java/org/springframework/beans/PropertyAccessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/PropertyAccessor.java @@ -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. @@ -120,7 +120,7 @@ public interface PropertyAccessor { * @throws InvalidPropertyException if there is no such property or * if the property isn't writable * @throws PropertyAccessException if the property was valid but the - * accessor method failed or a type mismatch occured + * accessor method failed or a type mismatch occurred */ void setPropertyValue(String propertyName, Object value) throws BeansException; @@ -130,7 +130,7 @@ public interface PropertyAccessor { * @throws InvalidPropertyException if there is no such property or * if the property isn't writable * @throws PropertyAccessException if the property was valid but the - * accessor method failed or a type mismatch occured + * accessor method failed or a type mismatch occurred */ void setPropertyValue(PropertyValue pv) throws BeansException; @@ -144,7 +144,7 @@ public interface PropertyAccessor { * @throws InvalidPropertyException if there is no such property or * if the property isn't writable * @throws PropertyBatchUpdateException if one or more PropertyAccessExceptions - * occured for specific properties during the batch update. This exception bundles + * occurred for specific properties during the batch update. This exception bundles * all individual PropertyAccessExceptions. All other properties will have been * successfully updated. */ @@ -164,7 +164,7 @@ public interface PropertyAccessor { * @throws InvalidPropertyException if there is no such property or * if the property isn't writable * @throws PropertyBatchUpdateException if one or more PropertyAccessExceptions - * occured for specific properties during the batch update. This exception bundles + * occurred for specific properties during the batch update. This exception bundles * all individual PropertyAccessExceptions. All other properties will have been * successfully updated. * @see #setPropertyValues(PropertyValues, boolean, boolean) @@ -185,7 +185,7 @@ public interface PropertyAccessor { * @throws InvalidPropertyException if there is no such property or * if the property isn't writable * @throws PropertyBatchUpdateException if one or more PropertyAccessExceptions - * occured for specific properties during the batch update. This exception bundles + * occurred for specific properties during the batch update. This exception bundles * all individual PropertyAccessExceptions. All other properties will have been * successfully updated. * @see #setPropertyValues(PropertyValues, boolean, boolean) @@ -208,7 +208,7 @@ public interface PropertyAccessor { * @throws InvalidPropertyException if there is no such property or * if the property isn't writable * @throws PropertyBatchUpdateException if one or more PropertyAccessExceptions - * occured for specific properties during the batch update. This exception bundles + * occurred for specific properties during the batch update. This exception bundles * all individual PropertyAccessExceptions. All other properties will have been * successfully updated. */ diff --git a/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java b/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java index f42d6c33..71825c38 100644 --- a/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java +++ b/spring-beans/src/main/java/org/springframework/beans/PropertyEditorRegistrySupport.java @@ -59,6 +59,7 @@ import org.springframework.beans.propertyeditors.FileEditor; import org.springframework.beans.propertyeditors.InputSourceEditor; import org.springframework.beans.propertyeditors.InputStreamEditor; import org.springframework.beans.propertyeditors.LocaleEditor; +import org.springframework.beans.propertyeditors.PathEditor; import org.springframework.beans.propertyeditors.PatternEditor; import org.springframework.beans.propertyeditors.PropertiesEditor; import org.springframework.beans.propertyeditors.ReaderEditor; @@ -87,11 +88,21 @@ import org.springframework.util.ClassUtils; */ public class PropertyEditorRegistrySupport implements PropertyEditorRegistry { + private static Class<?> pathClass; + private static Class<?> zoneIdClass; static { + ClassLoader cl = PropertyEditorRegistrySupport.class.getClassLoader(); + try { + pathClass = ClassUtils.forName("java.nio.file.Path", cl); + } + catch (ClassNotFoundException ex) { + // Java 7 Path class not available + pathClass = null; + } try { - zoneIdClass = ClassUtils.forName("java.time.ZoneId", PropertyEditorRegistrySupport.class.getClassLoader()); + zoneIdClass = ClassUtils.forName("java.time.ZoneId", cl); } catch (ClassNotFoundException ex) { // Java 8 ZoneId class not available @@ -211,6 +222,9 @@ public class PropertyEditorRegistrySupport implements PropertyEditorRegistry { this.defaultEditors.put(InputStream.class, new InputStreamEditor()); this.defaultEditors.put(InputSource.class, new InputSourceEditor()); this.defaultEditors.put(Locale.class, new LocaleEditor()); + if (pathClass != null) { + this.defaultEditors.put(pathClass, new PathEditor()); + } this.defaultEditors.put(Pattern.class, new PatternEditor()); this.defaultEditors.put(Properties.class, new PropertiesEditor()); this.defaultEditors.put(Reader.class, new ReaderEditor()); diff --git a/spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java b/spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java index dd63e8e9..908bfeaf 100644 --- a/spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java +++ b/spring-beans/src/main/java/org/springframework/beans/TypeConverterDelegate.java @@ -186,8 +186,11 @@ class TypeConverterDelegate { if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) && convertedValue instanceof String) { TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor(); - if (elementTypeDesc != null && Enum.class.isAssignableFrom(elementTypeDesc.getType())) { - convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue); + if (elementTypeDesc != null) { + Class<?> elementType = elementTypeDesc.getType(); + if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) { + convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue); + } } } if (editor == null) { @@ -266,7 +269,7 @@ class TypeConverterDelegate { } else { // convertedValue == null - if (javaUtilOptionalEmpty != null && requiredType.equals(javaUtilOptionalEmpty.getClass())) { + if (javaUtilOptionalEmpty != null && requiredType == javaUtilOptionalEmpty.getClass()) { convertedValue = javaUtilOptionalEmpty; } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/BeanCreationException.java b/spring-beans/src/main/java/org/springframework/beans/factory/BeanCreationException.java index f0c2fa1d..75a38386 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/BeanCreationException.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanCreationException.java @@ -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; @@ -123,7 +123,7 @@ public class BeanCreationException extends FatalBeanException { /** * Add a related cause to this bean creation exception, - * not being a direct cause of the failure but having occured + * not being a direct cause of the failure but having occurred * earlier in the creation of the same bean instance. * @param ex the related cause to add */ diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java index 3c6bd4c8..58b2fd97 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java @@ -188,12 +188,12 @@ public interface BeanFactory { * Return an instance, which may be shared or independent, of the specified bean. * <p>Allows for specifying explicit constructor arguments / factory method arguments, * overriding the specified default arguments (if any) in the bean definition. - * @param requiredType type the bean must match; can be an interface or superclass. - * {@code null} is disallowed. * <p>This method goes into {@link ListableBeanFactory} by-type lookup territory * but may also be translated into a conventional by-name lookup based on the name * of the given type. For more extensive retrieval operations across sets of beans, * use {@link ListableBeanFactory} and/or {@link BeanFactoryUtils}. + * @param requiredType type the bean must match; can be an interface or superclass. + * {@code null} is disallowed. * @param args arguments to use when creating a bean instance using explicit arguments * (only applied when creating a new instance as opposed to retrieving an existing one) * @return an instance of the bean diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/BeanInitializationException.java b/spring-beans/src/main/java/org/springframework/beans/factory/BeanInitializationException.java index c52d0ae7..5c5ed79c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/BeanInitializationException.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanInitializationException.java @@ -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 diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/BeanNotOfRequiredTypeException.java b/spring-beans/src/main/java/org/springframework/beans/factory/BeanNotOfRequiredTypeException.java index bd6d33fb..6f98f435 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/BeanNotOfRequiredTypeException.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanNotOfRequiredTypeException.java @@ -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. @@ -45,8 +45,8 @@ public class BeanNotOfRequiredTypeException extends BeansException { * the expected type */ public BeanNotOfRequiredTypeException(String beanName, Class<?> requiredType, Class<?> actualType) { - super("Bean named '" + beanName + "' must be of type [" + requiredType.getName() + - "], but was actually of type [" + actualType.getName() + "]"); + super("Bean named '" + beanName + "' is expected to be of type [" + requiredType.getName() + + "] but was actually of type [" + actualType.getName() + "]"); this.beanName = beanName; this.requiredType = requiredType; this.actualType = actualType; diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/InjectionPoint.java b/spring-beans/src/main/java/org/springframework/beans/factory/InjectionPoint.java new file mode 100644 index 00000000..0a3d55a9 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/InjectionPoint.java @@ -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()); + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/NoUniqueBeanDefinitionException.java b/spring-beans/src/main/java/org/springframework/beans/factory/NoUniqueBeanDefinitionException.java index 7db0bae6..6bb4f6db 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/NoUniqueBeanDefinitionException.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/NoUniqueBeanDefinitionException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 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. @@ -34,6 +34,8 @@ public class NoUniqueBeanDefinitionException extends NoSuchBeanDefinitionExcepti private int numberOfBeansFound; + private Collection<String> beanNamesFound; + /** * Create a new {@code NoUniqueBeanDefinitionException}. @@ -54,6 +56,7 @@ public class NoUniqueBeanDefinitionException extends NoSuchBeanDefinitionExcepti public NoUniqueBeanDefinitionException(Class<?> type, Collection<String> beanNamesFound) { this(type, beanNamesFound.size(), "expected single matching bean but found " + beanNamesFound.size() + ": " + StringUtils.collectionToCommaDelimitedString(beanNamesFound)); + this.beanNamesFound = beanNamesFound; } /** @@ -76,4 +79,14 @@ public class NoUniqueBeanDefinitionException extends NoSuchBeanDefinitionExcepti return this.numberOfBeansFound; } + /** + * Return the names of all beans found when only one matching bean was expected. + * Note that this may be {@code null} if not specified at construction time. + * @since 4.3 + * @see #getBeanType() + */ + public Collection<String> getBeanNamesFound() { + return this.beanNamesFound; + } + } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java b/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java new file mode 100644 index 00000000..ffa2683e --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java @@ -0,0 +1,61 @@ +/* + * 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.springframework.beans.BeansException; + +/** + * A variant of {@link ObjectFactory} designed specifically for injection points, + * allowing for programmatic optionality and lenient not-unique handling. + * + * @author Juergen Hoeller + * @since 4.3 + */ +public interface ObjectProvider<T> extends ObjectFactory<T> { + + /** + * Return an instance (possibly shared or independent) of the object + * managed by this factory. + * <p>Allows for specifying explicit construction arguments, along the + * lines of {@link BeanFactory#getBean(String, Object...)}. + * @param args arguments to use when creating a corresponding instance + * @return an instance of the bean + * @throws BeansException in case of creation errors + * @see #getObject() + */ + T getObject(Object... args) throws BeansException; + + /** + * Return an instance (possibly shared or independent) of the object + * managed by this factory. + * @return an instance of the bean, or {@code null} if not available + * @throws BeansException in case of creation errors + * @see #getObject() + */ + T getIfAvailable() throws BeansException; + + /** + * Return an instance (possibly shared or independent) of the object + * managed by this factory. + * @return an instance of the bean, or {@code null} if not available or + * not unique (i.e. multiple candidates found with none marked as primary) + * @throws BeansException in case of creation errors + * @see #getObject() + */ + T getIfUnique() throws BeansException; + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/UnsatisfiedDependencyException.java b/spring-beans/src/main/java/org/springframework/beans/factory/UnsatisfiedDependencyException.java index 9ec35a7e..0403abfc 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/UnsatisfiedDependencyException.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/UnsatisfiedDependencyException.java @@ -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 @@ -64,10 +67,42 @@ public class UnsatisfiedDependencyException extends BeanCreationException { * 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 + * @param beanName the name of the bean requested * @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; + } + } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/access/SingletonBeanFactoryLocator.java b/spring-beans/src/main/java/org/springframework/beans/factory/access/SingletonBeanFactoryLocator.java index d7758bc1..45be7328 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/access/SingletonBeanFactoryLocator.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/access/SingletonBeanFactoryLocator.java @@ -88,7 +88,7 @@ import org.springframework.core.io.support.ResourcePatternUtils; * use object from a BeanFactory/ApplicationContext. One solutions is to make the * class created by the third party code be just a stub or proxy, which gets the * real object from a BeanFactory/ApplicationContext, and delegates to it. However, - * it is is not normally workable for the stub to create the BeanFactory on each + * it is not normally workable for the stub to create the BeanFactory on each * use, as depending on what is inside it, that can be an expensive operation. * Additionally, there is a fairly tight coupling between the stub and the name of * the definition resource for the BeanFactory/ApplicationContext. This is where @@ -291,7 +291,7 @@ public class SingletonBeanFactoryLocator implements BeanFactoryLocator { } /** - * Returns an instance which uses the the specified selector, as the name of the + * Returns an instance which uses the specified selector, as the name of the * definition file(s). In the case of a name with a Spring 'classpath*:' prefix, * or with no prefix, which is treated the same, the current thread context * ClassLoader's {@code getResources} method will be called with this value @@ -341,7 +341,7 @@ public class SingletonBeanFactoryLocator implements BeanFactoryLocator { /** - * Constructor which uses the the specified name as the resource name + * Constructor which uses the specified name as the resource name * of the definition file(s). * @param resourceLocation the Spring resource location to use * (either a URL or a "classpath:" / "classpath*:" pseudo URL) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/Autowired.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/Autowired.java index fc8b49bc..73779365 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/Autowired.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/Autowired.java @@ -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. @@ -64,7 +64,7 @@ import java.lang.annotation.Target; * @see Qualifier * @see Value */ -@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Autowired { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java index c008a796..258f66bf 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 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,11 +45,12 @@ 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; -import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.LookupOverride; import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor; import org.springframework.beans.factory.support.RootBeanDefinition; @@ -107,6 +108,7 @@ import org.springframework.util.StringUtils; * * @author Juergen Hoeller * @author Mark Fisher + * @author Stephane Nicoll * @since 2.5 * @see #setAutowiredAnnotationType * @see Autowired @@ -270,6 +272,19 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean Constructor<?> defaultConstructor = null; for (Constructor<?> candidate : rawCandidates) { AnnotationAttributes ann = findAutowiredAnnotation(candidate); + if (ann == null) { + Class<?> userClass = ClassUtils.getUserClass(beanClass); + if (userClass != beanClass) { + try { + Constructor<?> superCtor = + userClass.getDeclaredConstructor(candidate.getParameterTypes()); + ann = findAutowiredAnnotation(superCtor); + } + catch (NoSuchMethodException ex) { + // Simply proceed, no equivalent superclass constructor found... + } + } + } if (ann != null) { if (requiredConstructor != null) { throw new BeanCreationException(beanName, @@ -312,6 +327,9 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean } candidateConstructors = candidates.toArray(new Constructor<?>[candidates.size()]); } + else if (rawCandidates.length == 1 && rawCandidates[0].getParameterTypes().length > 0) { + candidateConstructors = new Constructor<?>[] {rawCandidates[0]}; + } else { candidateConstructors = new Constructor<?>[0]; } @@ -330,6 +348,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); } @@ -348,6 +369,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); } @@ -504,9 +528,6 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean DependencyDescriptor descriptor = (DependencyDescriptor) cachedArgument; return this.beanFactory.resolveDependency(descriptor, beanName, null, null); } - else if (cachedArgument instanceof RuntimeBeanReference) { - return this.beanFactory.getBean(((RuntimeBeanReference) cachedArgument).getBeanName()); - } else { return cachedArgument; } @@ -532,45 +553,46 @@ 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); - } - else { - DependencyDescriptor desc = new DependencyDescriptor(field, this.required); - desc.setContainingClass(bean.getClass()); - Set<String> autowiredBeanNames = new LinkedHashSet<String>(1); - TypeConverter typeConverter = beanFactory.getTypeConverter(); + Object value; + if (this.cached) { + value = resolvedCachedArgument(beanName, this.cachedFieldValue); + } + else { + DependencyDescriptor desc = new DependencyDescriptor(field, this.required); + desc.setContainingClass(bean.getClass()); + Set<String> autowiredBeanNames = new LinkedHashSet<String>(1); + TypeConverter typeConverter = beanFactory.getTypeConverter(); + try { value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter); - synchronized (this) { - if (!this.cached) { - if (value != null || this.required) { - this.cachedFieldValue = desc; - registerDependentBeans(beanName, autowiredBeanNames); - if (autowiredBeanNames.size() == 1) { - String autowiredBeanName = autowiredBeanNames.iterator().next(); - if (beanFactory.containsBean(autowiredBeanName)) { - if (beanFactory.isTypeMatch(autowiredBeanName, field.getType())) { - this.cachedFieldValue = new RuntimeBeanReference(autowiredBeanName); - } + } + catch (BeansException ex) { + throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex); + } + synchronized (this) { + if (!this.cached) { + if (value != null || this.required) { + this.cachedFieldValue = desc; + registerDependentBeans(beanName, autowiredBeanNames); + if (autowiredBeanNames.size() == 1) { + String autowiredBeanName = autowiredBeanNames.iterator().next(); + if (beanFactory.containsBean(autowiredBeanName)) { + if (beanFactory.isTypeMatch(autowiredBeanName, field.getType())) { + this.cachedFieldValue = new ShortcutDependencyDescriptor( + desc, autowiredBeanName, field.getType()); } } } - else { - this.cachedFieldValue = null; - } - this.cached = true; } + else { + this.cachedFieldValue = null; + } + this.cached = true; } } - if (value != null) { - ReflectionUtils.makeAccessible(field); - field.set(bean, value); - } } - catch (Throwable ex) { - throw new BeanCreationException("Could not autowire field: " + field, ex); + if (value != null) { + ReflectionUtils.makeAccessible(field); + field.set(bean, value); } } } @@ -598,67 +620,70 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean return; } Method method = (Method) this.member; - try { - Object[] arguments; - if (this.cached) { - // Shortcut for avoiding synchronization... - arguments = resolveCachedArguments(beanName); - } - else { - Class<?>[] paramTypes = method.getParameterTypes(); - arguments = new Object[paramTypes.length]; - DependencyDescriptor[] descriptors = new DependencyDescriptor[paramTypes.length]; - Set<String> autowiredBeanNames = new LinkedHashSet<String>(paramTypes.length); - 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); + Object[] arguments; + if (this.cached) { + // Shortcut for avoiding synchronization... + arguments = resolveCachedArguments(beanName); + } + else { + Class<?>[] paramTypes = method.getParameterTypes(); + arguments = new Object[paramTypes.length]; + DependencyDescriptor[] descriptors = new DependencyDescriptor[paramTypes.length]; + Set<String> autowiredBeanNames = new LinkedHashSet<String>(paramTypes.length); + TypeConverter typeConverter = beanFactory.getTypeConverter(); + for (int i = 0; i < arguments.length; i++) { + MethodParameter methodParam = new MethodParameter(method, i); + 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; } - synchronized (this) { - if (!this.cached) { - if (arguments != null) { - this.cachedMethodArguments = new Object[arguments.length]; - for (int i = 0; i < arguments.length; i++) { - this.cachedMethodArguments[i] = descriptors[i]; - } - registerDependentBeans(beanName, autowiredBeanNames); - if (autowiredBeanNames.size() == paramTypes.length) { - Iterator<String> it = autowiredBeanNames.iterator(); - for (int i = 0; i < paramTypes.length; i++) { - String autowiredBeanName = it.next(); - if (beanFactory.containsBean(autowiredBeanName)) { - if (beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) { - this.cachedMethodArguments[i] = new RuntimeBeanReference(autowiredBeanName); - } + catch (BeansException ex) { + throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex); + } + } + synchronized (this) { + if (!this.cached) { + if (arguments != null) { + this.cachedMethodArguments = new Object[paramTypes.length]; + for (int i = 0; i < arguments.length; i++) { + this.cachedMethodArguments[i] = descriptors[i]; + } + registerDependentBeans(beanName, autowiredBeanNames); + if (autowiredBeanNames.size() == paramTypes.length) { + Iterator<String> it = autowiredBeanNames.iterator(); + for (int i = 0; i < paramTypes.length; i++) { + String autowiredBeanName = it.next(); + if (beanFactory.containsBean(autowiredBeanName)) { + if (beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) { + this.cachedMethodArguments[i] = new ShortcutDependencyDescriptor( + descriptors[i], autowiredBeanName, paramTypes[i]); } } } } - else { - this.cachedMethodArguments = null; - } - this.cached = true; } + else { + this.cachedMethodArguments = null; + } + this.cached = true; } } - if (arguments != null) { + } + if (arguments != null) { + try { ReflectionUtils.makeAccessible(method); method.invoke(bean, arguments); } - } - catch (InvocationTargetException ex) { - throw ex.getTargetException(); - } - catch (Throwable ex) { - throw new BeanCreationException("Could not autowire method: " + method, ex); + catch (InvocationTargetException ex){ + throw ex.getTargetException(); + } } } @@ -674,4 +699,27 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean } } + + /** + * DependencyDescriptor variant with a pre-resolved target bean name. + */ + @SuppressWarnings("serial") + private static class ShortcutDependencyDescriptor extends DependencyDescriptor { + + private final String shortcutName; + + private final Class<?> requiredType; + + public ShortcutDependencyDescriptor(DependencyDescriptor original, String shortcutName, Class<?> requiredType) { + super(original); + this.shortcutName = shortcutName; + this.requiredType = requiredType; + } + + @Override + public Object resolveShortcut(BeanFactory beanFactory) { + return resolveCandidate(this.shortcutName, this.requiredType, beanFactory); + } + } + } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/BeanFactoryAnnotationUtils.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/BeanFactoryAnnotationUtils.java index eb691a14..65901004 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/BeanFactoryAnnotationUtils.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/BeanFactoryAnnotationUtils.java @@ -26,6 +26,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.AutowireCandidateQualifier; import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.ObjectUtils; /** @@ -113,6 +114,7 @@ public class BeanFactoryAnnotationUtils { if (bf.containsBean(beanName)) { try { BeanDefinition bd = bf.getMergedBeanDefinition(beanName); + // Explicit qualifier metadata on bean definition? (typically in XML definition) if (bd instanceof AbstractBeanDefinition) { AbstractBeanDefinition abd = (AbstractBeanDefinition) bd; AutowireCandidateQualifier candidate = abd.getQualifier(Qualifier.class.getName()); @@ -121,15 +123,24 @@ public class BeanFactoryAnnotationUtils { return true; } } + // Corresponding qualifier on factory method? (typically in configuration class) if (bd instanceof RootBeanDefinition) { Method factoryMethod = ((RootBeanDefinition) bd).getResolvedFactoryMethod(); if (factoryMethod != null) { - Qualifier targetAnnotation = factoryMethod.getAnnotation(Qualifier.class); - if (targetAnnotation != null && qualifier.equals(targetAnnotation.value())) { - return true; + Qualifier targetAnnotation = AnnotationUtils.getAnnotation(factoryMethod, Qualifier.class); + if (targetAnnotation != null) { + return qualifier.equals(targetAnnotation.value()); } } } + // Corresponding qualifier on bean implementation class? (for custom user types) + Class<?> beanType = bf.getType(beanName); + if (beanType != null) { + Qualifier targetAnnotation = AnnotationUtils.getAnnotation(beanType, Qualifier.class); + if (targetAnnotation != null) { + return qualifier.equals(targetAnnotation.value()); + } + } } catch (NoSuchBeanDefinitionException ex) { // Ignore - can't compare qualifiers for a manually registered singleton object diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java index 3c7c0577..37433239 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/InitDestroyAnnotationBeanPostProcessor.java @@ -136,7 +136,7 @@ public class InitDestroyAnnotationBeanPostProcessor throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException()); } catch (Throwable ex) { - throw new BeanCreationException(beanName, "Couldn't invoke init method", ex); + throw new BeanCreationException(beanName, "Failed to invoke init method", ex); } return bean; } @@ -162,10 +162,15 @@ public class InitDestroyAnnotationBeanPostProcessor } } catch (Throwable ex) { - logger.error("Couldn't invoke destroy method on bean with name '" + beanName + "'", ex); + logger.error("Failed to invoke destroy method on bean with name '" + beanName + "'", ex); } } + @Override + public boolean requiresDestruction(Object bean) { + return findLifecycleMetadata(bean.getClass()).hasDestroyMethods(); + } + private LifecycleMetadata findLifecycleMetadata(Class<?> clazz) { if (this.lifecycleMetadataCache == null) { @@ -308,11 +313,11 @@ public class InitDestroyAnnotationBeanPostProcessor } public void invokeDestroyMethods(Object target, String beanName) throws Throwable { - Collection<LifecycleElement> destroyMethodsToIterate = + Collection<LifecycleElement> destroyMethodsToUse = (this.checkedDestroyMethods != null ? this.checkedDestroyMethods : this.destroyMethods); - if (!destroyMethodsToIterate.isEmpty()) { + if (!destroyMethodsToUse.isEmpty()) { boolean debug = logger.isDebugEnabled(); - for (LifecycleElement element : destroyMethodsToIterate) { + for (LifecycleElement element : destroyMethodsToUse) { if (debug) { logger.debug("Invoking destroy method on bean '" + beanName + "': " + element.getMethod()); } @@ -320,6 +325,12 @@ public class InitDestroyAnnotationBeanPostProcessor } } } + + public boolean hasDestroyMethods() { + Collection<LifecycleElement> destroyMethodsToUse = + (this.checkedDestroyMethods != null ? this.checkedDestroyMethods : this.destroyMethods); + return !destroyMethodsToUse.isEmpty(); + } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java index d8fcc731..0f6d1021 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 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. @@ -32,6 +32,8 @@ import org.springframework.beans.factory.support.AutowireCandidateResolver; import org.springframework.beans.factory.support.GenericTypeAwareAutowireCandidateResolver; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.core.MethodParameter; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -315,25 +317,20 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa * Determine a suggested value from any of the given candidate annotations. */ protected Object findValue(Annotation[] annotationsToSearch) { - for (Annotation annotation : annotationsToSearch) { - if (this.valueAnnotationType.isInstance(annotation)) { - return extractValue(annotation); - } - } - for (Annotation annotation : annotationsToSearch) { - Annotation metaAnn = annotation.annotationType().getAnnotation(this.valueAnnotationType); - if (metaAnn != null) { - return extractValue(metaAnn); - } + AnnotationAttributes attr = AnnotatedElementUtils.getMergedAnnotationAttributes( + AnnotatedElementUtils.forAnnotations(annotationsToSearch), this.valueAnnotationType); + if (attr != null) { + return extractValue(attr); } return null; } /** * Extract the value attribute from the given annotation. + * @since 4.3 */ - protected Object extractValue(Annotation valueAnnotation) { - Object value = AnnotationUtils.getValue(valueAnnotation); + protected Object extractValue(AnnotationAttributes attr) { + Object value = attr.get(AnnotationUtils.VALUE); if (value == null) { throw new IllegalStateException("Value annotation must have a value attribute"); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/AbstractFactoryBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/AbstractFactoryBean.java index bc8a42f7..85faab00 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/AbstractFactoryBean.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/AbstractFactoryBean.java @@ -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. @@ -208,7 +208,7 @@ public abstract class AbstractFactoryBean<T> * <p>Invoked on initialization of this FactoryBean in case of * a singleton; else, on each {@link #getObject()} call. * @return the object returned by this factory - * @throws Exception if an exception occured during object creation + * @throws Exception if an exception occurred during object creation * @see #getObject() */ protected abstract T createInstance() throws Exception; diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/AutowireCapableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/AutowireCapableBeanFactory.java index ffa8849a..43602b48 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/AutowireCapableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/AutowireCapableBeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 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. @@ -114,9 +114,9 @@ public interface AutowireCapableBeanFactory extends BeanFactory { * <p>Performs full initialization of the bean, including all applicable * {@link BeanPostProcessor BeanPostProcessors}. * <p>Note: This is intended for creating a fresh instance, populating annotated - * fields and methods as well as applying all standard bean initialiation callbacks. + * fields and methods as well as applying all standard bean initialization callbacks. * It does <i>not</> imply traditional by-name or by-type autowiring of properties; - * use {@link #createBean(Class, int, boolean)} for that purposes. + * use {@link #createBean(Class, int, boolean)} for those purposes. * @param beanClass the class of the bean to create * @return the new bean instance * @throws BeansException if instantiation or wiring failed @@ -129,7 +129,7 @@ public interface AutowireCapableBeanFactory extends BeanFactory { * <p>Note: This is essentially intended for (re-)populating annotated fields and * methods, either for new instances or for deserialized instances. It does * <i>not</i> imply traditional by-name or by-type autowiring of properties; - * use {@link #autowireBeanProperties} for that purposes. + * use {@link #autowireBeanProperties} for those purposes. * @param existingBean the existing bean instance * @throws BeansException if wiring failed */ @@ -159,7 +159,7 @@ public interface AutowireCapableBeanFactory extends BeanFactory { * @param descriptor the descriptor for the dependency * @param beanName the name of the bean which declares the present dependency * @return the resolved object, or {@code null} if none found - * @throws BeansException in dependency resolution failed + * @throws BeansException if dependency resolution failed */ Object resolveDependency(DependencyDescriptor descriptor, String beanName) throws BeansException; @@ -321,7 +321,7 @@ public interface AutowireCapableBeanFactory extends BeanFactory { * @param typeConverter the TypeConverter to use for populating arrays and * collections * @return the resolved object, or {@code null} if none found - * @throws BeansException in dependency resolution failed + * @throws BeansException if dependency resolution failed */ Object resolveDependency(DependencyDescriptor descriptor, String beanName, Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException; diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java index d04eb15d..302deec5 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/ConfigurableBeanFactory.java @@ -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. @@ -210,6 +210,13 @@ public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, Single void addEmbeddedValueResolver(StringValueResolver valueResolver); /** + * Determine whether an embedded value resolver has been registered with this + * bean factory, to be applied through {@link #resolveEmbeddedValue(String)}. + * @since 4.3 + */ + boolean hasEmbeddedValueResolver(); + + /** * Resolve the given embedded value, e.g. an annotation attribute. * @param value the value to resolve * @return the resolved value (may be the original value as-is) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java index 9a57c7ae..6f7d2d90 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 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. @@ -156,7 +156,7 @@ public class ConstructorArgumentValues { * @param requiredType the type to match (can be {@code null} to match * untyped values only) * @param requiredName the type to match (can be {@code null} to match - * unnamed values only) + * unnamed values only, or empty String to match any name) * @return the ValueHolder for the argument, or {@code null} if none set */ public ValueHolder getIndexedArgumentValue(int index, Class<?> requiredType, String requiredName) { @@ -165,7 +165,7 @@ public class ConstructorArgumentValues { if (valueHolder != null && (valueHolder.getType() == null || (requiredType != null && ClassUtils.matchesTypeName(requiredType, valueHolder.getType()))) && - (valueHolder.getName() == null || + (valueHolder.getName() == null || "".equals(requiredName) || (requiredName != null && requiredName.equals(valueHolder.getName())))) { return valueHolder; } @@ -268,7 +268,7 @@ public class ConstructorArgumentValues { * @param requiredType the type to match (can be {@code null} to find * an arbitrary next generic argument value) * @param requiredName the name to match (can be {@code null} to not - * match argument values by name) + * match argument values by name, or empty String to match any name) * @param usedValueHolders a Set of ValueHolder objects that have already been used * in the current resolution process and should therefore not be returned again * @return the ValueHolder for the argument, or {@code null} if none found @@ -278,7 +278,7 @@ public class ConstructorArgumentValues { if (usedValueHolders != null && usedValueHolders.contains(valueHolder)) { continue; } - if (valueHolder.getName() != null && + if (valueHolder.getName() != null && !"".equals(requiredName) && (requiredName == null || !valueHolder.getName().equals(requiredName))) { continue; } @@ -335,7 +335,7 @@ public class ConstructorArgumentValues { * @param requiredType the parameter type to match (can be {@code null} * to find an untyped argument value) * @param requiredName the parameter name to match (can be {@code null} - * to find an unnamed argument value) + * to find an unnamed argument value, or empty String to match any name) * @param usedValueHolders a Set of ValueHolder objects that have already * been used in the current resolution process and should therefore not * be returned again (allowing to return the next generic argument match diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java index 88642eb4..82c6883c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java @@ -19,17 +19,20 @@ 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; +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. @@ -40,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; @@ -64,7 +61,7 @@ public class DependencyDescriptor implements Serializable { private int nestingLevel = 1; - private transient Annotation[] fieldAnnotations; + private Class<?> containingClass; /** @@ -85,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(); @@ -97,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; } @@ -119,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; @@ -132,52 +127,84 @@ 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 + * Return whether this dependency is required. */ - public MethodParameter getMethodParameter() { - return this.methodParameter; + public boolean isRequired() { + return this.required; } /** - * Return the wrapped Field, if any. - * <p>Note: Either MethodParameter or Field is available. - * @return the Field, or {@code null} if none + * Return whether this dependency is 'eager' in the sense of + * eagerly resolving potential target beans for type matching. */ - public Field getField() { - return this.field; + public boolean isEager() { + return this.eager; } /** - * Return whether this dependency is required. + * Resolve the specified not-unique scenario: by default, + * throwing a {@link NoUniqueBeanDefinitionException}. + * <p>Subclasses may override this to select one of the instances or + * to opt out with no result at all through returning {@code null}. + * @param type the requested bean type + * @param matchingBeans a map of bean names and corresponding bean + * instances which have been pre-selected for the given type + * (qualifiers etc already applied) + * @return a bean instance to proceed with, or {@code null} for none + * @throws BeansException in case of the not-unique scenario being fatal + * @since 4.3 */ - public boolean isRequired() { - return this.required; + public Object resolveNotUnique(Class<?> type, Map<String, Object> matchingBeans) throws BeansException { + throw new NoUniqueBeanDefinitionException(type, matchingBeans.keySet()); } /** - * Return whether this dependency is 'eager' in the sense of - * eagerly resolving potential target beans for type matching. + * Resolve a shortcut for this dependency against the given factory, for example + * taking some pre-resolved information into account. + * <p>The resolution algorithm will first attempt to resolve a shortcut through this + * method before going into the regular type matching algorithm across all beans. + * Subclasses may override this method to improve resolution performance based on + * pre-cached information while still receiving {@link InjectionPoint} exposure etc. + * @param beanFactory the associated factory + * @return the shortcut result if any, or {@code null} if none + * @throws BeansException if the shortcut could not be obtained + * @since 4.3.1 */ - public boolean isEager() { - return this.eager; + public Object resolveShortcut(BeanFactory beanFactory) throws BeansException { + return null; + } + + /** + * Resolve the specified bean name, as a candidate result of the matching + * algorithm for this dependency, to a bean instance from the given factory. + * <p>The default implementation calls {@link BeanFactory#getBean(String)}. + * Subclasses may provide additional arguments or other customizations. + * @param beanName the bean name, as a candidate result for this dependency + * @param requiredType the expected type of the bean (as an assertion) + * @param beanFactory the associated factory + * @return the bean instance (never {@code null}) + * @throws BeansException if the bean could not be obtained + * @since 4.3.2 + * @see BeanFactory#getBean(String) + */ + public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory) + throws BeansException { + + return beanFactory.getBean(beanName, requiredType); } @@ -272,6 +299,7 @@ public class DependencyDescriptor implements Serializable { Type[] args = ((ParameterizedType) type).getActualTypeArguments(); type = args[args.length - 1]; } + // TODO: Object.class if unresolvable } if (type instanceof Class) { return (Class<?>) type; @@ -323,19 +351,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(); - } - return this.fieldAnnotations; + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; } - 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); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/DestructionAwareBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/DestructionAwareBeanPostProcessor.java index 76de1390..92316f13 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/DestructionAwareBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/DestructionAwareBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2015 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. @@ -43,4 +43,24 @@ public interface DestructionAwareBeanPostProcessor extends BeanPostProcessor { */ void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException; + /** + * Determine whether the given bean instance requires destruction by this + * post-processor. + * <p><b>NOTE:</b> Even as a late addition, this method has been introduced on + * {@code DestructionAwareBeanPostProcessor} itself instead of on a SmartDABPP + * subinterface. This allows existing {@code DestructionAwareBeanPostProcessor} + * implementations to easily provide {@code requiresDestruction} logic while + * retaining compatibility with Spring <4.3, and it is also an easier onramp to + * declaring {@code requiresDestruction} as a Java 8 default method in Spring 5. + * <p>If an implementation of {@code DestructionAwareBeanPostProcessor} does + * not provide a concrete implementation of this method, Spring's invocation + * mechanism silently assumes a method returning {@code true} (the effective + * default before 4.3, and the to-be-default in the Java 8 method in Spring 5). + * @param bean the bean instance to check + * @return {@code true} if {@link #postProcessBeforeDestruction} is supposed to + * be called for this bean instance eventually, or {@code false} if not needed + * @since 4.3 + */ + boolean requiresDestruction(Object bean); + } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/EmbeddedValueResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/EmbeddedValueResolver.java new file mode 100644 index 00000000..bd8c1666 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/EmbeddedValueResolver.java @@ -0,0 +1,59 @@ +/* + * 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.config; + +import org.springframework.util.StringValueResolver; + +/** + * {@link StringValueResolver} adapter for resolving placeholders and + * expressions against a {@link ConfigurableBeanFactory}. + * + * <p>Note that this adapter resolves expressions as well, in contrast + * to the {@link ConfigurableBeanFactory#resolveEmbeddedValue} method. + * The {@link BeanExpressionContext} used is for the plain bean factory, + * with no scope specified for any contextual objects to access. + * + * @author Juergen Hoeller + * @since 4.3 + * @see ConfigurableBeanFactory#resolveEmbeddedValue(String) + * @see ConfigurableBeanFactory#getBeanExpressionResolver() + * @see BeanExpressionContext + */ +public class EmbeddedValueResolver implements StringValueResolver { + + private final BeanExpressionContext exprContext; + + private final BeanExpressionResolver exprResolver; + + + public EmbeddedValueResolver(ConfigurableBeanFactory beanFactory) { + this.exprContext = new BeanExpressionContext(beanFactory, null); + this.exprResolver = beanFactory.getBeanExpressionResolver(); + } + + + @Override + public String resolveStringValue(String strVal) { + String value = this.exprContext.getBeanFactory().resolveEmbeddedValue(strVal); + if (this.exprResolver != null && value != null) { + Object evaluated = this.exprResolver.evaluate(value, this.exprContext); + value = (evaluated != null ? evaluated.toString() : null); + } + return value; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java index f508e3ff..964ee204 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessor.java @@ -96,7 +96,7 @@ public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor { * dependency types - which the factory handles specifically - already filtered out) * @param bean the bean instance created, but whose properties have not yet been set * @param beanName the name of the bean - * @return the actual property values to apply to to the given bean + * @return the actual property values to apply to the given bean * (can be the passed-in PropertyValues instance), or {@code null} * to skip property population * @throws org.springframework.beans.BeansException in case of errors diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/PlaceholderConfigurerSupport.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/PlaceholderConfigurerSupport.java index a0152ed4..f7223a9a 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/PlaceholderConfigurerSupport.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/PlaceholderConfigurerSupport.java @@ -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. @@ -107,6 +107,8 @@ public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfi /** Defaults to {@value #DEFAULT_VALUE_SEPARATOR} */ protected String valueSeparator = DEFAULT_VALUE_SEPARATOR; + protected boolean trimValues = false; + protected String nullValue; protected boolean ignoreUnresolvablePlaceholders = false; @@ -143,6 +145,16 @@ public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfi } /** + * Specify whether to trim resolved values before applying them, + * removing superfluous whitespace from the beginning and end. + * <p>Default is {@code false}. + * @since 4.3 + */ + public void setTrimValues(boolean trimValues) { + this.trimValues = trimValues; + } + + /** * Set a value that should be treated as {@code null} when resolved * as a placeholder value: e.g. "" (empty String) or "null". * <p>Note that this will only apply to full property values, diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertiesFactoryBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertiesFactoryBean.java index 93a97a8d..adc415d0 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertiesFactoryBean.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertiesFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 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. @@ -95,7 +95,7 @@ public class PropertiesFactoryBean extends PropertiesLoaderSupport * <p>Invoked on initialization of this FactoryBean in case of a * shared singleton; else, on each {@link #getObject()} call. * @return the object returned by this factory - * @throws IOException if an exception occured during properties loading + * @throws IOException if an exception occurred during properties loading * @see #mergeProperties() */ protected Properties createProperties() throws IOException { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.java index f98ce40e..a0243a33 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.java @@ -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. @@ -255,8 +255,11 @@ public class PropertyPlaceholderConfigurer extends PlaceholderConfigurerSupport @Override public String resolveStringValue(String strVal) throws BeansException { - String value = this.helper.replacePlaceholders(strVal, this.resolver); - return (value.equals(nullValue) ? null : value); + String resolved = this.helper.replacePlaceholders(strVal, this.resolver); + if (trimValues) { + resolved = resolved.trim(); + } + return (resolved.equals(nullValue) ? null : resolved); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/Scope.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/Scope.java index 8777fa61..2911a123 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/Scope.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/Scope.java @@ -100,7 +100,7 @@ public interface Scope { * at the appropriate time. If such a callback is not supported by the * underlying runtime environment at all, the callback <i>must be * ignored and a corresponding warning should be logged</i>. - * <p>Note that 'destruction' refers to to automatic destruction of + * <p>Note that 'destruction' refers to automatic destruction of * the object as part of the scope's own lifecycle, not to the individual * scoped object having been explicitly removed by the application. * If a scoped object gets removed via this facade's {@link #remove(String)} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java index e90712fc..bca9efeb 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinitionReader.java @@ -68,7 +68,7 @@ public abstract class AbstractBeanDefinitionReader implements EnvironmentCapable * {@link org.springframework.context.ApplicationContext} implementations. * <p>If given a plain BeanDefinitionRegistry, the default ResourceLoader will be a * {@link org.springframework.core.io.support.PathMatchingResourcePatternResolver}. - * <p>If the the passed-in bean factory also implements {@link EnvironmentCapable} its + * <p>If the passed-in bean factory also implements {@link EnvironmentCapable} its * environment will be used by this reader. Otherwise, the reader will initialize and * use a {@link StandardEnvironment}. All ApplicationContext implementations are * EnvironmentCapable, while normal BeanFactory implementations are not. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java index 80e91528..62dfa38e 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java @@ -799,6 +799,11 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp } @Override + public boolean hasEmbeddedValueResolver() { + return !this.embeddedValueResolvers.isEmpty(); + } + + @Override public String resolveEmbeddedValue(String value) { String result = value; for (StringValueResolver resolver : this.embeddedValueResolvers) { @@ -1619,7 +1624,8 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp */ protected boolean requiresDestruction(Object bean, RootBeanDefinition mbd) { return (bean != null && - (DisposableBeanAdapter.hasDestroyMethod(bean, mbd) || hasDestructionAwareBeanPostProcessors())); + (DisposableBeanAdapter.hasDestroyMethod(bean, mbd) || (hasDestructionAwareBeanPostProcessors() && + DisposableBeanAdapter.hasApplicableProcessors(bean, getBeanPostProcessors())))); } /** diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java index 41653c84..cbb9d2f1 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java @@ -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. @@ -115,7 +115,7 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt Class<?> subclass = createEnhancedSubclass(this.beanDefinition); Object instance; if (ctor == null) { - instance = BeanUtils.instantiate(subclass); + instance = BeanUtils.instantiateClass(subclass); } else { try { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java index 50c56791..7c871d46 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/ConstructorResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 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,12 +40,14 @@ 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; import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.core.GenericTypeResolver; import org.springframework.core.MethodParameter; +import org.springframework.core.NamedThreadLocal; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.util.ClassUtils; import org.springframework.util.MethodInvoker; @@ -68,6 +70,9 @@ import org.springframework.util.StringUtils; */ class ConstructorResolver { + private static final NamedThreadLocal<InjectionPoint> currentInjectionPoint = + new NamedThreadLocal<InjectionPoint>("Current injection point"); + private final AbstractAutowireCapableBeanFactory beanFactory; @@ -151,7 +156,7 @@ class ConstructorResolver { catch (Throwable ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Resolution of declared constructors on bean Class [" + beanClass.getName() + - "] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex); + "] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex); } } AutowireUtils.sortConstructors(candidates); @@ -159,8 +164,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) { @@ -182,8 +186,8 @@ class ConstructorResolver { paramNames = pnd.getParameterNames(candidate); } } - argsHolder = createArgumentArray( - beanName, mbd, resolvedValues, bw, paramTypes, paramNames, candidate, autowiring); + argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames, + getUserDeclaredConstructor(candidate), autowiring); } catch (UnsatisfiedDependencyException ex) { if (this.beanFactory.logger.isTraceEnabled()) { @@ -268,7 +272,7 @@ class ConstructorResolver { mbd, beanName, this.beanFactory, constructorToUse, argsToUse); } - bw.setWrappedInstance(beanInstance); + bw.setBeanInstance(beanInstance); return bw; } catch (Throwable ex) { @@ -444,10 +448,9 @@ class ConstructorResolver { minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues); } - List<Exception> causes = null; + LinkedList<UnsatisfiedDependencyException> causes = null; - for (int i = 0; i < candidates.length; i++) { - Method candidate = candidates[i]; + for (Method candidate : candidates) { Class<?>[] paramTypes = candidate.getParameterTypes(); if (paramTypes.length >= minNrOfArgs) { @@ -469,22 +472,12 @@ class ConstructorResolver { this.beanFactory.logger.trace("Ignoring factory method [" + candidate + "] of bean '" + beanName + "': " + ex); } - if (i == candidates.length - 1 && argsHolderToUse == null) { - if (causes != null) { - for (Exception cause : causes) { - this.beanFactory.onSuppressedException(cause); - } - } - throw ex; - } - else { - // Swallow and try next overloaded factory method. - if (causes == null) { - causes = new LinkedList<Exception>(); - } - causes.add(ex); - continue; + // Swallow and try next overloaded factory method. + if (causes == null) { + causes = new LinkedList<UnsatisfiedDependencyException>(); } + causes.add(ex); + continue; } } @@ -525,6 +518,13 @@ class ConstructorResolver { } if (factoryMethodToUse == null) { + if (causes != null) { + UnsatisfiedDependencyException ex = causes.removeLast(); + for (Exception cause : causes) { + this.beanFactory.onSuppressedException(cause); + } + throw ex; + } List<String> argTypes = new ArrayList<String>(minNrOfArgs); if (explicitArgs != null) { for (Object arg : explicitArgs) { @@ -592,7 +592,7 @@ class ConstructorResolver { if (beanInstance == null) { return null; } - bw.setWrappedInstance(beanInstance); + bw.setBeanInstance(beanInstance); return bw; } catch (Throwable ex) { @@ -609,8 +609,8 @@ class ConstructorResolver { private int resolveConstructorArguments(String beanName, RootBeanDefinition mbd, BeanWrapper bw, ConstructorArgumentValues cargs, ConstructorArgumentValues resolvedValues) { - TypeConverter converter = (this.beanFactory.getCustomTypeConverter() != null ? - this.beanFactory.getCustomTypeConverter() : bw); + TypeConverter customConverter = this.beanFactory.getCustomTypeConverter(); + TypeConverter converter = (customConverter != null ? customConverter : bw); BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this.beanFactory, beanName, mbd, converter); @@ -665,9 +665,8 @@ 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); + TypeConverter customConverter = this.beanFactory.getCustomTypeConverter(); + TypeConverter converter = (customConverter != null ? customConverter : bw); ArgumentsHolder args = new ArgumentsHolder(paramTypes.length); Set<ConstructorArgumentValues.ValueHolder> usedValueHolders = @@ -676,14 +675,14 @@ class ConstructorResolver { for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) { Class<?> paramType = paramTypes[paramIndex]; - String paramName = (paramNames != null ? paramNames[paramIndex] : null); + String paramName = (paramNames != null ? paramNames[paramIndex] : ""); // Try to find matching constructor argument value, either indexed or generic. ConstructorArgumentValues.ValueHolder valueHolder = resolvedValues.getArgumentValue(paramIndex, paramType, paramName, usedValueHolders); // If we couldn't find a direct match and are not supposed to autowire, // let's try the next generic, untyped argument value as fallback: // it could match after type conversion (for example, String -> int). - if (valueHolder == null && !autowiring) { + if (valueHolder == null && (!autowiring || paramTypes.length == resolvedValues.getArgumentCount())) { valueHolder = resolvedValues.getGenericArgumentValue(null, null, usedValueHolders); } if (valueHolder != null) { @@ -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 + "'"); } } @@ -768,12 +769,13 @@ class ConstructorResolver { private Object[] resolvePreparedArguments( String beanName, RootBeanDefinition mbd, BeanWrapper bw, Member methodOrCtor, Object[] argsToResolve) { - Class<?>[] paramTypes = (methodOrCtor instanceof Method ? - ((Method) methodOrCtor).getParameterTypes() : ((Constructor<?>) methodOrCtor).getParameterTypes()); - TypeConverter converter = (this.beanFactory.getCustomTypeConverter() != null ? - this.beanFactory.getCustomTypeConverter() : bw); + TypeConverter customConverter = this.beanFactory.getCustomTypeConverter(); + TypeConverter converter = (customConverter != null ? customConverter : bw); BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this.beanFactory, beanName, mbd, converter); + Class<?>[] paramTypes = (methodOrCtor instanceof Method ? + ((Method) methodOrCtor).getParameterTypes() : ((Constructor<?>) methodOrCtor).getParameterTypes()); + Object[] resolvedArgs = new Object[argsToResolve.length]; for (int argIndex = 0; argIndex < argsToResolve.length; argIndex++) { Object argValue = argsToResolve[argIndex]; @@ -793,28 +795,61 @@ 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()); } } return resolvedArgs; } + protected Constructor<?> getUserDeclaredConstructor(Constructor<?> constructor) { + Class<?> declaringClass = constructor.getDeclaringClass(); + Class<?> userClass = ClassUtils.getUserClass(declaringClass); + if (userClass != declaringClass) { + try { + return userClass.getDeclaredConstructor(constructor.getParameterTypes()); + } + catch (NoSuchMethodException ex) { + // No equivalent constructor on user class (superclass)... + // Let's proceed with the given constructor as we usually would. + } + } + return constructor; + } + /** * Template method for resolving the specified argument which is supposed to be autowired. */ protected Object resolveAutowiredArgument( MethodParameter param, String beanName, Set<String> autowiredBeanNames, TypeConverter typeConverter) { + if (InjectionPoint.class.isAssignableFrom(param.getParameterType())) { + InjectionPoint injectionPoint = currentInjectionPoint.get(); + if (injectionPoint == null) { + throw new IllegalStateException("No current InjectionPoint available for " + param); + } + return injectionPoint; + } return this.beanFactory.resolveDependency( new DependencyDescriptor(param, true), beanName, autowiredBeanNames, typeConverter); } + + static InjectionPoint setCurrentInjectionPoint(InjectionPoint injectionPoint) { + InjectionPoint old = currentInjectionPoint.get(); + if (injectionPoint != null) { + currentInjectionPoint.set(injectionPoint); + } + else { + currentInjectionPoint.remove(); + } + return old; + } + + /** * Private inner class for holding argument combinations. */ diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index 7a32060e..bb4ef59b 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -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,6 @@ import java.util.concurrent.ConcurrentHashMap; import javax.inject.Provider; import org.springframework.beans.BeansException; -import org.springframework.beans.FatalBeanException; import org.springframework.beans.TypeConverter; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanCurrentlyInCreationException; @@ -53,11 +52,14 @@ import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.BeanNotOfRequiredTypeException; import org.springframework.beans.factory.CannotLoadBeanClassException; import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InjectionPoint; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.SmartFactoryBean; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.config.BeanDefinition; @@ -615,10 +617,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto @Override public void registerResolvableDependency(Class<?> dependencyType, Object autowiredValue) { - Assert.notNull(dependencyType, "Type must not be null"); + Assert.notNull(dependencyType, "Dependency type must not be null"); if (autowiredValue != null) { - Assert.isTrue((autowiredValue instanceof ObjectFactory || dependencyType.isInstance(autowiredValue)), - "Value [" + autowiredValue + "] does not implement specified type [" + dependencyType.getName() + "]"); + if (!(autowiredValue instanceof ObjectFactory || dependencyType.isInstance(autowiredValue))) { + throw new IllegalArgumentException("Value [" + autowiredValue + + "] does not implement specified dependency type [" + dependencyType.getName() + "]"); + } this.resolvableDependencies.put(dependencyType, autowiredValue); } } @@ -999,14 +1003,15 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException { descriptor.initParameterNameDiscovery(getParameterNameDiscoverer()); - if (descriptor.getDependencyType().equals(javaUtilOptionalClass)) { + if (javaUtilOptionalClass == descriptor.getDependencyType()) { return new OptionalDependencyFactory().createOptionalDependency(descriptor, beanName); } - else if (ObjectFactory.class == descriptor.getDependencyType()) { - return new DependencyObjectFactory(descriptor, beanName); + else if (ObjectFactory.class == descriptor.getDependencyType() || + ObjectProvider.class == descriptor.getDependencyType()) { + return new DependencyObjectProvider(descriptor, beanName); } else if (javaxInjectProviderClass == descriptor.getDependencyType()) { - return new DependencyProviderFactory().createDependencyProvider(descriptor, beanName); + return new Jsr330ProviderFactory().createDependencyProvider(descriptor, beanName); } else { Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(descriptor, beanName); @@ -1020,29 +1025,79 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto public Object doResolveDependency(DependencyDescriptor descriptor, String beanName, Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException { - Class<?> type = descriptor.getDependencyType(); - Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor); - if (value != null) { - if (value instanceof String) { - String strVal = resolveEmbeddedValue((String) value); - BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null); - value = evaluateBeanDefinitionString(strVal, bd); + InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor); + try { + Object shortcut = descriptor.resolveShortcut(this); + if (shortcut != null) { + return shortcut; } - TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); - return (descriptor.getField() != null ? - converter.convertIfNecessary(value, type, descriptor.getField()) : - converter.convertIfNecessary(value, type, descriptor.getMethodParameter())); + + Class<?> type = descriptor.getDependencyType(); + Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor); + if (value != null) { + if (value instanceof String) { + String strVal = resolveEmbeddedValue((String) value); + BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null); + value = evaluateBeanDefinitionString(strVal, bd); + } + TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); + return (descriptor.getField() != null ? + converter.convertIfNecessary(value, type, descriptor.getField()) : + converter.convertIfNecessary(value, type, descriptor.getMethodParameter())); + } + + Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter); + if (multipleBeans != null) { + return multipleBeans; + } + + Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor); + if (matchingBeans.isEmpty()) { + if (descriptor.isRequired()) { + raiseNoMatchingBeanFound(type, descriptor.getResolvableType().toString(), descriptor); + } + return null; + } + if (matchingBeans.size() > 1) { + String primaryBeanName = determineAutowireCandidate(matchingBeans, descriptor); + if (primaryBeanName == null) { + if (descriptor.isRequired() || !indicatesMultipleBeans(type)) { + return descriptor.resolveNotUnique(type, matchingBeans); + } + else { + // In case of an optional Collection/Map, silently ignore a non-unique case: + // possibly it was meant to be an empty collection of multiple regular beans + // (before 4.3 in particular when we didn't even look for collection beans). + return null; + } + } + if (autowiredBeanNames != null) { + autowiredBeanNames.add(primaryBeanName); + } + return matchingBeans.get(primaryBeanName); + } + // We have exactly one match. + Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next(); + if (autowiredBeanNames != null) { + autowiredBeanNames.add(entry.getKey()); + } + return entry.getValue(); + } + finally { + ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint); } + } + + private Object resolveMultipleBeans(DependencyDescriptor descriptor, String beanName, + Set<String> autowiredBeanNames, TypeConverter typeConverter) { + Class<?> type = descriptor.getDependencyType(); if (type.isArray()) { Class<?> componentType = type.getComponentType(); DependencyDescriptor targetDesc = new DependencyDescriptor(descriptor); targetDesc.increaseNestingLevel(); Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType, targetDesc); if (matchingBeans.isEmpty()) { - if (descriptor.isRequired()) { - raiseNoSuchBeanDefinitionException(componentType, "array of " + componentType.getName(), descriptor); - } return null; } if (autowiredBeanNames != null) { @@ -1058,18 +1113,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto else if (Collection.class.isAssignableFrom(type) && type.isInterface()) { Class<?> elementType = descriptor.getCollectionType(); if (elementType == null) { - if (descriptor.isRequired()) { - throw new FatalBeanException("No element type declared for collection [" + type.getName() + "]"); - } return null; } DependencyDescriptor targetDesc = new DependencyDescriptor(descriptor); targetDesc.increaseNestingLevel(); Map<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType, targetDesc); if (matchingBeans.isEmpty()) { - if (descriptor.isRequired()) { - raiseNoSuchBeanDefinitionException(elementType, "collection of " + elementType.getName(), descriptor); - } return null; } if (autowiredBeanNames != null) { @@ -1085,26 +1134,16 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto else if (Map.class.isAssignableFrom(type) && type.isInterface()) { Class<?> keyType = descriptor.getMapKeyType(); if (String.class != keyType) { - if (descriptor.isRequired()) { - throw new FatalBeanException("Key type [" + keyType + "] of map [" + type.getName() + - "] must be [java.lang.String]"); - } return null; } Class<?> valueType = descriptor.getMapValueType(); if (valueType == null) { - if (descriptor.isRequired()) { - throw new FatalBeanException("No value type declared for map [" + type.getName() + "]"); - } return null; } DependencyDescriptor targetDesc = new DependencyDescriptor(descriptor); targetDesc.increaseNestingLevel(); Map<String, Object> matchingBeans = findAutowireCandidates(beanName, valueType, targetDesc); if (matchingBeans.isEmpty()) { - if (descriptor.isRequired()) { - raiseNoSuchBeanDefinitionException(valueType, "map with value type " + valueType.getName(), descriptor); - } return null; } if (autowiredBeanNames != null) { @@ -1113,32 +1152,15 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto return matchingBeans; } else { - Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor); - if (matchingBeans.isEmpty()) { - if (descriptor.isRequired()) { - raiseNoSuchBeanDefinitionException(type, "", descriptor); - } - return null; - } - if (matchingBeans.size() > 1) { - String primaryBeanName = determineAutowireCandidate(matchingBeans, descriptor); - if (primaryBeanName == null) { - throw new NoUniqueBeanDefinitionException(type, matchingBeans.keySet()); - } - if (autowiredBeanNames != null) { - autowiredBeanNames.add(primaryBeanName); - } - return matchingBeans.get(primaryBeanName); - } - // We have exactly one match. - Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next(); - if (autowiredBeanNames != null) { - autowiredBeanNames.add(entry.getKey()); - } - return entry.getValue(); + return null; } } + private boolean indicatesMultipleBeans(Class<?> type) { + return (type.isArray() || (type.isInterface() && + (Collection.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type)))); + } + private Comparator<Object> adaptDependencyComparator(Map<String, Object> matchingBeans) { Comparator<Object> comparator = getDependencyComparator(); if (comparator instanceof OrderComparator) { @@ -1189,14 +1211,23 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto } for (String candidateName : candidateNames) { if (!isSelfReference(beanName, candidateName) && isAutowireCandidate(candidateName, descriptor)) { - result.put(candidateName, getBean(candidateName)); + result.put(candidateName, descriptor.resolveCandidate(candidateName, requiredType, this)); } } - if (result.isEmpty()) { + if (result.isEmpty() && !indicatesMultipleBeans(requiredType)) { + // Consider fallback matches if the first pass failed to find anything... DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch(); for (String candidateName : candidateNames) { - if (!candidateName.equals(beanName) && isAutowireCandidate(candidateName, fallbackDescriptor)) { - result.put(candidateName, getBean(candidateName)); + if (!isSelfReference(beanName, candidateName) && isAutowireCandidate(candidateName, fallbackDescriptor)) { + result.put(candidateName, descriptor.resolveCandidate(candidateName, requiredType, this)); + } + } + if (result.isEmpty()) { + // Consider self references before as a final pass + for (String candidateName : candidateNames) { + if (isSelfReference(beanName, candidateName) && isAutowireCandidate(candidateName, fallbackDescriptor)) { + result.put(candidateName, descriptor.resolveCandidate(candidateName, requiredType, this)); + } } } } @@ -1362,17 +1393,43 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto } /** - * Raise a NoSuchBeanDefinitionException for an unresolvable dependency. + * Raise a NoSuchBeanDefinitionException or BeanNotOfRequiredTypeException + * for an unresolvable dependency. */ - private void raiseNoSuchBeanDefinitionException( - Class<?> type, String dependencyDescription, DependencyDescriptor descriptor) - throws NoSuchBeanDefinitionException { + private void raiseNoMatchingBeanFound( + Class<?> type, String dependencyDescription, DependencyDescriptor descriptor) throws BeansException { + + checkBeanNotOfRequiredType(type, descriptor); throw new NoSuchBeanDefinitionException(type, dependencyDescription, "expected at least 1 bean which qualifies as autowire candidate for this dependency. " + "Dependency annotations: " + ObjectUtils.nullSafeToString(descriptor.getAnnotations())); } + /** + * Raise a BeanNotOfRequiredTypeException for an unresolvable dependency, if applicable, + * i.e. if the target type of the bean would match but an exposed proxy doesn't. + */ + private void checkBeanNotOfRequiredType(Class<?> type, DependencyDescriptor descriptor) { + for (String beanName : this.beanDefinitionNames) { + RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); + Class<?> targetType = mbd.getTargetType(); + if (targetType != null && type.isAssignableFrom(targetType) && + isAutowireCandidate(beanName, mbd, descriptor, getAutowireCandidateResolver())) { + // Probably a poxy interfering with target type match -> throw meaningful exception. + Object beanInstance = getSingleton(beanName, false); + Class<?> beanType = (beanInstance != null ? beanInstance.getClass() : predictBeanType(beanName, mbd)); + if (type != beanType) { + throw new BeanNotOfRequiredTypeException(beanName, type, beanType); + } + } + } + + if (getParentBeanFactory() instanceof DefaultListableBeanFactory) { + ((DefaultListableBeanFactory) getParentBeanFactory()).checkBeanNotOfRequiredType(type, descriptor); + } + } + @Override public String toString() { @@ -1424,16 +1481,14 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto private Object readResolve() { Reference<?> ref = serializableFactories.get(this.id); - if (ref == null) { - throw new IllegalStateException( - "Cannot deserialize BeanFactory with id " + this.id + ": no factory registered for this id"); - } - Object result = ref.get(); - if (result == null) { - throw new IllegalStateException( - "Cannot deserialize BeanFactory with id " + this.id + ": factory has been garbage-collected"); + if (ref != null) { + Object result = ref.get(); + if (result != null) { + return result; + } } - return result; + // Lenient fallback: dummy factory in case of original factory not found... + return new StaticListableBeanFactory(Collections.<String, Object> emptyMap()); } } @@ -1444,12 +1499,17 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto @UsesJava8 private class OptionalDependencyFactory { - public Object createOptionalDependency(DependencyDescriptor descriptor, String beanName) { + public Object createOptionalDependency(DependencyDescriptor descriptor, String beanName, final Object... args) { DependencyDescriptor descriptorToUse = new DependencyDescriptor(descriptor) { @Override public boolean isRequired() { return false; } + @Override + public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory) { + return (!ObjectUtils.isEmpty(args) ? beanFactory.getBean(beanName, requiredType, args) : + super.resolveCandidate(beanName, requiredType, beanFactory)); + } }; descriptorToUse.increaseNestingLevel(); return Optional.ofNullable(doResolveDependency(descriptorToUse, beanName, null, null)); @@ -1458,9 +1518,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto /** - * Serializable ObjectFactory for lazy resolution of a dependency. + * Serializable ObjectFactory/ObjectProvider for lazy resolution of a dependency. */ - private class DependencyObjectFactory implements ObjectFactory<Object>, Serializable { + private class DependencyObjectProvider implements ObjectProvider<Object>, Serializable { private final DependencyDescriptor descriptor; @@ -1468,7 +1528,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto private final String beanName; - public DependencyObjectFactory(DependencyDescriptor descriptor, String beanName) { + public DependencyObjectProvider(DependencyDescriptor descriptor, String beanName) { this.descriptor = new DependencyDescriptor(descriptor); this.descriptor.increaseNestingLevel(); this.optional = this.descriptor.getDependencyType().equals(javaUtilOptionalClass); @@ -1484,15 +1544,67 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto return doResolveDependency(this.descriptor, this.beanName, null, null); } } + + @Override + public Object getObject(final Object... args) throws BeansException { + if (this.optional) { + return new OptionalDependencyFactory().createOptionalDependency(this.descriptor, this.beanName, args); + } + else { + DependencyDescriptor descriptorToUse = new DependencyDescriptor(descriptor) { + @Override + public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory) { + return ((AbstractBeanFactory) beanFactory).getBean(beanName, requiredType, args); + } + }; + return doResolveDependency(descriptorToUse, this.beanName, null, null); + } + } + + @Override + public Object getIfAvailable() throws BeansException { + if (this.optional) { + return new OptionalDependencyFactory().createOptionalDependency(this.descriptor, this.beanName); + } + else { + DependencyDescriptor descriptorToUse = new DependencyDescriptor(descriptor) { + @Override + public boolean isRequired() { + return false; + } + }; + return doResolveDependency(descriptorToUse, this.beanName, null, null); + } + } + + @Override + public Object getIfUnique() throws BeansException { + DependencyDescriptor descriptorToUse = new DependencyDescriptor(descriptor) { + @Override + public boolean isRequired() { + return false; + } + @Override + public Object resolveNotUnique(Class<?> type, Map<String, Object> matchingBeans) { + return null; + } + }; + if (this.optional) { + return new OptionalDependencyFactory().createOptionalDependency(descriptorToUse, this.beanName); + } + else { + return doResolveDependency(descriptorToUse, this.beanName, null, null); + } + } } /** * Serializable ObjectFactory for lazy resolution of a dependency. */ - private class DependencyProvider extends DependencyObjectFactory implements Provider<Object> { + private class Jsr330DependencyProvider extends DependencyObjectProvider implements Provider<Object> { - public DependencyProvider(DependencyDescriptor descriptor, String beanName) { + public Jsr330DependencyProvider(DependencyDescriptor descriptor, String beanName) { super(descriptor, beanName); } @@ -1506,10 +1618,10 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto /** * Separate inner class for avoiding a hard dependency on the {@code javax.inject} API. */ - private class DependencyProviderFactory { + private class Jsr330ProviderFactory { public Object createDependencyProvider(DependencyDescriptor descriptor, String beanName) { - return new DependencyProvider(descriptor, beanName); + return new Jsr330DependencyProvider(descriptor, beanName); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java index 9042bad5..d3d21347 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java @@ -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. @@ -449,10 +449,10 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements } private boolean isDependent(String beanName, String dependentBeanName, Set<String> alreadySeen) { - String canonicalName = canonicalName(beanName); if (alreadySeen != null && alreadySeen.contains(beanName)) { return false; } + String canonicalName = canonicalName(beanName); Set<String> dependentBeans = this.dependentBeanMap.get(canonicalName); if (dependentBeans == null) { return false; diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java index d3a6c361..b79403d6 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java @@ -139,7 +139,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { } } } - this.beanPostProcessors = filterPostProcessors(postProcessors); + this.beanPostProcessors = filterPostProcessors(postProcessors, bean); } /** @@ -155,7 +155,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { this.invokeDisposableBean = (this.bean instanceof DisposableBean); this.nonPublicAccessAllowed = true; this.acc = acc; - this.beanPostProcessors = filterPostProcessors(postProcessors); + this.beanPostProcessors = filterPostProcessors(postProcessors, bean); } /** @@ -214,16 +214,26 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { /** * Search for all DestructionAwareBeanPostProcessors in the List. - * @param postProcessors the List to search + * @param processors the List to search * @return the filtered List of DestructionAwareBeanPostProcessors */ - private List<DestructionAwareBeanPostProcessor> filterPostProcessors(List<BeanPostProcessor> postProcessors) { + private List<DestructionAwareBeanPostProcessor> filterPostProcessors(List<BeanPostProcessor> processors, Object bean) { List<DestructionAwareBeanPostProcessor> filteredPostProcessors = null; - if (!CollectionUtils.isEmpty(postProcessors)) { - filteredPostProcessors = new ArrayList<DestructionAwareBeanPostProcessor>(postProcessors.size()); - for (BeanPostProcessor postProcessor : postProcessors) { - if (postProcessor instanceof DestructionAwareBeanPostProcessor) { - filteredPostProcessors.add((DestructionAwareBeanPostProcessor) postProcessor); + if (!CollectionUtils.isEmpty(processors)) { + filteredPostProcessors = new ArrayList<DestructionAwareBeanPostProcessor>(processors.size()); + for (BeanPostProcessor processor : processors) { + if (processor instanceof DestructionAwareBeanPostProcessor) { + DestructionAwareBeanPostProcessor dabpp = (DestructionAwareBeanPostProcessor) processor; + try { + if (dabpp.requiresDestruction(bean)) { + filteredPostProcessors.add(dabpp); + } + } + catch (AbstractMethodError err) { + // A pre-4.3 third-party DestructionAwareBeanPostProcessor... + // As of 5.0, we can let requiresDestruction be a Java 8 default method which returns true. + filteredPostProcessors.add(dabpp); + } } } } @@ -401,9 +411,36 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { } String destroyMethodName = beanDefinition.getDestroyMethodName(); if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName)) { - return ClassUtils.hasMethod(bean.getClass(), CLOSE_METHOD_NAME); + return (ClassUtils.hasMethod(bean.getClass(), CLOSE_METHOD_NAME) || + ClassUtils.hasMethod(bean.getClass(), SHUTDOWN_METHOD_NAME)); } return StringUtils.hasLength(destroyMethodName); } + /** + * Check whether the given bean has destruction-aware post-processors applying to it. + * @param bean the bean instance + * @param postProcessors the post-processor candidates + */ + public static boolean hasApplicableProcessors(Object bean, List<BeanPostProcessor> postProcessors) { + if (!CollectionUtils.isEmpty(postProcessors)) { + for (BeanPostProcessor processor : postProcessors) { + if (processor instanceof DestructionAwareBeanPostProcessor) { + DestructionAwareBeanPostProcessor dabpp = (DestructionAwareBeanPostProcessor) processor; + try { + if (dabpp.requiresDestruction(bean)) { + return true; + } + } + catch (AbstractMethodError err) { + // A pre-4.3 third-party DestructionAwareBeanPostProcessor... + // As of 5.0, we can let requiresDestruction be a Java 8 default method which returns true. + return true; + } + } + } + } + return false; + } + } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java index d2ac971c..91ad5d8e 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 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. @@ -125,7 +125,7 @@ public class RootBeanDefinition extends AbstractBeanDefinition { setBeanClass(beanClass); setAutowireMode(autowireMode); if (dependencyCheck && getResolvedAutowireMode() != AUTOWIRE_CONSTRUCTOR) { - setDependencyCheck(RootBeanDefinition.DEPENDENCY_CHECK_OBJECTS); + setDependencyCheck(DEPENDENCY_CHECK_OBJECTS); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java index f878c57e..421805cb 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleInstantiationStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 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. @@ -81,7 +81,7 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy { } bd.resolvedConstructorOrFactoryMethod = constructorToUse; } - catch (Exception ex) { + catch (Throwable ex) { throw new BeanInstantiationException(clazz, "No default constructor found", ex); } } @@ -171,12 +171,12 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy { } } catch (IllegalArgumentException ex) { - throw new BeanInstantiationException(factoryMethod.getReturnType(), + throw new BeanInstantiationException(factoryMethod, "Illegal arguments to factory method '" + factoryMethod.getName() + "'; " + "args: " + StringUtils.arrayToCommaDelimitedString(args), ex); } catch (IllegalAccessException ex) { - throw new BeanInstantiationException(factoryMethod.getReturnType(), + throw new BeanInstantiationException(factoryMethod, "Cannot access factory method '" + factoryMethod.getName() + "'; is it public?", ex); } catch (InvocationTargetException ex) { @@ -186,7 +186,7 @@ public class SimpleInstantiationStrategy implements InstantiationStrategy { msg = "Circular reference involving containing bean '" + bd.getFactoryBeanName() + "' - consider " + "declaring the factory method as static for independence from its containing instance. " + msg; } - throw new BeanInstantiationException(factoryMethod.getReturnType(), msg, ex.getTargetException()); + throw new BeanInstantiationException(factoryMethod, msg, ex.getTargetException()); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java index 6e064c5d..32866846 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java @@ -34,6 +34,7 @@ import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.beans.factory.SmartFactoryBean; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** @@ -58,7 +59,31 @@ import org.springframework.util.StringUtils; public class StaticListableBeanFactory implements ListableBeanFactory { /** Map from bean name to bean instance */ - private final Map<String, Object> beans = new LinkedHashMap<String, Object>(); + private final Map<String, Object> beans; + + + /** + * Create a regular {@code StaticListableBeanFactory}, to be populated + * with singleton bean instances through {@link #addBean} calls. + */ + public StaticListableBeanFactory() { + this.beans = new LinkedHashMap<String, Object>(); + } + + /** + * Create a {@code StaticListableBeanFactory} wrapping the given {@code Map}. + * <p>Note that the given {@code Map} may be pre-populated with beans; + * or new, still allowing for beans to be registered via {@link #addBean}; + * or {@link java.util.Collections#emptyMap()} for a dummy factory which + * enforces operating against an empty set of beans. + * @param beans a {@code Map} for holding this factory's beans, with the + * bean name String as key and the corresponding singleton object as value + * @since 4.3 + */ + public StaticListableBeanFactory(Map<String, Object> beans) { + Assert.notNull(beans, "Beans Map must not be null"); + this.beans = beans; + } /** diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultBeanDefinitionDocumentReader.java b/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultBeanDefinitionDocumentReader.java index 494bcd39..6e2f6ad9 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultBeanDefinitionDocumentReader.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/xml/DefaultBeanDefinitionDocumentReader.java @@ -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. @@ -129,6 +129,10 @@ public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocume String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { + if (logger.isInfoEnabled()) { + logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec + + "] not matching: " + getReaderContext().getResource()); + } return; } } diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/CustomCollectionEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/CustomCollectionEditor.java index 607a99af..4c63ac79 100644 --- a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/CustomCollectionEditor.java +++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/CustomCollectionEditor.java @@ -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. @@ -154,9 +154,9 @@ public class CustomCollectionEditor extends PropertyEditorSupport { try { return collectionType.newInstance(); } - catch (Exception ex) { + catch (Throwable ex) { throw new IllegalArgumentException( - "Could not instantiate collection class [" + collectionType.getName() + "]: " + ex.getMessage()); + "Could not instantiate collection class: " + collectionType.getName(), ex); } } else if (List.class == collectionType) { diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/CustomMapEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/CustomMapEditor.java index 85865e54..05b5d872 100644 --- a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/CustomMapEditor.java +++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/CustomMapEditor.java @@ -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. @@ -132,9 +132,9 @@ public class CustomMapEditor extends PropertyEditorSupport { try { return mapType.newInstance(); } - catch (Exception ex) { + catch (Throwable ex) { throw new IllegalArgumentException( - "Could not instantiate map class [" + mapType.getName() + "]: " + ex.getMessage()); + "Could not instantiate map class: " + mapType.getName(), ex); } } else if (SortedMap.class == mapType) { diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/FileEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/FileEditor.java index a57b69ea..676fd0c6 100644 --- a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/FileEditor.java +++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/FileEditor.java @@ -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. @@ -59,16 +59,14 @@ public class FileEditor extends PropertyEditorSupport { /** - * Create a new FileEditor, - * using the default ResourceEditor underneath. + * Create a new FileEditor, using a default ResourceEditor underneath. */ public FileEditor() { this.resourceEditor = new ResourceEditor(); } /** - * Create a new FileEditor, - * using the given ResourceEditor underneath. + * Create a new FileEditor, using the given ResourceEditor underneath. * @param resourceEditor the ResourceEditor to use */ public FileEditor(ResourceEditor resourceEditor) { @@ -105,7 +103,7 @@ public class FileEditor extends PropertyEditorSupport { } catch (IOException ex) { throw new IllegalArgumentException( - "Could not retrieve File for " + resource + ": " + ex.getMessage()); + "Could not retrieve file for " + resource + ": " + ex.getMessage()); } } else { diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/InputStreamEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/InputStreamEditor.java index b4b014b7..dc982393 100644 --- a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/InputStreamEditor.java +++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/InputStreamEditor.java @@ -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. @@ -47,16 +47,14 @@ public class InputStreamEditor extends PropertyEditorSupport { /** - * Create a new InputStreamEditor, - * using the default ResourceEditor underneath. + * Create a new InputStreamEditor, using the default ResourceEditor underneath. */ public InputStreamEditor() { this.resourceEditor = new ResourceEditor(); } /** - * Create a new InputStreamEditor, - * using the given ResourceEditor underneath. + * Create a new InputStreamEditor, using the given ResourceEditor underneath. * @param resourceEditor the ResourceEditor to use */ public InputStreamEditor(ResourceEditor resourceEditor) { diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/PathEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/PathEditor.java new file mode 100644 index 00000000..4767b198 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/PathEditor.java @@ -0,0 +1,116 @@ +/* + * 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.propertyeditors; + +import java.beans.PropertyEditorSupport; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.FileSystemNotFoundException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceEditor; +import org.springframework.core.io.ResourceLoader; +import org.springframework.lang.UsesJava7; +import org.springframework.util.Assert; + +/** + * Editor for {@code java.nio.file.Path}, to directly populate a Path + * property instead of using a String property as bridge. + * + * <p>Based on {@link Paths#get(URI)}'s resolution algorithm, checking + * registered NIO file system providers, including the default file system + * for "file:..." paths. Also supports Spring-style URL notation: any fully + * qualified standard URL and Spring's special "classpath:" pseudo-URL, + * as well as Spring's context-specific relative file paths. + * + * <p>Note that, in contrast to {@link FileEditor}, relative paths are only + * supported by Spring's resource abstraction here. Direct {@code Paths.get} + * resolution in a file system always has to go through the corresponding + * file system provider's scheme, i.e. "file" for the default file system. + * + * @author Juergen Hoeller + * @since 4.3.2 + * @see java.nio.file.Path + * @see Paths#get(URI) + * @see ResourceEditor + * @see org.springframework.core.io.ResourceLoader + * @see FileEditor + * @see URLEditor + */ +@UsesJava7 +public class PathEditor extends PropertyEditorSupport { + + private final ResourceEditor resourceEditor; + + + /** + * Create a new PathEditor, using the default ResourceEditor underneath. + */ + public PathEditor() { + this.resourceEditor = new ResourceEditor(); + } + + /** + * Create a new PathEditor, using the given ResourceEditor underneath. + * @param resourceEditor the ResourceEditor to use + */ + public PathEditor(ResourceEditor resourceEditor) { + Assert.notNull(resourceEditor, "ResourceEditor must not be null"); + this.resourceEditor = resourceEditor; + } + + + @Override + public void setAsText(String text) throws IllegalArgumentException { + if (!text.startsWith("/") && !text.startsWith(ResourceLoader.CLASSPATH_URL_PREFIX)) { + try { + URI uri = new URI(text); + if (uri.getScheme() != null) { + // Let's try NIO file system providers via Paths.get(URI) + setValue(Paths.get(uri).normalize()); + return; + } + } + catch (URISyntaxException ex) { + // Not a valid URI: Let's try as Spring resource location. + } + catch (FileSystemNotFoundException ex) { + // URI scheme not registered for NIO: + // Let's try URL protocol handlers via Spring's resource mechanism. + } + } + + this.resourceEditor.setAsText(text); + Resource resource = (Resource) this.resourceEditor.getValue(); + try { + setValue(resource != null ? resource.getFile().toPath() : null); + } + catch (IOException ex) { + throw new IllegalArgumentException("Failed to retrieve file for " + resource, ex); + } + } + + @Override + public String getAsText() { + Path value = (Path) getValue(); + return (value != null ? value.toString() : ""); + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/ReaderEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/ReaderEditor.java index 826760ee..894d9770 100644 --- a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/ReaderEditor.java +++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/ReaderEditor.java @@ -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. @@ -47,16 +47,14 @@ public class ReaderEditor extends PropertyEditorSupport { /** - * Create a new ReaderEditor, - * using the default ResourceEditor underneath. + * Create a new ReaderEditor, using the default ResourceEditor underneath. */ public ReaderEditor() { this.resourceEditor = new ResourceEditor(); } /** - * Create a new ReaderEditor, - * using the given ResourceEditor underneath. + * Create a new ReaderEditor, using the given ResourceEditor underneath. * @param resourceEditor the ResourceEditor to use */ public ReaderEditor(ResourceEditor resourceEditor) { diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/StringArrayPropertyEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/StringArrayPropertyEditor.java index c8875b38..85adf515 100644 --- a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/StringArrayPropertyEditor.java +++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/StringArrayPropertyEditor.java @@ -84,7 +84,7 @@ public class StringArrayPropertyEditor extends PropertyEditorSupport { * @param emptyArrayAsNull {@code true} if an empty String array * is to be transformed into {@code null} * @param trimValues {@code true} if the values in the parsed arrays - * are to be be trimmed of whitespace (default is true). + * are to be trimmed of whitespace (default is true). */ public StringArrayPropertyEditor(String separator, boolean emptyArrayAsNull, boolean trimValues) { this(separator, null, emptyArrayAsNull, trimValues); @@ -112,7 +112,7 @@ public class StringArrayPropertyEditor extends PropertyEditorSupport { * @param emptyArrayAsNull {@code true} if an empty String array * is to be transformed into {@code null} * @param trimValues {@code true} if the values in the parsed arrays - * are to be be trimmed of whitespace (default is true). + * are to be trimmed of whitespace (default is true). */ public StringArrayPropertyEditor(String separator, String charsToDelete, boolean emptyArrayAsNull, boolean trimValues) { this.separator = separator; diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/URIEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/URIEditor.java index f5ac6d6a..7dc173a0 100644 --- a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/URIEditor.java +++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/URIEditor.java @@ -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. @@ -60,8 +60,7 @@ public class URIEditor extends PropertyEditorSupport { * standard URIs (not trying to resolve them into physical resources). */ public URIEditor() { - this.classLoader = null; - this.encode = true; + this(true); } /** @@ -74,7 +73,6 @@ public class URIEditor extends PropertyEditorSupport { this.encode = encode; } - /** * Create a new URIEditor, using the given ClassLoader to resolve * "classpath:" locations into physical resource URLs. @@ -82,8 +80,7 @@ public class URIEditor extends PropertyEditorSupport { * (may be {@code null} to indicate the default ClassLoader) */ public URIEditor(ClassLoader classLoader) { - this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader()); - this.encode = true; + this(classLoader, true); } /** diff --git a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/URLEditor.java b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/URLEditor.java index c5f9755d..85ef824d 100644 --- a/spring-beans/src/main/java/org/springframework/beans/propertyeditors/URLEditor.java +++ b/spring-beans/src/main/java/org/springframework/beans/propertyeditors/URLEditor.java @@ -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. @@ -50,7 +50,7 @@ public class URLEditor extends PropertyEditorSupport { /** - * Create a new URLEditor, using the default ResourceEditor underneath. + * Create a new URLEditor, using a default ResourceEditor underneath. */ public URLEditor() { this.resourceEditor = new ResourceEditor(); diff --git a/spring-beans/src/main/java/org/springframework/beans/support/ResourceEditorRegistrar.java b/spring-beans/src/main/java/org/springframework/beans/support/ResourceEditorRegistrar.java index 06d2ee5d..8cffcc78 100644 --- a/spring-beans/src/main/java/org/springframework/beans/support/ResourceEditorRegistrar.java +++ b/spring-beans/src/main/java/org/springframework/beans/support/ResourceEditorRegistrar.java @@ -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. @@ -33,6 +33,7 @@ import org.springframework.beans.propertyeditors.ClassEditor; import org.springframework.beans.propertyeditors.FileEditor; import org.springframework.beans.propertyeditors.InputSourceEditor; import org.springframework.beans.propertyeditors.InputStreamEditor; +import org.springframework.beans.propertyeditors.PathEditor; import org.springframework.beans.propertyeditors.ReaderEditor; import org.springframework.beans.propertyeditors.URIEditor; import org.springframework.beans.propertyeditors.URLEditor; @@ -43,6 +44,7 @@ import org.springframework.core.io.ResourceEditor; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.ResourceArrayPropertyEditor; import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.util.ClassUtils; /** * PropertyEditorRegistrar implementation that populates a given @@ -58,6 +60,19 @@ import org.springframework.core.io.support.ResourcePatternResolver; */ public class ResourceEditorRegistrar implements PropertyEditorRegistrar { + private static Class<?> pathClass; + + static { + try { + pathClass = ClassUtils.forName("java.nio.file.Path", ResourceEditorRegistrar.class.getClassLoader()); + } + catch (ClassNotFoundException ex) { + // Java 7 Path class not available + pathClass = null; + } + } + + private final PropertyResolver propertyResolver; private final ResourceLoader resourceLoader; @@ -103,6 +118,9 @@ public class ResourceEditorRegistrar implements PropertyEditorRegistrar { doRegisterEditor(registry, InputStream.class, new InputStreamEditor(baseEditor)); doRegisterEditor(registry, InputSource.class, new InputSourceEditor(baseEditor)); doRegisterEditor(registry, File.class, new FileEditor(baseEditor)); + if (pathClass != null) { + doRegisterEditor(registry, pathClass, new PathEditor(baseEditor)); + } doRegisterEditor(registry, Reader.class, new ReaderEditor(baseEditor)); doRegisterEditor(registry, URL.class, new URLEditor(baseEditor)); |