Статьи

Инъекция с CDI (часть III)

 

Если вы читаете этот блог, вы должны знать, что в последнее время я пишу (и говорю ) о CDI ( контекстах и внедрении зависимостей ). У CDI есть много аспектов, но до сих пор я фокусировался на том, как загрузить CDI в нескольких средах , как добавить CDI в существующее приложение Java EE 6 и совсем недавно, как использовать инъекцию с CDI. На самом деле этот пост является третьим по инъекции CDI:  Часть I  сфокусирована на внедрении по умолчанию и квалификаторах, а Часть II —  на всех возможных точках инъекции (поле, конструктор и сеттеры). В этом посте я объясню производителям  или « как можно вводить что-либо в любом месте безопасным способом«.

Вводить только бобы?

До сих пор я показывал вам, как вводить бины простым @Inject. Если мы сосредоточимся на примере генератора номеров книг, который я использовал, у нас есть сервлет и RESTService, внедряющие реализацию интерфейса NumberGenerator. Благодаря квалификаторам , сервлет может специально запросить IsbnGenerator, указав спецификатор @ThirteenDigit в точке инъекции, а остальная часть обслуживает IssnGenerator с @EightDigits (см. Мой первый  пост). Следующая диаграмма классов показывает некоторые комбинации инъекций бобов:

Но, как вы можете видеть, все это  бобы, которые вводят другие бобы . Можем ли мы просто ввести бобы с помощью CDI? Ответ — нет, вы можете вводить что угодно где угодно .

Производители

Да, вы можете вводить что угодно где угодно, единственное, что вам нужно сделать, это произвести то, что вы хотите ввести. Для этого у CDI есть производители  (хорошая реализация шаблона Factory ). Производитель предоставляет любой вид:

  • Класс : неограниченный набор типов компонентов, суперкласса и всех интерфейсов, которые он реализует прямо или косвенно
  • Интерфейс  : неограниченный набор типов компонентов, интерфейсы, которые он расширяет прямо или косвенно, и java.lang.Object
  • Примитив и тип массива Java

Таким образом, вы можете добавить java.util.Date, java.lang.String или даже int. Давайте начнем с создания и внедрения некоторых типов данных и примитивов.

Создание типов данных и примитивов

One example of injecting anything anywhere is the possibility to inject data types or primitives. So let’s inject a String and an int. For that I need to add extra classes to our model. Until now, the IsbnGenerator would generate a random number like 13-124-454644-4. I can say that this number is made of a String that acts like a prefix (13-124) and an int that acts like a suffix (4). The following class diagram shows the two new classes PrefixGenerator and PostfixGeneratorthat will be used by the number generators :

If we look at the code of the PrefixGenerator for example, you can see that the class itself is not annotated by any qualifier, but the methods are. The method getIsbnPrefix returns a String that is qualified with @ThirteenDigits. This String is produced by CDI (thanks to @Produces), meaning that you can now inject it with @Inject using its qualifier (@ThirteenDigits)

public class PrefixGenerator {

    @Produces @ThirteenDigits
    public String getIsbnPrefix() {
        return "13-84356";
    }

    @Produces @EightDigits
    public String getIssnPrefix() {
        return "8";
    }
}

And now look carefully at the class  PostfixGenerator. The code is exactly the same as previously except in this case we produce an intthat can now be injected.

public class PostfixGenerator {

    @Produces @ThirteenDigits
    public int getIsbnPostfix() {
        return 13;
    }

    @Produces @EightDigits
    public int getIssnPostfix() {
        return 8;
    }
}

So now let’s change the way an ISBN number is generated. The bean IsbnGenerator now injects both a String and an int with @Inject @ThirteenDigits. You understand now what strong typing means with CDI.  Using the same syntax (@Inject @ThirteenDigits), CDI knows that it needs to inject a String, an int or an implementation of a NumberGenerator.

@ThirteenDigits
public class IsbnGenerator implements NumberGenerator {

    @Inject @ThirteenDigits
    private String prefix;

    @Inject @ThirteenDigits
    private int postfix;

	public String generateNumber() {
        return prefix + "-" + Math.abs(new Random().nextInt()) + "-" + postfix;
    }
}

Injecting Java EE resources with producer fields

So if CDI can inject anything anywhere, if CDI can even inject Strings and integers, what about Java EE resources ? That’s another story and producers are here to help. As I’ve said before, CDI is all about type safety : CDI doesn’t like Strings, so there is no way to inject a resource by its JNDI name such as @Inject(name=»jms/OrderQueue»). A common example is the entity manager. This is how you must inject it in Java EE 6 if you don’t use CDI :

@Stateless
public class ItemEJB {

    @PersistenceContext(unitName = "cdiPU")
    private EntityManager em;
    ...
}

So why can’t you just @Inject an entity manager? If you remember my first post about ambiguous injection, this is the same problem. You can have several persistence units (named with a string), so if you just use an @Inject, CDI will not know which persistence unit to inject. Instead you must produce the entity manager first, give it a name (if you don’t want the @Default) and then an @Inject as follows :

public class DatabaseProducer {

    @Produces
    @PersistenceContext(unitName = "cdiPU")
    @BookStoreDatabase
    private EntityManager em;
}

The DatabaseProducer class uses the @PersistenceContext to inject the entity manager with the persistence unit cdiPU. It gives it a name using a qualifier (@BookStoreDatabase) and produces it so it can now be injected in an EJB as follow :

@Stateless
public class ItemEJB {

    @Inject
    @BookStoreDatabase
    private EntityManager em;
    ...
}

Another nice use case is to produce JMS factories and destinations. @Resource allows you to get a JNDI reference to a specific JMS factory or destination, the qualifier @Order gives it a name, and the @Produces allows you to inject it :

public class JMSResourceProducer {

    @Produces @Order @Resource(name = "jms/OrderConnectionFactory")
    private QueueConnectionFactory orderConnectionFactory;

    @Produces @Order @Resource(name = "jms/OrderQueue")
    private Queue orderQueue;
}

Now your EJB can use @Inject in a type-safe way :

@Stateless
public class ItemEJB {

    @Inject @Order
    private QueueConnectionFactory orderConnectionFactory;

    @Inject @Order
    private Queue orderQueue;
    ...
}

Producing Java EE resources with producer methods

The examples above are pretty simple : produce a field and you can then inject it. That’s called producer field. But sometimes you need a more complex business logic to produce a bean, that’s what we called producer method. Let’s take another use case. When you need to send a JMS message you always end up injecting a JMS Factory, a destination (queue or topic) creating a connection, then a session and so on until you actually send your message. Because this code is repetitive and error prone, why not externalize all of if into a single class and produce a session so the other components can use when sending a message. This class could look something like that :

public class JMSResourceProducer {

    @Resource(name = "jms/OrderConnectionFactory")
    private QueueConnectionFactory orderConnectionFactory;
    @Produces @Order @Resource(name = "jms/OrderQueue")
    private Queue orderQueue;

    @Produces @Order
    public QueueConnection createOrderConnection() throws JMSException {
        return orderConnectionFactory.createQueueConnection();
    }

    @Produces @Order
    public QueueSession createOrderSession(@Order QueueConnection conn) throws JMSException {
        return conn.createQueueSession(true, Session.AUTO_ACKNOWLEDGE);
    }
}

First, the class uses the @Resource to get a reference of the QueueConnectionFactory and Queue. For the same reason I just explain above, you can have several JMS factories and destinations and you must distinguish them by their JNDI name. And because CDI does not allow you to give a string name on an injection point, you must still use @Resource instead of @Inject. The createOrderConnection method take a QueueConnectionFactory creates a QueueConnection and produces it (while giving it the name of @Order). If you look carefully at the method createOrderSession it takes the produced QueueConnection and creates a QueueSession. An external component then just needs to inject the JMS session without going through the process of creating it :

@Stateless
public class ItemEJB {

    @Inject @Order
    private QueueSession session;

    @Inject @Order
    private Queue orderQueue;

    private void sendOrder(Book book) throws Exception {
        QueueSender sender = session.createSender(orderQueue);
        TextMessage message = session.createTextMessage();
        message.setText(marshall(book));
        sender.send(message);
    }
    ...
}

Producing and… disposing

I can hear you saying “ok, that’s nice, I have an external class doing all the plumbing and creating a connection and a session…. but who is going to close it ?“. Indeed, someone needs to free these resources by closing them. And that’s when CDI brings you another nice bit of magic : @Disposes.

public class JMSResourceProducer {

    @Resource(name = "jms/OrderConnectionFactory")
    private QueueConnectionFactory orderConnectionFactory;
    @Produces @Order @Resource(name = "jms/OrderQueue")
    private Queue orderQueue;

    @Produces @Order
    public QueueConnection createOrderConnection() throws JMSException {
        return orderConnectionFactory.createQueueConnection();
    }

    @Produces @Order
    public QueueSession createOrderSession(@Order QueueConnection conn) throws JMSException {
        return conn.createQueueSession(true, Session.AUTO_ACKNOWLEDGE);
    }

    public void closeOrderSession(@Disposes @Order QueueConnection conn) throws JMSException {
        conn.close();
    }

    public void closeOrderSession(@Disposes @Order QueueSession session) throws JMSException {
        session.close();
    }
}

To ask CDI to close a resource you just need to create a method which signature is the same as the one that created it (createOrderSession(@Order QueueConnection conn) creates a session and closeOrderSession(@Order QueueConnection conn) closes it) and add a @Disposes. CDI will dispose the resources in the right order (session first, connection second) for you. I haven’t mentioned it yet but CDI will create and dispose resources depending on the scope (resquest, session, application, conversation…). But that’s for another post.

Conclusion

As you’ve seen in my previous posts (Part I, Part II), CDI is about injection (I’ll explain other topics later). So far I’ve been showing you how to inject beans, but you know now that you can inject anything (String, integer, entity manager, JMS factories…) anywhere (in POJOs, Servlets, EJBs…). You just need to produce what you need to inject (either with field producers or method producers).
The next article will cover alternatives, so stay tuned.

Download

Download the code, give it a try, and give me some feedback.

References

From http://agoncal.wordpress.com/2011/09/25/injection-with-cdi-part-iii/