If you follow this blog, remember that lately I have been writing (and talking ) about CDI ( Contexts and Dependency Injection ). CDI has many aspects, but so far I have focused on how to get started with CDI in your environment and how to integrate CDI into an existing Java EE 6 application , and then focused on introducing dependencies into CDI. This is the third post about the implementation in CDI: in the first one I talked about the default implementation and specifiers, in the second about all possible implementation points (field, constructor, setter). In this post, I’ll talk about producers or " how you can implement anything and any type of safe way ."
Before that, I showed you how to implement beans with the usual @Inject
annotation. If you look at the example with the generation of the book number that I used earlier , you will see a servlet and a rest service in which the implementation of the NumberGenerator
interface is being implemented. Thanks to the specifiers, the servlet can get an IsbnGenerator
marked at the insertion point with the @ThirteenDigit
, and the rest of the service will get the IssnGenerator
marked with @EightDigits
(see my first post). The presented class diagram demonstrates some combination of bin injection:
But as you can see, all this is the injection of beans into other bins . In CDI, can we embed only beans? Not. You can embed anything and anywhere .
Yes, you can embed anything and anywhere. All you need to do is create something that you want to implement later. For this, there are producers in CDI (an excellent implementation of the Factory pattern). With their help, you can create:
java.lang.Object
So you can embed java.util.Date
, java.lang.String
or even an int
. Start by creating and implementing some data types and primitives.
One example of how to implement anything and anywhere is the introduction of data types and primitives. Well, let's try to embed String
and int
. For this, I need to add additional classes to our model. Prior to this, the IsbnGenerator
created a random number in the format 13-124-454644-4 . This number consists of a string serving as a prefix ( 13-124 ), and an integer value - a postfix ( 4 ). The following class diagram shows the two new PrefixGenerator
and PostfixGenerator
that will be used to generate the number:
If we look, for example, at the PrefixGenerator
code, we will see a class that itself is not marked by any specifier, unlike its methods. The getIsbnPrefix
method returns a string that is specified by the @ThirteenDigits
annotation. This string is created using CDI (thanks to @Produces
), which means that you can embed it using @Inject
and its specifier ( @ThirteenDigits
).
public class PrefixGenerator { @Produces @ThirteenDigits public String getIsbnPrefix() { return "13-84356"; } @Produces @EightDigits public String getIssnPrefix() { return "8"; } }
Now look closely at the PostfixGenerator
class. This code is similar to the previous one, except that it creates an int
, which can then be embedded.
public class PostfixGenerator { @Produces @ThirteenDigits public int getIsbnPostfix() { return 13; } @Produces @EightDigits public int getIssnPostfix() { return 8; } }
And now we will change the logic of generating ISBN numbers. The IsbnGenerator
bin is IsbnGenerator
String
and int
using @Inject
and @ThirteenDigits
. Now you understand what strict typing in CDI means. Using this syntax ( @Inject @ThirteenDigits
), CDI knows what to insert into the String
place, and what to the int place and which implementation of the NumberGenerator
implementation to 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; } }
So, if CDI can embed anything and anywhere, if it can even embed strings and integers, then what about Java EE resources? This is another story, and the producers will help us in this. As I said earlier, everything is type-safe in CDI, so there is no way in CDI to embed a resource by its JNDI name , for example, @Inject(name="jms/OrderQueue")
. The standard example is an entity manager. If you do not use CDI, then in Java EE 6 you can implement it as follows:
@Stateless public class ItemEJB { @PersistenceContext(unitName = "cdiPU") private EntityManager em; ... }
So why is it impossible to make an ordinary @Inject
for an entity manager? If you remember my first post about the ambiguity of implementation, then there is the same problem. You may have several units of persistence (named with a regular string), and if you just write @Inject
, then CDI will not be able to figure out which one of them needs to be implemented. Instead, you should create the entity manager first and name it somehow (if you don’t want to use @Default
):
public class DatabaseProducer { @Produces @PersistenceContext(unitName = "cdiPU") @BookStoreDatabase private EntityManager em; }
The DatabaseProducer
class uses @PersistenceContext
to implement the entity manager with the cdiPU persistence unit. It will be marked with a specifier ( @BookStoreDatabase
) and initialized, after which you can embed it in an EJB as follows:
@Stateless public class ItemEJB { @Inject @BookStoreDatabase private EntityManager em; ... }
Another good example is the creation of JMS factories and recipients. The @Resource
allows you to get a JNDI link to a JMS factory or destination, the @Order
names it, and @Produces
allows you to implement it in the future:
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 type-safe @Inject
:
@Stateless public class ItemEJB { @Inject @Order private QueueConnectionFactory orderConnectionFactory; @Inject @Order private Queue orderQueue; ... }
The examples considered are quite simple: we create a field and then we can implement it. This is called a producing field. But sometimes you need a more complex business logic to create a bean, and we will call this the producer method. Let's look at another example. When you need to send a JMS message, you always embed a JMS factory and destination (a queue or topic), create a connection, then a session, and so on, until you send a message. Since this code is often repeated and may contain errors, why not put it somewhere in a separate class and create a session with it and other components necessary to send a message? This class might look like this:
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 @Resource
to get references to QueueConnectionFactory
and Queue
. For the same reasons that I described above, you may have several JMS factories and recipients, and you must distinguish them by their JNDI name. And since CDI does not allow you to specify a name at the deployment point, you must use @Resource
instead of @Inject
. The createOrderConnection
method uses a QueueConnectionFactory
to create a QueueConnection
and then QueueConnection
it (simultaneously calling it @Order
). If you look closely at the createOrderSession method, you will see that its parameter is the one created earlier for implementing the QueueConnection
and is used to create a QueueSession
. As a result, it is enough just to embed a JMS session into an external component without 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); } ... }
You can say: “Ok, I have an external class for rough work and creating a connection and session ... but who will close them? ” Indeed, someone must release these resources by closing them. And at this moment CDI shows a little more 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(); } }
In order to ask CDI to close a resource, you simply need to define a method similar to the one that created this resource ( createOrderSession(@Order QueueConnection conn)
creates a session, and closeOrderSession(@Order QueueConnection conn)
to close it) and add the @Disposes
annotation. CDI will dispose of resources in the correct order for you (first session, then connection). I did not mention this, but CDI creates and utilizes resources depending on their scope (resquest, session, application, conversation ...). But more about that another time. (Information is in Beginning Java EE 7 [ translation ] - approx. Lane.)
As you understood from my previous posts ( part 1 , part 2 ), CDI is about dependency injection. Until now, I have shown how to implement beans, but now you know how to embed anything (strings, primitives, entity managers, JMS factories ...) anywhere (POJO, servlets, EJB ...). You just need to create what you want to implement (using either producing fields or producing methods).
Download the code and tell us what you think about it.
Source: https://habr.com/ru/post/302010/