CDI and Instance: 3 pitfalls you need to know


Recently on OpenWebBeans mailing list a nice discussion was started by Karl Kildén about its usage of CDI Instance.

This API is nice and useful but it is not as trivial as it can look. Let’s dig a bit into it.

As a reminder Instance allows you to dynamically look up a CDI Bean instance programmatically.

Simplest usage is:

@Inject
Instance<MyBean> instance;

public void useInstance() {
    instance.get().callASuperMethod();
}

In this case using Instance or Provider is exactly the same however it provides few helper methods like isUnsatisfied() or isAmbiguous() allowing you to not call get() and not fail if CDI is not able to resolve the instance you ask.

Another note is Instance supports as well qualifiers if your bean doesn’t use @Default.

However the common usage of Instance is to resolve dynamically at runtime an instance.

Let suppose I have the qualifier @Collocalized, @Jaxrs and @Fallback and that I have 3 implementations of MyBean each one with one of these qualifiers.

Using Instance I can select the one I desire programmatically:

@Inject
Instance<MyBean> instance;

public void useInstance() {
    instance.select(new AnnotationLiteral<Jaxrs>() {}).get().callASuperMethod();
}

This is great but how does it work? select() is basically adding to default set of qualifiers (from injection point) the parameter you give to the method. If you don’t want only to select by qualifiers but also by subtype (take the same example but using inheritance instead of qualifiers) there is a select method for it as well (you give the subtype you want).

Then once you fully described what you want as “CDI type” (type + qualifiers) you call get() which instantiates the instance.

Here is the main feature Instance gives you…but is there any pitfalls? As all features it has its pitfalls and Instance has 3 pitfalls you need to know before using it.

It is slow

The easiest one to understand and to solve is the fact using Instance forces the container to resolve the instance at runtime and not only at injection time. This means if you lookup/select the instance for each invocation you’ll slow down your process.

I recommand you to cache the resolved instance if you can:

@Inject
Instance<MyBean> instance;
MyBean cached;

@PostConstruct
private void resolve() {
    cached = instance.select(new AnnotationLiteral<Jaxrs>() {}).get();
}

public void useInstance() {
    cached.callASuperMethod();
}

It can leak

This is the issue which came over the list.

With CDI 1.0 the instance created from Instance was associated with the enclosing bean as a @Dependent bean if it didnt have any scope. It means if you use it in an @ApplicationScoped bean then
you’ll add a dependent bean each time you resolve a new instance. Dependent beans are released with the enclosing bean so basically you prevent the instances to be garbage collected while the @ApplicationScoped bean is not destroyed – ie while your application is deployed. I let you guess what can happen in such a case.

CDI 1.x, x > 0 solved it introducing in Instance API a destroy(X) method so each time you want to eagerly release a created instance you can call destroy(myInstance).

Of course if you apply previous caching here you solve it as well.

This is not a SPI

If you look Instance hierarchy you will realize it is an Iterable so it is very tempted to use such a pattern to get extensibility:

@Inject
@Any
Instance<MyPlugin> instance;

public void callPlugins() {
    for (MyPlugin plugin : instance) {
        plugin.doWhatAPLuginDoes();
    }
}

First note is: if you still decide to use it the caching tip still applies but on all instances instead of a single one.

Then Instance is not called Instances for a good reason: it resolves against all beans matching the type (including qualifiers). This means if you have 10 plugins but activate one through an @Alternative then the only resolvable one will be the alternative and you’ll loose all other plugins.

This is not always an issue and you can say to this plugin API users to not use alternatives but you need to be aware of it (personally I throw an exception if one of my plugin has @Alternative).

Conclusion

Instance API is really powerful but as always with great power you get big responsabilities so you need to ensure its usages are not perverted.

Want more content ? Keep up-to-date on my new blog

Or stay in touch on twitter @rmannibucau

Advertisements

6 thoughts on “CDI and Instance: 3 pitfalls you need to know

  1. Pingback: Java Weekly 11/15: Java Money, REST API evolution, CDI 2.0 and more… | Ceiba3D Studio

  2. Pingback: Java Weekly 11/15: Java Money, REST API evolution, CDI 2.0 and more… | Indie Game Developer!

  3. Rui Rodrigues

    Hi,

    what do you mean by previous caching in the leak section? What happens if get() is called and then destroy the created bean and only then call the method of the bean? Will it work? I tried it and it seems so but I don’t know if there will be any side effects.

    The problem here is if one uses the Instance in a factory. How to destroy it? It doesn’t make sense that all the consumer of the factory needs to know that they have to destroy it somehow.

    Thanks
    RAR

    Reply
    1. rmannibucau Post author

      It works while it doesnt depend on injections which would be available anymore or PreDestroy methods mutating the state of the injections.

      Not sure about your question: if you have a factory which is not symmetric – ie create but not destroy – then you leak by design – would you create a socket without being able to close it? Most of EE api changed to support the releasing of cdi instances and it is the same cdi or not since the lifecycle management of instances is a old problem.

      Reply
  4. Pingback: CDI: Lazy Injection at Runtime or How to Obtain Every Matching Implementation | Beyond Java

  5. Pingback: Building extensible and customizable Java EE Applications – Part 2 | {Stephan}Knitelius

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s