Add InstanceSupplier and RegisteredBean support

Add a new `InstanceSupplier` interface that can be used as an
alternative to a regular bean `Supplier` when details about the
bean being supplied are required to instantiate it. The new
interface accepts a `RegisteredBean` instance which provides
access to the bean name, the bean factory creating the bean
and the bean definition.

This interface is primarily designed to allow AOT generated code
to autowire dependencies into the instance.

See gh-28414
This commit is contained in:
Phillip Webb
2022-04-13 17:13:17 -07:00
parent d31eb4c0f1
commit 3209d7f126
6 changed files with 789 additions and 11 deletions

View File

@@ -0,0 +1,91 @@
/*
* Copyright 2002-2022 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.support;
import java.io.IOException;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.util.function.ThrowingSupplier;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link AbstractAutowireCapableBeanFactory} instance supplier
* support.
*
* @author Phillip Webb
*/
public class BeanFactorySupplierTests {
@Test
void getBeanWhenUsingRegularSupplier() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setInstanceSupplier(() -> "I am supplied");
beanFactory.registerBeanDefinition("test", beanDefinition);
assertThat(beanFactory.getBean("test")).isEqualTo("I am supplied");
}
@Test
void getBeanWhenUsingInstanceSupplier() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setInstanceSupplier(InstanceSupplier
.of(registeredBean -> "I am bean " + registeredBean.getBeanName()));
beanFactory.registerBeanDefinition("test", beanDefinition);
assertThat(beanFactory.getBean("test")).isEqualTo("I am bean test");
}
@Test
void getBeanWhenUsingThrowableSupplier() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setInstanceSupplier(ThrowingSupplier.of(() -> "I am supplied"));
beanFactory.registerBeanDefinition("test", beanDefinition);
assertThat(beanFactory.getBean("test")).isEqualTo("I am supplied");
}
@Test
void getBeanWhenUsingThrowableSupplierThatThrowsCheckedException() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setInstanceSupplier(ThrowingSupplier.of(() -> {
throw new IOException("fail");
}));
beanFactory.registerBeanDefinition("test", beanDefinition);
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> beanFactory.getBean("test"))
.withCauseInstanceOf(IOException.class);
}
@Test
void getBeanWhenUsingThrowableSupplierThatThrowsRuntimeException() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setInstanceSupplier(ThrowingSupplier.of(() -> {
throw new IllegalStateException("fail");
}));
beanFactory.registerBeanDefinition("test", beanDefinition);
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> beanFactory.getBean("test"))
.withCauseInstanceOf(IllegalStateException.class);
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2002-2022 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.support;
import org.junit.jupiter.api.Test;
import org.springframework.util.function.ThrowingBiFunction;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link InstanceSupplier}.
*
* @author Phillip Webb
*/
class InstanceSupplierTests {
private final RegisteredBean registeredBean = RegisteredBean
.of(new DefaultListableBeanFactory(), "test");
@Test
void getWithoutRegisteredBeanThrowsException() {
InstanceSupplier<String> supplier = registeredBean -> "test";
assertThatIllegalStateException().isThrownBy(() -> supplier.get())
.withMessage("No RegisteredBean parameter provided");
}
@Test
void getWithExceptionWithoutRegisteredBeanThrowsException() {
InstanceSupplier<String> supplier = registeredBean -> "test";
assertThatIllegalStateException().isThrownBy(() -> supplier.getWithException())
.withMessage("No RegisteredBean parameter provided");
}
@Test
void getReturnsResult() throws Exception {
InstanceSupplier<String> supplier = registeredBean -> "test";
assertThat(supplier.get(this.registeredBean)).isEqualTo("test");
}
@Test
void andThenWithBiFunctionWhenFunctionIsNullThrowsException() {
InstanceSupplier<String> supplier = registeredBean -> "test";
ThrowingBiFunction<RegisteredBean, String, String> after = null;
assertThatIllegalArgumentException().isThrownBy(() -> supplier.andThen(after))
.withMessage("After must not be null");
}
@Test
void andThenWithBiFunctionAppliesFunctionToObtainResult() throws Exception {
InstanceSupplier<String> supplier = registeredBean -> "bean";
supplier = supplier.andThen(
(registeredBean, string) -> registeredBean.getBeanName() + "-" + string);
assertThat(supplier.get(this.registeredBean)).isEqualTo("test-bean");
}
@Test
void ofSupplierWhenInstanceSupplierReturnsSameInstance() {
InstanceSupplier<String> supplier = registeredBean -> "test";
assertThat(InstanceSupplier.of(supplier)).isSameAs(supplier);
}
@Test
void usingSupplierAdaptsToInstanceSupplier() throws Exception {
InstanceSupplier<String> instanceSupplier = InstanceSupplier.using(() -> "test");
assertThat(instanceSupplier.get(this.registeredBean)).isEqualTo("test");
}
@Test
void ofInstanceSupplierAdaptsToInstanceSupplier() throws Exception {
InstanceSupplier<String> instanceSupplier = InstanceSupplier
.of(registeredBean -> "test");
assertThat(instanceSupplier.get(this.registeredBean)).isEqualTo("test");
}
}

View File

@@ -0,0 +1,215 @@
/*
* Copyright 2002-2022 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.support;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link RegisteredBean}.
*
* @author Phillip Webb
* @since 6.0
*/
class RegisteredBeanTests {
private DefaultListableBeanFactory beanFactory;
@BeforeEach
void setup() {
this.beanFactory = new DefaultListableBeanFactory();
this.beanFactory.registerBeanDefinition("bd",
new RootBeanDefinition(TestBean.class));
this.beanFactory.registerSingleton("sb", new TestBean());
}
@Test
void ofWhenBeanFactoryIsNullThrowsException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> RegisteredBean.of(null, "bd"))
.withMessage("BeanFactory must not be null");
}
@Test
void ofWhenBeanNameIsEmptyThrowsException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> RegisteredBean.of(this.beanFactory, null))
.withMessage("BeanName must not be empty");
}
@Test
void ofInnerBeanWhenInnerBeanIsNullThrowsException() {
RegisteredBean parent = RegisteredBean.of(this.beanFactory, "bd");
assertThatIllegalArgumentException().isThrownBy(
() -> RegisteredBean.ofInnerBean(parent, (BeanDefinitionHolder) null))
.withMessage("InnerBean must not be null");
}
@Test
void ofInnerBeanWhenParentIsNullThrowsException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> RegisteredBean.ofInnerBean(null,
new RootBeanDefinition(TestInnerBean.class)))
.withMessage("Parent must not be null");
}
@Test
void ofInnerBeanWhenInnerBeanDefinitionIsNullThrowsException() {
RegisteredBean parent = RegisteredBean.of(this.beanFactory, "bd");
assertThatIllegalArgumentException()
.isThrownBy(() -> RegisteredBean.ofInnerBean(parent, "ib", null))
.withMessage("InnerBeanDefinition must not be null");
}
@Test
void getBeanNameReturnsBeanName() {
RegisteredBean registeredBean = RegisteredBean.of(this.beanFactory, "bd");
assertThat(registeredBean.getBeanName()).isEqualTo("bd");
}
@Test
void getBeanNameWhenNamedInnerBeanReturnsBeanName() {
RegisteredBean parent = RegisteredBean.of(this.beanFactory, "bd");
RegisteredBean registeredBean = RegisteredBean.ofInnerBean(parent, "ib",
new RootBeanDefinition(TestInnerBean.class));
assertThat(registeredBean.getBeanName()).isEqualTo("ib");
}
@Test
void getBeanNameWhenUnnamedInnerBeanReturnsBeanName() {
RegisteredBean parent = RegisteredBean.of(this.beanFactory, "bd");
RegisteredBean registeredBean = RegisteredBean.ofInnerBean(parent,
new RootBeanDefinition(TestInnerBean.class));
assertThat(registeredBean.getBeanName()).startsWith("(inner bean)#");
}
@Test
void getBeanClassReturnsBeanClass() {
RegisteredBean registeredBean = RegisteredBean.of(this.beanFactory, "bd");
assertThat(registeredBean.getBeanClass()).isEqualTo(TestBean.class);
}
@Test
void getBeanClassWhenSingletonReturnsBeanClass() {
RegisteredBean registeredBean = RegisteredBean.of(this.beanFactory, "sb");
assertThat(registeredBean.getBeanClass()).isEqualTo(TestBean.class);
}
@Test
void getBeanTypeReturnsBeanType() {
RegisteredBean registeredBean = RegisteredBean.of(this.beanFactory, "bd");
assertThat(registeredBean.getBeanType().toClass()).isEqualTo(TestBean.class);
}
@Test
void getBeanTypeWhenSingletonReturnsBeanType() {
RegisteredBean registeredBean = RegisteredBean.of(this.beanFactory, "sb");
assertThat(registeredBean.getBeanType().toClass()).isEqualTo(TestBean.class);
}
@Test
void getMergedBeanDefinitionReturnsMergedBeanDefinition() {
RegisteredBean registeredBean = RegisteredBean.of(this.beanFactory, "bd");
assertThat(registeredBean.getMergedBeanDefinition().getBeanClass())
.isEqualTo(TestBean.class);
}
@Test
void getMergedBeanDefinitionWhenSingletonThrowsException() {
RegisteredBean registeredBean = RegisteredBean.of(this.beanFactory, "sb");
assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
.isThrownBy(() -> registeredBean.getMergedBeanDefinition());
}
@Test
void getMergedBeanDefinitionWhenInnerBeanReturnsMergedBeanDefinition() {
RegisteredBean parent = RegisteredBean.of(this.beanFactory, "bd");
RegisteredBean registeredBean = RegisteredBean.ofInnerBean(parent,
new RootBeanDefinition(TestInnerBean.class));
assertThat(registeredBean.getMergedBeanDefinition().getBeanClass())
.isEqualTo(TestInnerBean.class);
}
@Test
void isInnerBeanWhenInnerBeanReturnsTrue() {
RegisteredBean parent = RegisteredBean.of(this.beanFactory, "bd");
RegisteredBean registeredBean = RegisteredBean.ofInnerBean(parent,
new RootBeanDefinition(TestInnerBean.class));
assertThat(registeredBean.isInnerBean()).isTrue();
}
@Test
void isInnerBeanWhenNotInnerBeanReturnsTrue() {
RegisteredBean registeredBean = RegisteredBean.of(this.beanFactory, "bd");
assertThat(registeredBean.isInnerBean()).isFalse();
}
@Test
void getParentWhenInnerBeanReturnsParent() {
RegisteredBean parent = RegisteredBean.of(this.beanFactory, "bd");
RegisteredBean registeredBean = RegisteredBean.ofInnerBean(parent,
new RootBeanDefinition(TestInnerBean.class));
assertThat(registeredBean.getParent()).isSameAs(parent);
}
@Test
void getParentWhenNotInnerBeanReturnsNull() {
RegisteredBean registeredBean = RegisteredBean.of(this.beanFactory, "bd");
assertThat(registeredBean.getParent()).isNull();
}
@Test
void isGeneratedBeanNameWhenInnerBeanWithoutNameReturnsTrue() {
RegisteredBean parent = RegisteredBean.of(this.beanFactory, "bd");
RegisteredBean registeredBean = RegisteredBean.ofInnerBean(parent,
new RootBeanDefinition(TestInnerBean.class));
assertThat(registeredBean.isGeneratedBeanName()).isTrue();
}
@Test
void isGeneratedBeanNameWhenInnerBeanWithNameReturnsFalse() {
RegisteredBean parent = RegisteredBean.of(this.beanFactory, "bd");
RegisteredBean registeredBean = RegisteredBean.ofInnerBean(parent,
new BeanDefinitionHolder(new RootBeanDefinition(TestInnerBean.class),
"test"));
assertThat(registeredBean.isGeneratedBeanName()).isFalse();
}
@Test
void isGeneratedBeanNameWhenNotInnerBeanReturnsFalse() {
RegisteredBean registeredBean = RegisteredBean.of(this.beanFactory, "bd");
assertThat(registeredBean.isGeneratedBeanName()).isFalse();
}
static class TestBean {
}
static class TestInnerBean {
}
}