io.smallrye.beanbag.BeanBag Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of smallrye-beanbag Show documentation
Show all versions of smallrye-beanbag Show documentation
A trivial programmatic bean container implementation
The newest version!
package io.smallrye.beanbag;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedDeque;
import io.smallrye.common.constraint.Assert;
/**
* A basic bean container.
*/
public final class BeanBag {
private final Scope singletonScope;
private final ScopeDefinition scopeDefinition;
BeanBag(Builder builder) {
final List> definitions = new ArrayList<>();
final List> singletonBeans = new ArrayList<>();
for (BeanBuilder> beanBuilder : builder.beanBuilders) {
addDefinitionsTo(beanBuilder, beanBuilder.singleton ? singletonBeans : definitions);
}
// create a copy of the non-singleton scope so singletons can inject from there
final ScopeDefinition scopeDefinition = new ScopeDefinition(List.copyOf(definitions));
singletonScope = new Scope(this, null, scopeDefinition, new ScopeDefinition(singletonBeans));
this.scopeDefinition = scopeDefinition;
}
private void addDefinitionsTo(final BeanBuilder beanBuilder, List> definitions) {
final String name = beanBuilder.name;
final Set aliases = beanBuilder.aliases;
final Set> restrictedTypes = Set
.copyOf(Objects.requireNonNullElse(beanBuilder.restrictedTypes, List.of()));
final BeanSupplier supplier = beanBuilder.supplier;
final int priority = beanBuilder.priority;
final Class type = beanBuilder.type;
BeanDefinition definition = new BeanDefinition<>(name, priority, type, restrictedTypes, supplier);
definitions.add(definition);
if (aliases != null) {
for (String alias : aliases) {
definitions.add(
new BeanDefinition<>(alias, priority, type, restrictedTypes, scope -> scope.requireBean(definition)));
}
}
}
/**
* Create a new resolution scope.
* A resolution scope maintains independent instances of its beans.
*
* @return the new resolution scope (not {@code null})
*/
public Scope newScope() {
return new Scope(this, singletonScope, null, scopeDefinition);
}
/**
* Get all constructable beans of the given type from a new resolution scope.
*
* @param type the allowed bean type class (must not be {@code null})
* @return the (possibly empty) list of all matching beans
* @param the allowed bean type
*/
public List getAllBeans(final Class type) {
return newScope().getAllBeans(type);
}
/**
* Require a single bean with the given type from a new resolution scope.
*
* @param type the allowed bean type class (must not be {@code null})
* @return the single bean (not {@code null})
* @param the allowed bean type
* @throws NoSuchBeanException if the bean is not present
* @throws BeanInstantiationException if some error occurred when instantiating the bean
*/
public T requireBean(Class type) {
return newScope().requireBean(type);
}
/**
* Require a single bean with the given type and name from a new resolution scope.
*
* @param type the allowed bean type class (must not be {@code null})
* @param name the name of the bean which should be returned, or {@code ""} for any (must not be {@code null})
* @return the single bean (not {@code null})
* @param the allowed bean type
* @throws NoSuchBeanException if the bean is not present
* @throws BeanInstantiationException if some error occurred when instantiating the bean
*/
public T requireBean(Class type, String name) {
return newScope().requireBean(type, name);
}
/**
* Get a single bean with the given type from a new resolution scope, if it exists and can be instantiated.
*
* @param type the allowed bean type class (must not be {@code null})
* @return the single bean, or {@code null} if it is not present
* @param the allowed bean type
*/
public T getOptionalBean(Class type) {
return newScope().getOptionalBean(type);
}
/**
* Get a single bean with the given type and name from a new resolution scope, if it exists and can be instantiated.
*
* @param type the allowed bean type class (must not be {@code null})
* @param name the name of the bean which should be returned, or {@code ""} for any (must not be {@code null})
* @return the single bean, or {@code null} if it is not present
* @param the allowed bean type
*/
public T getOptionalBean(Class type, String name) {
return newScope().getOptionalBean(type, name);
}
/**
* Construct a new container builder.
*
* @return the new builder (not {@code null})
*/
public static Builder builder() {
return new Builder();
}
/**
* A builder for a new container.
*/
public static final class Builder {
/**
* Java package names that should be excluded during bean discovery
*/
private List excludePackages = List.of();
/**
* Java package names that should be included during bean discovery,
* unless they also match {@link #excludePackages}
*/
private List includePackages = List.of();
/**
* Bean builders may be added concurrently
*/
private final Collection> beanBuilders = new ConcurrentLinkedDeque<>();
Builder() {
}
/**
* Exclude beans whose Java packages start with the value of the argument.
*
* @param packageName Java package name to exclude during bean discovery
* @return this builder instance
*/
public Builder excludePackage(String packageName) {
Assert.checkNotNullParam("packageName", packageName);
if (excludePackages.isEmpty()) {
excludePackages = new ArrayList<>();
}
excludePackages.add(packageName);
return this;
}
/**
* Include beans whose Java packages start with the value of the argument,
* unless there is a matching exclude package filter.
* If inclusions weren't configured, all packages are assumed to be included.
*
* @param packageName Java package name to include during bean discovery
* @return this builder instance
*/
public Builder includePackage(String packageName) {
Assert.checkNotNullParam("packageName", packageName);
if (includePackages.isEmpty()) {
includePackages = new ArrayList<>();
}
includePackages.add(packageName);
return this;
}
public boolean isTypeFilteredOut(String type) {
return isPackageExcluded(type) || !isPackageIncluded(type);
}
private boolean isPackageIncluded(String type) {
if (includePackages.isEmpty()) {
return true;
}
for (var filter : includePackages) {
if (type.startsWith(filter)) {
return true;
}
}
return false;
}
private boolean isPackageExcluded(String type) {
for (var filter : excludePackages) {
if (type.startsWith(filter)) {
return true;
}
}
return false;
}
/**
* Add a new bean with the given type, returning a builder to configure it.
* The given type must be the concrete type of the bean or a class representing a supertype of that concrete
* type.
*
* @param type the bean type class (must not be {@code null})
* @return the bean builder (not {@code null})
* @param the bean type
*/
public BeanBuilder addBean(final Class type) {
Assert.checkNotNullParam("type", type);
return new BeanBuilder(this, type);
}
/**
* Add a new bean which resolves to the given instance.
*
* @param bean the bean instance (must not be {@code null})
* @return this builder (not {@code null})
* @param the bean type
*/
@SuppressWarnings("unchecked")
public Builder addBeanInstance(final T bean) {
Assert.checkNotNullParam("bean", bean);
return addBean((Class) bean.getClass()).setInstance(bean).build();
}
/**
* Build a new container instance with the beans that were previously configured in this builder.
*
* @return the new container (not {@code null})
*/
public BeanBag build() {
return new BeanBag(this);
}
}
/**
* A builder for an individual bean's configuration.
*
* @param the bean type
*/
public static final class BeanBuilder {
private final Builder builder;
private final Class type;
private int priority = 0;
private List> restrictedTypes;
private String name = "";
private Set aliases;
private BeanSupplier supplier;
private boolean singleton;
BeanBuilder(final Builder builder, final Class type) {
this.builder = builder;
this.type = type;
}
/**
* Set the bean priority. Higher numbers have higher precedence.
* Users should normally configure beans with a priority of {@code 0} or higher.
*
* @param priority the bean priority
* @return this builder (not {@code null})
*/
public BeanBuilder setPriority(final int priority) {
this.priority = priority;
return this;
}
/**
* Set the bean name. Beans with no name have a name of the empty string {@code ""}.
*
* @param name the bean name (must not be {@code null})
* @return this builder (not {@code null})
*/
public BeanBuilder setName(final String name) {
Assert.checkNotNullParam("name", name);
this.name = name;
return this;
}
/**
* Add another name that this bean can be identified by.
*
* @param alias the bean alias (must not be {@code null})
* @return this builder (not {@code null})
*/
public BeanBuilder addAlias(final String alias) {
Assert.checkNotNullParam("alias", alias);
if (aliases == null) {
aliases = new HashSet<>();
}
aliases.add(alias);
return this;
}
/**
* Set the supplier for this bean.
* Setting a supplier will overwrite a supplier created via {@link #buildSupplier()} (if any).
*
* @param supplier the supplier instance (must not be {@code null})
* @return this builder (not {@code null})
*/
public BeanBuilder setSupplier(final BeanSupplier supplier) {
Assert.checkNotNullParam("supplier", supplier);
this.supplier = supplier;
return this;
}
/**
* Set the supplier for this bean to a literal instance.
* Setting a supplier will overwrite a supplier created via {@link #buildSupplier()} (if any).
* The bean will be marked as a singleton.
*
* @param instance the bean instance (must not be {@code null})
* @return this builder (not {@code null})
*/
public BeanBuilder setInstance(final T instance) {
Assert.checkNotNullParam("instance", instance);
this.supplier = scope -> instance;
singleton = true;
return this;
}
/**
* Construct a reflective supplier for this bean.
* Completing this builder will overwrite the supplier created via {@link #setSupplier(BeanSupplier)} (if any).
*
* @return a new supplier builder (not {@code null})
*/
public SupplierBuilder buildSupplier() {
return new SupplierBuilder<>(this);
}
/**
* Set the singleton flag for this bean.
* A singleton is created in a scope which is global to a single container.
*
* @param singleton the value of the singleton flag
* @return this builder (not {@code null})
*/
public BeanBuilder setSingleton(final boolean singleton) {
this.singleton = singleton;
return this;
}
/**
* Restrict the types of this bean.
* The bean will only be able to be looked up using one of these types.
*
* @param types the restricted types (must not be {@code null})
* @return this builder (not {@code null})
*/
public BeanBuilder addRestrictedTypes(Collection> types) {
Assert.checkNotNullParam("types", types);
if (restrictedTypes != null) {
restrictedTypes.addAll(types);
} else {
restrictedTypes = new ArrayList<>(types);
}
return this;
}
/**
* Commit this bean definition into the enclosing container builder.
*
* @return the container builder (not {@code null})
*/
public Builder build() {
builder.beanBuilders.add(this);
return builder;
}
}
/**
* A builder for a bean supplier which constructs a bean using reflection.
*
* @param the bean type
*/
public static final class SupplierBuilder {
private final BeanBuilder beanBuilder;
private final List> argumentSuppliers = new ArrayList<>();
private Constructor constructor;
private List> injectors;
SupplierBuilder(final BeanBuilder beanBuilder) {
this.beanBuilder = beanBuilder;
}
/**
* Set the constructor to use to instantiate the bean.
*
* @param constructor the constructor (must not be {@code null})
* @return this builder (not {@code null})
*/
public SupplierBuilder setConstructor(final Constructor constructor) {
Assert.checkNotNullParam("constructor", constructor);
this.constructor = constructor;
return this;
}
private List> getInjectorList() {
final List> injectors = this.injectors;
if (injectors == null) {
return this.injectors = new ArrayList<>();
}
return injectors;
}
/**
* Add a general field injection.
*
* @param field the field to inject into (must not be {@code null})
* @param supplier the supplier of the field's value (must not be {@code null})
* @return this builder (not {@code null})
*/
public SupplierBuilder injectField(Field field, BeanSupplier> supplier) {
getInjectorList().add(Injector.forField(Assert.checkNotNullParam("field", field),
Assert.checkNotNullParam("supplier", supplier)));
return this;
}
/**
* Add a bean dependency field injection.
*
* @param field the field to inject into (must not be {@code null})
* @param injectType the bean type to inject (must not be {@code null})
* @param beanName the bean name to inject or {@code ""} for any (must not be {@code null})
* @param optional {@code true} to allow {@code null} to be injected, or {@code false} otherwise
* @param filter the filter to apply to determine whether a given bean should be included (must not be {@code null})
* @return this builder (not {@code null})
*/
public SupplierBuilder injectField(Field field, Class> injectType, String beanName, boolean optional,
DependencyFilter filter) {
return injectField(
field,
BeanSupplier.resolving(
injectType,
beanName,
optional,
filter));
}
/**
* Add a bean dependency field injection.
* The type of the bean is derived from the field's type.
*
* @param field the field to inject into (must not be {@code null})
* @return this builder (not {@code null})
*/
public SupplierBuilder injectField(Field field) {
return injectField(field, "");
}
/**
* Add a bean dependency field injection.
* The type of the bean is derived from the field's type.
*
* @param field the field to inject into (must not be {@code null})
* @param beanName the bean name to inject or {@code ""} for any (must not be {@code null})
* @return this builder (not {@code null})
*/
public SupplierBuilder injectField(Field field, String beanName) {
return injectField(field, beanName, false);
}
/**
* Add a bean dependency field injection.
* The type of the bean is derived from the field's type.
*
* @param field the field to inject into (must not be {@code null})
* @param optional {@code true} to allow {@code null} to be injected, or {@code false} otherwise
* @return this builder (not {@code null})
*/
public SupplierBuilder injectField(Field field, boolean optional) {
return injectField(field, "", optional);
}
/**
* Add a bean dependency field injection.
* The type of the bean is derived from the field's type.
*
* @param field the field to inject into (must not be {@code null})
* @param beanName the bean name to inject or {@code ""} for any (must not be {@code null})
* @param optional {@code true} to allow {@code null} to be injected, or {@code false} otherwise
* @return this builder (not {@code null})
*/
public SupplierBuilder injectField(Field field, String beanName, boolean optional) {
return injectField(field, beanName, optional, DependencyFilter.ACCEPT);
}
/**
* Add a bean dependency field injection.
* The type of the bean is derived from the field's type.
*
* @param field the field to inject into (must not be {@code null})
* @param beanName the bean name to inject or {@code ""} for any (must not be {@code null})
* @param optional {@code true} to allow {@code null} to be injected, or {@code false} otherwise
* @param filter the filter to apply to determine whether a given bean should be included (must not be {@code null})
* @return this builder (not {@code null})
*/
public SupplierBuilder injectField(Field field, String beanName, boolean optional, DependencyFilter filter) {
return injectField(Assert.checkNotNullParam("field", field), field.getType(), beanName, optional, filter);
}
/**
* Add a general method injection.
*
* @param method the method to inject into (must not be {@code null})
* @param supplier the supplier of the method's value (must not be {@code null})
* @return this builder (not {@code null})
*/
public SupplierBuilder injectMethod(Method method, BeanSupplier> supplier) {
getInjectorList().add(Injector.forSetterMethod(Assert.checkNotNullParam("method", method), supplier));
return this;
}
/**
* Add a bean dependency method injection.
*
* @param method the method to inject into (must not be {@code null})
* @param injectType the bean type to inject (must not be {@code null})
* @param beanName the bean name to inject or {@code ""} for any (must not be {@code null})
* @param optional {@code true} to allow {@code null} to be injected, or {@code false} otherwise
* @param filter the filter to apply to determine whether a given bean should be included (must not be {@code null})
* @return this builder (not {@code null})
*/
public SupplierBuilder injectMethod(Method method, Class> injectType, String beanName, boolean optional,
DependencyFilter filter) {
return injectMethod(
method,
BeanSupplier.resolving(
injectType,
beanName,
optional,
filter));
}
/**
* Add a bean dependency method injection.
* The type of the bean is derived from the method's sole argument type.
*
* @param method the method to inject into (must not be {@code null})
* @return this builder (not {@code null})
*/
public SupplierBuilder injectMethod(Method method) {
return injectMethod(method, "");
}
/**
* Add a bean dependency method injection.
* The type of the bean is derived from the method's sole argument type.
*
* @param method the method to inject into (must not be {@code null})
* @param beanName the bean name to inject or {@code ""} for any (must not be {@code null})
* @return this builder (not {@code null})
*/
public SupplierBuilder injectMethod(Method method, String beanName) {
return injectMethod(method, beanName, false);
}
/**
* Add a bean dependency method injection.
* The type of the bean is derived from the method's sole argument type.
*
* @param method the method to inject into (must not be {@code null})
* @param optional {@code true} to allow {@code null} to be injected, or {@code false} otherwise
* @return this builder (not {@code null})
*/
public SupplierBuilder injectMethod(Method method, boolean optional) {
return injectMethod(method, "", optional);
}
/**
* Add a bean dependency method injection.
* The type of the bean is derived from the method's sole argument type.
*
* @param method the method to inject into (must not be {@code null})
* @param beanName the bean name to inject or {@code ""} for any (must not be {@code null})
* @param optional {@code true} to allow {@code null} to be injected, or {@code false} otherwise
* @return this builder (not {@code null})
*/
public SupplierBuilder injectMethod(Method method, String beanName, boolean optional) {
return injectMethod(Assert.checkNotNullParam("method", method), beanName, optional, DependencyFilter.ACCEPT);
}
/**
* Add a bean dependency method injection.
* The type of the bean is derived from the method's sole argument type.
*
* @param method the method to inject into (must not be {@code null})
* @param beanName the bean name to inject or {@code ""} for any (must not be {@code null})
* @param optional {@code true} to allow {@code null} to be injected, or {@code false} otherwise
* @param filter the filter to apply to determine whether a given bean should be included (must not be {@code null})
* @return this builder (not {@code null})
*/
public SupplierBuilder injectMethod(Method method, String beanName, boolean optional, DependencyFilter filter) {
return injectMethod(Assert.checkNotNullParam("method", method), method.getParameterTypes()[0], beanName, optional,
filter);
}
/**
* Add a general constructor argument injection.
*
* @param supplier the supplier of the argument's value (must not be {@code null})
* @return this builder (not {@code null})
*/
public SupplierBuilder addConstructorArgument(BeanSupplier> supplier) {
argumentSuppliers.add(Assert.checkNotNullParam("supplier", supplier));
return this;
}
/**
* Add a bean dependency constructor argument injection.
*
* @param injectType the bean type to inject (must not be {@code null})
* @return this builder (not {@code null})
*/
public SupplierBuilder addConstructorArgument(Class> injectType) {
return addConstructorArgument(injectType, false);
}
/**
* Add a bean dependency constructor argument injection.
*
* @param injectType the bean type to inject (must not be {@code null})
* @param optional {@code true} to allow {@code null} to be injected, or {@code false} otherwise
* @return this builder (not {@code null})
*/
public SupplierBuilder addConstructorArgument(Class> injectType, boolean optional) {
return addConstructorArgument(injectType, "", optional);
}
/**
* Add a bean dependency constructor argument injection.
*
* @param injectType the bean type to inject (must not be {@code null})
* @param beanName the bean name to inject or {@code ""} for any (must not be {@code null})
* @return this builder (not {@code null})
*/
public SupplierBuilder addConstructorArgument(Class> injectType, String beanName) {
return addConstructorArgument(injectType, beanName, false);
}
/**
* Add a bean dependency constructor argument injection.
*
* @param injectType the bean type to inject (must not be {@code null})
* @param beanName the bean name to inject or {@code ""} for any (must not be {@code null})
* @param optional {@code true} to allow {@code null} to be injected, or {@code false} otherwise
* @return this builder (not {@code null})
*/
public SupplierBuilder addConstructorArgument(Class> injectType, String beanName, boolean optional) {
return addConstructorArgument(injectType, beanName, optional, DependencyFilter.ACCEPT);
}
/**
* Add a bean dependency constructor argument injection.
*
* @param injectType the bean type to inject (must not be {@code null})
* @param beanName the bean name to inject or {@code ""} for any (must not be {@code null})
* @param optional {@code true} to allow {@code null} to be injected, or {@code false} otherwise
* @param filter the filter to apply to determine whether a given bean should be included (must not be {@code null})
* @return this builder (not {@code null})
*/
public SupplierBuilder addConstructorArgument(Class> injectType, String beanName, boolean optional,
DependencyFilter filter) {
return addConstructorArgument(
BeanSupplier.resolving(
injectType,
beanName,
optional,
filter));
}
/**
* Commit this supplier definition into the enclosing bean builder.
* Any supplier previously set on the bean builder is overwritten.
*
* @return the enclosing bean builder (not {@code null})
*/
public BeanBuilder build() {
BeanSupplier supplier = new ConstructorSupplier<>(Assert.checkNotNullParam("constructor", constructor),
argumentSuppliers);
final List> injectors = this.injectors;
if (injectors != null) {
supplier = new InjectingSupplier<>(supplier, List.copyOf(injectors));
}
beanBuilder.setSupplier(supplier);
return beanBuilder;
}
}
}