Producers - JBoss Weld CDI for Java Platform (2013)

JBoss Weld CDI for Java Platform (2013)

Chapter 5. Producers

In this chapter we will expand on producers, which were introduced back in Chapter 1, What is a Bean?, by covering their uses in greater detail. We will also see what scopes our produced beans are in and how to ensure that the beans we produce don't leave pieces of themselves behind when they're destroyed.

We've already learned how producers can be useful when we want to make runtime decisions about which bean instance should be used in a given situation. But there can be many situations where producers are beneficial, such as enabling third-party frameworks to be used with CDI by exposing their objects as beans.

Producers allow us to utilize runtime polymorphism, such as:

@SessionScoped

public class SearchManager implements java.io.Serializable {

private SearchType searchType= SearchType.FICTION;

@Produces

@Preferred

public BookSearch getSearch() {

switch (searchType) {

case FICTION:

return new FictionSearch();

case NONFICTION:

return new NonFictionSearch();

default:

return null;

}

}

An injection point matching the bean we just produced is as follows:

@Inject

@Preferred

BookSearch search;

The scope of a producer

Beans created by a producer defaults to be @Dependent scoped. A producer with no scope specified will be called each and every time Weld needs to inject a bean that is matched by the producer's bean types and qualifiers. It also means there will be a separate instance of the bean for each call made to the producer, which is not always what we want or intend.

CDI makes it easy for us to modify this behavior by adding any of @RequestScoped, @ConversationScoped, @SessionScoped, or @ApplicationScoped onto the producer, depending on which lifecycle we want for the produced bean. If we were to annotate a producer with@ApplicationScoped, the producer would only be called once for the life of the application, and the created bean is stored in the application context to be shared by all clients.

Note

A producer does not derive its scope from the bean in which it is declared.

An important point to remember when thinking of producers is that they are themselves a bean in their own right, even though they may be a field or method of another bean. It's for this reason that a producer does not derive its scope from the bean it is contained within, as CDI treats the producer as a separate bean, and if it requires a scope then it must be specified on the producer.

Injection into producer methods

Coming back to our producer method at the start of the chapter, there is one problem that we may experience as the code is currently written. Creating FictionSearch and NonFictionSearch with the Java new operator means that they are not eligible for dependency injection and cannot have interceptors.

Both of those restrictions may be acceptable to our application, but if they aren't then we can use dependency injection into the producer method to ensure that we are using beans controlled by Weld. The producer would now be as follows:

@Produces

@Preferred

@SessionScoped

public BookSearch getSearch(FictionSearch fs, NonFictionSearch nfs) {

switch (searchType) {

case FICTION:

return fs;

case NONFICTION:

return nfs;

default:

return null;

}

}

In our example, FictionSearch and NonFictionSearch are both @RequestScoped beans; so what does that mean for our producer as we're producing a bean into @SessionScoped? Our producer method has the effect of promoting the request scope instance into the session scope, which is probably not what we intended to happen.

We can see the effect of that promotion when we create a simple JSF app with two pages, and each page outputs the description of two beans defined by these injection points:

@Inject

FictionSearch fictionSearch;

@Inject

@Preferred

BookSearch search;

We've seen the second injection point before; it receives the bean instance from our producer and the first is just a direct injection of the FictionSearch instance from the request scope.

To be able to compare direct injection of the request-scoped FictionSearch and the session-scoped BookSearch, we need to make a minor change to our producer to update the description:

case FICTION:

fs.setDescription("Hello from Fiction Search!");

return fs;

This small change allows us to see a different description for the bean instance that was returned from our producer and the request scoped instance.

Deploying our application and accessing http://localhost:8080/chapter5/index.jsf, we see the following screen:

Injection into producer methods

We can see that the request scope and session scope beans have different descriptions associated with them, as we expected after modifying our producer method.

What is probably not expected is that when we click on Next Request or refresh the same page, we see the following screen:

Injection into producer methods

What happened there? As soon as our producer had a bean from the request scope injected into it, which we then promoted to the session scope, that bean instance was destroyed at the end of the request. Instead of retaining the bean with the modified description between requests, what we retrieve from the session scope is actually the new bean instance from the request scope as our producer is not called again. That is why we see the same description for the bean in two different scopes; they are both retrieving the description from the same requestscoped bean instance.

There are several ways to resolve this unintended 'margin-left:18.0pt;text-indent:-18.0pt;line-height: normal'>· Change the scope on FictionSearch and NonFictionSearch, but that would result in changes to all places where these beans are used, which we may not want to do

· Change the scope of our producer to @Dependent or @RequestScoped, which doesn't do us any good if we want the produced bean to live longer

· Use the @New qualifier, which will be covered in the next section

Dependent beans for producers

In creating a new bean with a producer, there may be situations in which we do not want an existing instance of a bean to be injected into our producer, but would prefer an instance that is dependent on the scope of the bean that produced it.

Taking our example from the previous section, we can resolve the problem of a requestscoped bean being promoted to the session scope with the following producer:

@Produces

@Preferred

@SessionScoped

public BookSearch getSearch

(@New FictionSearch fs, @New NonFictionSearch nfs) {

switch (searchType) {

case FICTION:

fs.setDescription("Hello from Fiction Search!");

return fs;

case NONFICTION:

return nfs;

default:

return null;

}

}

Deploying our example and accessing http://localhost:8080/chapter5/index.jsf, we see the same initial screen as shown previously. If we now refresh the page or click on Next Request, we see that the session-scoped bean has been retained as we originally intended:

Dependent beans for producers

Note

Whatever scope a bean type has declared on it, injecting an instance of that bean type with @New will always provide a bean that is dependent on where it was injected.

Using @New on the parameters of the producer allows Weld to inject a new dependent instance of FictionSearch and NonFictionSearch that will be bound into the session context. These dependent objects won't be destroyed until SearchManager is removed when the user session expires.

Note

CDI 1.1 will deprecate the use of @New in favor of using @Dependent to the injection point to signify the need for a dependent instance of a bean type.

Cleanup of produced beans

Often, the producers we create produce a bean that either requires explicit destruction or closure, or another object may need to be destroyed once the bean that was using it is no longer required.

For these situations, CDI provides a means by which we can perform a customized cleanup within our application by creating a disposer method to match the producer.

public class AccountDatabase {

@Produces

@ConversationScoped

@AccountDB

public EntityManager create(EntityManagerFactory factory) {

return factory.createEntityManager();

}

public void close(@Disposes @AccountDB EntityManager em) {

em.close();

}

}

A disposer method is required to have a single parameter annotated with @Disposes that has the same bean type and qualifiers as the producer. When a disposer method declares additional parameters, these are treated as injection points by Weld, allowing us to inject loggers or other beans that are needed to perform the cleanup process.

Note

A disposer method must reside within the same bean class as the producer that created the bean instance being cleaned up.

For a given producer, there can only be one disposer method that matches the bean type and qualifiers, otherwise Weld will cease deployment of the application and log the error. Conversely, it is entirely fine for a single disposer method to be matched with more than one producer, which makes it convenient for cleaning up beans of a similar type in a single method.

Taking our earlier example, we can extend it for multiple producers with a single disposer:

public class Databases {

@Produces

@ConversationScoped

@AccountDB

public EntityManager accountDB(EntityManagerFactory factory) {

return factory.createEntityManager();

}

@Produces

@ConversationScoped

@OrderDB

public EntityManager orderDB(EntityManagerFactory factory) {

return factory.createEntityManager();

}

public void close(@Disposes @Any EntityManager em) {

em.close();

}

}

Summary

We now have a complete understanding of producers and their various aspects within CDI development for our applications. We covered what scope is assigned to a bean instance created from a producer and how to inject instances of beans into our producers; so that we may create producers that take advantage of runtime decision making about the exact bean instance that needs to be returned. We also learnt how to prevent unintended scope problems of bean instances being used in a producer by requesting a dependent instance from Weld with @New and creating a method to clean up any resources that may remain open when a bean instance is destroyed by Weld.