Skip to content

Commit

Permalink
Declare complete set of default methods on ObjectProvider
Browse files Browse the repository at this point in the history
Closes gh-33070
  • Loading branch information
jhoeller committed Jun 19, 2024
1 parent 4cbaaa3 commit 1047e1f
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -22,16 +22,27 @@
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.
*
* <p>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.
*
* <p>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.
*
* <p>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 <T> the object type
Expand All @@ -40,6 +51,19 @@
*/
public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {

@Override
default T getObject() throws BeansException {
Iterator<T> 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.
Expand All @@ -50,7 +74,10 @@ public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
* @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
Expand All @@ -60,7 +87,17 @@ public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
* @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
Expand Down Expand Up @@ -103,7 +140,14 @@ default void ifAvailable(Consumer<T> dependencyConsumer) throws BeansException {
* @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
Expand Down Expand Up @@ -157,7 +201,8 @@ default Iterator<T> iterator() {
* @see #orderedStream()
*/
default Stream<T> 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");
}

/**
Expand All @@ -168,12 +213,16 @@ default Stream<T> stream() {
* 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.
* <p>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<T> orderedStream() {
throw new UnsupportedOperationException("Ordered element access not supported");
return stream().sorted(OrderComparator.INSTANCE);
}

}
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -37,7 +37,6 @@
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;
Expand Down Expand Up @@ -344,10 +343,6 @@ public T getIfUnique() throws BeansException {
public Stream<T> stream() {
return Arrays.stream(getBeanNamesForType(requiredType)).map(name -> (T) getBean(name));
}
@Override
public Stream<T> orderedStream() {
return stream().sorted(OrderComparator.INSTANCE);
}
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<TestBean> 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<TestBean> 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<TestBean> 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<TestBean> provider = new ObjectProvider<>() {
@Override
public Stream<TestBean> 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<TestBean> provider = new ObjectProvider<>() {
@Override
public Stream<TestBean> 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<TestBean> provider = new ObjectProvider<>() {
@Override
public Stream<TestBean> stream() {
return Stream.of(tb1, tb2);
}
};

assertThatExceptionOfType(NoUniqueBeanDefinitionException.class).isThrownBy(provider::getObject);
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class).isThrownBy(provider::getIfAvailable);
assertThat(provider.getIfUnique()).isNull();
}

}

0 comments on commit 1047e1f

Please sign in to comment.