Ordered stream access on ObjectProvider with strong order guarantees

Issue: SPR-17272
This commit is contained in:
Juergen Hoeller
2018-09-14 23:56:25 +02:00
parent 12240c7524
commit 41d4cb5cbf
6 changed files with 92 additions and 71 deletions

View File

@@ -17,10 +17,8 @@
package org.springframework.beans.factory; package org.springframework.beans.factory;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
@@ -141,17 +139,7 @@ public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
} }
/** /**
* Return a sequential {@link Stream} over lazily resolved object instances, * Return an {@link Iterator} over all matching object instances,
* without specific ordering guarantees (but typically in registration order).
* @since 5.1
* @see #iterator()
*/
default Stream<T> stream() {
throw new UnsupportedOperationException("Multi-element access not supported");
}
/**
* Return an {@link Iterator} over lazily resolved object instances,
* without specific ordering guarantees (but typically in registration order). * without specific ordering guarantees (but typically in registration order).
* @since 5.1 * @since 5.1
* @see #stream() * @see #stream()
@@ -162,17 +150,30 @@ public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
} }
/** /**
* Return a {@link List} with fully resolved object instances, * Return a sequential {@link Stream} over all matching object instances,
* potentially pre-ordered according to a common comparator. * without specific ordering guarantees (but typically in registration order).
* <p>In a common Spring application context, this will be ordered * @since 5.1
* according to {@link org.springframework.core.Ordered} / * @see #iterator()
* {@link org.springframework.core.annotation.Order} conventions, * @see #orderedStream()
*/
default Stream<T> stream() {
throw new UnsupportedOperationException("Multi-element access not supported");
}
/**
* Return a sequential {@link Stream} over all matching object instances,
* pre-ordered according to the factory's common order comparator.
* <p>In a standard Spring application context, this will be ordered
* according to {@link org.springframework.core.Ordered} conventions,
* and in case of annotation-based configuration also considering the
* {@link org.springframework.core.annotation.Order} annotation,
* analogous to multi-element injection points of list/array type. * analogous to multi-element injection points of list/array type.
* @since 5.1 * @since 5.1
* @see #stream() * @see #stream()
* @see org.springframework.core.OrderComparator
*/ */
default List<T> toList() { default Stream<T> orderedStream() {
return stream().collect(Collectors.toList()); throw new UnsupportedOperationException("Multi-element access not supported");
} }
} }

View File

@@ -40,7 +40,6 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import javax.inject.Provider; import javax.inject.Provider;
@@ -388,7 +387,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
.filter(bean -> !(bean instanceof NullBean)); .filter(bean -> !(bean instanceof NullBean));
} }
@Override @Override
public List<T> toList() { public Stream<T> orderedStream() {
String[] beanNames = getBeanNamesForType(requiredType); String[] beanNames = getBeanNamesForType(requiredType);
Map<String, T> matchingBeans = new LinkedHashMap<>(beanNames.length); Map<String, T> matchingBeans = new LinkedHashMap<>(beanNames.length);
for (String beanName : beanNames) { for (String beanName : beanNames) {
@@ -397,12 +396,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
matchingBeans.put(beanName, (T) beanInstance); matchingBeans.put(beanName, (T) beanInstance);
} }
} }
List<T> result = new ArrayList<>(matchingBeans.values()); Stream<T> stream = matchingBeans.values().stream();
Comparator<Object> comparator = adaptDependencyComparator(matchingBeans); return stream.sorted(adaptOrderComparator(matchingBeans));
if (comparator != null) {
result.sort(comparator);
}
return result;
} }
}; };
} }
@@ -1267,16 +1262,13 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
if (autowiredBeanNames != null) { if (autowiredBeanNames != null) {
autowiredBeanNames.addAll(matchingBeans.keySet()); autowiredBeanNames.addAll(matchingBeans.keySet());
} }
Stream<Object> result = matchingBeans.keySet().stream() Stream<Object> stream = matchingBeans.keySet().stream()
.map(name -> descriptor.resolveCandidate(name, type, this)) .map(name -> descriptor.resolveCandidate(name, type, this))
.filter(bean -> !(bean instanceof NullBean)); .filter(bean -> !(bean instanceof NullBean));
if (((StreamDependencyDescriptor) descriptor).isSorted()) { if (((StreamDependencyDescriptor) descriptor).isOrdered()) {
Comparator<Object> comparator = adaptDependencyComparator(matchingBeans); stream = stream.sorted(adaptOrderComparator(matchingBeans));
if (comparator != null) {
result = result.sorted(comparator);
}
} }
return result; return stream;
} }
else if (type.isArray()) { else if (type.isArray()) {
Class<?> componentType = type.getComponentType(); Class<?> componentType = type.getComponentType();
@@ -1375,6 +1367,13 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
} }
} }
private Comparator<Object> adaptOrderComparator(Map<String, ?> matchingBeans) {
Comparator<Object> dependencyComparator = getDependencyComparator();
OrderComparator comparator = (dependencyComparator instanceof OrderComparator ?
(OrderComparator) dependencyComparator : OrderComparator.INSTANCE);
return comparator.withSourceProvider(createFactoryAwareOrderSourceProvider(matchingBeans));
}
private OrderComparator.OrderSourceProvider createFactoryAwareOrderSourceProvider(Map<String, ?> beans) { private OrderComparator.OrderSourceProvider createFactoryAwareOrderSourceProvider(Map<String, ?> beans) {
IdentityHashMap<Object, String> instancesToBeanNames = new IdentityHashMap<>(); IdentityHashMap<Object, String> instancesToBeanNames = new IdentityHashMap<>();
beans.forEach((beanName, instance) -> instancesToBeanNames.put(instance, beanName)); beans.forEach((beanName, instance) -> instancesToBeanNames.put(instance, beanName));
@@ -1779,15 +1778,15 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
*/ */
private static class StreamDependencyDescriptor extends DependencyDescriptor { private static class StreamDependencyDescriptor extends DependencyDescriptor {
private final boolean sorted; private final boolean ordered;
public StreamDependencyDescriptor(DependencyDescriptor original, boolean sorted) { public StreamDependencyDescriptor(DependencyDescriptor original, boolean ordered) {
super(original); super(original);
this.sorted = sorted; this.ordered = ordered;
} }
public boolean isSorted() { public boolean isOrdered() {
return this.sorted; return this.ordered;
} }
} }
@@ -1897,22 +1896,22 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
} }
} }
@SuppressWarnings("unchecked")
@Override @Override
public Stream<Object> stream() { public Stream<Object> stream() {
DependencyDescriptor descriptorToUse = new StreamDependencyDescriptor(this.descriptor, false); return resolveStream(false);
Object result = doResolveDependency(descriptorToUse, this.beanName, null, null); }
Assert.state(result instanceof Stream, "Stream expected");
return (Stream<Object>) result; @Override
public Stream<Object> orderedStream() {
return resolveStream(true);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override private Stream<Object> resolveStream(boolean ordered) {
public List<Object> toList() { DependencyDescriptor descriptorToUse = new StreamDependencyDescriptor(this.descriptor, ordered);
DependencyDescriptor descriptorToUse = new StreamDependencyDescriptor(this.descriptor, true);
Object result = doResolveDependency(descriptorToUse, this.beanName, null, null); Object result = doResolveDependency(descriptorToUse, this.beanName, null, null);
Assert.state(result instanceof Stream, "Stream expected"); Assert.state(result instanceof Stream, "Stream expected");
return ((Stream<Object>) result).collect(Collectors.toList()); return (Stream<Object>) result;
} }
} }

View File

@@ -35,6 +35,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.SmartFactoryBean; import org.springframework.beans.factory.SmartFactoryBean;
import org.springframework.core.OrderComparator;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
@@ -245,6 +246,10 @@ public class StaticListableBeanFactory implements ListableBeanFactory {
public Stream<T> stream() { public Stream<T> stream() {
return Arrays.stream(getBeanNamesForType(requiredType)).map(name -> (T) getBean(name)); return Arrays.stream(getBeanNamesForType(requiredType)).map(name -> (T) getBean(name));
} }
@Override
public Stream<T> orderedStream() {
return stream().sorted(OrderComparator.INSTANCE);
}
}; };
} }

View File

@@ -1289,15 +1289,13 @@ public class AutowiredAnnotationBeanPostProcessorTests {
@Test @Test
public void testObjectProviderInjectionWithTargetPrimary() { public void testObjectProviderInjectionWithTargetPrimary() {
bf.setDependencyComparator(Comparators.comparable());
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectProviderInjectionBean.class)); bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectProviderInjectionBean.class));
RootBeanDefinition tb1 = new RootBeanDefinition(TestBean.class); RootBeanDefinition tb1 = new RootBeanDefinition(OrderedTestBean.class);
tb1.getPropertyValues().add("name", "yours"); tb1.getPropertyValues().add("order", 1);
tb1.setPrimary(true); tb1.setPrimary(true);
bf.registerBeanDefinition("testBean1", tb1); bf.registerBeanDefinition("testBean1", tb1);
RootBeanDefinition tb2 = new RootBeanDefinition(TestBean.class); RootBeanDefinition tb2 = new RootBeanDefinition(OrderedTestBean.class);
tb2.getPropertyValues().add("name", "mine"); tb2.getPropertyValues().add("order", 0);
tb2.setLazyInit(true); tb2.setLazyInit(true);
bf.registerBeanDefinition("testBean2", tb2); bf.registerBeanDefinition("testBean2", tb2);
@@ -2885,7 +2883,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
} }
public List<TestBean> sortedTestBeans() { public List<TestBean> sortedTestBeans() {
return this.testBeanProvider.toList(); return this.testBeanProvider.orderedStream().collect(Collectors.toList());
} }
} }
@@ -2983,6 +2981,21 @@ public class AutowiredAnnotationBeanPostProcessorTests {
} }
public static class OrderedTestBean extends TestBean implements Ordered {
private int order;
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
}
public static class OrderedNestedTestBean extends NestedTestBean implements Ordered { public static class OrderedNestedTestBean extends NestedTestBean implements Ordered {
private int order; private int order;

View File

@@ -43,6 +43,7 @@ import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.TypedStringValue; import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.beans.propertyeditors.CustomNumberEditor; import org.springframework.beans.propertyeditors.CustomNumberEditor;
import org.springframework.core.Ordered;
import org.springframework.core.OverridingClassLoader; import org.springframework.core.OverridingClassLoader;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
@@ -53,7 +54,6 @@ import org.springframework.tests.sample.beans.GenericBean;
import org.springframework.tests.sample.beans.GenericIntegerBean; import org.springframework.tests.sample.beans.GenericIntegerBean;
import org.springframework.tests.sample.beans.GenericSetOfIntegerBean; import org.springframework.tests.sample.beans.GenericSetOfIntegerBean;
import org.springframework.tests.sample.beans.TestBean; import org.springframework.tests.sample.beans.TestBean;
import org.springframework.util.comparator.Comparators;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@@ -847,7 +847,6 @@ public class BeanFactoryGenericsTests {
public void testGenericMatchingWithFullTypeDifferentiation() { public void testGenericMatchingWithFullTypeDifferentiation() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.setAutowireCandidateResolver(new GenericTypeAwareAutowireCandidateResolver()); bf.setAutowireCandidateResolver(new GenericTypeAwareAutowireCandidateResolver());
bf.setDependencyComparator(Comparators.comparable());
bf.registerBeanDefinition("store1", new RootBeanDefinition(DoubleStore.class)); bf.registerBeanDefinition("store1", new RootBeanDefinition(DoubleStore.class));
bf.registerBeanDefinition("store2", new RootBeanDefinition(FloatStore.class)); bf.registerBeanDefinition("store2", new RootBeanDefinition(FloatStore.class));
@@ -907,7 +906,7 @@ public class BeanFactoryGenericsTests {
assertSame(bf.getBean("store1"), resolved.get(0)); assertSame(bf.getBean("store1"), resolved.get(0));
assertSame(bf.getBean("store2"), resolved.get(1)); assertSame(bf.getBean("store2"), resolved.get(1));
resolved = numberStoreProvider.toList(); resolved = numberStoreProvider.orderedStream().collect(Collectors.toList());
assertEquals(2, resolved.size()); assertEquals(2, resolved.size());
assertSame(bf.getBean("store2"), resolved.get(0)); assertSame(bf.getBean("store2"), resolved.get(0));
assertSame(bf.getBean("store1"), resolved.get(1)); assertSame(bf.getBean("store1"), resolved.get(1));
@@ -923,7 +922,7 @@ public class BeanFactoryGenericsTests {
assertEquals(1, resolved.size()); assertEquals(1, resolved.size());
assertTrue(resolved.contains(bf.getBean("store1"))); assertTrue(resolved.contains(bf.getBean("store1")));
resolved = (List) doubleStoreProvider.toList(); resolved = (List) doubleStoreProvider.orderedStream().collect(Collectors.toList());
assertEquals(1, resolved.size()); assertEquals(1, resolved.size());
assertTrue(resolved.contains(bf.getBean("store1"))); assertTrue(resolved.contains(bf.getBean("store1")));
@@ -938,7 +937,7 @@ public class BeanFactoryGenericsTests {
assertEquals(1, resolved.size()); assertEquals(1, resolved.size());
assertTrue(resolved.contains(bf.getBean("store2"))); assertTrue(resolved.contains(bf.getBean("store2")));
resolved = (List) floatStoreProvider.toList(); resolved = (List) floatStoreProvider.orderedStream().collect(Collectors.toList());
assertEquals(1, resolved.size()); assertEquals(1, resolved.size());
assertTrue(resolved.contains(bf.getBean("store2"))); assertTrue(resolved.contains(bf.getBean("store2")));
} }
@@ -1006,20 +1005,25 @@ public class BeanFactoryGenericsTests {
} }
public static class NumberStore<T extends Number> implements Comparable<NumberStore> { public static class NumberStore<T extends Number> {
}
public static class DoubleStore extends NumberStore<Double> implements Ordered {
@Override @Override
public int compareTo(NumberStore other) { public int getOrder() {
return getClass().getName().compareTo(other.getClass().getName()) * -1; return 1;
} }
} }
public static class DoubleStore extends NumberStore<Double> { public static class FloatStore extends NumberStore<Float> implements Ordered {
}
@Override
public static class FloatStore extends NumberStore<Float> { public int getOrder() {
return 0;
}
} }

View File

@@ -59,7 +59,7 @@ public class OrderComparator implements Comparator<Object> {
* @return the adapted comparator * @return the adapted comparator
* @since 4.1 * @since 4.1
*/ */
public Comparator<Object> withSourceProvider(final OrderSourceProvider sourceProvider) { public Comparator<Object> withSourceProvider(OrderSourceProvider sourceProvider) {
return (o1, o2) -> doCompare(o1, o2, sourceProvider); return (o1, o2) -> doCompare(o1, o2, sourceProvider);
} }
@@ -78,10 +78,9 @@ public class OrderComparator implements Comparator<Object> {
return 1; return 1;
} }
// Direct evaluation instead of Integer.compareTo to avoid unnecessary object creation.
int i1 = getOrder(o1, sourceProvider); int i1 = getOrder(o1, sourceProvider);
int i2 = getOrder(o2, sourceProvider); int i2 = getOrder(o2, sourceProvider);
return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0; return Integer.compare(i1, i2);
} }
/** /**