Skip to content

Commit

Permalink
Revisit API for BeanContainer
Browse files Browse the repository at this point in the history
  • Loading branch information
manovotn committed Sep 10, 2023
1 parent 21ec9a7 commit de5cf63
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 126 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public static class TestRecorder {

public void test(BeanContainer beanContainer) {
// This should trigger the warning - Gama was removed
Gama gama = beanContainer.beanInstance(Gama.class);
Gama gama = beanContainer.beanInstanceFactory(Gama.class).create().get();
// Test that fallback was used - no injection was performed
Assertions.assertNull(gama.beanManager);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,73 +1,63 @@
package io.quarkus.arc.runtime;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.util.function.Supplier;

import io.quarkus.arc.ManagedContext;

/**
* Represents a CDI bean container.
* <p/>
* Extensions using this API can also leverage arbitrary methods from running {@link io.quarkus.arc.ArcContainer}
* which can be obtained by invoking a static method {@link io.quarkus.arc.Arc#container()}.
*/
public interface BeanContainer {

/**
* Returns a bean instance for given bean type and qualifiers.
* Resolves a bean instance for given bean type and qualifiers.
* <p/>
* This method follows standard CDI rules meaning that if there are two or more eligible beans, an ambiguous
* dependency exception is thrown.
* Note that the method is allowed to return {@code null} if there is no matching bean which allows
* for fallback implementations.
* Performs standard CDI resolution meaning it either returns a bean instance or throws a corresponding exception
* if the dependency is either unsatisfied or ambiguous.
*
* @param type
* @param qualifiers
* @return a bean instance or {@code null} if no matching bean is found
* @param beanType type of the bean
* @param beanQualifiers bean qualifiers
* @return a bean instance; never {@code null}
*/
default <T> T beanInstance(Class<T> type, Annotation... qualifiers) {
return beanInstanceFactory(type, qualifiers).create().get();
}

/**
* This method is deprecated and will be removed in future versions.
* Use {@link #beanInstance(Class, Annotation...)} instead.
* </p>
* As opposed to {@link #beanInstance(Class, Annotation...)}, this method does <b>NOT</b> follow CDI
* resolution rules and in case of ambiguous resolution performs a choice based on the class type parameter.
*
* @param type
* @param qualifiers
* @return a bean instance or {@code null} if no matching bean is found
*/
@Deprecated
default <T> T instance(Class<T> type, Annotation... qualifiers) {
return instanceFactory(type, qualifiers).create().get();
}
<T> T beanInstance(Class<T> beanType, Annotation... beanQualifiers);

/**
* Returns an instance factory for given bean type and qualifiers.
* <p/>
* This method follows standard CDI rules meaning that if there are two or more beans, an ambiguous dependency
* exception is thrown.
* Note that the factory itself is still allowed to return {@code null} if there is no matching bean which allows
* for fallback implementations.
* This method performs CDI ambiguous dependency resolution and throws and exception if there are two or more beans
* with given type and qualifiers.
* <p/>
* If no matching bean is found, uses a default fallback provided by {@link DefaultInstanceFactory}. This factory
* will attempt to create a non-CDI instance of the given class via no-args constructor.
* <p/>
* If you need custom factory behavior, take a look at {@link #beanInstanceFactory(Supplier, Class, Annotation...)}
*
* @param type
* @param qualifiers
* @param type bean type
* @param qualifiers bean qualifiers
* @return a bean instance factory, never {@code null}
*/
<T> Factory<T> beanInstanceFactory(Class<T> type, Annotation... qualifiers);

/**
* This method is deprecated and will be removed in future versions.
* Use {@link #beanInstanceFactory(Class, Annotation...)} instead.
* </p>
* As opposed to {@link #beanInstanceFactory(Class, Annotation...)}, this method does <b>NOT</b> follow CDI
* resolution rules and in case of ambiguous resolution performs a choice based on the class type parameter.
* Returns an instance factory for given bean type and qualifiers.
* <p/>
* This method performs CDI ambiguous dependency resolution and throws and exception if there are two or more beans
* with given type and qualifiers.
* <p/>
* If no matching bean is found, delegates all calls to the supplied factory fallback.
*
* @param type
* @param qualifiers
* @param fallbackSupplier supplier to delegate to if there is no bean
* @param type bean type
* @param qualifiers bean qualifiers
* @return a bean instance factory, never {@code null}
*/
@Deprecated
<T> Factory<T> instanceFactory(Class<T> type, Annotation... qualifiers);
<T> Factory<T> beanInstanceFactory(Supplier<Factory<T>> fallbackSupplier, Class<T> type,
Annotation... qualifiers);

/**
* <pre>
Expand All @@ -91,13 +81,6 @@ default <T> T instance(Class<T> type, Annotation... qualifiers) {

interface Factory<T> {

Factory<Object> EMPTY = new Factory<Object>() {
@Override
public Instance<Object> create() {
return null;
}
};

/**
*
* @return a bean instance or {@code null} if no matching bean is found
Expand All @@ -119,4 +102,37 @@ interface Instance<T> extends AutoCloseable {
default void close() {
};
}

/**
* A default fallback {@link Factory} implementation used by
* {@link BeanContainer#beanInstanceFactory(Class, Annotation...)}.
* <p/>
* This factory attempts to create instances of given class by calling their no-arg constructor. Any exceptions
* related to lack of such constructor of failure to invoke it are simply re-thrown.
*
* @param <T> represents the type that this factory can create
*/
final class DefaultInstanceFactory<T> implements BeanContainer.Factory<T> {

private final Class<T> type;

DefaultInstanceFactory(Class<T> type) {
this.type = type;
}

@Override
public BeanContainer.Instance<T> create() {
try {
T instance = type.getDeclaredConstructor().newInstance();
return new BeanContainer.Instance<T>() {
@Override
public T get() {
return instance;
}
};
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.quarkus.arc.runtime;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.function.Supplier;

Expand All @@ -21,24 +20,36 @@ class BeanContainerImpl implements BeanContainer {
this.container = container;
}

@Override
public <T> T beanInstance(Class<T> beanType, Annotation... beanQualifiers) {
return container.select(beanType, beanQualifiers).get();
}

@Override
public <T> Factory<T> beanInstanceFactory(Class<T> type, Annotation... qualifiers) {
Supplier<InstanceHandle<T>> handleSupplier = container.beanInstanceSupplier(type, qualifiers);
return createFactory(handleSupplier, type, qualifiers);
return createFactory(handleSupplier, null, type, qualifiers);
}

@Override
public <T> Factory<T> instanceFactory(Class<T> type, Annotation... qualifiers) {
Supplier<InstanceHandle<T>> handleSupplier = container.instanceSupplier(type, qualifiers);
return createFactory(handleSupplier, type, qualifiers);
public <T> Factory<T> beanInstanceFactory(Supplier<Factory<T>> fallbackSupplier, Class<T> type,
Annotation... qualifiers) {
Supplier<InstanceHandle<T>> handleSupplier = container.beanInstanceSupplier(type, qualifiers);
return createFactory(handleSupplier, fallbackSupplier, type, qualifiers);
}

private <T> Factory<T> createFactory(Supplier<InstanceHandle<T>> handleSupplier, Class<T> type, Annotation... qualifiers) {
private <T> Factory<T> createFactory(Supplier<InstanceHandle<T>> handleSupplier, Supplier<Factory<T>> fallbackSupplier,
Class<T> type, Annotation... qualifiers) {
if (handleSupplier == null) {
LOGGER.debugf(
"No matching bean found for type %s and qualifiers %s. The bean might have been marked as unused and removed during build.",
type, Arrays.toString(qualifiers));
return new DefaultInstanceFactory<>(type);
if (fallbackSupplier != null) {
return fallbackSupplier.get();
} else {
// by default, if there is no bean, return factory that tries to instantiate non-cdi object
return new DefaultInstanceFactory<>(type);
}
}
return new Factory<T>() {
@Override
Expand All @@ -64,28 +75,4 @@ public ManagedContext requestContext() {
return container.requestContext();
}

private static final class DefaultInstanceFactory<T> implements BeanContainer.Factory<T> {

private final Class<T> type;

DefaultInstanceFactory(Class<T> type) {
this.type = type;
}

@Override
public BeanContainer.Instance<T> create() {
try {
T instance = type.getDeclaredConstructor().newInstance();
return new BeanContainer.Instance<T>() {
@Override
public T get() {
return instance;
}
};
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -88,34 +88,13 @@ public interface ArcContainer {
/**
* Returns a supplier that can be used to create new instances, or null if no matching bean can be found.
*
* Note that if there are multiple sub classes of the given type this will return the exact match. This means
* that this can be used to directly instantiate superclasses of other beans without causing problems. This behavior differs
* to standard CDI rules where an ambiguous dependency would exist.
*
* see https://github.com/quarkusio/quarkus/issues/3369
*
* @param type
* @param qualifiers
* @param <T>
* @return
*/
<T> Supplier<InstanceHandle<T>> beanInstanceSupplier(Class<T> type, Annotation... qualifiers);

/**
* This method is deprecated and will be removed in future versions.
* Use {@link #beanInstanceSupplier(Class, Annotation...)} instead.
* </p>
* As opposed to {@link #beanInstanceSupplier(Class, Annotation...)}, this method does <b>NOT</b> follow CDI
* resolution rules and in case of ambiguous resolution performs a choice based on the class type parameter.
*
* @param type
* @param qualifiers
* @return
* @param <T>
*/
@Deprecated
<T> Supplier<InstanceHandle<T>> instanceSupplier(Class<T> type, Annotation... qualifiers);

/**
*
* @param bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,16 +291,6 @@ public <X> InstanceHandle<X> instance(Type type, Annotation... qualifiers) {

@Override
public <T> Supplier<InstanceHandle<T>> beanInstanceSupplier(Class<T> type, Annotation... qualifiers) {
return createInstanceSupplier(false, type, qualifiers);
}

@Override
public <T> Supplier<InstanceHandle<T>> instanceSupplier(Class<T> type, Annotation... qualifiers) {
return createInstanceSupplier(true, type, qualifiers);
}

private <T> Supplier<InstanceHandle<T>> createInstanceSupplier(boolean resolveAmbiguities, Class<T> type,
Annotation... qualifiers) {
if (qualifiers == null || qualifiers.length == 0) {
qualifiers = new Annotation[] { Default.Literal.INSTANCE };
}
Expand All @@ -311,20 +301,8 @@ private <T> Supplier<InstanceHandle<T>> createInstanceSupplier(boolean resolveAm
}
Set<InjectableBean<?>> filteredBean = resolvedBeans;
if (resolvedBeans.size() > 1) {
if (resolveAmbiguities) {
// this is non-standard CDI behavior that we momentarily keep to retain compatibility
// if there are multiple beans we look for an exact match
// this method is only called with the exact type required
// so ignoring subclasses is the correct behaviour
filteredBean = new HashSet<>();
for (InjectableBean<?> i : resolvedBeans) {
if (i.getBeanClass().equals(type)) {
filteredBean.add(i);
}
}
} else {
throw new AmbiguousResolutionException("Beans: " + resolvedBeans);
}
throw new AmbiguousResolutionException("Beans: " + resolvedBeans);

}
@SuppressWarnings("unchecked")
InjectableBean<T> bean = filteredBean.size() != 1 ? null
Expand Down

0 comments on commit de5cf63

Please sign in to comment.