所有EJB3.0开发商都必须提供一个JMS provider的实现,JMS provider对于message-driven bean而言绝对是必须的。JMS是一套用于访问企业消息系统的开发商中立的API。JMS在其中扮演的角色与JDBC很相似:JDBC提供一套用于访问各种不同关系数据库的公共API,JMS也提供了独立于特定厂商的企业消息系统访问方式。JMS使用消息服务(messaging service)来帮助enterprise bean发送信息,消息服务有时也称为消息代理。JMS是专门为不同java应用系统之间传递各类信息而设计的。

使用JMS重新实现TravelAgentEJB 
修改11章开发的TravelAgentEJB,使其能在预订完成时利用JMS来通知其他java应用程序。 
以下相关类(如Connection)属于JSM API 
@Resource(mappedName=”connectionFactoryNameGoesHere”) 
private ConnectionFactory connectionFactory;//注入 
@Resource(mappedName=”TicketTopic”)//注入 
private Topic topic; 
@Remove 
public TicketDO bookPassage(CreditCardDO card,double price){ 
    ……….. 
    Connection connect= connectionFactory.createConnection();//创建JMS provider连接 
    Session session=connect.createSession(true,0);//创建Session对象 
    // createSession(boolean transacted,int acknowledgeMode); 
    //这两个参数会被容器忽略,EJB规范建议分别设置为true和0 
    MessageProducer producer=session.createProducer(topic);//创建消息生产者 
    TextMessage textMsg=session.createTextMessage();//创建文本消息 
    textMsg.setText(ticketDescription); 
    producer.send(textMsg); 
    connect.close(); 
    ……….. 
}

ConnectionFactory和Topic 
为了发送JMS,我们需要一个指向JMS provider的连接和一个消息目标地址。使用JMS连接工厂(JMS connection factory)可以获得JMS provider连接;而消息目标地址则可以由一个Topic(主题)对象来表示,也可以由queue(队列)来表示。

消息的类型 
在JMS中,消息是一个java对象,它包含两个部分:消息头和消息正文。JMS API定义了多种不同的消息类型(TextMessage、MapMessage、ObjectMessage、StreamMessage和BytesMessage),并提供了不发送和接收这些消息的方法。 
使用MapMessage的例子: 
MapMessage mapMsg=session.createMapMessage(); 
mapMsg.setInt(“CustomerID”,ticket.customerID.intValue()); 
mapMsg.setInt(“Price”,ticket.price); 
producer.send(mapMsg); 
使用ObjectMessage的例子: 
ObjectMessage objectMsg=session.createObjectMessage(); 
objctMsg.setObject(ticket); 
producer.send(objectMsg);

JMS应用客户端 
用于接收和处理乘客的预订信息,它在收到消息之后将每和票据的信息输出到屏幕 
public class JmsClient_TicketConsumer implements javax.jms.MessageListener{//监听消息

public static void main(String [] args) throws Exception{ 
      new JmsClient_TicketConsumer(); 
      while(true) { Thread.sleep (10000); }     
   } 
  
   public JmsClient_TicketConsumer () throws Exception{ 
      Context jndiContext = getInitialContext (); 
     
      ConnectionFactory factory = (ConnectionFactory) 
      jndiContext.lookup ("ConnectionFactory"); 
     
      Queue ticketQueue = (Queue) 
      jndiContext.lookup ("queue/titan-TicketQueue"); 
     
      Connection connect = factory.createConnection();     
      Session session = connect.createSession(false,Session.AUTO_ACKNOWLEDGE);     
      MessageConsumer receiver = session.createConsumer(ticketQueue); //消息消费者 
      receiver.setMessageListener(this);

connect.start ();//开始监听是否有信息到达 
   } 
  
   public void onMessage (Message message){//每当有新消息送达,该方法被调用 
      try{ 
         ObjectMessage objMsg = (ObjectMessage)message;//强制转换成发送信息时的信息类型 
         TicketDO ticket = (TicketDO)objMsg.getObject (); 
         System.out.println ("********************************"); 
         System.out.println (ticket); 
         System.out.println ("********************************"); 
      } catch (JMSException displayed){ 
         displayed.printStackTrace (); 
      } 
   } 
  
   public static Context getInitialContext ()throws javax.naming.NamingException{ 
    Properties env=new Properties(); 
    env.put(…….);//使用开发商专有属性来配置InitialContext,也可以在jndi.properties文件中设置 
          return new InitialContext (env); 
   } 
}

JMS的异步性 
JMS消息处理的一个主要优势它的异步性。当JMS客户端发送消息时,它并不等待回应,只是将消息发送到消息路由,由消息路由来负责将消息传递给其他客户端。而使用java RMI和JAX-RPC时,客户端以同步方式调用bean方法,当前线程会被阻塞直到方法执行完毕为止。当需要与其他应用程序时行通信时,RMI的种种不足使JMS成为一种极富吸引力的替代方案。同时发送方的事务与安全上下文是不会传播到接收方的,不过JMS客户端和JMS provider可以共同拥有一个分布式事务。

JMS的消息传递模型 
JMS provider提供了两种消息传递模型:发布-订阅模型(publicsh-and-subscribe,pub/sub)和点对点模型(point-to-point,p2p),JMS规范将其称为消息传递域(messaging domain)。通过情况下,发布-订阅主要用于一对多的消息广播,而点对点则用于一对一的消息传递。

发布-订阅 
在发布-订阅消息传递模型中,生产者可以通过一个被称为主题的虚拟通道,将消息发送给多个消费者。任何发送到某一主题的消息都会被转送给订阅了该主题的所有消费者。此模型基本上是一个推模型(push-based model),消息会自动广播,消费者无须通过主动请求或轮询主题方式获得新的消息。

点对点 
点对点的消息传递模型允许JMS客户端通过一个被称为队列的虚拟通道,以同步或异步的方式收发消息。它是一个拉模型(pull-based model)或轮询模型,在此类模型中,消息浊自动推送给客户端的,而是由客户端从队列中获得。一个队列可以有多个接收者,但是每条消息只能由一个接收者接收。

应该选用哪一种消息传递模型 
两者实际上都可以使用“推”或“拉”的方式。JMS提供了一套同时适用于这两种模型的API。采用pub/sub模型所能完成的工作几乎都可以采用点对点来完成,反之亦然。某些情况下,这取决于个人偏好,或熟悉的程度。利用pub/sub模式,JMS通过使用专门的主题,可以先对消息进行过滤,而后再分发给消费者;而p2p保证每条消息只由一个消费者处理,当消息需要被分别处理,同时又要保证消息的先后顺序时,这一能力尤为重要。

Session Bean不应该接收消息 
Session Bean响应来自EJB客户端的调用,但是它们不能像message-driven bean那样响应JMS消息(消息驱动,也就是接收到消息时,一些方法自动执行),我们可以编写一个业务方法中消费JMS消息的session bean,但是这个方法必须由EJB客户端来调用。如 
Connection connect = factory.createConnection();     
Session session = connect.createSession(true,0);     
MessageConsumer receiver = session.createConsumer(queue);      //消息消费者 
TextMessage textMsg=( TextMessage) receiver.receive();

connect.close(); 
可见使用session bean编写代码来接收信息是不明智的。

了解更多有关JMS的内容 
JMS代表了分布式计算环境下一种功能强大的范式,以上对其的简介的目的是为讨论message-driven bean作铺垫的。

基于JMS的Message-Driven Bean 
MDB是无状态的,事务感知的服务器组件,用于处理通过JMS进行递送的异步消息。MDB负责处理消息,而容器则负责管理包括事务、安全、资源、并发和消息确认在内的组件外环境。MDB是用来响应异步消息的,它不有远程或本地接口(方法自动调用,而非由客户端调用)

ReservationProcessor EJB 
ReservationProcessor EJB是一个message-driven bean,其作用是接收代表新到预订通知的JMS消息,当它收到消息时,会创建一个新的Reservatin EJB,然后使用ProcessPayment EJB处理支付,并送出一张票据,它是TravelAgent EJB的一个自动化版本。源码如下: 
@MessageDriven(activationConfig={//含义见下一节 
       @ActivationConfigProperty(propertyName="destinationType", 
                             propertyValue="javax.jms.Queue"), 
       @ActivationConfigProperty(propertyName="acknowledgeMode", 
                             propertyValue="Auto-acknowledge")}) 
public class ReservationProcessorBean implements javax.jms.MessageListener { 
//MessageListener 监听器接口只定义了onMessage()方法 
   @PersistenceContext(unitName= "titanDB")//注入 
   private EntityManager em;

@EJB 
   private ProcessPaymentLocal process;

@Resource(mappedName="ConnectionFactory") 
   private ConnectionFactory connectionFactory;

public void onMessage(Message message) { 
   //类似于TravelAgentEJB的bookPassage()方法,此方法会自动执行,所以称为自动化 
      System.out.println("Received Message"); 
      try { 
        //从收到的消息中取出数据,类似于得到请求参数 
        MapMessage reservationMsg = (MapMessage)message;     
         int customerPk = reservationMsg.getInt("CustomerID"); 
         int cruisePk = reservationMsg.getInt("CruiseID"); 
         int cabinPk = reservationMsg.getInt("CabinID"); 
    
         double price = reservationMsg.getDouble("Price"); 
    
         // 获取信用卡 
         Date expirationDate =  new Date(reservationMsg.getLong("CreditCardExpDate")); 
         String cardNumber = reservationMsg.getString("CreditCardNum"); 
         String cardType = reservationMsg.getString("CreditCardType"); 
         CreditCardDO card = new CreditCardDO(cardNumber, expirationDate, cardType); 
        
         Customer customer = em.find(Customer.class, customerPk); 
         Cruise cruise = em.find(Cruise.class, cruisePk); 
         Cabin cabin = em.find(Cabin.class, cabinPk); 
         //创建代表舱位预订记录的Reservation 
         Reservation reservation = new Reservation(customer, cruise, cabin, price, 
                 new Date()); 
         em.persist(reservation); 
            //处理信用卡支付 
         process.byCredit(customer, card, price); 
    
         TicketDO ticket = new TicketDO(customer,cruise,cabin,price); 
         deliverTicket(reservationMsg, ticket);//发送信息给客户端,见下文 
      } 
      catch(Exception e) { 
         throw new EJBException(e); 
      } 
   } 
    //MDB也可以使用JMS来发送信息 
   public void deliverTicket (MapMessage reservationMsg, TicketDO ticket) throws JMSException, NamingException{ 
      Queue queue = (Queue)reservationMsg.getJMSReplyTo();//从接收的信息中得到回复消息目标地址 
      Connection connect = connectionFactory.createConnection(); 
      Session session = connect.createSession(true,0); 
      MessageProducer sender = session.createProducer(queue); 
      ObjectMessage message = session.createObjectMessage(); 
      message.setObject(ticket);//把ticket发送到客户端 
      sender.send(message); 
      connect.close(); 
   } 
}

@MessageDriven 
MDB使用@javax.ejb.MessageDriven注解。

@ActivationConfigProperty 
@Message-Driven.activationConfig()属性接受一组@ ActivationConfigProperty注解作为参数,用一组简单的“名/值”对来描述MDB配置.EJB3.0为基于JMS的message-driven bean定义了一组固定属性,它们是:acknowledgeMode、messageSelector、destinationType和subscriptionDurability

消息选择器(message Selector) 
消息选择器让MDB选择性地接收来自特定主题或队列的消息,它以SQL-92条件表达式语法(SQL中的where子句)的一个子集为基础的,如: 
@ActivationConfigProperty(propertyName="messageSelector", propertyValue="MessageFormat=’Versin 3.4’") 
JMS消息生产者通过以下代码设置Message的MessageFormat属性 
Message message=session.createMapMessage(); 
message.setStringProperty(“MessageFormat”,”Versin 3.4”);

确认模式 
JMS确认是指JMS客户端通知JMS provider确认消息已经到达的一种机制。在EJB中,收到信息后发送确认是MDB容器的职责。 
@ActivationConfigProperty(propertyName="acknowledgeMode", propertyValue="Auto-acknowledge") 
确认模式有两个取值:Auto-acknowledge和Dups-ok-acknowledge。前者告诉容器,在消息交给MDB实例处理后,容器应该立即向JMS provider发送确认。后者容器任何时间发送确认都是可接受的,由于MDB容器可能延迟很长时间才发送确认,JMS provider可能认为容器没有收到消息而重发信息,所以使用这种模式时,MDB必须能够正确处理重复消息。在实际上大多数确认模式都会被忽略,如果事务由容器来管理,确认行为是在事务上下文中执行:如果事务成功,消息就会被确认;如果事务失败,消息就不会被确认

订阅的持续性 
当基于JMS的MDB使用javax.jms.Topic时,我们必须在部署描述文件中声明:这一订阅是Durable或NonDurable。如果EJB容器由于某些原因失去与JMS provider的连接,Durable表示,在此期间Bean本应收到的消息是不会丢失的,当容器重新获得连接后,这些消息会由容器发送给MDB,这种行为称为保存-转发消息传递机制。声明为NonDurable的MDB,会丢失连接期间本应收到的消息,所以它可以改善JMS provier的性能,但降低MDB的可靠性。声明方式: 
@ActivationConfigProperty(propertyName="subscriptionDurability", propertyValue="Durable")

ReservationProcessor EJB的部署描述文件 
<?xml version="1.0"?> 
<ejb-jar …...> 
   <enterprise-beans> 
      <message-driven> 
             <ejb-name>ReservationProcessorBean</ejb-name> 
             <ejb-class>com.titan.reservationprocessor. ReservationProcessorBean </ejb-class> 
             <message -type>javax.jms.MessageListener</ message -type> 
    <transacton-type>Container</transacton-type > 
    <message-destination-type>javax.jms.Queue</message-destination-type> 
    <activation-config> 
        <activation-property> 
            <activation-config-property-name>destinationType</activation-config-property-name> 
            <activation-config-property-value>javax.jms.Queue</activation-config-property-value> 
        <activation-property> 
        ………… 
<activation-config> 
             <ejb-local-ref> 
                 <ejb-ref-name>ejb/PaymentProcessor</ejb-ref-name> 
                 <ejb-ref-type>Session</ejb-ref-type> 
                 <local>com.titan.processpayment.ProcessPaymentLocal</local> 
                 <injection-target> 
                        <injection-target-class> 
                               com.titan.reservationprocessor. ReservationProcessorBean 
                        </injection-target-class> 
                    <injection-target-name>process</injection-target-name> 
                 </injection-target> 
          </ejb-local-ref> 
          <persistence-context-ref> 
                 <persistence-context-ref-name>persistence/titan</persistence-context-ref-name> 
                <persistence-unit-name>titan</persistence-unit-name> 
                 <injection-target> 
                         <injection-target-class> 
                                com.titan.reservationprocessor. ReservationProcessorBean 
                         </injection-target-class> 
                         <injection-target-name>em</injection-target-name> 
                 </injection-target> 
              </persistence-context-ref> 
    <resource-ref> 
                 <res-ref-name>jms/ConnectionFactory</res-ref-name> 
                 <res-type>javax.jms.ConnectonFactory </res-type> 
                 <res-auth>Container</res-auth> 
                 <mapped-name>ConnectionFactory</mapped-name> 
                 <injection-target> 
                        <injection-target-class> 
                               com.titan.reservationprocessor. ReservationProcessorBean 
                        </injection-target-class> 
                        <injection-target-name>connectionFactory</injection-target-name> 
                 </injection-target> 
          </resource-ref> 
       </message-driven> 
   </enterprise-beans> 
</ejb-jar>

ReservationProcessor客户端 
发送预订消息的客户端 
代码实现见前面的章节“使用JMS重新实现TravelAgentEJB”

接收票据信息的客户端 
代码实现见前面的章节“JMS应用客户端” 
             
Message-driven bean的生命週期 
MDB实例的生命周期有两个状态:Does Not Exist和Method-Ready Pool. 
Does Not Exist状态 
MDB没有被实例化。

Method-Ready Pool状态 
和stateless session bean对应状态相似,也使用了实例池。EJB服务器首次启动时,它可能创建一些MDB实例,当MDB实例不足以处理发来的消息时,容器会调用bean class的Class.newInstance()方法创建一个新的bean实例,然后根据bean的元数据或XML声明注入相关资源,如果bean定义了PostConstruction回调函数,容器最后会调用该方法。当有消息送递MDB时,容器会将这条消息委派给任何一个处于MRP状态的bean实例。当服务器移除一部分内存对象以减少处于MRP状态的bean总数时,MDB将到达Does Not Exist状态,这时可能触发@PreDestroy回调事件。

基于连接器的Message-Driven Bean 
基于JMS的MDB非常有用,但它也存在局限:EJB开发商往往只支持少量的JMS provider(消息收发双方都使用相同的JMS API)。它只支持JMS编程模型而不支持其他类型的消息系统。EJB3.0还支持一个可扩展的message-driven bean定义,使之可以对任何开发商的任何消息系统提供服务,即基于JCA的MDB,JCA定义了EJB容器和异步连接器之间的规则,从而使用MDB可以自动处理来自EIS的消息。JCA连接器可以从开发商X处购得,使用JCA连接器的MDB,可以自动处理开发商X开发的EIS系统发送过来的消息。例子,一个处理email的MDB 
package com.vendorx.email;//来自开发商X的JCA 
public inteface EmilListener{ 
    public void onMessage(javax.mail.Message message); 
//这个方法可以有不同形式的方法名称和签名,并允许拥有返回值,由JCA开发商决定 

@MessageDriven(activationConfig={//属性依赖于连接器的类型及具体要求 
       @ActivationConfigProperty(propertyName="mailServer", 
                             propertyValue="mail.ispx.com"), 
       @ActivationConfigProperty(propertyName="serverType", 
                             propertyValue="POP3")}) 
public class EmailBean implements com.vendorx.email.EmilListener{//实现JCA开发商的接口 
    public void onMessage(javax.mail.Message message){ 
        javax.mial.internet.MimeMessage msg=( javax.mial.internet.MimeMessage)message; 
        Address[] addressed=msg.getFrom();//来自那里的email. 
        …….. 

}

消息连接 
允许由任一enterprise bean发出的消息,能够最终路由到位于 同一部署环境中的指定message-driven bean处,通过使用消息连接,可以在同一应用程序内的不同组件之间控制消息流转。可以定义一个TicketDistributor EJB分发由TravelAgent EJB发送到同一个JMS主题的消息,专门定义一个MDB来处理分发工作可以获得列好的灵活性和系统性能。EJB规范没有为消息连接提供注解,必须使用XML部署描述文件来使用这个功能。 
<session> 
    ….. 
    <message-destination-link>Distributor</message-destination-link> 
….. 
</session> 
<message-driven> 
    <ejb-name>TicketDistributorEJB</ejb-name> 
    <message-destination-link>Distributor</message-destination-link> 
</ message-driven > 
<assembly-descriptor> 
    <message-destination> 
        <message-destination-name>Distributor</message-destination-name> 
    </message-destination> 
<assembly-descriptor> 
这时由session bean 发送到目标地址的消息会由MDB TicketDistributorEJB收到转发。session bean可以发送消息,但不要接收消息。

转载:http://zscomehuyue.iteye.com/blog/627218

ebj笔记的更多相关文章

  1. Java学习笔记4

    Java学习笔记4 1. JDK.JRE和JVM分别是什么,区别是什么? 答: ①.JDK 是整个Java的核心,包括了Java运行环境.Java工具和Java基础类库. ②.JRE(Java Run ...

  2. git-简单流程(学习笔记)

    这是阅读廖雪峰的官方网站的笔记,用于自己以后回看 1.进入项目文件夹 初始化一个Git仓库,使用git init命令. 添加文件到Git仓库,分两步: 第一步,使用命令git add <file ...

  3. js学习笔记:webpack基础入门(一)

    之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...

  4. SQL Server技术内幕笔记合集

    SQL Server技术内幕笔记合集 发这一篇文章主要是方便大家找到我的笔记入口,方便大家o(∩_∩)o Microsoft SQL Server 6.5 技术内幕 笔记http://www.cnbl ...

  5. PHP-自定义模板-学习笔记

    1.  开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的“章节7:创建TPL自定义模板”,做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. 2.  整体架构图 ...

  6. PHP-会员登录与注册例子解析-学习笔记

    1.开始 最近开始学习李炎恢老师的<PHP第二季度视频>中的“章节5:使用OOP注册会员”,做一个学习笔记,通过绘制基本页面流程和UML类图,来对加深理解. 2.基本页面流程 3.通过UM ...

  7. NET Core-学习笔记(三)

    这里将要和大家分享的是学习总结第三篇:首先感慨一下这周跟随netcore官网学习是遇到的一些问题: a.官网的英文版教程使用的部分nuget包和我当时安装的最新包版本不一致,所以没法按照教材上给出的列 ...

  8. springMVC学习笔记--知识点总结1

    以下是学习springmvc框架时的笔记整理: 结果跳转方式 1.设置ModelAndView,根据view的名称,和视图渲染器跳转到指定的页面. 比如jsp的视图渲染器是如下配置的: <!-- ...

  9. 读书笔记汇总 - SQL必知必会(第4版)

    本系列记录并分享学习SQL的过程,主要内容为SQL的基础概念及练习过程. 书目信息 中文名:<SQL必知必会(第4版)> 英文名:<Sams Teach Yourself SQL i ...

随机推荐

  1. jquerymobile使用技巧

    1)ajax开关(默认jquery以ajax方式加载页面) $.mobile.ajaxEnabled = false; 2)不编译指定标签 $.mobile.page.prototype.option ...

  2. UVA 11383 Golden Tiger Claw(最佳二分图完美匹配)

    题意:在一个N*N的方格中,各有一个整数w(i,j),现在要求给每行构造row(i),给每列构造col(j),使得任意w(i,j)<=row(i)+col(j),输出row(i)与col(j)之 ...

  3. BZOJ 3631 松鼠的新家

    链剖. #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> ...

  4. 学习java之HashMap和TreeMap

    HashMap和TreeMap是Map接口的两种实现,ArrayDeque和LinkedList是Queue接口的两种实现方式.下面的代码是我今天学习这四个数据结构之后写的.还是不熟悉,TreeMap ...

  5. 阻塞、非阻塞的概念和select函数的阻塞功能

    其它文档: http://www.cnitblog.com/zouzheng/archive/2010/11/25/71711.html (1)阻塞block     所谓阻塞方式block,顾名思义 ...

  6. hibernate的组成部分

    持久化类 实现对应的序列化接口 必须有默认的构造函数 持久化类的属性不能使用关键字 标示符 映射文件 类型 java类型和hibernate类型 主键的产生器 increment identity a ...

  7. ERROR:The requested URL could not be retrieved解决方法

    ERROR 错误 The requested URL could not be retrieved 您所请求的网址(URL)无法获取 While trying to retrieve the URL: ...

  8. selenium python (七)层级定位(二次定位)

    #!/usr/bin/python# -*- coding: utf-8 -*-__author__ = 'zuoanvip' #在实际测试过程中,一个页面可能有多个属性基本相同的元素,如果要定位到其 ...

  9. Oracle数据泵

    要使用数据泵必须先创建数据库目录        数据库目录只允许sys创建        普通用户使用 必须授权 假设scott 用户是导出导入用户 SQL> ! mkdir dp_dir SQ ...

  10. [转]Linux文件和目录操作命令

    转自:http://www.linuxdiyf.com/bbs/thread-416176-1-1.html 一.文件操作命令1.1 查看文件 Linux下查看文件的命令有很多,下面列出的几个是几乎所 ...