JMS - 如何将消息发送回客户端?

时间:2017-10-12 17:52:13

标签: jms objectoutputstream

这是我的客户端代码:

公共类ABCServlet扩展了HttpServlet {

protected void doGet(HttpServletRequest request,
                 HttpServletResponse response){
//do blah blah
String msg = null;

java.io.OutputStream os = response.getOutputStream();
java.io.ObjectOutputStream oos = new java.io.ObjectOutputStream(os);
oos.writeObject(msg);
msg = null;
oos.flush();
oos.close();
}

我不知道如何使用上面的代码来启动听众 -

public class ABCListener implements MessageListener {

@Override
public void onMessage(Message arg0) {
 AbstractJDBCFacade façade = null;
 try{
    façade = something;
    throw new UserException();
 }catch(UserException ex){
    log.error("ABC Exception " + ex);
 }

配置:

<bean id="jmsConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">....

<bean id="jmsQueue" class="org.springframework.jndi.JndiObjectFactoryBean">

<bean id="listenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer102">

我有3个问题: 1.没有明确地将它放在队列上,如何调用监听器? 2.当onMessage方法抛出UserException时,而不是记录我想将消息传递给客户端。我怎样才能做到这一点 ? 3.为什么有人会使用JndiObjectFactoryBean而不是ActiveMQ ...

1 个答案:

答案 0 :(得分:2)

设计JMS应该是异步和单向的。甚至&#34;同步&#34;使用user方法的jms将在内部转变​​为创建一个新的临时队列。在这里,我们谈到了它的单向性。 JMS队列应该是单向的,这就是它被称为点对点(http://www.enterpriseintegrationpatterns.com/patterns/messaging/PointToPointChannel.html)的原因。当然,技术上有一些舞蹈,你将设法实现你想要的,但这是不好的做法,由于你需要过滤,这也会导致性能下降。

为了让这个东西快速工作,最好的办法就是拥有一个逻辑接收器(当然你可以为一个接收器使用并发消费者,但这应该是一个逻辑消费者而不需要过滤消息)。

  
      
  1. 没有明确地将它放在队列中,如何调用侦听器?
  2.   

只有在消息进入队列时才会调用侦听器。这是让它工作的唯一方法,因为它应该工作。

通常,有两种类型的消息消费模型:推送(也称为事件驱动消费)和轮询。在使用推模型的情况下,所有侦听器(根据规范观察者模式)在代理中的某处注册,然后,当代理在某个队列中接收新消息时,它执行侦听器的方法。在投票模型的另一方面,消费者会关注接收消息。因此,在某个时间间隔内,它会到达代理并检查队列中的新消息。

推模型:http://www.enterpriseintegrationpatterns.com/patterns/messaging/EventDrivenConsumer.html

民意调查模型:http://www.enterpriseintegrationpatterns.com/patterns/messaging/PollingConsumer.html

  
      
  1. 当onMessage方法抛出UserException时,而不是记录我想将消息传递给客户端。我该怎么办?
  2.   

这是一个非常糟糕的做法。当然,从技术上讲,你可以用肮脏的技巧来实现它,但这不是使用jms的正确方法。当onMessage抛出异常时,消息将不会从队列中获取(当然,如果你没有重新配置确认模式或使用其他技巧)。因此,解决您的probem fmpv的最佳方法是使用重新传递限制消息和死信队列(http://www.enterpriseintegrationpatterns.com/patterns/messaging/DeadLetterChannel.html)。如果系统在一些尝试之后无法处理消息(重新传递限制显示这一点),则代理从队列中删除消息并将其发送到所谓的死信队列,其中所有失败(从代理点)消息都存储。然后客户端可以读取该队列并决定如何处理消息。

在amq:http://activemq.apache.org/message-redelivery-and-dlq-handling.html

如果你想使用所谓的&#34;同步&#34; JMS中的功能实际上没有办法使用死信队列或smth那样实际上你可以在客户端上使用consumer.recieve方法。但在这种情况下,您应该发送每条消息的响应。如果成功,您可以发送一条消息并在出现故障时出现错误消息。因此,客户将能够了解正在发生的事情。但我不认为你需要这么大的开销,实际上你只需要失败的消息。同样在这种情况下,您必须注意适当的接收超时。

  
      
  1. 为什么有人会使用JndiObjectFactoryBean而不是ActiveMQ ......
  2.   

这是因为你正在使用Spring,还有其他功能,特别是春天。

PS: 1.消费:

  

如何使用这段代码发送消息?我不需要   将它放在队列中? java.io.OutputStream os =   response.getOutputStream(); java.io.ObjectOutputStream oos = new   java.io.ObjectOutputStream中(OS); oos.writeObject(MSG);   为了接收这样的smth:         `                                                                                                    

    <bean id="connectionFactory" class="org.springframework.
      jndi.JndiObjectFactoryBean">
        <property name="jndiTemplate" ref="baseJNDITemplate"/>
        <property name="jndiName"  

        value="weblogic.jms.ConnectionFactory"/>
    </bean>

    <bean id="queue"  class="org.springframework.
     jndi.JndiObjectFactoryBean">
        <property name="jndiTemplate" ref="baseJNDITemplate"/>
        <property name="jndiName" value="#{properties.queueName}"/>
    </bean>

    <bean id="messageListenerContainer"              
    class="org.springframework.jms.listener.
      DefaultMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="destination" ref="queue"/>
        <property name="messageListener" ref="messageListener"/>
        <property name="sessionTransacted" value="true"/>
    </bean>

    <bean id="messageListener" class="com.example.ABCListener"/>

然后,所有用于消息处理的逻辑都将在监听器中。

在config中发送这样的smth:

<bean id="jmsQueueTemplate" 
  class="org.springframework.
     jms.core.JmsTemplate">
<property name="connectionFactory">
    <ref bean="jmsConnectionFactory"/>
</property>
<property name="destinationResolver">
    <ref bean="jmsDestResolver"/>
</property>

...
</bean>

<bean id="jmsDestResolver"
      class=" org.springframework.jms.support.destination.
      JndiDestinationResolver"/>

<bean id="jmsConnectionFactory"
      class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:comp/env/jms/myCF"/>
    <property name="lookupOnStartup" value="false"/>
    <property name="cache" value="true"/>
    <property name="proxyInterface" value="amq con fact here"/>
</bean>

并且在代码中只使用jmsTemplate.send(queue,messageCreator)方法:

@Autowired
ConnectionFactory connectionFactory;

@Test(enabled = false)
public void testJmsSend(final String msg) throws Exception {
    JmsTemplate template = new JmsTemplate(connectionFactory);
    template.send("test_queue", new MessageCreator() {
        @Override
        public Message createMessage(Session session) 
          throws JMSException {
            return session.createTextMessage(msg);
        }
    });
}

https://www.ibm.com/support/knowledgecenter/en/SSAW57_8.5.5/com.ibm.websphere.nd.doc/ae/cspr_data_access_jms.html

  
      
  1. 我认为只有当接收者没有正确接收到消息时,死信道才会出现。在我的情况下,接收器   接收并处理它,但处理失败时   一些例外。我想让发件人知道有一个   异常并且消息未成功处理。我可以做这个   使用响应队列,但我不想这样做,接收器可以   在同一队列中收到发件人的邮件?怎么样?
  2.   

死信通道也是一种消息处理的错误处理方式。如果消息处理失败,那么在限制结束后它就转移到了那里。它实际上不仅适用于运输问题,也适用于处理问题。如果消息处理因异常而失败,则消息将保留在队列中,默认情况下不会被激活。那么我们应该对这条消息做些什么呢?例如,如果由于我们的数据库错误或像这样的smth失败了?我们应该启动错误处理流程,通知保证系统和利益相关者,收集所有必要的信息并保留消息。由于这种类型的队列正是为此创建的,因此更容易。然后,客户支持团队将调查错误队列,以进一步分析已发生的情况。此外,我们还有针对此类错误的通知和统计数据收集的监控工具在了解发生了什么之后,消息已从队列中删除并存档。

  

处理完消息后,消费者负责删除   消息。例如,如果消费者没有删除该消息   因为它在处理消息时崩溃了   在消息的可见性超时到期后再次可见。   每次发生这种情况时,邮件的接收计数都会增加   当此计数达到配置的限制时,消息将放入a   指定的死信队列。

http://www.enterpriseintegrationpatterns.com/patterns/messaging/DeadLetterChannel.html

  

我可以使用响应队列执行此操作,但我不想这样做,可以   接收方是否在同一队列上收到发件人的消息?   怎么样?

对于您而言,它看起来像是相同的队列,但会创建内部新的临时队列。要实现这一点,您应该使用jms request \ reply消息模式。更多信息:http://activemq.apache.org/how-should-i-implement-request-response-with-jms.html

  

唯一令我困惑的是:如果我期待我的JMS听众   (接收者)听队列,然后我的发送者也应该   实现JMS并连接到同一队列并发送消息。但在   我支持的ABCListener应用程序没有任何   将发件人配置到队列的配置。一切   sender是3行代码:java.io.OutputStream os =   response.getOutputStream(); java.io.ObjectOutputStream oos = new   java.io.ObjectOutputStream中(OS); oos.writeObject(MSG);从字面上看,那   是吗。我不知道它是如何运作的!

当然,使用outputstream的3行代码除了填充msg字符串外什么都不做。要将任何jms消息发送到队列,您无论如何都必须使用JMS Api或某些像Spring这样的库来添加其他功能。

我已经编写了简单的样本以使其更清晰。

  1. 使用死信队列进行异步处理的已修改servlet(对于dlq,您还应该创建另一个侦听器ofc)

    public class AsynchronousJmsSenderServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String msg = null;
    
        try (java.io.OutputStream os = response.getOutputStream()) {
            try(java.io.ObjectOutputStream oos = new java.io.ObjectOutputStream(os)) {
                oos.writeObject(msg);
            }
        }
    
        sendJmsMessage(msg);
    }
    
    private void sendJmsMessage(final String msg) {
        ConnectionFactory connectionFactory = null; //here get it in some way from spring
        JmsTemplate template = new JmsTemplate(connectionFactory);
        template.send("your_queue_name", new MessageCreator() {
            @Override
            public Message createMessage(Session session)
                    throws JMSException {
                return session.createTextMessage(msg);
            }
        });
    }
    }
    
  2. 以下是&#34;同步&#34;的代码处理和状态回复消息

    public class SynchronousJmsSenderServlet extends HttpServlet {
    
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String msg = null;
        try (java.io.OutputStream os = response.getOutputStream()) {
            try(java.io.ObjectOutputStream oos = new java.io.ObjectOutputStream(os)) {
                oos.writeObject(msg);
            }
        }
        sendJmsMessage(msg);
    }
    
    private void sendJmsMessage(final String msg) {
        ConnectionFactory connectionFactory = null; //here get it in some way from spring
        JmsTemplate template = new JmsTemplate(connectionFactory);
    
        Message reply = template.sendAndReceive("your_queue_name", new MessageCreator() {
            @Override
            public Message createMessage(Session session)
                    throws JMSException {
                return session.createTextMessage(msg);
            }
        });
    
        if(reply instanceof TextMessage) {
            try {
                String status = ((TextMessage) reply).getText();
                //do error handling if status is error
            } catch (JMSException ex) {
                throw new RuntimeException("Unable to get status message", ex);
            }
        } else {
            throw new RuntimeException("Only text messages are supported");
        }
    }
    }
    
    
    public class SynchronousJmsMessageListener implements SessionAwareMessageListener {
    
    @Override
    public void onMessage(Message request, Session session) throws JMSException {
        try {
            //do some processing
            sendReply(request, session, "OK");
        } catch (Exception ex) {
            sendReply(request, session, "Error: " + ex.toString());
        }
    }
    
    private void sendReply(Message request, Session session, String status) {
        try {
            TextMessage reply = null; //for example you can use ActiveMQTextMessage here
            reply.setJMSCorrelationID(request.getJMSCorrelationID());
            reply.setText(status);
    
            MessageProducer producer = session.createProducer(reply.getJMSReplyTo());
            producer.send(reply);
        } catch (JMSException exception) {
            throw new RuntimeException("Unable to send reply", exception);
        }
    }
    }
    
  3. 你需要Spring 5才能在jmsTemplate上使用sendAndReceive方法。或者您必须手动完成所有操作。

    PS1:请告诉我这是否有效