diff --git a/spring-beans/src/main/java/org/springframework/beans/BeanMetadataElement.java b/spring-beans/src/main/java/org/springframework/beans/BeanMetadataElement.java index e4436a1d57..7126c64ef2 100644 --- a/spring-beans/src/main/java/org/springframework/beans/BeanMetadataElement.java +++ b/spring-beans/src/main/java/org/springframework/beans/BeanMetadataElement.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2019 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 @@ public interface BeanMetadataElement { * (may be {@code null}). */ @Nullable - Object getSource(); + default Object getSource() { + return null; + } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/AutowiredPropertyMarker.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/AutowiredPropertyMarker.java new file mode 100644 index 0000000000..6da668ebc8 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/AutowiredPropertyMarker.java @@ -0,0 +1,68 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.beans.factory.config; + +import java.io.Serializable; + +import org.springframework.lang.Nullable; + +/** + * Simple marker class for an individually autowired property value, to be added + * to {@link BeanDefinition#getPropertyValues()} for a specific bean property. + * + *

At runtime, this will be replaced with a {@link DependencyDescriptor} + * for the corresponding bean property's write method, eventually to be resolved + * through a {@link AutowireCapableBeanFactory#resolveDependency} step. + * + * @author Juergen Hoeller + * @since 5.2 + * @see AutowireCapableBeanFactory#resolveDependency + * @see BeanDefinition#getPropertyValues() + * @see org.springframework.beans.factory.support.BeanDefinitionBuilder#addAutowiredProperty + */ +public final class AutowiredPropertyMarker implements Serializable { + + /** + * The canonical instance for the autowired marker value. + */ + public static final Object INSTANCE = new AutowiredPropertyMarker(); + + + private AutowiredPropertyMarker() { + } + + private Object readResolve() { + return INSTANCE; + } + + + @Override + public boolean equals(@Nullable Object obj) { + return (this == obj); + } + + @Override + public int hashCode() { + return AutowiredPropertyMarker.class.hashCode(); + } + + @Override + public String toString() { + return "(autowired)"; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/RuntimeBeanReference.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/RuntimeBeanReference.java index caf10c538d..11b4f1ac79 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/RuntimeBeanReference.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/RuntimeBeanReference.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2019 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. @@ -26,12 +26,16 @@ import org.springframework.util.Assert; * @author Rod Johnson * @author Juergen Hoeller * @see BeanDefinition#getPropertyValues() - * @see org.springframework.beans.factory.BeanFactory#getBean + * @see org.springframework.beans.factory.BeanFactory#getBean(String) + * @see org.springframework.beans.factory.BeanFactory#getBean(Class) */ public class RuntimeBeanReference implements BeanReference { private final String beanName; + @Nullable + private final Class beanType; + private final boolean toParent; @Nullable @@ -39,9 +43,7 @@ public class RuntimeBeanReference implements BeanReference { /** - * Create a new RuntimeBeanReference to the given bean name, - * without explicitly marking it as reference to a bean in - * the parent factory. + * Create a new RuntimeBeanReference to the given bean name. * @param beanName name of the target bean */ public RuntimeBeanReference(String beanName) { @@ -50,27 +52,64 @@ public class RuntimeBeanReference implements BeanReference { /** * Create a new RuntimeBeanReference to the given bean name, - * with the option to mark it as reference to a bean in - * the parent factory. + * with the option to mark it as reference to a bean in the parent factory. * @param beanName name of the target bean - * @param toParent whether this is an explicit reference to - * a bean in the parent factory + * @param toParent whether this is an explicit reference to a bean in the + * parent factory */ public RuntimeBeanReference(String beanName, boolean toParent) { Assert.hasText(beanName, "'beanName' must not be empty"); this.beanName = beanName; + this.beanType = null; + this.toParent = toParent; + } + + /** + * Create a new RuntimeBeanReference to a bean of the given type. + * @param beanType type of the target bean + * @since 5.2 + */ + public RuntimeBeanReference(Class beanType) { + this(beanType, false); + } + + /** + * Create a new RuntimeBeanReference to a bean of the given type, + * with the option to mark it as reference to a bean in the parent factory. + * @param beanType type of the target bean + * @param toParent whether this is an explicit reference to a bean in the + * parent factory + * @since 5.2 + */ + public RuntimeBeanReference(Class beanType, boolean toParent) { + Assert.notNull(beanType, "'beanType' must not be empty"); + this.beanName = beanType.getName(); + this.beanType = beanType; this.toParent = toParent; } + /** + * Return the requested bean name, or the fully-qualified type name + * in case of by-type resolution. + * @see #getBeanType() + */ @Override public String getBeanName() { return this.beanName; } /** - * Return whether this is an explicit reference to a bean - * in the parent factory. + * Return the requested bean type if resolution by type is demanded. + * @since 5.2 + */ + @Nullable + public Class getBeanType() { + return this.beanType; + } + + /** + * Return whether this is an explicit reference to a bean in the parent factory. */ public boolean isToParent() { return this.toParent; @@ -100,7 +139,8 @@ public class RuntimeBeanReference implements BeanReference { return false; } RuntimeBeanReference that = (RuntimeBeanReference) other; - return (this.beanName.equals(that.beanName) && this.toParent == that.toParent); + return (this.beanName.equals(that.beanName) && this.beanType == that.beanType && + this.toParent == that.toParent); } @Override diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index beb00f5078..f59ca87481 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -61,6 +61,7 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InjectionPoint; import org.springframework.beans.factory.UnsatisfiedDependencyException; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.beans.factory.config.AutowiredPropertyMarker; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.ConfigurableBeanFactory; @@ -1683,6 +1684,13 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac else { String propertyName = pv.getName(); Object originalValue = pv.getValue(); + if (originalValue == AutowiredPropertyMarker.INSTANCE) { + Method writeMethod = bw.getPropertyDescriptor(propertyName).getWriteMethod(); + if (writeMethod == null) { + throw new IllegalArgumentException("Autowire marker for property without write method: " + pv); + } + originalValue = new DependencyDescriptor(new MethodParameter(writeMethod, 0), true); + } Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue); Object convertedValue = resolvedValue; boolean convertible = bw.isWritableProperty(propertyName) && diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionBuilder.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionBuilder.java index 2b04fb155e..dcd428cb71 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionBuilder.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionBuilder.java @@ -18,6 +18,7 @@ package org.springframework.beans.factory.support; import java.util.function.Supplier; +import org.springframework.beans.factory.config.AutowiredPropertyMarker; import org.springframework.beans.factory.config.BeanDefinitionCustomizer; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.lang.Nullable; @@ -209,7 +210,7 @@ public final class BeanDefinitionBuilder { } /** - * Add the supplied property value under the given name. + * Add the supplied property value under the given property name. */ public BeanDefinitionBuilder addPropertyValue(String name, @Nullable Object value) { this.beanDefinition.getPropertyValues().add(name, value); @@ -226,6 +227,17 @@ public final class BeanDefinitionBuilder { return this; } + /** + * Add an autowired marker for the specified property on the specified bean. + * @param name the name of the property to mark as autowired + * @since 5.2 + * @see AutowiredPropertyMarker + */ + public BeanDefinitionBuilder addAutowiredProperty(String name) { + this.beanDefinition.getPropertyValues().add(name, AutowiredPropertyMarker.INSTANCE); + return this; + } + /** * Set the init method for this definition. */ diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValueResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValueResolver.java index 776a610ea0..abb5040f89 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValueResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanDefinitionValueResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -30,10 +30,13 @@ import org.springframework.beans.BeansException; import org.springframework.beans.TypeConverter; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.config.DependencyDescriptor; +import org.springframework.beans.factory.config.NamedBeanHolder; import org.springframework.beans.factory.config.RuntimeBeanNameReference; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.config.TypedStringValue; @@ -57,7 +60,7 @@ import org.springframework.util.StringUtils; */ class BeanDefinitionValueResolver { - private final AbstractBeanFactory beanFactory; + private final AbstractAutowireCapableBeanFactory beanFactory; private final String beanName; @@ -73,8 +76,8 @@ class BeanDefinitionValueResolver { * @param beanDefinition the BeanDefinition of the bean that we work on * @param typeConverter the TypeConverter to use for resolving TypedStringValues */ - public BeanDefinitionValueResolver( - AbstractBeanFactory beanFactory, String beanName, BeanDefinition beanDefinition, TypeConverter typeConverter) { + public BeanDefinitionValueResolver(AbstractAutowireCapableBeanFactory beanFactory, String beanName, + BeanDefinition beanDefinition, TypeConverter typeConverter) { this.beanFactory = beanFactory; this.beanName = beanName; @@ -130,6 +133,17 @@ class BeanDefinitionValueResolver { ObjectUtils.getIdentityHexString(bd); return resolveInnerBean(argName, innerBeanName, bd); } + else if (value instanceof DependencyDescriptor) { + Set autowiredBeanNames = new LinkedHashSet<>(4); + Object result = this.beanFactory.resolveDependency( + (DependencyDescriptor) value, this.beanName, autowiredBeanNames, this.typeConverter); + for (String autowiredBeanName : autowiredBeanNames) { + if (this.beanFactory.containsBean(autowiredBeanName)) { + this.beanFactory.registerDependentBean(autowiredBeanName, this.beanName); + } + } + return result; + } else if (value instanceof ManagedArray) { // May need to resolve contained runtime references. ManagedArray array = (ManagedArray) value; @@ -281,6 +295,54 @@ class BeanDefinitionValueResolver { return value.resolveTargetType(this.beanFactory.getBeanClassLoader()); } + /** + * Resolve a reference to another bean in the factory. + */ + @Nullable + private Object resolveReference(Object argName, RuntimeBeanReference ref) { + try { + Object bean; + Class beanType = ref.getBeanType(); + if (ref.isToParent()) { + BeanFactory parent = this.beanFactory.getParentBeanFactory(); + if (parent == null) { + throw new BeanCreationException( + this.beanDefinition.getResourceDescription(), this.beanName, + "Cannot resolve reference to bean " + ref + + " in parent factory: no parent factory available"); + } + if (beanType != null) { + bean = parent.getBean(beanType); + } + else { + bean = parent.getBean(String.valueOf(doEvaluate(ref.getBeanName()))); + } + } + else { + String resolvedName; + if (beanType != null) { + NamedBeanHolder namedBean = this.beanFactory.resolveNamedBean(beanType); + bean = namedBean.getBeanInstance(); + resolvedName = namedBean.getBeanName(); + } + else { + resolvedName = String.valueOf(doEvaluate(ref.getBeanName())); + bean = this.beanFactory.getBean(resolvedName); + } + this.beanFactory.registerDependentBean(resolvedName, this.beanName); + } + if (bean instanceof NullBean) { + bean = null; + } + return bean; + } + catch (BeansException ex) { + throw new BeanCreationException( + this.beanDefinition.getResourceDescription(), this.beanName, + "Cannot resolve reference to bean '" + ref.getBeanName() + "' while setting " + argName, ex); + } + } + /** * Resolve an inner bean definition. * @param argName the name of the argument that the inner bean is defined for @@ -345,48 +407,13 @@ class BeanDefinitionValueResolver { return actualInnerBeanName; } - /** - * Resolve a reference to another bean in the factory. - */ - @Nullable - private Object resolveReference(Object argName, RuntimeBeanReference ref) { - try { - Object bean; - String refName = ref.getBeanName(); - refName = String.valueOf(doEvaluate(refName)); - if (ref.isToParent()) { - if (this.beanFactory.getParentBeanFactory() == null) { - throw new BeanCreationException( - this.beanDefinition.getResourceDescription(), this.beanName, - "Can't resolve reference to bean '" + refName + - "' in parent factory: no parent factory available"); - } - bean = this.beanFactory.getParentBeanFactory().getBean(refName); - } - else { - bean = this.beanFactory.getBean(refName); - this.beanFactory.registerDependentBean(refName, this.beanName); - } - if (bean instanceof NullBean) { - bean = null; - } - return bean; - } - catch (BeansException ex) { - throw new BeanCreationException( - this.beanDefinition.getResourceDescription(), this.beanName, - "Cannot resolve reference to bean '" + ref.getBeanName() + "' while setting " + argName, ex); - } - } - /** * For each element in the managed array, resolve reference if necessary. */ private Object resolveManagedArray(Object argName, List ml, Class elementType) { Object resolved = Array.newInstance(elementType, ml.size()); for (int i = 0; i < ml.size(); i++) { - Array.set(resolved, i, - resolveValueIfNecessary(new KeyedArgName(argName, i), ml.get(i))); + Array.set(resolved, i, resolveValueIfNecessary(new KeyedArgName(argName, i), ml.get(i))); } return resolved; } @@ -397,8 +424,7 @@ class BeanDefinitionValueResolver { private List resolveManagedList(Object argName, List ml) { List resolved = new ArrayList<>(ml.size()); for (int i = 0; i < ml.size(); i++) { - resolved.add( - resolveValueIfNecessary(new KeyedArgName(argName, i), ml.get(i))); + resolved.add(resolveValueIfNecessary(new KeyedArgName(argName, i), ml.get(i))); } return resolved; } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java index 28da7ed3c8..e245650110 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java @@ -55,6 +55,7 @@ import org.springframework.beans.PropertyValue; import org.springframework.beans.TypeConverter; import org.springframework.beans.TypeMismatchException; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.beans.factory.config.AutowiredPropertyMarker; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanExpressionContext; import org.springframework.beans.factory.config.BeanExpressionResolver; @@ -575,10 +576,94 @@ public class DefaultListableBeanFactoryTests { RootBeanDefinition bd = new RootBeanDefinition(TestBean.class); bd.setPropertyValues(pvs); lbf.registerBeanDefinition("self", bd); + TestBean self = (TestBean) lbf.getBean("self"); assertThat(self.getSpouse()).isEqualTo(self); } + @Test + public void testReferenceByName() { + MutablePropertyValues pvs = new MutablePropertyValues(); + pvs.add("doctor", new RuntimeBeanReference("doc")); + RootBeanDefinition bd = new RootBeanDefinition(TestBean.class); + bd.setPropertyValues(pvs); + lbf.registerBeanDefinition("self", bd); + lbf.registerBeanDefinition("doc", new RootBeanDefinition(NestedTestBean.class)); + + TestBean self = (TestBean) lbf.getBean("self"); + assertThat(self.getDoctor()).isEqualTo(lbf.getBean("doc")); + } + + @Test + public void testReferenceByType() { + MutablePropertyValues pvs = new MutablePropertyValues(); + pvs.add("doctor", new RuntimeBeanReference(NestedTestBean.class)); + RootBeanDefinition bd = new RootBeanDefinition(TestBean.class); + bd.setPropertyValues(pvs); + lbf.registerBeanDefinition("self", bd); + lbf.registerBeanDefinition("doc", new RootBeanDefinition(NestedTestBean.class)); + + TestBean self = (TestBean) lbf.getBean("self"); + assertThat(self.getDoctor()).isEqualTo(lbf.getBean("doc")); + } + + @Test + public void testReferenceByAutowire() { + MutablePropertyValues pvs = new MutablePropertyValues(); + pvs.add("doctor", AutowiredPropertyMarker.INSTANCE); + RootBeanDefinition bd = new RootBeanDefinition(TestBean.class); + bd.setPropertyValues(pvs); + lbf.registerBeanDefinition("self", bd); + lbf.registerBeanDefinition("doc", new RootBeanDefinition(NestedTestBean.class)); + + TestBean self = (TestBean) lbf.getBean("self"); + assertThat(self.getDoctor()).isEqualTo(lbf.getBean("doc")); + } + + @Test + public void testArrayReferenceByName() { + MutablePropertyValues pvs = new MutablePropertyValues(); + pvs.add("stringArray", new RuntimeBeanReference("string")); + RootBeanDefinition bd = new RootBeanDefinition(TestBean.class); + bd.setPropertyValues(pvs); + lbf.registerBeanDefinition("self", bd); + lbf.registerSingleton("string", "A"); + + TestBean self = (TestBean) lbf.getBean("self"); + assertThat(self.getStringArray()).hasSize(1); + assertThat(self.getStringArray()).contains("A"); + } + + @Test + public void testArrayReferenceByType() { + MutablePropertyValues pvs = new MutablePropertyValues(); + pvs.add("stringArray", new RuntimeBeanReference(String.class)); + RootBeanDefinition bd = new RootBeanDefinition(TestBean.class); + bd.setPropertyValues(pvs); + lbf.registerBeanDefinition("self", bd); + lbf.registerSingleton("string", "A"); + + TestBean self = (TestBean) lbf.getBean("self"); + assertThat(self.getStringArray()).hasSize(1); + assertThat(self.getStringArray()).contains("A"); + } + + @Test + public void testArrayReferenceByAutowire() { + MutablePropertyValues pvs = new MutablePropertyValues(); + pvs.add("stringArray", AutowiredPropertyMarker.INSTANCE); + RootBeanDefinition bd = new RootBeanDefinition(TestBean.class); + bd.setPropertyValues(pvs); + lbf.registerBeanDefinition("self", bd); + lbf.registerSingleton("string1", "A"); + lbf.registerSingleton("string2", "B"); + + TestBean self = (TestBean) lbf.getBean("self"); + assertThat(self.getStringArray()).hasSize(2); + assertThat(self.getStringArray()).contains("A"); + assertThat(self.getStringArray()).contains("B"); + } + @Test public void testPossibleMatches() { MutablePropertyValues pvs = new MutablePropertyValues(); @@ -586,6 +671,7 @@ public class DefaultListableBeanFactoryTests { RootBeanDefinition bd = new RootBeanDefinition(TestBean.class); bd.setPropertyValues(pvs); lbf.registerBeanDefinition("tb", bd); + assertThatExceptionOfType(BeanCreationException.class).as("invalid property").isThrownBy(() -> lbf.getBean("tb")) .withCauseInstanceOf(NotWritablePropertyException.class)