Skip to content

Commit

Permalink
ArC - document "remove unused beans" optimization
Browse files Browse the repository at this point in the history
- log a warning message when an extension template attempts to use a
removed bean
- resolves #658
  • Loading branch information
mkouba committed Jan 30, 2019
1 parent 23a4bb3 commit 883cf4c
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 24 deletions.
24 changes: 23 additions & 1 deletion docs/src/main/asciidoc/cdi-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ BeanDefiningAnnotationBuildItem additionalBeanDefiningAnnotation() {
}
----

NOTE: Bean registrations that are result of an `BeanDefiningAnnotationBuildItem` are unremovable by default. See also <<remove_unused_beans>>.

=== Resource Annotations

`ResourceAnnotationBuildItem` is used to specify resource annotations that make it possible to resolve non-CDI injection points, such as Java EE resources.
Expand Down Expand Up @@ -168,6 +170,8 @@ List<AdditionalBeanBuildItem> additionalBeans() {
}
----

NOTE: A bean registration that is a result of an `AdditionalBeanBuildItem` is removable by default. See also <<remove_unused_beans>>.

=== Synthetic Beans

Sometimes it's useful to register a synthetic bean, i.e. a bean that doesn't need to have a corresponding java class.
Expand Down Expand Up @@ -222,4 +226,22 @@ BeanDeploymentValidatorBuildItem beanDeploymentValidator() {
}
----

NOTE: See also `org.jboss.protean.arc.processor.BuildExtension.Key` to discover the available metadata.
NOTE: See also `org.jboss.protean.arc.processor.BuildExtension.Key` to discover the available metadata.

[[remove_unused_beans]]
== Removing Unused Beans

The container attempts to remove all unused beans during build by default.
This optimization can be disabled: `shamrock.arc.remove-unused-beans=false`.

An unused bean:

* is not a built-in bean or an interceptor,
* is not eligible for injection to any injection point,
* is not excluded by any extension,
* does not have a name,
* does not declare an observer,
* does not declare any producer which is eligible for injection to any injection point,
* is not directly eligible for injection into any `javax.enterprise.inject.Instance` injection point

The extensions can eliminate possible false positives by producing `UnremovableBeanBuildItem`.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import org.jboss.protean.arc.ArcContainer;
import org.jboss.protean.arc.processor.AnnotationsTransformer;
import org.jboss.protean.arc.processor.BeanDefiningAnnotation;
import org.jboss.protean.arc.processor.BeanDeployment;
import org.jboss.protean.arc.processor.BeanProcessor;
import org.jboss.protean.arc.processor.BeanProcessor.Builder;
import org.jboss.protean.arc.processor.DotNames;
Expand Down Expand Up @@ -234,14 +235,25 @@ public void writeResource(Resource resource) throws IOException {
}

BeanProcessor beanProcessor = builder.build();
beanProcessor.process();

BeanDeployment beanDeployment = beanProcessor.process();
ArcContainer container = arcTemplate.getContainer(shutdown);
BeanContainer bc = arcTemplate.initBeanContainer(container,
beanContainerListenerBuildItems.stream().map(BeanContainerListenerBuildItem::getBeanContainerListener).collect(Collectors.toList()));
injectionProvider.produce(new InjectionProviderBuildItem(arcTemplate.setupInjection(container)));

return new BeanContainerBuildItem(bc);
BeanContainer beanContainer =
arcTemplate.initBeanContainer(
container,
beanContainerListenerBuildItems
.stream()
.map(BeanContainerListenerBuildItem::getBeanContainerListener)
.collect(Collectors.toList()),
beanDeployment
.getRemovedBeans()
.stream()
.flatMap(b -> b.getTypes().stream())
.map(t -> t.name().toString())
.collect(Collectors.toSet()));
injectionProvider.produce(new InjectionProviderBuildItem(arcTemplate.setupInjection(container)));

return new BeanContainerBuildItem(beanContainer);
}

private void indexBeanClass(String beanClass, Indexer indexer, IndexView shamrockIndex, Set<DotName> additionalIndex) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
package org.jboss.shamrock.arc.runtime;

import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.List;
import java.util.function.Supplier;

import org.jboss.logging.Logger;
import org.jboss.protean.arc.Arc;
import org.jboss.protean.arc.ArcContainer;
import org.jboss.protean.arc.InstanceHandle;
Expand All @@ -34,6 +36,8 @@
*/
@Template
public class ArcDeploymentTemplate {

private static final Logger LOGGER = Logger.getLogger(ArcDeploymentTemplate.class.getName());

public ArcContainer getContainer(ShutdownContext shutdown) throws Exception {
ArcContainer container = Arc.initialize();
Expand All @@ -46,19 +50,30 @@ public void run() {
return container;
}

public BeanContainer initBeanContainer(ArcContainer container, List<BeanContainerListener> listeners) throws Exception {
public BeanContainer initBeanContainer(ArcContainer container, List<BeanContainerListener> listeners, Collection<String> removedBeanTypes)
throws Exception {
BeanContainer beanContainer = new BeanContainer() {

@SuppressWarnings("unchecked")
@Override
public <T> Factory<T> instanceFactory(Class<T> type, Annotation... qualifiers) {
Supplier<InstanceHandle<T>> handle = container.instanceSupplier(type, qualifiers);
if (handle == null) {
return null;
Supplier<InstanceHandle<T>> handleSupplier = container.instanceSupplier(type, qualifiers);
if (handleSupplier == null) {
if (removedBeanTypes.contains(type.getName())) {
// Note that this only catches the simplest use cases
LOGGER.warnf(
"Bean matching %s was marked as unused and removed during build.\nExtensions can eliminate false positives using:\n\t- a custom UnremovableBeanBuildItem\n\t- AdditionalBeanBuildItem(false, beanClazz)",
type);
} else {
LOGGER.warnf(
"No matching bean found for type %s and qualifiers %s. The bean might have been marked as unused and removed during build.",
type, qualifiers);
}
return (Factory<T>) Factory.EMPTY;
}
return new Factory<T>() {
@Override
public T get() {
return handle.get().get();
return handleSupplier.get().get();
}
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,27 @@

import org.jboss.protean.arc.ManagedContext;

/**
* Represents a CDI bean container.
*/
public interface BeanContainer {

/**
*
* @param type
* @param qualifiers
* @return a bean instance or {@code null} if no matching bean is found
*/
default <T> T instance(Class<T> type, Annotation... qualifiers) {
return instanceFactory(type, qualifiers).get();
}

/**
*
* @param type
* @param qualifiers
* @return a bean instance factory, never {@code null}
*/
<T> Factory<T> instanceFactory(Class<T> type, Annotation... qualifiers);

/**
Expand All @@ -49,7 +64,18 @@ default <T> T instance(Class<T> type, Annotation... qualifiers) {
ManagedContext requestContext();

interface Factory<T> {


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

/**
*
* @return a bean instance or {@code null} if no matching bean is found
*/
T get();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ public class BeanDeployment {

private final boolean removeUnusedBeans;
private final List<Predicate<BeanInfo>> unusedExclusions;
private final Set<BeanInfo> removedBeans;

BeanDeployment(IndexView index, Collection<BeanDefiningAnnotation> additionalBeanDefiningAnnotations, List<AnnotationsTransformer> annotationTransformers) {
this(index, additionalBeanDefiningAnnotations, annotationTransformers, Collections.emptyList(), Collections.emptyList(), null, false, null);
Expand All @@ -99,6 +100,7 @@ public class BeanDeployment {
this.annotationStore = new AnnotationStore(annotationTransformers, buildContext);
this.removeUnusedBeans = removeUnusedBeans;
this.unusedExclusions = removeUnusedBeans ? unusedExclusions : null;
this.removedBeans = new HashSet<>();

if (buildContext != null) {
buildContext.putInternal(Key.ANNOTATION_STORE.asString(), annotationStore);
Expand Down Expand Up @@ -182,7 +184,11 @@ private void validateBeans(List<Throwable> errors) {
}

public Collection<BeanInfo> getBeans() {
return beans;
return Collections.unmodifiableList(beans);
}

public Collection<BeanInfo> getRemovedBeans() {
return Collections.unmodifiableSet(removedBeans);
}

Collection<ObserverInfo> getObservers() {
Expand Down Expand Up @@ -275,7 +281,7 @@ void init() {
beans.forEach(BeanInfo::init);
observers.forEach(ObserverInfo::init);
interceptors.forEach(InterceptorInfo::init);

if (removeUnusedBeans) {
long removalStart = System.currentTimeMillis();
Set<BeanInfo> removable = new HashSet<>();
Expand Down Expand Up @@ -327,10 +333,11 @@ void init() {
}
if (!removable.isEmpty()) {
beans.removeAll(removable);
removedBeans.addAll(removable);
removedBeans.forEach(b -> LOGGER.debugf("Removed unused %s", b));
}
LOGGER.debugf("Removed %s unused beans in %s ms", removable.size(), System.currentTimeMillis() - removalStart);
}

LOGGER.debugf("Bean deployment initialized in %s ms", System.currentTimeMillis() - start);
}

Expand Down Expand Up @@ -552,9 +559,9 @@ private List<BeanInfo> findBeans(Collection<DotName> beanDefiningAnnotations, Li
}
}

if (LOGGER.isDebugEnabled()) {
if (LOGGER.isTraceEnabled()) {
for (BeanInfo bean : beans) {
LOGGER.logf(Level.DEBUG, "Created %s", bean);
LOGGER.logf(Level.TRACE, "Created %s", bean);
}
}
return beans;
Expand Down Expand Up @@ -605,9 +612,9 @@ private List<InterceptorInfo> findInterceptors(List<InjectionPointInfo> injectio
for (ClassInfo interceptorClass : interceptorClasses) {
interceptors.add(Interceptors.createInterceptor(interceptorClass, this));
}
if (LOGGER.isDebugEnabled()) {
if (LOGGER.isTraceEnabled()) {
for (InterceptorInfo interceptor : interceptors) {
LOGGER.logf(Level.DEBUG, "Created %s", interceptor);
LOGGER.logf(Level.TRACE, "Created %s", interceptor);
}
}
for(InterceptorInfo i : interceptors) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -445,10 +445,10 @@ public String toString() {
builder.append(", qualifiers=");
builder.append(qualifiers);
builder.append(", target=");
builder.append(target);
builder.append(target.isPresent() ? target.get() : "n/a");
if (declaringBean != null) {
builder.append(", declaringBean=");
builder.append(declaringBean.target);
builder.append(declaringBean.target.isPresent() ? declaringBean.target.get() : "n/a");
}
builder.append("]");
return builder.toString();
Expand Down

0 comments on commit 883cf4c

Please sign in to comment.