Support for fine-grained by-type references and autowired properties

Closes gh-23032
This commit is contained in:
Juergen Hoeller 2019-06-11 18:27:46 +02:00
parent eeb79c8dde
commit 859923b732
7 changed files with 299 additions and 57 deletions

View File

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

View File

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

View File

@ -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

View File

@ -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) &&

View File

@ -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.
*/

View File

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

View File

@ -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)