Ordered stream access on ObjectProvider with strong order guarantees
Issue: SPR-17272
This commit is contained in:
parent
12240c7524
commit
41d4cb5cbf
|
|
@ -17,10 +17,8 @@
|
|||
package org.springframework.beans.factory;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
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,
|
||||
* 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,
|
||||
* Return an {@link Iterator} over all matching object instances,
|
||||
* without specific ordering guarantees (but typically in registration order).
|
||||
* @since 5.1
|
||||
* @see #stream()
|
||||
|
|
@ -162,17 +150,30 @@ public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Return a {@link List} with fully resolved object instances,
|
||||
* potentially pre-ordered according to a common comparator.
|
||||
* <p>In a common Spring application context, this will be ordered
|
||||
* according to {@link org.springframework.core.Ordered} /
|
||||
* {@link org.springframework.core.annotation.Order} conventions,
|
||||
* Return a sequential {@link Stream} over all matching object instances,
|
||||
* without specific ordering guarantees (but typically in registration order).
|
||||
* @since 5.1
|
||||
* @see #iterator()
|
||||
* @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.
|
||||
* @since 5.1
|
||||
* @see #stream()
|
||||
* @see org.springframework.core.OrderComparator
|
||||
*/
|
||||
default List<T> toList() {
|
||||
return stream().collect(Collectors.toList());
|
||||
default Stream<T> orderedStream() {
|
||||
throw new UnsupportedOperationException("Multi-element access not supported");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ import java.util.Map;
|
|||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import javax.inject.Provider;
|
||||
|
||||
|
|
@ -388,7 +387,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
|||
.filter(bean -> !(bean instanceof NullBean));
|
||||
}
|
||||
@Override
|
||||
public List<T> toList() {
|
||||
public Stream<T> orderedStream() {
|
||||
String[] beanNames = getBeanNamesForType(requiredType);
|
||||
Map<String, T> matchingBeans = new LinkedHashMap<>(beanNames.length);
|
||||
for (String beanName : beanNames) {
|
||||
|
|
@ -397,12 +396,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
|||
matchingBeans.put(beanName, (T) beanInstance);
|
||||
}
|
||||
}
|
||||
List<T> result = new ArrayList<>(matchingBeans.values());
|
||||
Comparator<Object> comparator = adaptDependencyComparator(matchingBeans);
|
||||
if (comparator != null) {
|
||||
result.sort(comparator);
|
||||
}
|
||||
return result;
|
||||
Stream<T> stream = matchingBeans.values().stream();
|
||||
return stream.sorted(adaptOrderComparator(matchingBeans));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -1267,16 +1262,13 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
|||
if (autowiredBeanNames != null) {
|
||||
autowiredBeanNames.addAll(matchingBeans.keySet());
|
||||
}
|
||||
Stream<Object> result = matchingBeans.keySet().stream()
|
||||
Stream<Object> stream = matchingBeans.keySet().stream()
|
||||
.map(name -> descriptor.resolveCandidate(name, type, this))
|
||||
.filter(bean -> !(bean instanceof NullBean));
|
||||
if (((StreamDependencyDescriptor) descriptor).isSorted()) {
|
||||
Comparator<Object> comparator = adaptDependencyComparator(matchingBeans);
|
||||
if (comparator != null) {
|
||||
result = result.sorted(comparator);
|
||||
}
|
||||
if (((StreamDependencyDescriptor) descriptor).isOrdered()) {
|
||||
stream = stream.sorted(adaptOrderComparator(matchingBeans));
|
||||
}
|
||||
return result;
|
||||
return stream;
|
||||
}
|
||||
else if (type.isArray()) {
|
||||
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) {
|
||||
IdentityHashMap<Object, String> instancesToBeanNames = new IdentityHashMap<>();
|
||||
beans.forEach((beanName, instance) -> instancesToBeanNames.put(instance, beanName));
|
||||
|
|
@ -1779,15 +1778,15 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
|||
*/
|
||||
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);
|
||||
this.sorted = sorted;
|
||||
this.ordered = ordered;
|
||||
}
|
||||
|
||||
public boolean isSorted() {
|
||||
return this.sorted;
|
||||
public boolean isOrdered() {
|
||||
return this.ordered;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1897,22 +1896,22 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Stream<Object> stream() {
|
||||
DependencyDescriptor descriptorToUse = new StreamDependencyDescriptor(this.descriptor, false);
|
||||
Object result = doResolveDependency(descriptorToUse, this.beanName, null, null);
|
||||
Assert.state(result instanceof Stream, "Stream expected");
|
||||
return (Stream<Object>) result;
|
||||
return resolveStream(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Object> orderedStream() {
|
||||
return resolveStream(true);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public List<Object> toList() {
|
||||
DependencyDescriptor descriptorToUse = new StreamDependencyDescriptor(this.descriptor, true);
|
||||
private Stream<Object> resolveStream(boolean ordered) {
|
||||
DependencyDescriptor descriptorToUse = new StreamDependencyDescriptor(this.descriptor, ordered);
|
||||
Object result = doResolveDependency(descriptorToUse, this.beanName, null, null);
|
||||
Assert.state(result instanceof Stream, "Stream expected");
|
||||
return ((Stream<Object>) result).collect(Collectors.toList());
|
||||
return (Stream<Object>) result;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
|||
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.SmartFactoryBean;
|
||||
import org.springframework.core.OrderComparator;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
|
@ -245,6 +246,10 @@ public class StaticListableBeanFactory implements ListableBeanFactory {
|
|||
public Stream<T> stream() {
|
||||
return Arrays.stream(getBeanNamesForType(requiredType)).map(name -> (T) getBean(name));
|
||||
}
|
||||
@Override
|
||||
public Stream<T> orderedStream() {
|
||||
return stream().sorted(OrderComparator.INSTANCE);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1289,15 +1289,13 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
|||
|
||||
@Test
|
||||
public void testObjectProviderInjectionWithTargetPrimary() {
|
||||
bf.setDependencyComparator(Comparators.comparable());
|
||||
|
||||
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectProviderInjectionBean.class));
|
||||
RootBeanDefinition tb1 = new RootBeanDefinition(TestBean.class);
|
||||
tb1.getPropertyValues().add("name", "yours");
|
||||
RootBeanDefinition tb1 = new RootBeanDefinition(OrderedTestBean.class);
|
||||
tb1.getPropertyValues().add("order", 1);
|
||||
tb1.setPrimary(true);
|
||||
bf.registerBeanDefinition("testBean1", tb1);
|
||||
RootBeanDefinition tb2 = new RootBeanDefinition(TestBean.class);
|
||||
tb2.getPropertyValues().add("name", "mine");
|
||||
RootBeanDefinition tb2 = new RootBeanDefinition(OrderedTestBean.class);
|
||||
tb2.getPropertyValues().add("order", 0);
|
||||
tb2.setLazyInit(true);
|
||||
bf.registerBeanDefinition("testBean2", tb2);
|
||||
|
||||
|
|
@ -2885,7 +2883,7 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
|||
}
|
||||
|
||||
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 {
|
||||
|
||||
private int order;
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ import org.springframework.beans.factory.ObjectProvider;
|
|||
import org.springframework.beans.factory.config.TypedStringValue;
|
||||
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
|
||||
import org.springframework.beans.propertyeditors.CustomNumberEditor;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.OverridingClassLoader;
|
||||
import org.springframework.core.ResolvableType;
|
||||
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.GenericSetOfIntegerBean;
|
||||
import org.springframework.tests.sample.beans.TestBean;
|
||||
import org.springframework.util.comparator.Comparators;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
|
|
@ -847,7 +847,6 @@ public class BeanFactoryGenericsTests {
|
|||
public void testGenericMatchingWithFullTypeDifferentiation() {
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
bf.setAutowireCandidateResolver(new GenericTypeAwareAutowireCandidateResolver());
|
||||
bf.setDependencyComparator(Comparators.comparable());
|
||||
|
||||
bf.registerBeanDefinition("store1", new RootBeanDefinition(DoubleStore.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("store2"), resolved.get(1));
|
||||
|
||||
resolved = numberStoreProvider.toList();
|
||||
resolved = numberStoreProvider.orderedStream().collect(Collectors.toList());
|
||||
assertEquals(2, resolved.size());
|
||||
assertSame(bf.getBean("store2"), resolved.get(0));
|
||||
assertSame(bf.getBean("store1"), resolved.get(1));
|
||||
|
|
@ -923,7 +922,7 @@ public class BeanFactoryGenericsTests {
|
|||
assertEquals(1, resolved.size());
|
||||
assertTrue(resolved.contains(bf.getBean("store1")));
|
||||
|
||||
resolved = (List) doubleStoreProvider.toList();
|
||||
resolved = (List) doubleStoreProvider.orderedStream().collect(Collectors.toList());
|
||||
assertEquals(1, resolved.size());
|
||||
assertTrue(resolved.contains(bf.getBean("store1")));
|
||||
|
||||
|
|
@ -938,7 +937,7 @@ public class BeanFactoryGenericsTests {
|
|||
assertEquals(1, resolved.size());
|
||||
assertTrue(resolved.contains(bf.getBean("store2")));
|
||||
|
||||
resolved = (List) floatStoreProvider.toList();
|
||||
resolved = (List) floatStoreProvider.orderedStream().collect(Collectors.toList());
|
||||
assertEquals(1, resolved.size());
|
||||
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
|
||||
public int compareTo(NumberStore other) {
|
||||
return getClass().getName().compareTo(other.getClass().getName()) * -1;
|
||||
public int getOrder() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class DoubleStore extends NumberStore<Double> {
|
||||
}
|
||||
public static class FloatStore extends NumberStore<Float> implements Ordered {
|
||||
|
||||
|
||||
public static class FloatStore extends NumberStore<Float> {
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ public class OrderComparator implements Comparator<Object> {
|
|||
* @return the adapted comparator
|
||||
* @since 4.1
|
||||
*/
|
||||
public Comparator<Object> withSourceProvider(final OrderSourceProvider sourceProvider) {
|
||||
public Comparator<Object> withSourceProvider(OrderSourceProvider sourceProvider) {
|
||||
return (o1, o2) -> doCompare(o1, o2, sourceProvider);
|
||||
}
|
||||
|
||||
|
|
@ -78,10 +78,9 @@ public class OrderComparator implements Comparator<Object> {
|
|||
return 1;
|
||||
}
|
||||
|
||||
// Direct evaluation instead of Integer.compareTo to avoid unnecessary object creation.
|
||||
int i1 = getOrder(o1, sourceProvider);
|
||||
int i2 = getOrder(o2, sourceProvider);
|
||||
return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
|
||||
return Integer.compare(i1, i2);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in New Issue