Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ArC - document "remove unused beans" optimization #674

Merged
merged 1 commit into from
Jan 30, 2019
Merged
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
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 a `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));
gsmet marked this conversation as resolved.
Show resolved Hide resolved
}
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