diff --git a/docs/src/main/asciidoc/cdi-reference.adoc b/docs/src/main/asciidoc/cdi-reference.adoc index e9bec0a73f208..d4a61a33d03a7 100644 --- a/docs/src/main/asciidoc/cdi-reference.adoc +++ b/docs/src/main/asciidoc/cdi-reference.adoc @@ -137,6 +137,8 @@ BeanDefiningAnnotationBuildItem additionalBeanDefiningAnnotation() { } ---- +NOTE: Bean registrations that are result of a `BeanDefiningAnnotationBuildItem` are unremovable by default. See also <>. + === Resource Annotations `ResourceAnnotationBuildItem` is used to specify resource annotations that make it possible to resolve non-CDI injection points, such as Java EE resources. @@ -168,6 +170,8 @@ List additionalBeans() { } ---- +NOTE: A bean registration that is a result of an `AdditionalBeanBuildItem` is removable by default. See also <>. + === 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. @@ -222,4 +226,22 @@ BeanDeploymentValidatorBuildItem beanDeploymentValidator() { } ---- -NOTE: See also `org.jboss.protean.arc.processor.BuildExtension.Key` to discover the available metadata. \ No newline at end of file +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`. \ No newline at end of file diff --git a/extensions/arc/deployment/src/main/java/org/jboss/shamrock/arc/deployment/ArcAnnotationProcessor.java b/extensions/arc/deployment/src/main/java/org/jboss/shamrock/arc/deployment/ArcAnnotationProcessor.java index 716cf63ecceef..78c3788dc7422 100644 --- a/extensions/arc/deployment/src/main/java/org/jboss/shamrock/arc/deployment/ArcAnnotationProcessor.java +++ b/extensions/arc/deployment/src/main/java/org/jboss/shamrock/arc/deployment/ArcAnnotationProcessor.java @@ -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; @@ -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 additionalIndex) { diff --git a/extensions/arc/runtime/src/main/java/org/jboss/shamrock/arc/runtime/ArcDeploymentTemplate.java b/extensions/arc/runtime/src/main/java/org/jboss/shamrock/arc/runtime/ArcDeploymentTemplate.java index 027c98d8bb2ce..1618a4b22f735 100644 --- a/extensions/arc/runtime/src/main/java/org/jboss/shamrock/arc/runtime/ArcDeploymentTemplate.java +++ b/extensions/arc/runtime/src/main/java/org/jboss/shamrock/arc/runtime/ArcDeploymentTemplate.java @@ -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; @@ -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(); @@ -46,19 +50,30 @@ public void run() { return container; } - public BeanContainer initBeanContainer(ArcContainer container, List listeners) throws Exception { + public BeanContainer initBeanContainer(ArcContainer container, List listeners, Collection removedBeanTypes) + throws Exception { BeanContainer beanContainer = new BeanContainer() { - + @SuppressWarnings("unchecked") @Override public Factory instanceFactory(Class type, Annotation... qualifiers) { - Supplier> handle = container.instanceSupplier(type, qualifiers); - if (handle == null) { - return null; + Supplier> 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) Factory.EMPTY; } return new Factory() { @Override public T get() { - return handle.get().get(); + return handleSupplier.get().get(); } }; } diff --git a/extensions/arc/runtime/src/main/java/org/jboss/shamrock/arc/runtime/BeanContainer.java b/extensions/arc/runtime/src/main/java/org/jboss/shamrock/arc/runtime/BeanContainer.java index 8af21f916e07d..fbcb211330e52 100644 --- a/extensions/arc/runtime/src/main/java/org/jboss/shamrock/arc/runtime/BeanContainer.java +++ b/extensions/arc/runtime/src/main/java/org/jboss/shamrock/arc/runtime/BeanContainer.java @@ -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 instance(Class type, Annotation... qualifiers) { return instanceFactory(type, qualifiers).get(); } + /** + * + * @param type + * @param qualifiers + * @return a bean instance factory, never {@code null} + */ Factory instanceFactory(Class type, Annotation... qualifiers); /** @@ -49,7 +64,18 @@ default T instance(Class type, Annotation... qualifiers) { ManagedContext requestContext(); interface Factory { - + + Factory EMPTY = new Factory() { + @Override + public Object get() { + return null; + } + }; + + /** + * + * @return a bean instance or {@code null} if no matching bean is found + */ T get(); } diff --git a/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanDeployment.java b/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanDeployment.java index 2c15a50dd5484..964c0ce148cc7 100644 --- a/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanDeployment.java +++ b/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanDeployment.java @@ -85,6 +85,7 @@ public class BeanDeployment { private final boolean removeUnusedBeans; private final List> unusedExclusions; + private final Set removedBeans; BeanDeployment(IndexView index, Collection additionalBeanDefiningAnnotations, List annotationTransformers) { this(index, additionalBeanDefiningAnnotations, annotationTransformers, Collections.emptyList(), Collections.emptyList(), null, false, null); @@ -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); @@ -182,7 +184,11 @@ private void validateBeans(List errors) { } public Collection getBeans() { - return beans; + return Collections.unmodifiableList(beans); + } + + public Collection getRemovedBeans() { + return Collections.unmodifiableSet(removedBeans); } Collection getObservers() { @@ -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 removable = new HashSet<>(); @@ -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); } @@ -552,9 +559,9 @@ private List findBeans(Collection 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; @@ -605,9 +612,9 @@ private List findInterceptors(List 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) { diff --git a/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanInfo.java b/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanInfo.java index e84eb95f48618..889a9d6315f1d 100644 --- a/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanInfo.java +++ b/independent-projects/arc/processor/src/main/java/org/jboss/protean/arc/processor/BeanInfo.java @@ -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();