diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java b/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java
index a9dc61eea42..465162e0ba2 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2024 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.
@@ -22,16 +22,27 @@ import java.util.function.Supplier;
import java.util.stream.Stream;
import org.springframework.beans.BeansException;
+import org.springframework.core.OrderComparator;
import org.springframework.lang.Nullable;
/**
* A variant of {@link ObjectFactory} designed specifically for injection points,
* allowing for programmatic optionality and lenient not-unique handling.
*
+ *
In a {@link BeanFactory} environment, every {@code ObjectProvider} obtained
+ * from the factory will be bound to its {@code BeanFactory} for a specific bean
+ * type, matching all provider calls against factory-registered bean definitions.
+ *
*
As of 5.1, this interface extends {@link Iterable} and provides {@link Stream}
* support. It can be therefore be used in {@code for} loops, provides {@link #forEach}
* iteration and allows for collection-style {@link #stream} access.
*
+ *
As of 6.2, this interface declares default implementations for all methods.
+ * This makes it easier to implement in a custom fashion, e.g. for unit tests.
+ * For typical purposes, implement {@link #stream()} to enable all other methods.
+ * Alternatively, you may implement the specific methods that your callers expect,
+ * e.g. just {@link #getObject()} or {@link #getIfAvailable()}.
+ *
* @author Juergen Hoeller
* @since 4.3
* @param the object type
@@ -40,6 +51,19 @@ import org.springframework.lang.Nullable;
*/
public interface ObjectProvider extends ObjectFactory, Iterable {
+ @Override
+ default T getObject() throws BeansException {
+ Iterator it = iterator();
+ if (!it.hasNext()) {
+ throw new NoSuchBeanDefinitionException(Object.class);
+ }
+ T result = it.next();
+ if (it.hasNext()) {
+ throw new NoUniqueBeanDefinitionException(Object.class, 2, "more than 1 matching bean");
+ }
+ return result;
+ }
+
/**
* Return an instance (possibly shared or independent) of the object
* managed by this factory.
@@ -50,7 +74,10 @@ public interface ObjectProvider extends ObjectFactory, Iterable {
* @throws BeansException in case of creation errors
* @see #getObject()
*/
- T getObject(Object... args) throws BeansException;
+ default T getObject(Object... args) throws BeansException {
+ throw new UnsupportedOperationException("Retrieval with arguments not supported -" +
+ "for custom ObjectProvider classes, implement getObject(Object...) for your purposes");
+ }
/**
* Return an instance (possibly shared or independent) of the object
@@ -60,7 +87,17 @@ public interface ObjectProvider extends ObjectFactory, Iterable {
* @see #getObject()
*/
@Nullable
- T getIfAvailable() throws BeansException;
+ default T getIfAvailable() throws BeansException {
+ try {
+ return getObject();
+ }
+ catch (NoUniqueBeanDefinitionException ex) {
+ throw ex;
+ }
+ catch (NoSuchBeanDefinitionException ex) {
+ return null;
+ }
+ }
/**
* Return an instance (possibly shared or independent) of the object
@@ -103,7 +140,14 @@ public interface ObjectProvider extends ObjectFactory, Iterable {
* @see #getObject()
*/
@Nullable
- T getIfUnique() throws BeansException;
+ default T getIfUnique() throws BeansException {
+ try {
+ return getObject();
+ }
+ catch (NoSuchBeanDefinitionException ex) {
+ return null;
+ }
+ }
/**
* Return an instance (possibly shared or independent) of the object
@@ -157,7 +201,8 @@ public interface ObjectProvider extends ObjectFactory, Iterable {
* @see #orderedStream()
*/
default Stream stream() {
- throw new UnsupportedOperationException("Multi element access not supported");
+ throw new UnsupportedOperationException("Element access not supported - " +
+ "for custom ObjectProvider classes, implement stream() to enable all other methods");
}
/**
@@ -168,12 +213,16 @@ public interface ObjectProvider extends ObjectFactory, Iterable {
* 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.
+ * The default method applies an {@link OrderComparator} to the
+ * {@link #stream()} method. You may override this to apply an
+ * {@link org.springframework.core.annotation.AnnotationAwareOrderComparator}
+ * if necessary.
* @since 5.1
* @see #stream()
* @see org.springframework.core.OrderComparator
*/
default Stream orderedStream() {
- throw new UnsupportedOperationException("Ordered element access not supported");
+ return stream().sorted(OrderComparator.INSTANCE);
}
}
diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java
index e93b7da2e35..c9bfc61de58 100644
--- a/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java
+++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2023 the original author or authors.
+ * Copyright 2002-2024 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.
@@ -37,7 +37,6 @@ 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.AnnotatedElementUtils;
import org.springframework.lang.Nullable;
@@ -344,10 +343,6 @@ public class StaticListableBeanFactory implements ListableBeanFactory {
public Stream stream() {
return Arrays.stream(getBeanNamesForType(requiredType)).map(name -> (T) getBean(name));
}
- @Override
- public Stream orderedStream() {
- return stream().sorted(OrderComparator.INSTANCE);
- }
};
}
diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/CustomObjectProviderTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/CustomObjectProviderTests.java
new file mode 100644
index 00000000000..278753c0bdb
--- /dev/null
+++ b/spring-beans/src/test/java/org/springframework/beans/factory/CustomObjectProviderTests.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2002-2024 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;
+
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.testfixture.beans.TestBean;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+
+/**
+ * @author Juergen Hoeller
+ * @since 6.2
+ */
+public class CustomObjectProviderTests {
+
+ @Test
+ void getObject() {
+ TestBean tb1 = new TestBean("tb1");
+
+ ObjectProvider provider = new ObjectProvider<>() {
+ @Override
+ public TestBean getObject() throws BeansException {
+ return tb1;
+ }
+ };
+
+ assertThat(provider.getObject()).isSameAs(tb1);
+ assertThat(provider.getIfAvailable()).isSameAs(tb1);
+ assertThat(provider.getIfUnique()).isSameAs(tb1);
+ }
+
+ @Test
+ void noObject() {
+ ObjectProvider provider = new ObjectProvider<>() {
+ @Override
+ public TestBean getObject() throws BeansException {
+ throw new NoSuchBeanDefinitionException(Object.class);
+ }
+ };
+
+ assertThatExceptionOfType(NoSuchBeanDefinitionException.class).isThrownBy(provider::getObject);
+ assertThat(provider.getIfAvailable()).isNull();
+ assertThat(provider.getIfUnique()).isNull();
+ }
+
+ @Test
+ void noUniqueObject() {
+ ObjectProvider provider = new ObjectProvider<>() {
+ @Override
+ public TestBean getObject() throws BeansException {
+ throw new NoUniqueBeanDefinitionException(Object.class);
+ }
+ };
+
+ assertThatExceptionOfType(NoUniqueBeanDefinitionException.class).isThrownBy(provider::getObject);
+ assertThatExceptionOfType(NoUniqueBeanDefinitionException.class).isThrownBy(provider::getIfAvailable);
+ assertThat(provider.getIfUnique()).isNull();
+ }
+
+ @Test
+ void emptyStream() {
+ ObjectProvider provider = new ObjectProvider<>() {
+ @Override
+ public Stream stream() {
+ return Stream.empty();
+ }
+ };
+
+ assertThatExceptionOfType(NoSuchBeanDefinitionException.class).isThrownBy(provider::getObject);
+ assertThat(provider.getIfAvailable()).isNull();
+ assertThat(provider.getIfUnique()).isNull();
+ }
+
+ @Test
+ void streamWithOneObject() {
+ TestBean tb1 = new TestBean("tb1");
+
+ ObjectProvider provider = new ObjectProvider<>() {
+ @Override
+ public Stream stream() {
+ return Stream.of(tb1);
+ }
+ };
+
+ assertThat(provider.getObject()).isSameAs(tb1);
+ assertThat(provider.getIfAvailable()).isSameAs(tb1);
+ assertThat(provider.getIfUnique()).isSameAs(tb1);
+ }
+
+ @Test
+ void streamWithTwoObjects() {
+ TestBean tb1 = new TestBean("tb1");
+ TestBean tb2 = new TestBean("tb2");
+
+ ObjectProvider provider = new ObjectProvider<>() {
+ @Override
+ public Stream stream() {
+ return Stream.of(tb1, tb2);
+ }
+ };
+
+ assertThatExceptionOfType(NoUniqueBeanDefinitionException.class).isThrownBy(provider::getObject);
+ assertThatExceptionOfType(NoUniqueBeanDefinitionException.class).isThrownBy(provider::getIfAvailable);
+ assertThat(provider.getIfUnique()).isNull();
+ }
+
+}