Programmatic lookup improvements

2016-5-18   api , draft   Martin Kouba

javax.enterprise.inject.Instance is sometimes invaluable companion. For example, Instance allows you to handle unsatisfied and ambiguous dependencies gracefully. E.g. it’s possible to avoid unnecessary deployment problems:

interface OrderProcessor {
  void process(Order order);
  int getPriority();
}

class OrderService {

  @Inject
  Instance<OrderProcessor> instance;

  void create(Order order) {
    if (!instance.isUnsatisfied() && !instance.isAmbiguous()) {
        instance.get().process(order);
    } else {
        // Log a warning or throw an exception
    }
  }
}

It is less known that Instance extends Iterable and so it allows to iterate through contextual references of beans with the specified combination of required type and qualifiers. This might be useful if the set of beans satisfying the given type and qualifiers is not known beforehand. We may also need to resolve ambiguities manually, e.g. inspect all the instances and choose only those matching our needs.

@ApplicationScoped
class OrderService {

  @Inject
  @Any
  Instance<OrderProcessor> instance;

  void create(Order order) {
    for (OrderProcessor processor : instance) {
        if (processor.getPriority() > 10) {
            processor.process(order);
        }
    }
  }
}

Another interesting use case might be selecting exactly one implementation:

@ApplicationScoped
class OrderService {

  @Inject
  @Any
  Instance<OrderProcessor> instance;

  void create(Order order) {
    List<OrderProcessor> processors = new ArrayList<>();
    for (OrderProcessor processor : instance) {
        processors.add(processor);
    }
    Collections.sort(processors, Comparator.<OrderProcessor> comparingInt(p -> p.getPriority()).reversed());
    // Use the processor with highest priority
    processors.get(0).ping();
  }
}

This works nice. But we have to pay extra attention to the scopes. If a Processor implementation is @Dependent a similar usage results in memory leaks. What’s the reason? The Processor instance is the dependent object of Instance<OrderProcessor> which is the dependent object of OrderService. And so each Instance.get() will produce a new Processor bound to the lifecycle of the OrderService. To avoid the leak we should always call Instance.destroy() method in similar cases. However, Instance.destroy() always destroys the underlying contextual instance! Even if you pass e.g. a client proxy of an @ApplicationScoped bean. This is not always desirable. Currently, it’s quite complicated to detect a dependent bean inside the loop.

To extend the possibilities the Weld team is experimenting with org.jboss.weld.inject.WeldInstance - an enhanced version of javax.enterprise.inject.Instance. There are three new methods we find useful. The first one - getHandler() - allows to obtain a contextual reference handler which not only holds the contextual reference but also allows to inspect the metadata of the relevant bean and to destroy the underlying contextual instance. Moreover, the handler implements AutoCloseable:

import org.jboss.weld.inject.WeldInstance;

class Foo {

  @Inject
  WeldInstance<Bar> instance;

  void doWork() {
    try (Handler<Bar> bar = instance.getHandler()) {
        bar.get().doBusiness();
        // Note that Bar will be automatically destroyed at the end of the try-with-resources statement
    }

    Handler<Bar> bar = instance.getHandler()
    bar.get().doBusiness();
    // Calls Instance.destroy()
    bar.destroy();
  }

}

The next method - handlerIterator() - returns an iterator over contextual reference handlers. This might be useful if you need more control over contextual references inside the loop:

@ApplicationScoped
class OrderService {

  @Inject
  @Any
  WeldInstance<OrderProcessor> instance;

  void create(Order order) {
    for (Iterator<Handler<OrderProcessor>> iterator = instance.handlerIterator(); iterator.hasNext();) {
            Handler<OrderProcessor> processor = iterator.next();
            processor.get().process(order);
            if (processor.getBean().getScope().equals(Dependent.class)) {
                // Destroy only dependent processors
                processor.destroy();
            }
        }
  }
}

The last one is just a convenient method - isResolvable() - a replacement for !isUnsatisfied() && !isAmbiguous() which is the expression most users are interested in:

class OrderService {

  @Inject
  Instance<OrderProcessor> instance;

  void create(Order order) {
    if (instance.isResolvable()) {
        instance.get().process(order);
    } else {
        // Log a warning or throw an exception
    }
  }
}

Weld team is considering adding org.jboss.weld.inject.WeldInstance to the Weld API (2.4 and 3.0). WeldInstance would be automatically available in Weld SE and Weld Servlet where the Weld API is always on the class path. It would be also available in Weld-powered EE containers - in this case, users would have to compile their application against the Weld API and exclude the Weld API artifact from the deployment (e.g. use provided scope in Maven).

See also WELD-2151 and the work in progress: https://github.com/mkouba/core/tree/WELD-2151. And feel free to add comments to this blog post. Any feedback is appreciated!