Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -189,23 +189,52 @@ public BeanInstanceSupplier<T> withShortcut(String... beanNames) {
@Override
public T get(RegisteredBean registeredBean) {
Assert.notNull(registeredBean, "'registeredBean' must not be null");
return get(registeredBean, (AutowiredArguments) null);
}

@Override
public T get(RegisteredBean registeredBean, @Nullable Object @Nullable ... args) {
Assert.notNull(registeredBean, "'registeredBean' must not be null");
return get(registeredBean, (args != null ? AutowiredArguments.of(args) : null));
}

@Override
public boolean supportsExplicitArguments(@Nullable Object @Nullable ... args) {
return (this.generatorWithoutArguments == null && this.lookup.supportsArguments(args));
}

@SuppressWarnings("unchecked")
private T get(RegisteredBean registeredBean, @Nullable AutowiredArguments explicitArguments) {
if (this.generatorWithoutArguments != null) {
Assert.isTrue(explicitArguments == null || explicitArguments.toArray().length == 0,
"Explicit arguments are not supported");
Executable executable = getFactoryMethodForGenerator();
return invokeBeanSupplier(executable, () -> this.generatorWithoutArguments.apply(registeredBean));
}
else if (this.generatorWithArguments != null) {
Executable executable = getFactoryMethodForGenerator();
AutowiredArguments arguments = resolveArguments(registeredBean,
executable != null ? executable : this.lookup.get(registeredBean));
Executable argumentsExecutable = (executable != null ? executable : this.lookup.get(registeredBean));
AutowiredArguments arguments = (explicitArguments != null ? explicitArguments :
resolveArguments(registeredBean, argumentsExecutable));
validateArgumentCount(argumentsExecutable, arguments);
return invokeBeanSupplier(executable, () -> this.generatorWithArguments.apply(registeredBean, arguments));
}
else {
Executable executable = this.lookup.get(registeredBean);
@Nullable Object[] arguments = resolveArguments(registeredBean, executable).toArray();
@Nullable Object[] arguments = (explicitArguments != null ?
explicitArguments.toArray() : resolveArguments(registeredBean, executable).toArray());
validateArgumentCount(executable, AutowiredArguments.of(arguments));
return invokeBeanSupplier(executable, () -> (T) instantiate(registeredBean, executable, arguments));
}
}

private void validateArgumentCount(Executable executable, AutowiredArguments arguments) {
int argumentCount = arguments.toArray().length;
int parameterCount = executable.getParameterCount();
Assert.isTrue(argumentCount == parameterCount,
() -> "Incorrect number of arguments: expected " + parameterCount + " but got " + argumentCount);
}

@Override
public @Nullable Method getFactoryMethod() {
// Cached factory method retrieval for qualifier introspection etc.
Expand Down Expand Up @@ -383,6 +412,25 @@ private static String toCommaSeparatedNames(Class<?>... parameterTypes) {
abstract static class ExecutableLookup {

abstract Executable get(RegisteredBean registeredBean);

abstract Class<?>[] getParameterTypes();

boolean supportsArguments(@Nullable Object @Nullable [] args) {
if (args == null) {
return true;
}
Class<?>[] parameterTypes = getParameterTypes();
if (args.length != parameterTypes.length) {
return false;
}
for (int i = 0; i < args.length; i++) {
@Nullable Object arg = args[i];
if (arg == null || ClassUtils.resolvePrimitiveIfNecessary(parameterTypes[i]) != arg.getClass()) {
return false;
}
}
return true;
}
}


Expand All @@ -409,6 +457,11 @@ public Executable get(RegisteredBean registeredBean) {
}
}

@Override
Class<?>[] getParameterTypes() {
return this.parameterTypes;
}

@Override
public String toString() {
return "Constructor with parameter types [%s]".formatted(toCommaSeparatedNames(this.parameterTypes));
Expand Down Expand Up @@ -451,6 +504,11 @@ Method get() {
return method;
}

@Override
Class<?>[] getParameterTypes() {
return this.parameterTypes;
}

@Override
public String toString() {
return "Factory method '%s' with parameter types [%s] declared on %s".formatted(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1181,11 +1181,9 @@ protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd
"Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
}

if (args == null) {
Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
if (instanceSupplier != null) {
return obtainFromSupplier(instanceSupplier, beanName, mbd);
}
Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
if (instanceSupplier != null && supportsInstanceSupplierWithArguments(instanceSupplier, args)) {
return obtainFromSupplier(instanceSupplier, beanName, mbd, args);
}

if (mbd.getFactoryMethodName() != null) {
Expand Down Expand Up @@ -1229,19 +1227,28 @@ protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd
return instantiateBean(beanName, mbd);
}

private boolean supportsInstanceSupplierWithArguments(
Supplier<?> instanceSupplier, @Nullable Object @Nullable [] args) {

return (args == null || (instanceSupplier instanceof InstanceSupplier<?> supplier &&
supplier.supportsExplicitArguments(args)));
}

/**
* Obtain a bean instance from the given supplier.
* @param supplier the configured supplier
* @param beanName the corresponding bean name
* @return a BeanWrapper for the new instance
*/
private BeanWrapper obtainFromSupplier(Supplier<?> supplier, String beanName, RootBeanDefinition mbd) {
private BeanWrapper obtainFromSupplier(Supplier<?> supplier, String beanName, RootBeanDefinition mbd,
@Nullable Object @Nullable [] args) {

String outerBean = this.currentlyCreatedBean.get();
this.currentlyCreatedBean.set(beanName);
Object instance;

try {
instance = obtainInstanceFromSupplier(supplier, beanName, mbd);
instance = obtainInstanceFromSupplier(supplier, beanName, mbd, args);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException bce && beanName.equals(bce.getBeanName())) {
Expand Down Expand Up @@ -1283,6 +1290,22 @@ private BeanWrapper obtainFromSupplier(Supplier<?> supplier, String beanName, Ro
return supplier.get();
}

/**
* Obtain a bean instance from the given supplier.
* @param supplier the configured supplier
* @param beanName the corresponding bean name
* @param mbd the bean definition for the bean
* @param args explicit arguments passed in programmatically via the getBean method,
* or {@code null} if none
* @return the bean instance (possibly {@code null})
* @since 7.1
*/
protected @Nullable Object obtainInstanceFromSupplier(Supplier<?> supplier, String beanName, RootBeanDefinition mbd,
@Nullable Object @Nullable [] args) throws Exception {

return obtainInstanceFromSupplier(supplier, beanName, mbd);
}

/**
* Overridden in order to implicitly register the currently created bean as
* dependent on further beans getting programmatically retrieved during a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1026,6 +1026,17 @@ protected boolean isBeanEligibleForMetadataCaching(String beanName) {
return super.obtainInstanceFromSupplier(supplier, beanName, mbd);
}

@Override
protected @Nullable Object obtainInstanceFromSupplier(Supplier<?> supplier, String beanName, RootBeanDefinition mbd,
@Nullable Object @Nullable [] args) throws Exception {

if (args != null && supplier instanceof InstanceSupplier<?> instanceSupplier) {
RegisteredBean registeredBean = RegisteredBean.of(this, beanName, mbd);
return instanceSupplier.get(registeredBean, args);
}
return obtainInstanceFromSupplier(supplier, beanName, mbd);
}

@Override
protected void cacheMergedBeanDefinition(RootBeanDefinition mbd, String beanName) {
super.cacheMergedBeanDefinition(mbd, beanName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.jspecify.annotations.Nullable;

import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.function.ThrowingBiFunction;
import org.springframework.util.function.ThrowingSupplier;

Expand Down Expand Up @@ -54,6 +55,37 @@ default T getWithException() {
*/
T get(RegisteredBean registeredBean) throws Exception;

/**
* Get the supplied instance using the specified explicit arguments.
* @param registeredBean the registered bean requesting the instance
* @param args the explicit arguments to use
* @return the supplied instance
* @throws Exception on error
* @since 7.1
* @see #supportsExplicitArguments(Object...)
*/
default T get(RegisteredBean registeredBean, @Nullable Object @Nullable ... args) throws Exception {
if (!ObjectUtils.isEmpty(args)) {
throw new UnsupportedOperationException("Retrieval with arguments not supported - " +
"for custom InstanceSupplier classes, implement get(RegisteredBean, Object...) for your purposes");
}
return get(registeredBean);
}

/**
* Return whether this supplier supports the specified explicit arguments.
* <p>The bean factory calls this method before using
* {@link #get(RegisteredBean, Object...)} for explicit arguments. Custom
* implementations that override {@code get(RegisteredBean, Object...)}
* should return {@code true} for supported argument arrangements.
* @param args the explicit arguments to check
* @return {@code true} if this supplier supports explicit arguments
* @since 7.1
*/
default boolean supportsExplicitArguments(@Nullable Object @Nullable ... args) {
return false;
}

/**
* Return the factory method that this supplier uses to create the
* instance, or {@code null} if it is not known or this supplier uses
Expand Down Expand Up @@ -83,6 +115,14 @@ public V get(RegisteredBean registeredBean) throws Exception {
return after.applyWithException(registeredBean, InstanceSupplier.this.get(registeredBean));
}
@Override
public V get(RegisteredBean registeredBean, @Nullable Object @Nullable ... args) throws Exception {
return after.applyWithException(registeredBean, InstanceSupplier.this.get(registeredBean, args));
}
@Override
public boolean supportsExplicitArguments(@Nullable Object @Nullable ... args) {
return InstanceSupplier.this.supportsExplicitArguments(args);
}
@Override
public @Nullable Method getFactoryMethod() {
return InstanceSupplier.this.getFactoryMethod();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,86 @@ void getWithGeneratorCallsBiFunction() {
assertThat(((AutowiredArguments) result.get(0)).toArray()).containsExactly("1");
}

@Test
void getWithGeneratorAndExplicitArgumentsCallsBiFunction() {
BeanRegistrar registrar = new BeanRegistrar(SingleArgConstructor.class);
RegisteredBean registerBean = registrar.registerBean(this.beanFactory);
List<Object> result = new ArrayList<>();
BeanInstanceSupplier<Object> resolver = BeanInstanceSupplier.forConstructor(String.class)
.withGenerator((registeredBean, args) -> result.add(args));
resolver.get(registerBean, "test");
assertThat(result).hasSize(1);
assertThat(((AutowiredArguments) result.get(0)).toArray()).containsExactly("test");
}

@Test
void getWithGeneratorAndIncorrectExplicitArgumentCountThrowsException() {
BeanRegistrar registrar = new BeanRegistrar(SingleArgConstructor.class);
RegisteredBean registerBean = registrar.registerBean(this.beanFactory);
BeanInstanceSupplier<Object> resolver = BeanInstanceSupplier.forConstructor(String.class)
.withGenerator((registeredBean, args) -> args.get(0));
assertThatIllegalArgumentException()
.isThrownBy(() -> resolver.get(registerBean, "test", "extra"))
.withMessage("Incorrect number of arguments: expected 1 but got 2");
}

@Test
void getWithNoGeneratorAndExplicitArgumentsUsesReflection() {
BeanRegistrar registrar = new BeanRegistrar(SingleArgConstructor.class);
RegisteredBean registerBean = registrar.registerBean(this.beanFactory);
BeanInstanceSupplier<SingleArgConstructor> resolver = BeanInstanceSupplier.forConstructor(String.class);
assertThat(resolver.get(registerBean, "test").getString()).isEqualTo("test");
}

@Test
void supportsExplicitArgumentsReturnsTrue() {
BeanInstanceSupplier<Object> resolver = BeanInstanceSupplier.forConstructor();
assertThat(resolver.supportsExplicitArguments()).isTrue();
}

@Test
void supportsExplicitArgumentsWhenArgumentCountMatchesReturnsTrue() {
BeanInstanceSupplier<Object> resolver = BeanInstanceSupplier.forConstructor(String.class);
assertThat(resolver.supportsExplicitArguments("test")).isTrue();
}

@Test
void supportsExplicitArgumentsWhenArgumentCountDoesNotMatchReturnsFalse() {
BeanInstanceSupplier<Object> resolver = BeanInstanceSupplier.forConstructor(String.class);
assertThat(resolver.supportsExplicitArguments("test", "extra")).isFalse();
}

@Test
void supportsExplicitArgumentsWhenArgumentTypeDoesNotMatchReturnsFalse() {
BeanInstanceSupplier<Object> resolver = BeanInstanceSupplier.forConstructor(String.class);
assertThat(resolver.supportsExplicitArguments(1)).isFalse();
}

@Test
void supportsExplicitArgumentsWhenArgumentTypeIsAssignableButNotExactReturnsFalse() {
BeanInstanceSupplier<Object> resolver = BeanInstanceSupplier.forConstructor(Object.class);
assertThat(resolver.supportsExplicitArguments("test")).isFalse();
}

@Test
void supportsExplicitArgumentsWhenArgumentIsNullReturnsFalse() {
BeanInstanceSupplier<Object> resolver = BeanInstanceSupplier.forConstructor(String.class);
assertThat(resolver.supportsExplicitArguments((Object) null)).isFalse();
}

@Test
void supportsExplicitArgumentsWhenPrimitiveParameterMatchesWrapperReturnsTrue() {
BeanInstanceSupplier<Object> resolver = BeanInstanceSupplier.forConstructor(int.class);
assertThat(resolver.supportsExplicitArguments(1)).isTrue();
}

@Test
void supportsExplicitArgumentsWhenUsingGeneratorWithoutArgumentsReturnsFalse() {
BeanInstanceSupplier<String> resolver = BeanInstanceSupplier.<String>forConstructor()
.withGenerator(registeredBean -> "test");
assertThat(resolver.supportsExplicitArguments()).isFalse();
}

@Test
void getWithGeneratorCallsFunction() {
BeanRegistrar registrar = new BeanRegistrar(SingleArgConstructor.class);
Expand Down
Loading