Skip to content

Commit 1047e1f

Browse files
committed
Declare complete set of default methods on ObjectProvider
Closes gh-33070
1 parent 4cbaaa3 commit 1047e1f

File tree

3 files changed

+182
-12
lines changed

3 files changed

+182
-12
lines changed

Diff for: spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java

+55-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,16 +22,27 @@
2222
import java.util.stream.Stream;
2323

2424
import org.springframework.beans.BeansException;
25+
import org.springframework.core.OrderComparator;
2526
import org.springframework.lang.Nullable;
2627

2728
/**
2829
* A variant of {@link ObjectFactory} designed specifically for injection points,
2930
* allowing for programmatic optionality and lenient not-unique handling.
3031
*
32+
* <p>In a {@link BeanFactory} environment, every {@code ObjectProvider} obtained
33+
* from the factory will be bound to its {@code BeanFactory} for a specific bean
34+
* type, matching all provider calls against factory-registered bean definitions.
35+
*
3136
* <p>As of 5.1, this interface extends {@link Iterable} and provides {@link Stream}
3237
* support. It can be therefore be used in {@code for} loops, provides {@link #forEach}
3338
* iteration and allows for collection-style {@link #stream} access.
3439
*
40+
* <p>As of 6.2, this interface declares default implementations for all methods.
41+
* This makes it easier to implement in a custom fashion, e.g. for unit tests.
42+
* For typical purposes, implement {@link #stream()} to enable all other methods.
43+
* Alternatively, you may implement the specific methods that your callers expect,
44+
* e.g. just {@link #getObject()} or {@link #getIfAvailable()}.
45+
*
3546
* @author Juergen Hoeller
3647
* @since 4.3
3748
* @param <T> the object type
@@ -40,6 +51,19 @@
4051
*/
4152
public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
4253

54+
@Override
55+
default T getObject() throws BeansException {
56+
Iterator<T> it = iterator();
57+
if (!it.hasNext()) {
58+
throw new NoSuchBeanDefinitionException(Object.class);
59+
}
60+
T result = it.next();
61+
if (it.hasNext()) {
62+
throw new NoUniqueBeanDefinitionException(Object.class, 2, "more than 1 matching bean");
63+
}
64+
return result;
65+
}
66+
4367
/**
4468
* Return an instance (possibly shared or independent) of the object
4569
* managed by this factory.
@@ -50,7 +74,10 @@ public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
5074
* @throws BeansException in case of creation errors
5175
* @see #getObject()
5276
*/
53-
T getObject(Object... args) throws BeansException;
77+
default T getObject(Object... args) throws BeansException {
78+
throw new UnsupportedOperationException("Retrieval with arguments not supported -" +
79+
"for custom ObjectProvider classes, implement getObject(Object...) for your purposes");
80+
}
5481

5582
/**
5683
* Return an instance (possibly shared or independent) of the object
@@ -60,7 +87,17 @@ public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
6087
* @see #getObject()
6188
*/
6289
@Nullable
63-
T getIfAvailable() throws BeansException;
90+
default T getIfAvailable() throws BeansException {
91+
try {
92+
return getObject();
93+
}
94+
catch (NoUniqueBeanDefinitionException ex) {
95+
throw ex;
96+
}
97+
catch (NoSuchBeanDefinitionException ex) {
98+
return null;
99+
}
100+
}
64101

65102
/**
66103
* Return an instance (possibly shared or independent) of the object
@@ -103,7 +140,14 @@ default void ifAvailable(Consumer<T> dependencyConsumer) throws BeansException {
103140
* @see #getObject()
104141
*/
105142
@Nullable
106-
T getIfUnique() throws BeansException;
143+
default T getIfUnique() throws BeansException {
144+
try {
145+
return getObject();
146+
}
147+
catch (NoSuchBeanDefinitionException ex) {
148+
return null;
149+
}
150+
}
107151

108152
/**
109153
* Return an instance (possibly shared or independent) of the object
@@ -157,7 +201,8 @@ default Iterator<T> iterator() {
157201
* @see #orderedStream()
158202
*/
159203
default Stream<T> stream() {
160-
throw new UnsupportedOperationException("Multi element access not supported");
204+
throw new UnsupportedOperationException("Element access not supported - " +
205+
"for custom ObjectProvider classes, implement stream() to enable all other methods");
161206
}
162207

163208
/**
@@ -168,12 +213,16 @@ default Stream<T> stream() {
168213
* and in case of annotation-based configuration also considering the
169214
* {@link org.springframework.core.annotation.Order} annotation,
170215
* analogous to multi-element injection points of list/array type.
216+
* <p>The default method applies an {@link OrderComparator} to the
217+
* {@link #stream()} method. You may override this to apply an
218+
* {@link org.springframework.core.annotation.AnnotationAwareOrderComparator}
219+
* if necessary.
171220
* @since 5.1
172221
* @see #stream()
173222
* @see org.springframework.core.OrderComparator
174223
*/
175224
default Stream<T> orderedStream() {
176-
throw new UnsupportedOperationException("Ordered element access not supported");
225+
return stream().sorted(OrderComparator.INSTANCE);
177226
}
178227

179228
}

Diff for: spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java

+1-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -37,7 +37,6 @@
3737
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
3838
import org.springframework.beans.factory.ObjectProvider;
3939
import org.springframework.beans.factory.SmartFactoryBean;
40-
import org.springframework.core.OrderComparator;
4140
import org.springframework.core.ResolvableType;
4241
import org.springframework.core.annotation.AnnotatedElementUtils;
4342
import org.springframework.lang.Nullable;
@@ -344,10 +343,6 @@ public T getIfUnique() throws BeansException {
344343
public Stream<T> stream() {
345344
return Arrays.stream(getBeanNamesForType(requiredType)).map(name -> (T) getBean(name));
346345
}
347-
@Override
348-
public Stream<T> orderedStream() {
349-
return stream().sorted(OrderComparator.INSTANCE);
350-
}
351346
};
352347
}
353348

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.beans.factory;
18+
19+
import java.util.stream.Stream;
20+
21+
import org.junit.jupiter.api.Test;
22+
23+
import org.springframework.beans.BeansException;
24+
import org.springframework.beans.testfixture.beans.TestBean;
25+
26+
import static org.assertj.core.api.Assertions.assertThat;
27+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
28+
29+
/**
30+
* @author Juergen Hoeller
31+
* @since 6.2
32+
*/
33+
public class CustomObjectProviderTests {
34+
35+
@Test
36+
void getObject() {
37+
TestBean tb1 = new TestBean("tb1");
38+
39+
ObjectProvider<TestBean> provider = new ObjectProvider<>() {
40+
@Override
41+
public TestBean getObject() throws BeansException {
42+
return tb1;
43+
}
44+
};
45+
46+
assertThat(provider.getObject()).isSameAs(tb1);
47+
assertThat(provider.getIfAvailable()).isSameAs(tb1);
48+
assertThat(provider.getIfUnique()).isSameAs(tb1);
49+
}
50+
51+
@Test
52+
void noObject() {
53+
ObjectProvider<TestBean> provider = new ObjectProvider<>() {
54+
@Override
55+
public TestBean getObject() throws BeansException {
56+
throw new NoSuchBeanDefinitionException(Object.class);
57+
}
58+
};
59+
60+
assertThatExceptionOfType(NoSuchBeanDefinitionException.class).isThrownBy(provider::getObject);
61+
assertThat(provider.getIfAvailable()).isNull();
62+
assertThat(provider.getIfUnique()).isNull();
63+
}
64+
65+
@Test
66+
void noUniqueObject() {
67+
ObjectProvider<TestBean> provider = new ObjectProvider<>() {
68+
@Override
69+
public TestBean getObject() throws BeansException {
70+
throw new NoUniqueBeanDefinitionException(Object.class);
71+
}
72+
};
73+
74+
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class).isThrownBy(provider::getObject);
75+
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class).isThrownBy(provider::getIfAvailable);
76+
assertThat(provider.getIfUnique()).isNull();
77+
}
78+
79+
@Test
80+
void emptyStream() {
81+
ObjectProvider<TestBean> provider = new ObjectProvider<>() {
82+
@Override
83+
public Stream<TestBean> stream() {
84+
return Stream.empty();
85+
}
86+
};
87+
88+
assertThatExceptionOfType(NoSuchBeanDefinitionException.class).isThrownBy(provider::getObject);
89+
assertThat(provider.getIfAvailable()).isNull();
90+
assertThat(provider.getIfUnique()).isNull();
91+
}
92+
93+
@Test
94+
void streamWithOneObject() {
95+
TestBean tb1 = new TestBean("tb1");
96+
97+
ObjectProvider<TestBean> provider = new ObjectProvider<>() {
98+
@Override
99+
public Stream<TestBean> stream() {
100+
return Stream.of(tb1);
101+
}
102+
};
103+
104+
assertThat(provider.getObject()).isSameAs(tb1);
105+
assertThat(provider.getIfAvailable()).isSameAs(tb1);
106+
assertThat(provider.getIfUnique()).isSameAs(tb1);
107+
}
108+
109+
@Test
110+
void streamWithTwoObjects() {
111+
TestBean tb1 = new TestBean("tb1");
112+
TestBean tb2 = new TestBean("tb2");
113+
114+
ObjectProvider<TestBean> provider = new ObjectProvider<>() {
115+
@Override
116+
public Stream<TestBean> stream() {
117+
return Stream.of(tb1, tb2);
118+
}
119+
};
120+
121+
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class).isThrownBy(provider::getObject);
122+
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class).isThrownBy(provider::getIfAvailable);
123+
assertThat(provider.getIfUnique()).isNull();
124+
}
125+
126+
}

0 commit comments

Comments
 (0)