(7)EJB3.0 EJB开发消息驱动bean 【经典】 JMS

一: Java消息服务(Java Message Service)


二:jms中的消息

消息传递系统的中心就是消息。一条 Message 由三个部分组成:
            头(header),属性(property)和主体(body)。

消息有下面几种类型,他们都是派生自 Message 接口。
          StreamMessage:一种主体中包含 Java 基元值流的消息。其填充和读取均按顺序进行。
          MapMessage:一种主体中包含一组名-值对的消息。没有定义条目顺序。
          TextMessage:一种主体中包含 Java字符串的消息(例如,XML消息)。
          ObjectMessage:一种主体中包含序列化 Java 对象的消息。
          BytesMessage:一种主体中包含连续字节流的消息。


    消息的传递模型
             JMS 支持两种消息传递模型:点对点(point-to-point,简称 PTP)和发布/订阅(publish/subscribe,简称 pub/sub)。这两种消息传递模型非常相似,但有以下区别:

             PTP 消息传递模型规定了一条消息只能传递给一个接收方。 采用javax.jms.Queue 表示。
             Pub/sub 消息传递模型允许一条消息传递给多个接收方。采用javax.jms.Topic表示

            这两种模型都通过扩展公用基类来实现。例如:javax.jms.Queue 和javax.jms.Topic 都扩展自javax.jms.Destination 类。

点对点模型:

发布或订阅模式:

三:   配置目标地址

          开始JMS编程前,我们需要先配置消息到达的目标地址(Destination),因为只有目标地址存在了,我们才能发送消息到这个地址。由于每个应用服务器关于目标地址的配置方式都有所不同,下面以jboss为例,配置一个queue类型的目标地址。

本文用的配置地址:将这个文件在 jboss中发布。

<?xml version="1.0" encoding="UTF-8"?>
<server>
  <mbean code="org.jboss.mq.server.jmx.Queue"
         name="jboss.mq.destination:service=Queue,name=itmQueue">
		    <attribute name="JNDIName">queue/itmQueue</attribute>
		    <depends optional-attribute-name="DestinationManager">jboss.mq:service=DestinationManager</depends>
  </mbean>
  
  
  <mbean code="org.jboss.mq.server.jmx.Topic"
         name="jboss.mq.destination:service=Topic,name=itmTopic">  
    <attribute name="JNDIName">topic/itmTopic</attribute>
    <depends optional-attribute-name="DestinationManager">jboss.mq:service=DestinationManager</depends>
  </mbean>
</server> 

     目标地址itmQueue发布前,必须先发布目标地址的管理器DestinationManager,所以采用依赖元素depends申明这个依赖。

      它发布的过程,和数据源和EJB都是一样的。只需把它拷贝到JBoss的server/default/deploy目录下就行了。 拷贝完后就可以在JBoss控制台看到信息:
[itcastQueue] Bound to JNDI name: queue/itmQueue

     说明目标地址itcastQueue绑定了queue/itcastQueue这个监单名称里面去。
    queue/itcastQueue这个监单名称前面并没有java:,也就是说这个名称是在全局命名空间里面的,它可以被外部应用进行访问。
     发布完后也可以进入JBoss管理后台,找到jboss.mq.destination这个栏目,找到刚才所发布的目标地址 name=itcastQueue.service=Queue,点击进去可以看到里面的一些信息。


    四:当目标地址建立好了之后,就可以进行消息的发送。

    在java类中发送消息,一般发送消息有以下步骤:

          (1) 得到一个JNDI初始化上下文(Context)
              InitialContext ctx = new InitialContext();
          (2) 根据上下文查找一个连接工厂 QueueConnectionFactory 。该连接工厂是由JMS提供的,不需我们自己创建,每个厂商都为它绑定了一个全局JNDI,我们通过它的全局JNDI便可获取它;
           QueueConnectionFactory factory = (QueueConnectionFactory) ctx.lookup("QueueConnectionFactory");
          (3) 从连接工厂得到一个连接 QueueConnection
                         conn = factory.createQueueConnection();
           (4) 通过连接来建立一个会话(Session);
                     session = conn.createQueueSession(false, QueueSession.AUTO_ACKNOWLEDGE);
                   这句代码意思是:建立不需要事务的并且能自动确认消息已接收的会话。
           (5) 查找目标地址:
                     例子对应代码:Destination destination = (Destination ) ctx.lookup("queue/foshanshop");
           (6) 根据会话以及目标地址来建立消息生产者MessageProducer (QueueSender和TopicPublisher都扩展自MessageProducer接口)


例子对应代码:

MessageProducer producer = session.createProducer(destination); 
TextMessage msg = session.createTextMessage("您好,这是我的第一个消息驱动Bean"); 
producer.send(msg); 


第一种方式:

package cn.itm.app;

import javax.jms.Destination;
import javax.jms.MessageProducer;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSession;
import javax.jms.TextMessage;
import javax.naming.InitialContext;

public class QueueSender {

	public static void main(String[] args){
		try { 
            InitialContext ctx = new InitialContext(); 
            // 查找 QUeue类型的连接工厂。
            QueueConnectionFactory factory = (QueueConnectionFactory) ctx 
                    .lookup("QueueConnectionFactory"); 
            QueueConnection conn = factory.createQueueConnection(); 
            // 创建一个 到该地址的会话, 第二个参数:消息的确认模式,这里用自动的确认模式。
            QueueSession session = conn.createQueueSession(false, 
                    QueueSession.AUTO_ACKNOWLEDGE); 
            // 目标地址的连接名称。
            Destination destination = (Destination) ctx 
                    .lookup("queue/itmQueue"); 
            // 建立消息的发送者。
            MessageProducer producer = session.createProducer(destination); 
			TextMessage msg = session.createTextMessage("您好,这是我的第一个消息驱动Bean"); 
            producer.send(msg); 
//            System.out.println(msg);
            
            session.close(); 
            conn.close(); 
        } catch (Exception e) { 
            e.printStackTrace(); 
        } 
	}
}



运行QueueSender,往目标地址发送一条消息。
    消息发送完后,就可以编写消息的接收者。

    采用消息驱动Bean (Message Driven Bean)接收消息。

     消息驱动Bean(MDB)是设计用来专门处理基于消息请求的组件。它和无状态Session Bean一样也使用了实例池技术,容器可以使用一定数量的bean实例并发处理成百上千个JMS消息。正因为MDB具有处理大量并发消息的能力,所以非常适合应用在一些消息网关产品。如果一个业务执行的时间很长,而执行结果无需实时向用户反馈时,也很适合使用MDB。如订单成功后给用户发送一封电子邮件或发送一条短信等。

    一个MDB通常要实现MessageListener接口,该接口定义了onMessage()方法。Bean通过它来处理收到的JMS消息。

package javax.jms; 
public interface MessageListener { 
    public void onMessage(Message message); 
} 

         当容器检测到bean守候的目标地址有消息到达时,容器调用onMessage()方法,将消息作为参数传入MDB。MDB在onMessage()中决定如何处理该消息。你可以使用注释指定MDB监听哪一个目标地址(Destination)。当MDB部署时,容器将读取其中的配置信息。



    消息驱动Bean (Message Driven Bean)

@MessageDriven(activationConfig = 
{ 
  @ActivationConfigProperty(propertyName="destinationType", 
    propertyValue="javax.jms.Queue"), 
  @ActivationConfigProperty(propertyName="destination", 
    propertyValue="queue/foshanshop"), 
  @ActivationConfigProperty(propertyName="acknowledgeMode", 
    propertyValue="Auto-acknowledge") 
}) 
public class PrintBean implements MessageListener { 
    public void onMessage(Message msg) {} 
} 



package cn.itm.message;

import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

@MessageDriven(activationConfig = {
		@ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
		@ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/itmQueue"),
		
		// 当我们使用的是容器来管理事务的时候,acknowledgeMode这个属性设置也没什么意义, 
		// 这里可以省略掉 自动确认模式,也可以不要,。
		@ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge") }) 
public class MessageDrivenBean implements MessageListener {

	@Override
	public void onMessage(Message message) {
		/**
		 * 当容器检测到MessageDrivenBean这个消息驱动bean它所监听的目标地址有消息到达的时候,
		 * 它会把消息作为输入参数,传入消息驱动bean的onMessage方法,在方法里面就可以对消息进行处理 那么容器它是怎么知道消息驱动bean
		 * 它所监听的目标地址,这时候需要用注解来告诉容器: 这个消息驱动bean它监听的是哪个 目标地址,还有这个目标地址的类型
		 */

		// 因为发送的消息 是文本类型:
		TextMessage msg = (TextMessage) message; 
        try { 
            System.out.println(msg.getText()); 
        } catch (JMSException e) { 
            e.printStackTrace(); 
        } 
	}

}

jndi:

java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.provider.url=localhost\:1099


消息驱动bean开发完后,就要对它进行打包,部署。

    编写build.xml,然后把消息驱动bean发布到JBoss中。当消息驱动bean成功发布完之后,正常情况下会在控制台中把刚才发送的消息打印到控制台中。

    消息驱动bean一旦布署到JBoss中的时候,容器就会根据bean所配置的信息到目标地址获取到消息,然后传入到onMessage方法里面去,执行方法里面的内容。

     通过这个例子也可以发现,JMS编程可以允许发送者和接收方可以不同时在线。也就是说发送消息的时候,消息接收方可以不在线。只要消息接收方登录到系统的时候,它也可以获取到消息。而且消息接收方和消息发送方它们之间并没有代码的直接关联。两者之间实现很好的解耦。

   

build.xml

<?xml version="1.0" encoding="UTF-8"?>  

<!-- name中指的是项目名称。basedir指的是与build.xml的同级目录 -->  
<!-- 
(1)basedir 代表  项目所在的路径。

-->
<project name="MessageDriverBean" basedir=".">  
	<!-- 项目源文件 所在的目录。-->
    <property name="src.dir" value="${basedir}\src" />  
    <!-- 指向环境变量中系统变量 -->  
    <property environment="env" />  
    <!-- 指向系统变量中的JBOSS_HOME变量 ,可以得知JBOSS_HOME的安装地址 -->  
    <property name="jboss.home" value="${env.JBOSS_HOME}" />  
    <property name="jboss.server.config" value="default" />  
	<!-- class文件存在的目录 -->
    <property name="build.dir" value="${basedir}\build" />  
  
    <path id="build.classpath">  
    	<!-- 包含 client路径下的所有  jar文件-->
        <fileset dir="${jboss.home}\client">  
            <include name="*.jar" />  
        </fileset>  
        <pathelement location="${build.dir}" />  
    </path>  
  
    	
    <target name="prepare">  
        <delete dir="${build.dir}" />  
        <mkdir dir="${build.dir}" />  
    </target>  
        	
    <!-- depends="prepare" 确保 先执行上面的代码,在执行下面的代码  -->
  
    <target name="compile" depends="prepare" description="编译">  
    	<!-- class 存放到 destdir这个目录。在调用javac 这个目录就必须建立起来-->
        <javac srcdir="${src.dir}" destdir="${build.dir}" >  
            <classpath refid="build.classpath" />  
        </javac>  
    </target>  
  
    <target name="ejbjar" depends="compile" description="创建EJB发布包">  
        <jar jarfile="${basedir}\${ant.project.name}.jar">  
            <fileset dir="${build.dir}">  
                <include name="**/*.class" />  
            </fileset>  
        </jar>  
    </target>  
  
    <target name="deploy" depends="ejbjar" description="发布ejb">  
        <copy file="${basedir}\${ant.project.name}.jar" todir="${jboss.home}\server\${jboss.server.config}\deploy" />  
    </target>  
  
    <target name="undeploy" description="卸载ejb">  
        <delete  
            file="${jboss.home}\server\${jboss.server.config}\deploy\${ant.project.name}.jar" />  
    </target>  
</project>   


          关于Queue类型的发送和接收就已经介绍完了,现在开始学习下关于topic类型的消息发送和接收。也就是发布/订阅传递模型的消息发送和接受。在这种模型里,消息是可以被多个接收方进行接受的。

第二种方式:发布/订阅传递模型的消息发送和接受



package cn.itm.app;

import javax.jms.Destination;
import javax.jms.MessageProducer;
import javax.jms.QueueSession;
import javax.jms.TextMessage;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicSession;
import javax.naming.InitialContext;

public class TopicSender {

	public static void main(String[] args){
		try { 
            InitialContext ctx = new InitialContext(); 
            TopicConnectionFactory factory = (TopicConnectionFactory) ctx 
                    .lookup("TopicConnectionFactory"); 
            TopicConnection conn = factory.createTopicConnection(); 
            TopicSession session = conn.createTopicSession(false, 
                    QueueSession.AUTO_ACKNOWLEDGE); 
            Destination destination = (Destination) ctx 
                    .lookup("topic/itmTopic"); 
            
            MessageProducer producer = session.createProducer(destination); 
            TextMessage msg = session.createTextMessage("您好,这是我的第一个topic消息"); 
            producer.send(msg); 
            session.close(); 
            conn.close(); 
            System.out.println("发送完"); 
        } catch (Exception e) { 
            e.printStackTrace(); 
        } 
	}
}

package cn.itm.message;

import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

@MessageDriven(activationConfig = { 
        @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Topic"), 
        @ActivationConfigProperty(propertyName = "destination", propertyValue = "topic/itmTopic")
}) 
        
public class ReceiveBean implements MessageListener { 
 
    @Override 
    public void onMessage(Message message) { 
        TextMessage msg = (TextMessage) message; 
        try { 
            System.out.println(this.getClass() + msg.getText()); 
        } catch (JMSException e) { 
            e.printStackTrace(); 
        } 
    } 
 
}

package cn.itm.message;

import javax.ejb.ActivationConfigProperty; 
import javax.ejb.MessageDriven; 
import javax.jms.JMSException; 
import javax.jms.Message; 
import javax.jms.MessageListener; 
import javax.jms.TextMessage; 
 
@MessageDriven(activationConfig = { 
        @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Topic"), 
        @ActivationConfigProperty(propertyName = "destination", propertyValue = "topic/itmTopic")
        
}) 
public class ReceiveOtherBean implements MessageListener { 
 
    @Override 
    public void onMessage(Message message) { 
        TextMessage msg = (TextMessage) message; 
        try { 
            System.out.println(this.getClass() + msg.getText()); 
        } catch (JMSException e) { 
            e.printStackTrace(); 
        } 
    } 
 
}


操作步骤:

首先启动jboss,接着把itm-service.xml 发布。

接着:build.xml发布。




     这两个消息驱动 bean都开发完了,它们两个都会从目标地址里面获取到消息,现在对它进行发布。 但是在控制台并没有看到刚才发送的topic消息,那是因为对于topic类型的消息,如果当时接收方没有监听topic类型的消息的话,那么它就获取不到消息。尽管我们把它布署到JBoss中,因为它当时并没有监听topic这个目标地址,所以它是无法得到消息的。
    现在执行下TopicSender.java,就会看见这两个消息驱动bean获取到消息了。控制台输出:

INFO [STDOUT] class cn.itm.message.ReceiveOtherBean您好,这是我的第一个topic消息
INFO [STDOUT] class cn.itm.message.ReceiveBean您好,这是我的第一个topic消息


    也就证明,对于发布/订阅传递模型,多个接收方都可以得到这条消息

    回头再测试下Queue类型的发送,运行QueueSender.java,对于queue类型的发送,它是一个消息只允许一个接收方进行接收。


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值