Part 2 of these series articles describe how to coding on MDB with EJB3.
MDB is the MVP(most valuable player) both in previous EJB version and EJB3. Although coding with MDB is simple in EJB2.x, EJB3 make it much more friendly to you. Let's get to the point.
Following is what you should know before write the first MDB in EJB3:
- MDB is just a POJO which must implement javax.jms.MessageListener directly or indirectly.
- MDB can not extends from another MDB. This should be fine, because each MDB should has its own destination or queue.
- Do not throw runtime exception which will cause the instance of this MDB to be destroyed.
- One no argument construct method like session bean does.
Let's see how everything goes on, first of all is the new version EJB3 MDB example source code:
1
package
com.ramon.expejb3.session.impl;
2
3
import
javax.annotation.PostConstruct;
4
import
javax.annotation.PreDestroy;
5
import
javax.annotation.Resource;
6
import
javax.ejb.ActivationConfigProperty;
7
import
javax.ejb.MessageDriven;
8
import
javax.jms.JMSException;
9
import
javax.jms.Message;
10
import
javax.jms.MessageListener;
11
import
javax.jms.Queue;
12
import
javax.jms.QueueConnection;
13
import
javax.jms.QueueConnectionFactory;
14
import
javax.jms.QueueSender;
15
import
javax.jms.QueueSession;
16
import
javax.jms.Session;
17
import
javax.jms.TextMessage;
18
19
@MessageDriven(
20
name
=
"
greetingSender
"
,
21
activationConfig
=
{
22
@ActivationConfigProperty(propertyName
=
"
destinationType
"
, propertyValue
=
"
javax.jms.Queue
"
),
23
@ActivationConfigProperty(propertyName
=
"
destination
"
, propertyValue
=
"
queue/ramonQueue
"
)
24
}
25
)
26
public
class
GreetingSenderMDB
implements
MessageListener {
27
28
@Resource(mappedName
=
"
java:/XAConnectionFactory
"
)
29
private
QueueConnectionFactory qconFactory;
30
31
@Resource(mappedName
=
"
queue/ramonRecoderQueue
"
)
32
private
Queue queue;
33
34
private
QueueConnection qcon;
35
36
private
QueueSession qsession;
37
38
private
QueueSender qsender;
39
40
private
TextMessage msg;
41
42
@PostConstruct
43
public
void
init() {
44
try
{
45
qcon
=
qconFactory.createQueueConnection();
46
qsession
=
qcon.createQueueSession(
false
, Session.AUTO_ACKNOWLEDGE);
47
qsender
=
qsession.createSender(queue);
48
msg
=
qsession.createTextMessage();
49
qcon.start();
50
System.out.println(
this
.getClass()
+
"
init done.
"
);
51
}
catch
(JMSException e) {
52
//
TODO Auto-generated catch block
53
e.printStackTrace();
54
}
55
}
56
57
private
void
send(String message)
throws
JMSException {
58
msg.setText(message);
59
qsender.send(msg);
60
}
61
62
public
void
onMessage(Message arg0) {
63
TextMessage txt
=
(TextMessage)arg0;
64
try
{
65
System.out.println(
"
Message '
"
+
txt.getText()
+
"
' has been received.
"
);
66
send(txt.getText());
67
System.out.println(
"
>>> Record msg for '
"
+
txt.getText()
+
"
' has been sent out.
"
);
68
}
catch
(JMSException e) {
69
//
TODO Auto-generated catch block
70
e.printStackTrace();
71
}
72
}
73
74
@PreDestroy
75
public
void
gc() {
76
try
{
77
qcon.close();
78
System.out.println(
"
GC for
"
+
this
.getClass()
+
"
.
"
);
79
}
catch
(JMSException e) {
80
//
TODO Auto-generated catch block
81
e.printStackTrace();
82
}
83
}
84
85
}
86
This is a smiple example, we use @MessageDriven to make this POJO a EJB3 MDB, the "destinationType" attribute make this MDB register itself to a JMS queue not a topic, the "destination" attribute tell the MDB where to listen for the queue, it's a jndi name of the queue you specified in your container. Other part of this code is also self-explanation, you should just focus on how to implement the MessageListener interface -- the onMessage() method.
For our example, the logic in onMessage() is simple, described as follow:
1. Receive message from client invocation.
2. Create a new message according to received message and then send it out to another JMS queue.
Let's see the source code:
1
public
void
onMessage(Message arg0) {
2
TextMessage txt
=
(TextMessage)arg0;
3
try
{
4
System.out.println(
"
Message '
"
+
txt.getText()
+
"
' has been received.
"
);
5
send(txt.getText());
6
System.out.println(
"
>>> Record msg for '
"
+
txt.getText()
+
"
' has been sent out.
"
);
7
}
catch
(JMSException e) {
8
//
TODO Auto-generated catch block
9
e.printStackTrace();
10
}
11
}
Yup, it's simple, just like the ordinary JMS listener implementation. Let's see how does the send() method in the line 5 get the ConnectionFactory and Queue object and then send message to another queue. EJB3 give us an anotation named "Resource", with this anotation container can inject resource such as DataSourceConnectionFactory, JMSConnectionFactory... into our bean instance, we use this anotation to inject JMSConnectionFactory and JMSQueue, see the code snatch:
1
@Resource(mappedName
=
"
java:/XAConnectionFactory
"
)
2
private
QueueConnectionFactory qconFactory;
3
4
@Resource(mappedName
=
"
queue/ramonRecoderQueue
"
)
5
private
Queue queue;
With this anotation we can remove the boring JNDI lookup code, that's really great, because I always copy and paste for JNDI lookup code:) What important is that you should use the "
mappedName" attribute instead of the "name" attribute when you want to lookup some JNDI, because the "name" attribute always triger an "env" prefix before the actual JNDI name you specified.
Other part of this code is simple, so I just paste the code here, GreetingRecordMDB.java:
1
package
com.ramon.expejb3.session.impl;
2
3
import
javax.ejb.ActivationConfigProperty;
4
import
javax.ejb.MessageDriven;
5
import
javax.jms.JMSException;
6
import
javax.jms.Message;
7
import
javax.jms.MessageListener;
8
import
javax.jms.TextMessage;
9
10
@MessageDriven(
11
name
=
"
greetingRecoder
"
,
12
activationConfig
=
{
13
@ActivationConfigProperty(propertyName
=
"
destinationType
"
, propertyValue
=
"
javax.jms.Queue
"
),
14
@ActivationConfigProperty(propertyName
=
"
destination
"
, propertyValue
=
"
queue/ramonRecoderQueue
"
)
15
}
16
)
17
public
class
GreetingRecordMDB
implements
MessageListener {
18
19
public
void
onMessage(Message arg0) {
20
TextMessage msg
=
(TextMessage)arg0;
21
try
{
22
String name
=
msg.getText();
23
System.out.println(name
+
"
has been recorded.
"
);
24
}
catch
(JMSException e) {
25
//
TODO Auto-generated catch block
26
e.printStackTrace();
27
}
28
}
29
30
}
31
The client side code GreetingSenderMDBTest.java:
1
package
com.ramon.expejb3.session.impl;
2
3
import
javax.jms.JMSException;
4
import
javax.jms.Queue;
5
import
javax.jms.QueueConnection;
6
import
javax.jms.QueueConnectionFactory;
7
import
javax.jms.QueueSender;
8
import
javax.jms.QueueSession;
9
import
javax.jms.Session;
10
import
javax.jms.TextMessage;
11
import
javax.naming.Context;
12
13
import
com.ramon.expejb3.session.ExpEJB3BaseTestCase;
14
15
public
class
GreetingSenderMDBTest
extends
ExpEJB3BaseTestCase {
16
17
private
QueueConnectionFactory qconFactory;
18
private
QueueConnection qcon;
19
private
QueueSession qsession;
20
private
QueueSender qsender;
21
private
Queue queue;
22
private
TextMessage msg;
23
24
@Override
25
protected
void
setUp()
throws
Exception {
26
//
TODO Auto-generated method stub
27
super
.setUp();
28
Context ctx
=
this
.getContext();
29
qconFactory
=
(QueueConnectionFactory) ctx
30
.lookup(
"
java:/XAConnectionFactory
"
);
31
qcon
=
qconFactory.createQueueConnection();
32
qsession
=
qcon.createQueueSession(
false
, Session.AUTO_ACKNOWLEDGE);
33
queue
=
(Queue) ctx.lookup(
"
queue/ramonQueue
"
);
34
qsender
=
qsession.createSender(queue);
35
msg
=
qsession.createTextMessage();
36
qcon.start();
37
}
38
39
public
void
send(String message)
throws
JMSException {
40
msg.setText(message);
41
qsender.send(msg);
42
}
43
44
public
void
close()
throws
JMSException {
45
qcon.close();
46
}
47
48
@Override
49
protected
void
tearDown()
throws
Exception {
50
//
TODO Auto-generated method stub
51
super
.tearDown();
52
}
53
54
public
void
testOnMessage() {
55
try
{
56
for
(
int
i
=
0
; i
<
5
; i
++
) {
57
String msg
=
"
Ramon
"
+
i;
58
send(msg);
59
System.out.println(
"
send msg:
"
+
msg);
60
}
61
send(
"
end
"
);
62
}
catch
(JMSException e) {
63
//
TODO Auto-generated catch block
64
e.printStackTrace();
65
}
finally
{
66
try
{
67
close();
68
}
catch
(JMSException e) {
69
//
TODO Auto-generated catch block
70
e.printStackTrace();
71
}
72
}
73
}
74
75
}
76
The JMS configuration snatch in file "jbossmq-destinations-service.xml" of Jboss server:
1
<
mbean
code
="org.jboss.mq.server.jmx.Queue"
2
name
="jboss.mq.destination:service=Queue,name=ramonQueue"
>
3
<
depends
optional-attribute-name
="DestinationManager"
>
jboss.mq:service=DestinationManager
</
depends
>
4
<
depends
optional-attribute-name
="SecurityManager"
>
jboss.mq:service=SecurityManager
</
depends
>
5
<
attribute
name
="MessageCounterHistoryDayLimit"
>
-1
</
attribute
>
6
<
attribute
name
="SecurityConf"
>
7
<
security
>
8
<
role
name
="guest"
read
="true"
write
="true"
/>
9
<
role
name
="publisher"
read
="true"
write
="true"
create
="false"
/>
10
<
role
name
="noacc"
read
="false"
write
="false"
create
="false"
/>
11
</
security
>
12
</
attribute
>
13
</
mbean
>
14
15
<
mbean
code
="org.jboss.mq.server.jmx.Queue"
16
name
="jboss.mq.destination:service=Queue,name=ramonRecoderQueue"
>
17
<
depends
optional-attribute-name
="DestinationManager"
>
jboss.mq:service=DestinationManager
</
depends
>
18
<
depends
optional-attribute-name
="SecurityManager"
>
jboss.mq:service=SecurityManager
</
depends
>
19
<
attribute
name
="MessageCounterHistoryDayLimit"
>
-1
</
attribute
>
20
<
attribute
name
="SecurityConf"
>
21
<
security
>
22
<
role
name
="guest"
read
="true"
write
="true"
/>
23
<
role
name
="publisher"
read
="true"
write
="true"
create
="false"
/>
24
<
role
name
="noacc"
read
="false"
write
="false"
create
="false"
/>
25
</
security
>
26
</
attribute
>
27
</
mbean
>