6 장. JBoss에서의 메시징

JMS 설정과 아키텍쳐

JMS API는 자바 메시지 서비스 어플리케이션 프로그래밍 인터페이스를 의미하며, 다른 어플리케이션으로 비동기적인 business-quality 메시지들을 저송하는 어플리케이션에서 사용됩니다. JMS 세계에서는 메시지들이 다른 어플리케이션에 직접적으로 전송되지 않습니다. 대신 메시지들은 큐 혹은 토픽으로 알려진 목적지(destination)로 전송됩니다. 어플리케이션에서 메시지를 보낼때는 수신하는 어플리케이션이 시작되고 구동되는지를 걱정할 필요가 없으며, 이와 반대로 수신받는 어플리케이션 또한 전송하는 어플리케이션의 상태에 대한 걱정을 할 필요가 없습니다. 전송자(sender)와 수신자(reciever) 모두 목적지하고만 작용하게 됩니다.

JMS API는 메시지 지향의 미들웨어(MOM) 시스템으로 불려지기도 하는 JMS 프로바이더의 인터페이스를 표준화한 것입니다. JBoss에서는 JBoss 메시징 또는 JBossMQ로 불려지는 JMS 1.0.2b와 호환되는 JMS 프로바이더를 함께 제공하고 있습니다. 여러분이 JBoss에서 JMS API를 사용할 경우, 투명하게 JBoss의 메시징 엔진을 이용하게 됩니다. JBoss 메시징은 JMS 사양을 완전히 구현하였기 때문에 JBoss 메시징을 위한 가장 좋은 사용자 가이드는 바로 JMS 사양서가 됩니다. JMS API에 대한 보다 자세한 정보는 JMS 자습서나 JMS 다운로드& 사양서를 참조하십시오.

이번 장에서는 JBoss 메시징 환경설정과 MBean 뿐만아니라 JMS와 메시지 구동 빈즈를 사용할 때 JBoss에서 고려해주어야할 사항들에 촛점을 맞추었습니다.

6.1. JMS 예제들

이번 섹션에서 우리는 JBoss JMS 구현을 사용하기 위해 필요한 기본사항들에 대해서 알아보도록 하겠습니다. JMS에서는 프로바이더에 특화된 세부사항으로써 JMS 커넥션 팩토리들과 목적지들에 액세스하는 상세한 내용은 다루지 않겠습니다. 여러분이 JBoss 메시징 레이어를 사용하기위해 알아야할 필요가 있는것들은 다음과 같습니다:

이후에 오는 서브섹션들에서 우리는 다양한 JMS 메시징 모델과 메시지 구동 빈즈의 예를 살펴보도록 할 것입니다. 이번 장의 예제 소스는 이 책에서 제공되는 예제의 src/main/org/jboss/chap6 디렉터리밑에 위치해 있습니다.

6.1.1. Point-To-Point 예제

point-to-point (P2P) 예제를 갖고 시작해보도록 하겠습니다. P2P 모델에서는 하나의 전송자가 큐에 메시지들을 전달하고 단일 수신자가 큐로부터 메시지를 끌어옵니다. 수신자는 메시지가 전송된 때 큐로부터 수신할 필요는 없습니다. 예제 6.1, “P2P JMS 클라이언트 예제”에서는 queue/testQueue 큐쪽으로 javax.jms.TextMessage 메시지를 전송하고 동일한 큐로부터 메시지를 비동기방식으로 수신합니다.

예제 6.1. P2P JMS 클라이언트 예제

package org.jboss.chap6.ex1;
                
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueReceiver;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.TextMessage;
import javax.naming.InitialContext;
import javax.naming.NamingException;
                
import EDU.oswego.cs.dl.util.concurrent.CountDown;

/**
 *  큐에 TextMessage 를 전송하고
 *  동일한 큐로부터 메시지를 비동기적으로 수신받는 
 *  완전한 JMS 클라이언트 예제 프로그램
 * 
 *  @author Scott.Stark@jboss.org
 *  @version $Revision: 1.10 $
 */
public class SendRecvClient
{
    static CountDown done = new CountDown(1);
    QueueConnection conn;
    QueueSession session;
    Queue que;
    
    public static class ExListener 
        implements MessageListener
    {
        public void onMessage(Message msg)
        {
            done.release();
            TextMessage tm = (TextMessage) msg;
            try {
                System.out.println("onMessage, recv text=" + tm.getText());
            } catch(Throwable t) {
                t.printStackTrace();
            }
        }
    }
                
    public void setupPTP()
        throws JMSException, 
               NamingException
    {
        InitialContext iniCtx = new InitialContext();
        Object tmp = iniCtx.lookup("ConnectionFactory");
        QueueConnectionFactory qcf = (QueueConnectionFactory) tmp;
        conn = qcf.createQueueConnection();
        que = (Queue) iniCtx.lookup("queue/testQueue");
        session = conn.createQueueSession(false,
                                          QueueSession.AUTO_ACKNOWLEDGE);
        conn.start();
    }
    
    public void sendRecvAsync(String text)
        throws JMSException,
               NamingException
    {
        System.out.println("Begin sendRecvAsync");
        // Setup the PTP connection, session
        setupPTP();

        // Set the async listener
        QueueReceiver recv = session.createReceiver(que);
        recv.setMessageListener(new ExListener());

        // Send a text msg
        QueueSender send = session.createSender(que);
        TextMessage tm = session.createTextMessage(text);
        send.send(tm);
        System.out.println("sendRecvAsync, sent text=" + tm.getText());
        send.close();
        System.out.println("End sendRecvAsync");
    }
                
    public void stop()
        throws JMSException
    {
        conn.stop(); 
        session.close();
        conn.close();
    }
    
    public static void main(String args[])
        throws Exception
    {
        SendRecvClient client = new SendRecvClient();
        client.sendRecvAsync("A text msg");
        client.done.acquire();
        client.stop();
        System.exit(0);
    }
}

클라이언트는 다음과 같은 명령어를 실행시켜 구동시킬 수 있습니다:

[nr@toki examples]$ ant -Dchap=chap6 -Dex=1p2p run-example
...
run-example1p2p:
     [java] [INFO,SendRecvClient] Begin SendRecvClient, now=1098416473521
     [java] [INFO,SendRecvClient] Begin sendRecvAsync
     [java] [INFO,SendRecvClient] onMessage, recv text=A text msg
     [java] [INFO,SendRecvClient] sendRecvAsync, sent text=A text msg
     [java] [INFO,SendRecvClient] End sendRecvAsync
     [java] [INFO,SendRecvClient] End SendRecvClient

6.1.2. Pub-Sub 예제

JMS의 발행/구독(publish/subscribe) 메시지 모델(Pub-Sub)은 일대다(one-to-many) 모델입니다. publisher가 토픽쪽에 하나의 메시지를 전송하면 모든 동작하고 있는 subscriber들은 토픽으로부터 메시지를 수신합니다. 토픽을 수신하고 있지 않는 subscriber들은 발행된 메시지를 수신하지 못하게 될 것입니다. javax.jms.TextMessage를 토픽에 전송하는 하나의 완전한 JMS 클라이언트를 아래에 보여주고 있으며, 이 JMS 클라이언트는 동일한 토픽으로부터 비동기적으로 메시지를 수신받습니다.

예제 6.2. Pub-Sub JMS 클라이언트 예제

package org.jboss.chap6.ex1;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicPublisher;
import javax.jms.TopicSubscriber;
import javax.jms.TopicSession;
import javax.jms.TextMessage;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import EDU.oswego.cs.dl.util.concurrent.CountDown;

/**
 *  하나의 완전한 JMS 클라이언트 예제 프로그램. 
 *  한 토픽에 TextMessage를 전송하고, 
 *  동일한 토픽으로부터 비동기적으로 메시지를 수신합니다.
 * 
 *  @author Scott.Stark@jboss.org
 *  @version $Revision: 1.10 $
 */

public class TopicSendRecvClient
{
    static CountDown done = new CountDown(1);
    TopicConnection conn = null;
    TopicSession session = null;
    Topic topic = null;
    
    public static class ExListener implements MessageListener
    {
        public void onMessage(Message msg)
        {
            done.release();
            TextMessage tm = (TextMessage) msg;
            try {
                System.out.println("onMessage, recv text=" + tm.getText());
            } catch(Throwable t) {
                t.printStackTrace();
            }
        }
    }
    
    public void setupPubSub()
        throws JMSException, NamingException
    {
        InitialContext iniCtx = new InitialContext();
        Object tmp = iniCtx.lookup("ConnectionFactory");
        TopicConnectionFactory tcf = (TopicConnectionFactory) tmp;
        conn = tcf.createTopicConnection();
        topic = (Topic) iniCtx.lookup("topic/testTopic");
        session = conn.createTopicSession(false,
                                          TopicSession.AUTO_ACKNOWLEDGE);
        conn.start();
    }
    
    public void sendRecvAsync(String text)
        throws JMSException, NamingException
    {
        System.out.println("Begin sendRecvAsync");
        // Setup the PubSub connection, session
        setupPubSub();
        // Set the async listener
        
        TopicSubscriber recv = session.createSubscriber(topic);
        recv.setMessageListener(new ExListener());
        // Send a text msg
        TopicPublisher send = session.createPublisher(topic);
        TextMessage tm = session.createTextMessage(text);
        send.publish(tm);
        System.out.println("sendRecvAsync, sent text=" + tm.getText());
        send.close();
        System.out.println("End sendRecvAsync");
    }
    
    public void stop() throws JMSException
    {
        conn.stop();
        session.close();
        conn.close();
    }
    
    public static void main(String args[]) throws Exception
    {
        System.out.println("Begin TopicSendRecvClient, now=" + 
                           System.currentTimeMillis());
        TopicSendRecvClient client = new TopicSendRecvClient();
        client.sendRecvAsync("A text msg, now="+System.currentTimeMillis());
        client.done.acquire();
        client.stop();
        System.out.println("End TopicSendRecvClient");
        System.exit(0);
    }
    
}

이 클라이언트 예제 프로그램은 다음과 같은 명령을 통해 동작시킬 수 있습니다:

[nr@toki examples]$ ant -Dchap=chap6 -Dex=1ps run-example
...
run-example1ps:
     [java] Begin TopicSendRecvClient, now=1098416563162
     [java] Begin sendRecvAsync
     [java] onMessage, recv text=A text msg, now=1098416563171
     [java] sendRecvAsync, sent text=A text msg, now=1098416563171
     [java] End sendRecvAsync
     [java] End TopicSendRecvClient

이제 publisher와 subscriber들을 별도의 프로그램으로 분리시켜 subscriber들은 하나의 토픽을 리스닝하도록 함으로써 오로지 메시지 수신만을 하도록 하겠습니다. 예제 6.3, “JMS publisher 클라이언트”에서는 앞에서 살펴보았던 pub-sub 클라이언트를 변형시켜 topic/testTopic 토픽으로 메시지를 발행하도록만 하겠습니다. subscriber 전용 클라이언트는 예제 6.4, “JMS subscriber 클라이언트”와 같습니다.

예제 6.3. JMS publisher 클라이언트

package org.jboss.chap6.ex1;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicPublisher;
import javax.jms.TopicSlistubscriber;
import javax.jms.TopicSession;
import javax.jms.TextMessage;
import javax.naming.InitialContext;
import javax.naming.NamingException;

/** 
 *  토픽에 TextMessage를 전송하는 JMS 클라이언트 예제 프로그램
 *    
 *  @author Scott.Stark@jboss.org
 *  @version $Revision: 1.10 $
 */
public class TopicSendClient
{
    TopicConnection conn = null;
    TopicSession session = null;
    Topic topic = null;
    
    public void setupPubSub()
        throws JMSException, NamingException
    {
        InitialContext iniCtx = new InitialContext();
        Object tmp = iniCtx.lookup("ConnectionFactory");
        TopicConnectionFactory tcf = (TopicConnectionFactory) tmp;
        conn = tcf.createTopicConnection();
        topic = (Topic) iniCtx.lookup("topic/testTopic");
        session = conn.createTopicSession(false,
		                                  TopicSession.AUTO_ACKNOWLEDGE);
        conn.start();
    }
    
    public void sendAsync(String text)
        throws JMSException, NamingException
    {
        System.out.println("Begin sendAsync");
        // Setup the pub/sub connection, session
        setupPubSub();
        // Send a text msg
        TopicPublisher send = session.createPublisher(topic);
        TextMessage tm = session.createTextMessage(text);
        send.publish(tm);
        System.out.println("sendAsync, sent text=" +  tm.getText());
        send.close();
        System.out.println("End sendAsync");
    }
    
    public void stop() 
        throws JMSException
    {
        conn.stop();
        session.close();
        conn.close();
    }
    
    public static void main(String args[]) 
        throws Exception
    {
        System.out.println("Begin TopicSendClient, now=" + 
		                   System.currentTimeMillis());
        TopicSendClient client = new TopicSendClient();
	    client.sendAsync("A text msg, now="+System.currentTimeMillis());
        client.stop();
        System.out.println("End TopicSendClient");
        System.exit(0);
    }
    
}

에제 6.4. JMS subscriber 클라이언트

package org.jboss.chap6.ex1;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicPublisher;
import javax.jms.TopicSubscriber;
import javax.jms.TopicSession;
import javax.jms.TextMessage;
import javax.naming.InitialContext;
import javax.naming.NamingException;

/**
 * 토픽에서 메시지를 동기적으로(synchronously) 수신받는 JMS 클라이언트 예제 프로그램 
 *  
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.10 $
 */
public class TopicRecvClient
{
    TopicConnection conn = null;
    TopicSession session = null;
    Topic topic = null;
    
    public void setupPubSub()
        throws JMSException, NamingException
    {
        InitialContext iniCtx = new InitialContext();
        Object tmp = iniCtx.lookup("ConnectionFactory");
        TopicConnectionFactory tcf = (TopicConnectionFactory) tmp;
        conn = tcf.createTopicConnection();
        topic = (Topic) iniCtx.lookup("topic/testTopic");
        session = conn.createTopicSession(false,
                                          TopicSession.AUTO_ACKNOWLEDGE);
        conn.start();
    }
    
    public void recvSync()
        throws JMSException, NamingException
    {
        System.out.println("Begin recvSync");
        // Setup the pub/sub connection, session
        setupPubSub();

        // Wait upto 5 seconds for the message
        TopicSubscriber recv = session.createSubscriber(topic);
        Message msg = recv.receive(5000);
        if (msg == null) {
            System.out.println("Timed out waiting for msg");
        } else {
            System.out.println("TopicSubscriber.recv, msgt="+msg);
        }
    }
    
    public void stop()
        throws JMSException
    {
        conn.stop();
        session.close();
        conn.close();
    }
    
    public static void main(String args[]) 
        throws Exception
    {
        System.out.println("Begin TopicRecvClient, now=" +
                           System.currentTimeMillis());
        TopicRecvClient client = new TopicRecvClient();
        client.recvSync();
        client.stop();
        System.out.println("End TopicRecvClient");
        System.exit(0);
    }
    
}

다음과 같은 명령을 실행하여 TopicSendClient가 실행되고 바로 이어서 TopicRecvClient가 동작되도록 합니다:

[nr@toki examples]$ ant -Dchap=chap6 -Dex=1ps2 run-example
...
run-example1ps2:
     [java] Begin TopicSendClient, now=1098416676618
     [java] Begin sendAsync
     [java] sendAsync, sent text=A text msg, now=1098416676621
     [java] End sendAsync
     [java] End TopicSendClient
     [java] Begin TopicRecvClient, now=1098416683857
     [java] Begin recvSync
     [java] Timed out waiting for msg
     [java] End TopicRecvClient

위의 명령을 통해서 우리는 토픽 subscriber 클라이언트(TopicRecvClient)가 publisher에서 전송한 메시지를 timeout때문에 수신받지 못했다는 것을 알 수 있습니다.

6.1.3. Pub-Sub 모델에서 지속가능한 토픽 예제

JMS에서는 P2P와 pub-sub 모델 사이를 왔다갔다할 수 있는 메시징 모델을 지원합니다. 즉, 하나의 pub-sub 클라이언트가 토픽으로 올라오는 모든 메시지들을 수신받고자 한다면, 토픽을 실제로 리스닝하지 않을 경우조차도 메시지들을 구독할 수 있게 하기위해 클라이언트는 지속가능한(durable) 토픽을 사용하여 이러한 기능을 구현할 수 있습니다. 이제 클라이언트가 토픽을 수신하지 않을 때에라도 토픽에 올려진 모든 메시지들을 수신받을 수 있도록 하기위해 지속가능한 토픽을 사용하는 subscriber 클라이언트를 앞에서 보여주었던 예제를 수정하여 만들어 보도록 하겠습니다. 예제 6.5, “지속가능한 토픽 JMS 클라이언트 예제”에서는 예제 6.4, “JMS subscriber 클라이언트”에서 달라진 사항들을 굵은체로 눈에 띠도록 보여주고 있습니다.

예제 6.5. 지속가능한 토픽 JMS 클라이언트 예제

package org.jboss.chap6.ex1;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicPublisher;
import javax.jms.TopicSubscriber;
import javax.jms.TopicSession;
import javax.jms.TextMessage;
import javax.naming.InitialContext;
import javax.naming.NamingException;

/**
 *  토픽으로부터 메시지를 동기적으로 수신받는 JMS 클라이언트 예제 프로그램 
 *     
 *  @author Scott.Stark@jboss.org
 *  @version $Revision: 1.10 $
 */
public class DurableTopicRecvClient
{
    TopicConnection conn = null;
    TopicSession session = null;
    Topic topic = null;
    
    public void setupPubSub()
        throws JMSException, NamingException
    {
        InitialContext iniCtx = new InitialContext();
        Object tmp = iniCtx.lookup("ConnectionFactory");

        TopicConnectionFactory tcf = (TopicConnectionFactory) tmp;
        conn = tcf.createTopicConnection("john", "needle");
        topic = (Topic) iniCtx.lookup("topic/testTopic");

        session = conn.createTopicSession(false,TopicSession.AUTO_ACKNOWLEDGE);
        conn.start();
    }
    
    public void recvSync()
        throws JMSException, NamingException
    {
        System.out.println("Begin recvSync");
        // Setup the pub/sub connection, session
        setupPubSub();
        // Wait upto 5 seconds for the message
        TopicSubscriber recv = session.createDurableSubscriber(topic, "chap6-ex1dtps");
        Message msg = recv.receive(5000);
        if (msg == null) {
            System.out.println("Timed out waiting for msg");
        } else {
            System.out.println("DurableTopicRecvClient.recv, msgt=" + msg);
        } 
    }
    
    public void stop() 
        throws JMSException
    {
        conn.stop();
        session.close();
        conn.close();
    }
    
    public static void main(String args[]) 
        throws Exception
    {
        System.out.println("Begin DurableTopicRecvClient, now=" + 
                           System.currentTimeMillis());
        DurableTopicRecvClient client = new DurableTopicRecvClient();
        client.recvSync();
        client.stop();
        System.out.println("End DurableTopicRecvClient");
        System.exit(0);
    }
    
}

다음과 같은 명령을 통해 앞쪽에서 보여주었던 토픽 publisher 와 함께 지속가능한 토픽 subscriber를 실행시킵니다:

[nr@toki examples]$ ant -Dchap=chap6 -Dex=1psdt run-example
run-example1psdt:
     [java] Begin DurableTopicSetup
     [java] End DurableTopicSetup
     [java] Begin TopicSendClient, now=1098420531772
     [java] Begin sendAsync
     [java] sendAsync, sent text=A text msg, now=1098420531775
     [java] End sendAsync
     [java] End TopicSendClient
     [java] Begin DurableTopicRecvClient, now=1098420538269
     [java] Begin recvSync
     [java] DurableTopicRecvClient.recv, msgt=SpyTextMessage {
     [java] Header { 
     [java]    jmsDestination  : TOPIC.testTopic.DurableSubscription[clientId=DurableSubscriberExample name=chap6-ex1dtps selector=null]
     [java]    jmsDeliveryMode : 2
     [java]    jmsExpiration   : 0
     [java]    jmsPriority     : 4
     [java]    jmsMessageID    : ID:29-10984205372121
     [java]    jmsTimeStamp    : 1098420537212
     [java]    jmsCorrelationID: null
     [java]    jmsReplyTo      : null
     [java]    jmsType         : null
     [java]    jmsRedelivered  : false
     [java]    jmsProperties   : {}
     [java]    jmsPropReadWrite: false
     [java]    msgReadOnly     : true
     [java]    producerClientId: ID:29
     [java] }
     [java] Body {
     [java]    text            :A text msg, now=1098420531775
     [java] }
     [java] }
     [java] End DurableTopicRecvClient

지속가능한 토픽 예제에 포함되어 있는 항목들은 다음과 같습니다:

6.1.4. MDB에서의 Point-To-Point 예제

EJB 2.0 사양서에는 메시지 지향 빈즈(MDB)의 표기가 추가되었습니다. MDB는 비동기적으로 호출될 수 있는 비즈니스 컴포넌트입니다. EJB 2.0 사양서에 따라 JMS는 MDB들이 액세스할 수 있는 유일한 메커니즘이었습니다. 예제 6.6, “MDB에서 TextMessage 처리하기”에서는 MDB가 수신한 TextMessages를 변형하고, 변형된 메시지를 수신받는 메시지 JMSReplyTo 헤더내에서 찾을 수 있는 큐쪽으로 전송합니다.

예제 6.6. MDB에서 TextMessage 처리하기

package org.jboss.chap6.ex2;
                
import javax.ejb.MessageDrivenBean;
import javax.ejb.MessageDrivenContext;
import javax.ejb.EJBException;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.TextMessage;
import javax.naming.InitialContext;
import javax.naming.NamingException;

/** 
 * 수신한 TextMessage들을 변형하고 
 * 들어오는 메시지 JMSReplyTo 헤더에서 찾은 Queue쪽으로 
 * 변형된 메시지들을 전송하는 MDB.
 * 
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.10 $
 */
public class TextMDB 
    implements MessageDrivenBean, MessageListener
{
    private MessageDrivenContext ctx = null;
    private QueueConnection conn;
    private QueueSession session;
    
    public TextMDB()
    {
        System.out.println("TextMDB.ctor, this="+hashCode());
    }
    
    public void setMessageDrivenContext(MessageDrivenContext ctx)
    {
        this.ctx = ctx;
        System.out.println("TextMDB.setMessageDrivenContext, this=" + 
                           hashCode());
    }
    
    public void ejbCreate()
    {
        System.out.println("TextMDB.ejbCreate, this="+hashCode());
        try {
            setupPTP();
        } catch (Exception e) {
            throw new EJBException("Failed to init TextMDB", e);
        }
    }

    public void ejbRemove()
    {
        System.out.println("TextMDB.ejbRemove, this="+hashCode());
        ctx = null;
        try {
            if (session != null) {
                session.close();
            }
            if (conn != null) {
                conn.close();
            }
        } catch(JMSException e) {
            e.printStackTrace();
        }
    }
                
    public void onMessage(Message msg)
    {
        System.out.println("TextMDB.onMessage, this="+hashCode());
        try {
            TextMessage tm = (TextMessage) msg;
            String text = tm.getText() + "processed by: "+hashCode();
            Queue dest = (Queue) msg.getJMSReplyTo();
            sendReply(text, dest);
        } catch(Throwable t) {
            t.printStackTrace();
        }
    }
                
    private void setupPTP()
        throws JMSException, NamingException
    {
        InitialContext iniCtx = new InitialContext();
        Object tmp = iniCtx.lookup("java:comp/env/jms/QCF");
        QueueConnectionFactory qcf = (QueueConnectionFactory) tmp;
        conn = qcf.createQueueConnection();
        session = conn.createQueueSession(false,
                                          QueueSession.AUTO_ACKNOWLEDGE);
        conn.start();
    }

    private void sendReply(String text, Queue dest)
        throws JMSException
    {
        System.out.println("TextMDB.sendReply, this=" + 
                           hashCode() + ", dest="+dest);
        QueueSender sender = session.createSender(dest);
        TextMessage tm = session.createTextMessage(text);
        sender.send(tm);
        sender.close();
    }
}

MDB ejb-jar.xml 와 jboss.xml 배치 서술자들이 예제 6.7, “MDB ejb-jar.xml 서술자”에 보여지고 있습니다.

예제 6.7. MDB ejb-jar.xml 서술자

<?xml version="1.0"?>
<!DOCTYPE ejb-jar PUBLIC 
          "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN"
          "http://java.sun.com/dtd/ejb-jar_2_0.dtd">
<ejb-jar>
    <enterprise-beans>
        <message-driven>
            <ejb-name>TextMDB</ejb-name>
            <ejb-class>org.jboss.chap6.ex2.TextMDB</ejb-class>
            <transaction-type>Container</transaction-type>
            <acknowledge-mode>AUTO_ACKNOWLEDGE</acknowledge-mode>
            <message-driven-destination>
                <destination-type>javax.jms.Queue</destination-type>
            </message-driven-destination>
            <res-ref-name>jms/QCF</res-ref-name>
            <resource-ref>
                <res-type>javax.jms.QueueConnectionFactory</res-type>
                <res-auth>Container</res-auth>
            </resource-ref>
        </message-driven>
    </enterprise-beans>
</ejb-jar>

예제 6.8. MDB jboss.xml 서술자

<?xml version="1.0"?>
<jboss>
    <enterprise-beans>
        <message-driven>
            <ejb-name>TextMDB</ejb-name>
            <destination-jndi-name>queue/B</destination-jndi-name>
            <resource-ref>
                <res-ref-name>jms/QCF</res-ref-name>
                <jndi-name>ConnectionFactory</jndi-name>
            </resource-ref>
        </message-driven>
    </enterprise-beans>
</jboss>

예제 6.9, “TextMDB와 상호작용하는 JMS 클라이언트”에서는 P2P 클라이언트를 변경하여 queue/B 목적지(destination)로 메시지들을 전송하고 큐 A로부터 TextMDB에 의해 수정된 메시지들을 비동기적으로 수신받게 한 예제가 보여지고 있습니다.

예제 6.9. TextMDB와 상호작용하는 JMS 클라이언트

package org.jboss.chap6.ex2;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueReceiver;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.TextMessage;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import EDU.oswego.cs.dl.util.concurrent.CountDown;

/**
 *  Queue B쪽으로 N TextMessage들을 전송하고 
 *  Queue A쪽으로부터 TextMDB에 의해 수정되어진 메시지들을 
 *  비동기적으로 수신받는 완전한 하나의 JMS 클라이언트 예제
 *
 *  @author Scott.Stark@jboss.org
 *  @version $Revision: 1.10 $
 */
public class SendRecvClient
{
    static final int N = 10;
    static CountDown done = new CountDown(N);

    QueueConnection conn;
    QueueSession session;
    Queue queA;
    Queue queB;
    
    public static class ExListener 
        implements MessageListener
    {
        public void onMessage(Message msg)
        {
            done.release();
            TextMessage tm = (TextMessage) msg;
            try {
                System.out.println("onMessage, recv text="+tm.getText());
            } catch(Throwable t) {
                t.printStackTrace();
            }
        }
    }
    
    public void setupPTP()
        throws JMSException, NamingException
    {
        InitialContext iniCtx = new InitialContext();
        Object tmp = iniCtx.lookup("ConnectionFactory");
        QueueConnectionFactory qcf = (QueueConnectionFactory) tmp;
        conn = qcf.createQueueConnection();
        queA = (Queue) iniCtx.lookup("queue/A");
        queB = (Queue) iniCtx.lookup("queue/B");
        session = conn.createQueueSession(false, QueueSession.AUTO_ACKNOWLEDGE);
        conn.start();
    }
    
    public void sendRecvAsync(String textBase)
        throws JMSException, NamingException, InterruptedException
    {
        System.out.println("Begin sendRecvAsync");

        // PTP 커넥션, 세션 설정 
        setupPTP();

        // queA를 위한 비동기 리스너 설정 
        QueueReceiver recv = session.createReceiver(queA);
        recv.setMessageListener(new ExListener());

        // queB쪽으로 몇개의 텍스트 메시지 전송 
        QueueSender send = session.createSender(queB);

        for(int m = 0; m < 10; m ++) {
            TextMessage tm = session.createTextMessage(textBase+"#"+m);
            tm.setJMSReplyTo(queA);
            send.send(tm);
            System.out.println("sendRecvAsync, sent text="+tm.getText());
        }
        System.out.println("End sendRecvAsync");
    }
    
    public void stop() 
        throws JMSException
    {
        conn.stop();
        session.close();
        conn.close();
    }
    
    public static void main(String args[]) 
        throws Exception
    {
        System.out.println("Begin SendRecvClient,now=" + 
                           System.currentTimeMillis());
        SendRecvClient client = new SendRecvClient();
        client.sendRecvAsync("A text msg");
        client.done.acquire();
        client.stop();
        System.exit(0);
        System.out.println("End SendRecvClient");
    }
    
}

다음과 같은 명령을 통해 클라이언트를 실행시킵니다:

[nr@toki examples]$ ant -Dchap=chap6 -Dex=2 run-example
...
run-example2:
     [copy] Copying 1 file to /tmp/jboss-3.2.6/server/default/deploy
     [echo] Waiting 5 seconds for deploy...
     [java] Begin SendRecvClient, now=1098419197580
     [java] Begin sendRecvAsync
     [java] onMessage, recv text=A text msg#0processed by: 13929978
     [java] sendRecvAsync, sent text=A text msg#0
     [java] sendRecvAsync, sent text=A text msg#1
     [java] onMessage, recv text=A text msg#2processed by: 5495387
     [java] sendRecvAsync, sent text=A text msg#2
     [java] sendRecvAsync, sent text=A text msg#3
     [java] onMessage, recv text=A text msg#1processed by: 13929978
     [java] sendRecvAsync, sent text=A text msg#4
     [java] sendRecvAsync, sent text=A text msg#5
     [java] onMessage, recv text=A text msg#5processed by: 5495387
     [java] sendRecvAsync, sent text=A text msg#6
     [java] sendRecvAsync, sent text=A text msg#7
     [java] onMessage, recv text=A text msg#4processed by: 13929978
     [java] sendRecvAsync, sent text=A text msg#8
     [java] sendRecvAsync, sent text=A text msg#9
     [java] End sendRecvAsync
     [java] onMessage, recv text=A text msg#3processed by: 15690844
     [java] onMessage, recv text=A text msg#8processed by: 15690844
     [java] onMessage, recv text=A text msg#7processed by: 13929978
     [java] onMessage, recv text=A text msg#6processed by: 5495387
     [java] onMessage, recv text=A text msg#9processed by: 14089812

클라이언트가 실행되면 다음과 같은 JBoss 서버 콘솔 출력이 보여지게 됩니다:

23:26:36,720 INFO  [EjbModule] Deploying TextMDB
23:26:37,073 INFO  [EJBDeployer] Deployed: file:/private/tmp/jboss-3.2.6/server/default/deploy/chap6-ex2.jar
23:26:43,216 INFO  [TextMDB] TextMDB.ctor, this=13929978
23:26:43,224 INFO  [TextMDB] TextMDB.setMessageDrivenContext, this=13929978
23:26:43,299 INFO  [TextMDB] TextMDB.ejbCreate, this=13929978
23:26:43,401 INFO  [TextMDB] TextMDB.onMessage, this=13929978
23:26:43,408 INFO  [TextMDB] TextMDB.sendReply, this=13929978, dest=QUEUE.A
23:26:43,494 INFO  [TextMDB] TextMDB.onMessage, this=13929978
23:26:43,518 INFO  [TextMDB] TextMDB.sendReply, this=13929978, dest=QUEUE.A
23:26:43,571 INFO  [TextMDB] TextMDB.ctor, this=5495387
23:26:43,573 INFO  [TextMDB] TextMDB.setMessageDrivenContext, this=5495387
23:26:43,574 INFO  [TextMDB] TextMDB.ejbCreate, this=5495387
23:26:43,596 INFO  [TextMDB] TextMDB.onMessage, this=5495387
23:26:43,597 INFO  [TextMDB] TextMDB.sendReply, this=5495387, dest=QUEUE.A
23:26:43,802 INFO  [TextMDB] TextMDB.onMessage, this=13929978
23:26:43,803 INFO  [TextMDB] TextMDB.sendReply, this=13929978, dest=QUEUE.A
23:26:43,825 INFO  [TextMDB] TextMDB.onMessage, this=5495387
23:26:43,825 INFO  [TextMDB] TextMDB.sendReply, this=5495387, dest=QUEUE.A
23:26:43,880 INFO  [TextMDB] TextMDB.ctor, this=15690844
23:26:43,884 INFO  [TextMDB] TextMDB.setMessageDrivenContext, this=15690844
23:26:43,887 INFO  [TextMDB] TextMDB.ejbCreate, this=15690844
23:26:43,944 INFO  [TextMDB] TextMDB.onMessage, this=15690844
23:26:43,945 INFO  [TextMDB] TextMDB.sendReply, this=15690844, dest=QUEUE.A
23:26:44,022 INFO  [TextMDB] TextMDB.onMessage, this=13929978
23:26:44,022 INFO  [TextMDB] TextMDB.sendReply, this=13929978, dest=QUEUE.A
23:26:44,041 INFO  [TextMDB] TextMDB.onMessage, this=15690844
23:26:44,041 INFO  [TextMDB] TextMDB.sendReply, this=15690844, dest=QUEUE.A
23:26:44,065 INFO  [TextMDB] TextMDB.ctor, this=14089812
23:26:44,069 INFO  [TextMDB] TextMDB.setMessageDrivenContext, this=14089812
23:26:44,069 INFO  [TextMDB] TextMDB.ejbCreate, this=14089812
23:26:44,200 INFO  [TextMDB] TextMDB.onMessage, this=5495387
23:26:44,201 INFO  [TextMDB] TextMDB.sendReply, this=5495387, dest=QUEUE.A
23:26:44,249 INFO  [TextMDB] TextMDB.onMessage, this=14089812
23:26:44,250 INFO  [TextMDB] TextMDB.sendReply, this=14089812, dest=QUEUE.A

이 예제내에 포함된 항목들의 표기는 다음과 같습니다:

6.2. JBoss 메시징 개요

JBossMQ는 클라이언트 어플리케이션에서 JMS API 레벨 서비스들을 제공하기위해 함께 동작하는 여러개의 서비스들로 구성되어 있습니다. 이번 섹션에서는 JBossMQ JMS 구현을 구성하는 서비스들 소개하도록 하겠습니다.

6.2.1. 호출 계층(Invocation Layer)

호출 레이어(IL) 서비스는 클라이언트가 메시지들을 전송하고 수신하는데 사용하는 통신 프로토콜을 처리하는 임무를 담당합니다. JBossMQ는 동시에 서로 다른 타입의 호출 레이어를 실행시킬 수 있도록 지원됩니다. 모든 호출 레이어들은 클라이언트가 동시에 메시지를 전송하고 수신할 수 있도록 하는 양방향(bidirectional) 커뮤니케이션을 지원합니다. IL에서는 메시징의 전송에 관련된 세부사항만을 처리합니다. 여기서는 메시지들을 호출자(invoker)라고 알려진 JMS 서버의 JMX 게이트웨이쪽으로 메시지들을 위임합니다. 이것은 분리된 호출자들이 서로 다른 전송(transport)을 통해 EJB 컨테이너를 노출시키는 방법과 유사합니다.

JMS 커넥션 팩토리를 획득하는데 사용되는 JNDI 위치(location)에서 사용하기 원하는 프로토콜을 클라이언트가 선택합니다. 현재 JBossMQ는 6개의 호출 레이어를 갖고 있으며, 이것들은 다음에 오는 섹션들에서 소개하게 됩니다.

6.2.1.1. RMI IL (경시됨)

첫 번째로 개발되었던 IL로 자바의 원격 메쏘드 호출(RMI)에 기반을 두고 있습니다. 표준 RMI 기술에 기반을 두고 있기 때문에 견고성은 있으나 다른 IL들과 비교해볼때 매우 큰 과부하(overhead)를 유발시키므로 향후 출시되는 버전에서는 없어질 것 같습니다.

참고: 이 IL은 서버쪽에서 클라이언트쪽으로 TCP/IP 소켓을 만들려고 시도됩니다. 따라서 방화벽이나 보안정책에 따라 SeverSockets을 사용하는 연결을 제한하고 있는 클라이언트에서는 사용할 수 없습니다.

6.2.1.2. OIL IL (경시됨)

두 번째로 개발된 IL이 바로 최적화된(Optimized) IL(OIL) 입니다. OIL은 커스텀 TCP/IP 프로토콜과 매우 적은 부하만을 갖는 프로토콜 직렬화(serialization)을 사용합니다. 이 IL은 UIL2 프로토콜이 추가되기전까지는 소켓 기반 프로토콜의 권장사항이었습니다.

참고: 이 IL은 서버쪽에서 클라이언트로 TCP/IP 소켓을 연결하려 시도됩니다. 따라서 방화벽이나 보안정책에 따라 SeverSockets을 사용하는 연결을 제한하고 있는 클라이언트에서는 사용할 수 없습니다.

6.2.1.3. UIL IL (경시됨)

통합된(Unified) 호출 레이어(UIL)는 방화벽이나 다른 제약때문에 서버쪽에서 클라이언트로 연결을 만들 수 없는 클라이언트를 위해 개발되어졌습니다. 이것은 양방향 커뮤니케이션을 지원하기위해 멀티플렉싱(multiplexing) 레이어를 사용한다는 것만 빼면 OIL 프로토콜과 동일합니다. 멀티플렉싱 레이어는 두 개의 가상 소켓을 하나의 물리적인 소켓에 대해 만들게 됩니다. 이 IL은 멀티플렉싱 레이어를 사용하기 때문에 OIL에 비해 보다 높은 부하가 유발되어 느립니다. 이 호출 레이어는 이제 UIL2때문에 대체되고 있습니다.

6.2.1.4. UIL2 IL

통합된 버전 2 호출 레이어(UIL2)는 UIL 프로토콜의 변형으로 역시 클라이언트와 서버사이에서 단일 소켓을 사용합니다. 하지만, 소켓 레벨에서 메시지의 라운드-트립 블럭킹을 사용하는 RMI, UIL 그리고 OLI과 같은 소켓 기반의 호출 레이어와는 다르게 전송 레벨에서 메시지의 진정한 비동기적 전송과 수신을 UIL2 프로토콜에서는 사용합니다. 이 레이어는 향상된 성능과 기능들을 제공하기 때문에 소켓 호출 레이어 방식에서 선호되고 있습니다.

6.2.1.5. JVM IL

자바 가상 머신(JVM) 호출 레이어는 JMS 클라이언트가 서버와 동일한 JVM내에서 동작하는 경우 TCP/IP 오버헤드를 제거하기 위해 개발되어졌습니다. 이 IL은 클라이언트 요청들을 서비스하기위한 서버측의 직접적인 메쏘드 호출에 사용됩니다. 결국 어떠한 소켓도 만들어지지 않기때문에 소켓에 관련된 worker 쓰레드가 필요하지 않게되어 효율을 그 만큼 증가시키게 됩니다. 메시지 구동 빈즈(MDB)나 서블릿, MBeans 혹은 EJB와 같은 서버로써 동일한 가상 머신내에서 동작하는 다른 컴포넌트들에서는 이 IL을 사용되어야 하는 합니다.

6.2.1.6. HTTP IL

HTTP 호출 레이어(HTTPIL)은 HTTP 혹은 HTTPS 프로토콜을 통해 JBossMQ 서비스에 액세스할 수 있도록 합니다. 이 IL은 http 트래픽을 처리하는 deploy/jms/jbossmq-httpil.sar내에 배치된 서블릿에 의존적입니다. 이 IL은 HTTP만을 허용하는 방화벽을 통해 JMS에 액세스할 때 유용합니다.

6.2.2. 보안 관리자(Security Manager)

JBossMQ 보안 관리자(Security Manager)는 여러분의 목적지(destinations)들에 액세스하는 것을 방어하기 위한 액세스 컨트롤 리스트를 적용시키는 서비스입니다. 이 서브시스템은 StateManager 서비스와 밀접한 관계를 갖고 동작합니다.

6.2.3. 목적지 관리자(Destination Manager)

목적지 관리자(Destination Manager)는 JBossMQ에서의 중앙 서비스라고 생각할 수 있습니다. 목적지 관리자는 서버상에 만들어진 모든 목적지들에 대한 내역을 유지합니다. 또한 MessageCache, StateManagerPersistenceManager와 같은 다른 중요한 서비스들에 대한 내역까지도 유지합니다.

6.2.4. Message Cache

서버쪽에 생성된 메시지들은 메모리 관리를 위해 MessageCache쪽으로 넘겨집니다. 메시지들로써 올려진 JVM 메모리 사용(usage)은 수신자(receiver)들을 하나도 갖지 않고 있는 목적지(destination)쪽에 추가되어집니다. 이 메시지들은 수신자가 끄집어가져가기전까지는 메인 메모리내에 유지되어집니다. 만약 MessageCache가 JVM 메모리 사용에서 정의된 제한을 넘어서는 시점이라는 통지를 받게되면, 여기에 해당되는 메시지들을 메모리에서 디스크상의 영속적인 스토리지쪽으로 옮기기 시작합니다. MessageCache는 어떤 메시지들을 디스크쪽으로 이동시킬지를 최근 가장 적게 사용한(LRU-least recently used) 알고리즘에 따라 결정합니다.

6.2.5. 상태 관리자(State Manager)

StateManager (SM)는 어떤 것들이 서버쪽에 로그인 되도록 허용되었는지와 지속가능한(durable) 서브스크립션에 대한 정보를 유지하는 책임을 갖고 있습니다.

6.2.6. 영속성 관리자(Persistence Manager)

PersistenceManager (PM)는 영속적이어야 하는 표시가 달린 메시지를 저장하기 위해 목적지(destination)에서 사용됩니다. JBossMQ는 영속적 관리자의 구현을 위해 몇몇 상이한 구현을 갖고있지만, 서버 인스턴스당 오직 하나만을 사용할 수 있습니다. 여러분은 여러분의 요구사항에 최적인 영속성 관리자를 선택하여 사용해야만 합니다.

6.2.6.1. 파일 PM

파일 PM은 JBossMQ에 내장된 견고한 영속성 관리자입니다. 이 관라지는 서버쪽에 만들어진 목적지 각각에 대해 별도의 디렉터리를 만들고, 각각의 영속적인 메시지들을 별도의 파일로 적절한 디렉토리에 저장합니다. 이 관리자는 파일을 자주 열고 닫기때문에 낮은 성능을 보입니다.

6.2.6.2. Rolling Logged PM

Rolling Logged PM 또한 파일 기반의 영속성 관리자로써 여러개의 파일을 열고/닫을 때 발생하는 오버헤드를 감소시키기 위해 하나의 파일내에서 여러개의 메시지를 저장하도록 하였기 때문에 파일 PM보다는 조금 나은 성능을 갖습니다. 이 관리자는 매우 빠른 PM이기는 하나 FileOutputStream.flush() 메쏘드 호출을 사용하기 때문에 파일 PM에 비해 덜 안정적인 트랜잭셔널 특성을 갖습니다. 몇몇 오퍼레이팅 시스템/JVM에서 FileOutputStream.flush() 메쏘드는 호출에 따른 리턴으로써 디스크에 데이터가 쓰여진다는 보장을 받을 수 없는 경우도 있을 수 있습니다.

6.2.6.3. JDBC2 PM

JDBC2 PM 은 JBossMQ 2.4.x 버전에서 처음 도입된 JDBC PM 버전의 두 번째 버전입니다. 두 번째 버전에서는 매우 간단하면서도 성능이 대폭 향상되었습니다. 이 PM을 통해 여러분은 JDBC를 사용하여 영속적인 메시지들을 관계형 데이터베이스에 저장할 수 있습니다. 이 PM의 성능은 데이터베이스쪽의 성능과 직접적으로 연관되어 있습니다. 이 PM은 다른 영속성 관리자들과 비교해 볼때 매우 낮은 메모리 오버헤드만을 갖습니다. 더 나아가 이 관리자는 매우 활동적인 MessageCache를 갖는 시스템상에서 효과적인 영속성을 제공하는 MessageCache와의 높은 통합이 가능합니다.

6.2.7. 목적지(Destinations)

목적지는 클라이언트가 메시지들을 전송하고 수신받는데 사용하는 JBossMQ 서버상의 객체입니다. 목적지는 QueuesTopics 이라는 2개의 다른 타입으로 나눌 수 있습니다. JBossMQ에 의해 만들어진 목적지의 참조는 JNDI안에 저장됩니다.

6.2.7.1. 큐(Queues)

일반적으로 Point-to-Point 패러다임을 사용하는 클라이언트들은 Queue를 사용합니다. 큐쪽으로 전송된 메시지들은 오직 하나의 다른 클라이언트에 의해 한번만 수신되어집니다. 만약 단일 큐로부터 여러 클라이언트들이 메시지를 수신한다면, 메시지들은 수신자(receiver)들 사이에서 로드 밸런싱되어질 것입니다. 기본적으로 큐 객체(Queue object)는 JNDI의 queue/ 서브 컨텍스트 밑에 저장되어지게 됩니다.

6.2.7.2. 토픽(Topics)

토픽은 publish-subscribe패러다임내에서 사용됩니다. 클라이언트가 토픽쪽으로 메시지를 발행하면, 토픽은 자신을 구독하고 있는 각각의 클라이언트들쪽으로 메시지의 사본을 전달하게 될 것입니다. 토픽 메시지들은 마치 텔레비젼을 통해 쇼 프로그램이 수신자들에게 전달되는 형태와 동일합니다. 여러분이 TV를 켜고 쇼 프로그램을 시청하지 않는다면, 이 프로그램을 시청하지 못할 것입니다. 이와 유사하게 클라이언트가 준비되지 않고 토픽으로부터 메시지들을 수신하지 않는다면, 토픽쪽으로 발행된 메시지들을 수신하지 못하게 될 것입니다. 이렇게 메시지를 놓치게 되는 문제를 해결하기 위해서는 클라이언트가 지속가능한 구독(durable subscription)을 시작할 수 있어야 합니다. 이것은 마치 쇼 프로그램이 방영되는 시간대에 해당 프로그램을 시청하지 못할 경우 VCR로 녹화를 한 후, 나중에 그 프로그램을 보게 되는 것과 유사합니다.

6.3. JBoss 메시징 환경설정과 MBeans

이번 섹션에서는 앞쪽 섹션에서 소개하였던 컴포넌트들과 함께 대응되는 MBean 속성들을 갖는 MBean 서비스를 정의하겠습니다. JBossMQ 시스템을 구성하는 환경설정과 서비스 파일들은 다음과 같습니다:

다음 하부 섹션에서는 관련된 MBeans에 대해 논의해보도록 하겠습니다.

6.3.1. org.jboss.mq.il.jvm.JVMServerILService

org.jboss.mq.il.jvm.JVMServerILService MBean은 JVM IL 설정에 사용됩니다. 설정 가능한 속성들은 다음과 같습니다:

6.3.2. org.jboss.mq.il.rmi.RMIServerILService (경시됨)

org.jboss.mq.il.rmi.RMIServerILService는 RMI IL 설정에 사용됩니다. 설정 가능한 속성들은 다음과 같습니다:

6.3.3. org.jboss.mq.il.oil.OILServerILService (경시됨)

org.jboss.mq.il.oil.OILServerILService는 OIL IL 설정에 사용됩니다. 설정가능한 속성들은 다음과 같습니다:

6.3.4. org.jboss.mq.il.uil.UILServerILService (경시됨)

org.jboss.mq.il.uil.UILServerILService는 UIL IL 설정을 위해 사용되어집니다. 이 서비스가 JBoss 3.2.2 배포판부터는 기본적으로 제거되어 있지만, docs/examples/jca directory 안에 위치된 예제 환경설정 파일에는 들어있습니다.

UILServerILService의 설정가능한 속성들은 다음과 같습니다:

6.3.5. org.jboss.mq.il.uil2.UILServerILService

org.jboss.mq.il.uil2.UILServerILService는 UIL2 IL을 설정하는데 사용됩니다. 설정가능한 속성들은 다음과 같습니다:

6.3.5.1. SSL을 위한 IL 설정하기

UIL2와 OIL 서비스들은 IL 서비스와 관련된 보안 도메인을 사용하여 JSSE를 통합하는 커스텀 소켓 팩토리를 통해 SSL 사용을 지원합니다. 예제 6.10, “SSL을 사용하는 예제 UIL2 config 일부분”은 커스텀 JBoss SSL 소켓 팩토리의 사용예를 보여주는 UIL2 서비스 서술자 예제의 일부분입니다.

예제 6.10. SSL을 사용하는 예제 UIL2 config 일부분

<mbean code="org.jboss.mq.il.uil2.UILServerILService"
    name="jboss.mq:service=InvocationLayer,type=HTTPSUIL2">
    <depends optional-attribute-name="Invoker">jboss.mq:service=Invoker</depends>
    <attribute name="ConnectionFactoryJNDIRef">SSLConnectionFactory</attribute>
    <attribute name="XAConnectionFactoryJNDIRef">SSLXAConnectionFactory</attribute>
    
    <!-- ... -->

    <!-- SSL Socket Factories -->
    <attribute name="ClientSocketFactory">
        org.jboss.security.ssl.ClientSocketFactory
    </attribute>
    <attribute name="ServerSocketFactory">
        org.jboss.security.ssl.DomainServerSocketFactory
    </attribute>
    <!-- Security domain - see below -->
    <attribute name="SecurityDomain">java:/jaas/SSL</attribute>
</mbean>

<!-- Configures the keystore on the "SSL" security domain
     This mbean is better placed in conf/jboss-service.xml where it
     can be used by other services, but it will work from anywhere.
     Use keytool from the sdk to create the keystore. -->
     
<mbean code="org.jboss.security.plugins.JaasSecurityDomain"
       name="jboss.security:service=JaasSecurityDomain,domain=SSL">
    <!-- This must correlate with the java:/jaas/SSL above -->
    <constructor>
        <arg type="java.lang.String" value="SSL"/>
    </constructor>
    <!-- The location of the keystore resource: loads from the
         classpath and the server conf dir is a good default -->
    <attribute name="KeyStoreURL">resource:uil2.keystore</attribute>
    <attribute name="KeyStorePass">changeme</attribute>
</mbean>

6.3.5.2. UIL2 전송을 위한 JMS 클라이언트 속성들

UIL2 전송을 사용하는 JMS 클라이언트가 서버쪽에 다시 클라이언트 연결 제어를 설정할 수 있는 시스템 속성들이 있습니다 :

  • org.jboss.mq.il.uil2.useServerHost: 이 시스템 속성을 통해 클라이언트는 InetAddress.getHostAddress 값보다 서버의 InetAddress.getHostName에 연결할 수 있도록 합니다. 이것은 서버와 클라이언트 환경사이에서 이름과 주소가 서로 다른 경우에만 달라집니다.
  • org.jboss.mq.il.uil2.localAddr: 이 시스템 속성은 자신의 소켓이 연결되어져야만 하는 로컬 인터페이스를 클라이언트가 정의할 수 있도록 합니다.
  • org.jboss.mq.il.uil2.localPort: 이 시스템 속성은 자신의 소켓이 연결되어져야만 하는 로컬 포트를 클라이언트가 정의할 수 있도록 합니다.
  • org.jboss.mq.il.uil2.serverAddr: 이 시스템 속성은 클라이언트가 연결 시도를 하게되는 주소를 클라이언트쪽에서 덮어쓸 수 있도록 합니다. 이것은 클라이언트와 JMS 서버사이가 NAT(네트워크 주소 치환)인 네트윅 환경에서 유용합니다.
  • org.jboss.mq.il.uil2.serverPort: 이 시스템 속성은 클라이언트가 연결을 시도할 포트를 클라이언트에서 덮어쓸 수 있도록 합니다. 이것은 클라이언트와 JMS 서버사이에서 포트 포워딩이 발생되는 네트윅 환경에서 유용합니다.
  • org.jboss.mq.il.uil2.retryCount: 이 시스템 속성은 JMS 서버쪽에 재 연결되는 시도의 횟수를 제어합니다. 재시도는 java.net.ConnectException 실패시에만 일어납니다. 이 값으로 <= 0 을 설정하였다면 재시도가 일어나지 않습니다.
  • org.jboss.mq.il.uil2.retryDelay: 이 시스템 속성은 ConnectException 실패로 인해 발생하는 재시도 사이의 간격을 밀리세컨드 단위의 지연 시간으로 제어합니다.

6.3.6. org.jboss.mq.il.http.HTTPServerILService

org.jboss.mq.il.http.HTTPServerILService는 HTTP/S IL 관리에 사용됩니다. 이 IL은 HTTP나 HTTPS 커넥션을 통해 JMS 서비스를 사용할 수 있도록 합니다. HTTP 트래픽을 처리하기위해 deploy/jms/jbossmq-httpil.sar내에 배치된 서블릿에 의존적입니다. 설정가능한 속성들은 다음과 같습니다:

6.3.7. org.jboss.mq.server.jmx.Invoker

org.jboss.mq.server.jmx.Invoker는 인터셉터 스택을 통해 목적지 관리자 서비스쪽으로 IL 요청들을 내려주는데 사용됩니다. 설정가능한 속성들은 다음과 같습니다:

6.3.8. org.jboss.mq.server.jmx.InterceptorLoader

org.jboss.mq.server.jmx.InterceptorLoader는 범용적인 인터셉터를 로드하고 인터셉터 스택의 부분으로 만드는데 사용되어집니다. 이 MBean은 일반적으로 트레이스 레벨 로그 메시지들을 통해 모든 클라이언트 요청들을 효과적으로 로깅하는데 사용될 수 있는 org.jboss.mq.server.TracingInterceptor와 같은 커스텀 인터셉터들을 로딩하는데 사용됩니다. 설정가능한 속성들은 다음과 같습니다:

6.3.9. org.jboss.mq.sm.file.DynamicStateManager

org.jboss.mq.sm.file.DynamicStateManager MBean은 DestinationManager 서비스에 할당되는 디폴트 상태 관리자로 사용됩니다. 인증(authentication), 권한부여(authorization) 그리고 지속가능한 구독 정보(durable subscriber information)를 제공하는 XML 사용자 보안 저장소를 관리합니다. 설정가능한 속성들은 다음과 같습니다:

The jbossmq-state.xml content model

그림 6.1. jbossmq-state.xml 컨텐츠 모델

6.3.10. org.jboss.mq.security.SecurityManager

org.jboss.mq.security.SecurityManager가 인터셉터 스택의 일부라면, 목적지에 할당된 액세스 컨트롤 목록을 시행시킬 것입니다. SecurityManager가 JAAS를 사용하고 JBoss login-config.xml 파일내에서 설정되어지는 이러한 어플리케이션 정책을 필요로 합니다. 디폴트 환경설정은 다음과 같습니다.

<application-policy name="jbossmq">
    <authentication>
        <login-module code="org.jboss.mq.sm.file.DynamicLoginModule" flag="required">
            <module-option name="unauthenticatedIdentity">guest</module-option>
            <module-option name="sm.objectname">
                jboss.mq:service=StateManager
            </module-option>
        </login-module>
    </authentication>
</application-policy>

이것은 DynamicStateManager jbossmq-state.xml 보안 저장소를 org.jboss.mq.sm.file.DynamicLoginModule을 통하여 JAAS 기반의 프레임워크에 통합시킵니다. 또한 환경설정은 어떠한 비인증된 JBossMQ 클라이언트라도 guest 역할로 매핑시킵니다.

SecurityManager의 설정가능한 속성들은 다음과 같습니다:

목적지 보안 config 컨텐츠 모델

그림 6.2. 목적지 보안 config 컨텐츠 모델

여러분이 인증과 인가에 필요한 정보를 XML 파일내에 유지한다는 것에 불안감을 갖을 수도 있습니다. 동일한 username과 password 그리고 DynamicStateManager로써의 role 매핑을 위한 user를 제공하는 JAAS login-config.xml을 업데이트함으로써 간단히 데이터베이스나 LDAP 서버와 같은 표준 보안 저장소를 이용할 수 있습니다. 예를 들어 JDBC 데이터베이스를 사용하고자 한다면, 다음에 제공되는 샘플 데이터베이스 테이블과 login-config.xml 엔트리로 가능할것입니다.

표 6.1. username과 password를 갖는 예제 JMSPasswords 테이블

usernamepassword
jduketheduke

표 6.2.  username과 액세스 role을 갖는 예제 JMSRoles 테이블

usernamerole
jduke create
jduke read
jduke write

예제 6.11. JBossMQ를 위한 변경된 login-config.xml 환경설정

<application-policy name="jbossmq">
    <authentication>
        <login-module
            code="org.jboss.security.auth.spi.DatabaseServerLoginModule" flag="required">
            <module-option name="unauthenticatedIdentity">guest </module-option>
            <module-option name="dsJndiName">java:/DefaultDS </module-option>
            <module-option name="principalsQuery"> select password from
                JMSPasswords where username = ? </module-option>
            <module-option name="rolesQuery"> select role, "Roles" from JMSRoles
                where username= ? </module-option>
        </login-module>
    </authentication>
</application-policy>

DatabaseServerLoginModule에 대한 완전한 설명은 8.4.6.4 절, “org.jboss.security.auth.spi.DatabaseServerLoginModule” 을 참조하십시오.

6.3.11. org.jboss.mq.server.jmx.DestinationManager

org.jboss.mq.server.jmx.DestinationManager는 인터셉터 스택내에서 마지막 인터셉터이어야만 합니다. 설정가능한 속성들은 다음과 같습니다:

모니터링을 지원하는 추가적인 읽기만 가능한 속성과 오퍼레이션들은 다음과 같습니다:

6.3.12. org.jboss.mq.server.MessageCache

서버는 언제 메시지들을 org.jboss.mq.server.MessageCache MBean을 사용해서 이차 저장소로 옮길지를 결정합니다. 설정가능한 속성들은 다음과 같습니다:

통계를 제공하는 추가적인 읽기전용 캐쉬 속성에는 다음이 포함됩니다:

6.3.13. org.jboss.mq.pm.file.CacheStore

org.jboss.mq.pm.file.CacheStore MBean은 파일이나 Rolling Logged PM을 사용하는 경우의 MessageCache 서비스를 위한 캐쉬 저장소로써 사용되어져야만 합니다. 설정가능한 속성들은 다음과 같습니다:

6.3.14. org.jboss.mq.pm.file.PersistenceManager

org.jboss.mq.pm.file.PersistenceManager는 파일 PM을 사용하기 원할때 DestinationManager에 할당되는 영속 관리자로 사용되어야만 합니다. 설정가능한 속성들로는 다음의 것들이 있습니다:

6.3.15. org.jboss.mq.pm.rollinglogged.PersistenceManager

org.jboss.mq.pm.rollinglogged.PersistenceManager는 Rolling Logged PM을 사용하고자 할때 DestinationManager쪽에 할당되는 PersistenceManager로써 사용되어져야만 합니다. 설정가능한 속성들은 다음과 같습니다:

6.3.16. org.jboss.mq.pm.jdbc2.PersistenceManager

org.jboss.mq.pm.jdbc.PersistenceManager는 데이터베이스내에 메시지들을 저장하고자 할때 DestinationManager에 할당되는 영속 관리자로써 사용되어야만 합니다. 이 PM은 HypersonSQL, MS SQL, Oracle, MySQL 그리고 Postgres 데이터베이스에서 테스트되어졌습니다. 설정가능한 속성들은 다음과 같습니다:

예제 6.12. 디폴트 JDBC2 PeristenceManager SqlProperties

<attribute name="SqlProperties">
    BLOB_TYPE=OBJECT_BLOB
    INSERT_TX = INSERT INTO JMS_TRANSACTIONS (TXID) values(?)
    INSERT_MESSAGE = INSERT INTO JMS_MESSAGES (MESSAGEID, DESTINATION,
        MESSAGEBLOB, TXID, TXOP) VALUES(?,?,?,?,?)
    SELECT_ALL_UNCOMMITED_TXS = SELECT TXID FROM JMS_TRANSACTIONS
    SELECT_MAX_TX = SELECT MAX(TXID) FROM JMS_MESSAGES
    SELECT_MESSAGES_IN_DEST = SELECT MESSAGEID, MESSAGEBLOB FROM JMS_MESSAGES \
        WHERE DESTINATION=?
    SELECT_MESSAGE = SELECT MESSAGEID, MESSAGEBLOB FROM JMS_MESSAGES WHERE \
        MESSAGEID=? AND DESTINATION=?
    MARK_MESSAGE = UPDATE JMS_MESSAGES SET TXID=?, TXOP=? WHERE MESSAGEID=? AND \
        DESTINATION=?
    UPDATE_MESSAGE = UPDATE JMS_MESSAGES SET MESSAGEBLOB=? WHERE MESSAGEID=? AND \
        DESTINATION=?
    UPDATE_MARKED_MESSAGES = UPDATE JMS_MESSAGES SET TXID=?, TXOP=? WHERE TXOP=?
    UPDATE_MARKED_MESSAGES_WITH_TX = UPDATE JMS_MESSAGES SET TXID=?, TXOP=? WHERE
        TXOP=? AND TXID=?
    DELETE_MARKED_MESSAGES_WITH_TX = DELETE FROM JMS_MESSAGES WHERE TXID IN \
     (SELECT TXID FROM JMS_TRANSACTIONS) AND TXOP=?
    DELETE_TX = DELETE FROM JMS_TRANSACTIONS WHERE TXID = ?
    DELETE_MARKED_MESSAGES = DELETE FROM JMS_MESSAGES WHERE TXID=? AND TXOP=?
    DELETE_MESSAGE = DELETE FROM JMS_MESSAGES WHERE MESSAGEID=? AND DESTINATION=?
    CREATE_MESSAGE_TABLE = CREATE TABLE JMS_MESSAGES ( MESSAGEID INTEGER NOT NULL, \
        DESTINATION VARCHAR(255) NOT NULL, TXID INTEGER, TXOP CHAR(1), \
        MESSAGEBLOB OBJECT, PRIMARY KEY (MESSAGEID, DESTINATION) )
    CREATE_TX_TABLE = CREATE TABLE JMS_TRANSACTIONS ( TXID INTEGER )
</attribute> 

예제 6.13. 오라클을 위한 JDBC2 PeristenceManager SqlProperties 샘플

<attribute name="SqlProperties">
    BLOB_TYPE=BINARYSTREAM_BLOB
    INSERT_TX = INSERT INTO JMS_TRANSACTIONS (TXID) values(?)
    INSERT_MESSAGE = INSERT INTO JMS_MESSAGES (MESSAGEID, DESTINATION, \
        MESSAGEBLOB, TXID, TXOP) VALUES(?,?,?,?,?)
    SELECT_ALL_UNCOMMITED_TXS = SELECT TXID FROM JMS_TRANSACTIONS
    SELECT_MAX_TX = SELECT MAX(TXID) FROM JMS_MESSAGES
    SELECT_MESSAGES_IN_DEST = SELECT MESSAGEID, MESSAGEBLOB FROM JMS_MESSAGES \
        WHERE DESTINATION=?
    SELECT_MESSAGE = SELECT MESSAGEID, MESSAGEBLOB FROM JMS_MESSAGES WHERE \
        MESSAGEID=? AND DESTINATION=?
    MARK_MESSAGE = UPDATE JMS_MESSAGES SET TXID=?, TXOP=? WHERE MESSAGEID=? \
        AND DESTINATION=?
    UPDATE_MESSAGE = UPDATE JMS_MESSAGES SET MESSAGEBLOB=? WHERE MESSAGEID=? \
        AND DESTINATION=?
    UPDATE_MARKED_MESSAGES = UPDATE JMS_MESSAGES SET TXID=?, TXOP=? WHERE TXOP=?
    UPDATE_MARKED_MESSAGES_WITH_TX = UPDATE JMS_MESSAGES SET TXID=?, TXOP=? WHERE \
        TXOP=? AND TXID=?
    DELETE_MARKED_MESSAGES_WITH_TX = DELETE FROM JMS_MESSAGES WHERE TXID IN \
        (SELECT TXID FROM JMS_TRANSACTIONS) AND TXOP=?
    DELETE_TX = DELETE FROM JMS_TRANSACTIONS WHERE TXID = ?
    DELETE_MARKED_MESSAGES = DELETE FROM JMS_MESSAGES WHERE TXID=? AND TXOP=?
    DELETE_MESSAGE = DELETE FROM JMS_MESSAGES WHERE MESSAGEID=? AND DESTINATION=?
    CREATE_MESSAGE_TABLE = CREATE TABLE JMS_MESSAGES ( MESSAGEID INTEGER NOT NULL, \
        DESTINATION VARCHAR(255) NOT NULL, TXID INTEGER, TXOP CHAR(1), \
        MESSAGEBLOB BLOB, PRIMARY KEY (MESSAGEID, DESTINATION) )
    CREATE_TX_TABLE = CREATE TABLE JMS_TRANSACTIONS ( TXID INTEGER )
</attribute>

6.3.17. 목적지(Destination) MBeans

이번 섹션에서는 jbossmq-destinations-service.xmljbossmq-service.xml 서술자내에서 사용되는 목적지 MBeans에 대하여 설명합니다.

6.3.17.1. org.jboss.mq.server.jmx.Queue

org.jboss.mq.server.jmx.Queue는 JBossMQ 서버측의 큐 목적지를 정의하는데 사용됩니다. 이 MBean의 JMX 객체 이름의 name 속성은 목적지 이름을 결정하는데 사용됩니다. 즉, JMX MBean이 다음과 같이 시작된다면:

<mbean code="org.jboss.mq.server.jmx.Queue"
       name="jboss.mq.destination:service=Queue,name=testQueue">

JMX 객체의 이름은 jboss.mq.destination:service=Queue,name=testQueue이 되고, 큐의 이름은 "testQueue"입니다. 설정가능한 속성들은 다음과 같습니다:

  • DestinationManager: 서버를 위한 목적지 관리자 서비스의 JMX ObjectName. 이 속성은 <depends optional-attribute-name="DestinationManager"> XML 태그를 통해 설정되어야만 합니다.
  • SecurityManager: 클라이언트 요청들을 검증하는데 사용하는 보안 관리자 서비스의 JMX ObjectName. 이 속성은 <depends optional-attribute-name="SecurityManager"> XML 태그를 통해 설정되어야만 합니다.
  • SecurityConf: 이 요소에는 목적지에 대한 클라이언트의 오퍼레이션을 인가해주는 SecurityManager에서 사용되는 액세스 컨트롤 리스트를 설명하는 XML 부분을 지정합니다. 컨텐츠 모델은 SecurityManagerSecurityConf 속성의 것과 동일합니다.
  • JNDIName: 큐 객체가 연결되어지는 JNDI내의 위치. 이 값을 설정하지 않으면 기본값은 queue/queue-name이 됩니다.
  • MaxDepth: MaxDepth는 목적지를 위해 존재하는 메시지들의 backlog 제한에 대한 상한값(upper limit)이며, 이 값을 초과하게되면 org.jboss.mq.DestinationFullException에 새로운 메시지가 던져지게 됩니다. MaxDepth는 다양한 상황에서 초과될 수 있습니다. 즉 메시지가 큐를 매우 많이 소비하게 되는 경우로써, 트랜잭션이 read committed 프로세싱을 수행하거나 현재 큐의 크기를 살펴볼때, 현재 트랜잭션이나 다른 트랜잭션의 결과로 추가되어지는 메시지들의 무시등과 같은 경우에 메시지는 큐를 매우 소비하게 되어 때때로 MaxDepth를 초과하게 됩니다. 이것은 메시지가 물리적으로 큐쪽에 추가될 때 commit 상태(phase)동안 트랜잭션이 실패하길 원하지 않기때문에 일어나게 됩니다.
  • MessageCounterHistoryDayLimit: 목적지 메시지 카운터의 기록 날짜(history day)를 정하는 것으로 그 값이 0보다 작은(< 0) 경우는 기록날짜의 무제한을 의미하며, 0인 경우에는 기록을 하지 않는 것이고, 0 보다 크면( > 0) 기록 날짜의 카운트입니다.

통계 정보를 제공하는 추가적인 읽기전용 속성들은 다음과 같습니다:

  • MessageCounter: 이 목적지에 대한 통계를 제공하는 org.jboss.mq.server.MessageCounter 인스턴스들의 배열.
  • QueueDepth: 메시지들을 대기하는 현재 backlog.
  • ReceiversCount: 현재 큐에 연관된 수신자(receivers)들의 수.
  • ScheduledMessageCount: 큐에서 도착할 수 있도록 전달되는 시간을 할당받아(scheduled) 대기중인 메시지들의 수.
  • String listMessageCounter(): 이 오퍼레이션은 다음을 포함하는 HTML 테이블을 생성합니다:
    • Type: 목적지의 타입을 가리키는 Queue 또는 Topic 중의 하나가 올 수 있습니다.
    • Name: 목적지의 이름.
    • Subscription: 토픽에 대한 구독 ID.
    • Durable: 토픽 구독이 지속가능한지를 가리키는 부울린 값.
    • Count: 목적지로 전달되어진 메시지의 개수.
    • CountDelta: 이전에 카운트한 메시지 갯수로부터 변화된 차이.
    • Depth: 목적지내의 메시지 개수.
    • DepthDelta: depth의 이전 액세스이후 변화된 목적지에서의 메시지 갯수의 차이.
    • Last Add: 메시지가 목적지에 추가된 최종 시간을 DateFormat.SHORT/DateFormat.MEDIUM 포맷으로 나타낸 date/time 문자열.
  • void resetMessageCounter(): 모든 목적지 카운터들과 최종 추가 시간들을 0으로 설정.
  • String listMessageCounterHistory(): 이 오퍼레이션은 그날 그날의 기록에 대해 시간대별로 메시지 카운트를 보여주는 HTML 테이블을 표시합니다.
  • void resetMessageCounterHistory(): 이 오퍼레이션은 날짜 기록 메시지 카운트를 리셋시킵니다.

6.3.17.2. org.jboss.mq.server.jmx.Topic

org.jboss.mq.server.jmx.Topic은 JBossMQ 서버의 토픽 목적지를 정의하는데 사용됩니다. 이 MBean의 JMX 객체 이름의 name 속성은 목적지 이름을 결정하는데 사용합니다. 즉, JMX MBean이 다음과 같이 시작한다면:

<mbean code="org.jboss.mq.server.jmx.Topic"
       name="jboss.mq.destination:service=Topic,name=testTopic">

JMX 객체 이름은 jboss.mq.destination:service=Topic,name=testTopic 이고, 토픽의 이름은 testTopic이 됩니다. 설정가능한 속성들은 다음과 같습니다:

  • DestinationManager: 서버에 대한 설정된 목적지 관리자의 JMX 객체 이름. 이 속성은 <depends optional-attribute-name="DestinationManager"> XML 태그를 통해 설정되어야만 합니다.
  • SecurityManager: 클라이언트 요청들을 검증하는데 사용되는 보안 관리자의 JMX 객체 이름. 이 속성은 <depends optional-attribute-name="SecurityManager"> XML 태그를 통해 설정되어야만 합니다.
  • SecurityConf: 이 요소에는 목적지에 대해 클라이언트 오퍼레이션을 인가해주는 SecurityManager에서 사용되는 액세스 컨트롤 리스트를 설명하는 XML 부분(fragment)을 지정합니다. 컨텐츠 모델은 SecurityManagerSecurityConf 속성의 것과 동일합니다.
  • JNDIName: 큐 객체가 연결되어질 JNDI내의 위치. 지정하지 않은 경우에는 디폴트로 topic/topic-name이 됩니다.
  • MaxDepth: MaxDepth는 목적지를 위해 존재하는 메시지들의 backlog 제한에 대한 상한값(upper limit)이며, 이 값을 초과하게되면 org.jboss.mq.DestinationFullException에 새로운 메시지가 던져지게 됩니다. MaxDepth는 다양한 상황에서 초과될 수 있습니다. 즉 메시지가 큐를 매우 많이 소비하게 되는 경우로써, 트랜잭션이 read committed 프로세싱을 수행하거나 현재 큐의 크기를 살펴볼때, 현재 트랜잭션이나 다른 트랜잭션의 결과로 추가되어지는 메시지들의 무시등과 같은 경우에 메시지는 큐를 매우 소비하게 되어 때때로 MaxDepth를 초과하게 됩니다. 이것은 메시지가 물리적으로 토픽쪽에 추가될 때 commit 상태(phase)동안 트랜잭션이 실패하길 원하지 않기때문에 일어나게 됩니다.
  • MessageCounterHistoryDayLimit: 목적지 메시지 카운터의 기록 날짜(history day)를 정하는 것으로 그 값이 0보다 작은(< 0) 경우는 기록날짜의 무제한을 의미하며, 0인 경우에는 기록을 하지 않는 것이고, 0 보다 크면( > 0) 기록 날짜의 카운트입니다.

통계 정보를 제공하는 추가적인 읽기전용 속성들은 다음과 같습니다:

  • AllMessageCount: 토픽에 관련되어 있는 모든 큐 타입들에 대한 메시지의 카운트.
  • AllSubscriptionsCount: 지속가능과 비-지속 구독에 대한 카운트.
  • DurableMessageCount: 지속가능한 구독 큐내에 있는 메시지의 개수.
  • DurableSubscriptionsCount: 지속가능한 구독자의 카운트.
  • MessageCounter: 이 목적지에 대한 통계를 제공하는 org.jboss.mq.server.MessageCounter 인스턴스들의 배열.
  • NonDurableMessageCount: 비-지속 구독 큐내의 메시지 카운트.
  • NonDurableSubscriptionsCount: 비-지속 구독자의 카운트.
  • String listMessageCounter(): 이 오퍼레이션은 다음을 포함하는 HTML 테이블을 생성합니다:
    • Type: 목적지의 타입을 가리키는 Queue 또는 Topic 중의 하나가 올 수 있습니다.
    • Name: 목적지의 이름.
    • Subscription: 토픽에 대한 구독 ID.
    • Durable: 토픽 구독이 지속가능한지를 가리키는 부울린 값.
    • Count: 목적지로 전달되어진 메시지의 개수.
    • CountDelta: 이전에 카운트한 메시지 갯수로부터 변화된 차이.
    • Depth: 목적지내의 메시지 개수.
    • DepthDelta: depth의 이전 액세스이후 변화된 목적지에서의 메시지 갯수의 차이.
    • Last Add: 메시지가 목적지에 추가된 최종 시간을 DateFormat.SHORT/DateFormat.MEDIUM 포맷으로 나타낸 date/time 문자열.
  • void resetMessageCounter(): 모든 목적지 카운터들과 최종 추가 시간들을 0으로 설정.
  • String listMessageCounterHistory(): 이 오퍼레이션은 그날 그날의 기록에 대해 시간대별로 메시지 카운트를 보여주는 HTML 테이블을 표시합니다.
  • void resetMessageCounterHistory(): 이 오퍼레이션은 날짜 기록 메시지 카운트를 리셋시킵니다.

6.3.18. JMX를 통한 관리(Administration)

JMX를 통해 JBossMQ 통계와 몇몇 관리 기능을 사용할 수 있습니다. JMX는 웹 어플리케이션이나 JMX API를 이용해 프로그램적으로 액세스할 수 있습니다. 서버쪽에서 동작되고 있는 모든 JBossMQ JMX MBean들과 JMX 콘솔을 통해 해당 MBean의 메쏘드를 호출하는데 친숙해질 수 있도록 http://localhost:8080/jmx-console 웹 어플리케이션을 사용하는 것을 권장합니다. 이번 섹션에서는 관리자가 수행해야만 하는 일반적은 런타임 관리 작업에 대한 아웃라인을 살펴보도록 하겠습니다.

6.3.18.1. 런타임에서 큐 생성시키기

런타임시에 큐를 동적으로 생성시킬 필요가 있는 어플리케이션들은 목적지 관리자 MBean의 createQueue 메쏘드를 사용하면 됩니다:
void createQueue(String name, String jndiLocation)

이 메쏘드는 주어진 name으로 하나의 큐를 만들고 JNDI내의 jndiLocation에 연결합니다. 이 메쏘드를 통해 생성된 큐들은 서버가 재시작되기전까지 살아있게 됩니다. 이전에 만든 큐를 제거하려면 void destroyQueue(String name) 메쏘드를 실행시키십시오.

6.3.18.2. 런타임에서 토픽 생성시키기

런타임에서 동적으로 토픽을 생성시킬 필요가 있는 어플리케이션들은 목적지 관리자 MBean의 createTopic 메쏘드를 사용하면 됩니다:
void createTopic(String name,String jndiLocation)

이 메쏘드는 주어진 name으로 하나의 토픽을 생성하고 JNDI내의 jndiLocation에 연결합니다. 이 메쏘드를 통해 생성된 토픽들은 서버가 재시작되기전까지 살아있게 됩니다. 이전에 생성된 토픽을 제거하기위해서는 void destroyTopic(String name)을 실행시키십시오.

6.3.18.3. 런타임에서 JBossMQ User ID 관리하기

org.jboss.mq.sm.file.DynamicStateManager의 MBean은 런타임에서 user id들과 roles을 추가하거나 삭제할 수 있습니다. user id를 추가하기위해서는 void addUser(String name, String password, String clientID)을 사용합니다.

이 메쏘드는 주어진 clientID를 갖는 name과 password를 갖는 user id를 만듭니다. 이전에 만들어진 user id를 제거하려면 void removeUser(String name) 메쏘드를 실행시킵니다.

user id가 속해있는 role 관리를 위해서는 role의 생성, 제거 그리고 user의 추가 및 제거와 같은 일련의 메쏘드를 사용하게 됩니다:

  • void addRole(String name)
  • void removeRole(String name)
  • void addUserToRole(String roleName, String user)
  • void removeUserFromRole(String roleName, String user)

6.4. MDB JMS 프로바이더 지정하기

지금까지는 표준 JMS 클라이언트/서버 아키텍쳐에 대해 살펴보았습니다. JMS 사양서에는 목적지의 메시지들에 대한 동시적인 프로세싱을 허용하는 향상된 인터페이스들의 세트와 이러한 기능성을 집약시켜 어플리케이션 서버 기능(ASF-Application Server Facility)으로 참조하게 합니다. 동시적으로 메시지를 프로세싱하는 javax.jms.ServerSessionPooljavax.jms.ServerSession 2개의 인터페이스는 프로세싱이 일어나는 어플리케이션 서버에 의해 제공되어져야만 합니다. 따라서 JBossMQ ASF를 구성하는 컴포넌트들의 세트에는 JBoss 서버 컴포넌트들뿐만 아니라 JBossMQ 컴포넌트들까지도 포함됩니다. JBoss 서버의 MDB 컨테이너는 JMS 서비스의 ASF를 사용하여 MDB로 전송되는 메시지 처리를 동시적으로 하게됩니다.

ASF 도메인의 책임은 JMS 사양서에 의해 잘 정의되어 있기때문에 ASF 컴포넌트들이 어떻게 구현되어졌는가를 논의하지는 않겠씁니다. 그보다는 JBoss MDB 계층에서 ASF 컴포넌트들을 사용하여 어플리케이션 서버 인터페이스나 JMS 프로바이더 인터페이스중 하나에 대체될 수 있는 구현을 가능케 하는 MBean을 사용하는 통합과정에 대해 논의해보도록 하겠습니다.

먼저 org.jboss.jms.jndi.JMSProviderLoader MBean으로 시작해보도록 하겠습니다. 이 MBean은 JBoss 서버쪽으로 org.jboss.jms.jndi.JMSProviderAdaptor 인터페이스의 인스턴스를 로딩시키고 JNDI쪽에 연결할 책임을 갖고 있습니다. JMSProviderAdaptor 인터페이스는 JMS 프로바이더에 대한 루트 JNDI 컨텍스트를 얻는 방법과 루트 InitialContext에 대한 Context.PROVIDER_URL , 루트 컨텍스트에서의 QueueConnectionFactoryTopicConnectionFactory 위치에 대한 JNDI 이름을 얻고 설정하는 인터페이스에 대한 추상화(abstraction)입니다. 이것은 JMS 프로바이더를 사용하는 부트스트랩에서 실제로 필요로하는 모든 것입니다. 이러한 정보를 인터페이스쪽으로 추상화시킴으로써 대리(alternate) JMS ASF 프로바이더 구현을 JBoss MDB 컨테이너와 함께 사용할 수 있습니다. org.jboss.jms.jndi.JBossMQProviderJMSProviderAdaptor 인터페이스의 디폴트 구현이며 JBossMQ JMS 프로바이더용 아답터를 제공합니다. JBossMQ 프로바이더를 대리 JMS ASF 구현으로 교체하려면 간단히 JMSProviderAdaptor 인터페이스의 구현을 생성하고 구현 클래스 이름을 갖는 JMSProviderLoader를 설정하면 됩니다.

MDB에 대해 사용되는 JMS 프로바이더를 교체하는 것에 더하여, 여러분은 javax.jms.ServerSessionPool인터페이스 구현까지도 대체할 수 있습니다. 이것은 org.jboss.jms.asf.ServerSessionPoolLoader MBean PoolFactoryClass 속성을 사용하여 org.jboss.jms.asf.ServerSessionPoolFactory 구현 클래스 이름을 설정함으로써 가능합니다. 디폴트 ServerSessionPoolFactory 팩토리 구현은 JBoss org.jboss.jms.asf.StdServerSessionPoolFactory 클래스입니다.

6.4.1. org.jboss.jms.jndi.JMSProviderLoader MBean

JMSProviderLoader MBean 서비스는 JMS 프로바이더 아답터를 생성하고 이것을 JNDI쪽에 연결합니다. JMS 프로바이더 아답터는 org.jboss.jms.jndi.JMSProviderAdapter 인터페이스를 구현하는 클래스입니다. 이것은 메시지 구동 빈즈 컨테이너에서 프로바이더 독립 형태로 JMS 서비스 프로바이더에 액세스하기 위해 사용됩니다. JMSProviderLoader 서비스의 설정가능한 속성들은 다음과 같습니다:

  • ProviderName: JMS 프로바이더의 고유한 이름. 이것은 AdapterJNDIName 속성에 의해 덮어씌여지지 않는다면, JMSProviderAdapter 인스턴스를 JNDI의 java:/<ProviderName>쪽으로 연결할 때 사용되어 집니다.
  • ProviderAdapterClass: 인스턴스를 만들기위한 org.jboss.jms.jndi.JMSProviderAdapter 인터페이스의 완전한 형태를 갖는 클래스 이름. SonicMQ와 같은 대체 JMS 프로바이더를 사용하기위해서는 JNDI내의 InitialContext provider URL, QueueConnectionFactory 그리고 TopicConnectionFactory 위치의 관리가 가능케하는 JMSProviderAdaptor 인터페이스의 구현을 생성시켜주어야 합니다.
  • AdapterJNDIName: JMSProviderAdapter 인스턴스가 연결되어지는 JNDI 아래쪽의 정확한 이름을 지정합니다.
  • ProviderURL: JMS provider root InitialContext를 생성시킬 때 사용하는 JNDI Context.PROVIDER_URL 값.
  • QueueFactoryRef: javax.jms.QueueConnectionFactory 프로바이더가 연결되어지는 JNDI 이름.
  • TopicFactoryRef: javax.jms.TopicConnectionFactory이 연결되어지는 JNDI 이름.

예제 6.14. 원격 JBossMQ서버에 액세스하기위한 JMSProviderLoader

<mbean code="org.jboss.jms.jndi.JMSProviderLoader"
       name="jboss.mq:service=JMSProviderLoader,name=RemoteJBossMQProvider">
    <attribute name="ProviderName">RemoteJMSProvider</attribute>
    <attribute name="ProviderUrl">jnp://remotehost:1099</attribute>
    <attribute name="ProviderAdapterClass">
        org.jboss.jms.jndi.JBossMQProvider
    </attribute>
    <attribute name="QueueFactoryRef">XAConnectionFactory</attribute>
    <attribute name="TopicFactoryRef">XAConnectionFactory</attribute>
</mbean> 

RemoteJMSProvider는 예제 6.15, “ MDB JMS provider 아답터 지정을 위한 jboss.xml 일부분”에서 보여지는 것처럼 MDB invoker config을 참조할 수 있습니다.

예제 6.15.  MDB JMS provider 아답터 지정을 위한 jboss.xml 일부분

<proxy-factory-config>
    <JMSProviderAdapterJNDI>RemoteJMSProvider</JMSProviderAdapterJNDI>
    <ServerSessionPoolFactoryJNDI>StdJMSPool</ServerSessionPoolFactoryJNDI>
    <MaximumSize>15</MaximumSize>
    <MaxMessages>1</MaxMessages>
    <MDBConfig>
        <ReconnectIntervalSec>10</ReconnectIntervalSec>
        <DLQConfig>
            <DestinationQueue>queue/DLQ</DestinationQueue>
            <MaxTimesRedelivered>10</MaxTimesRedelivered>
            <TimeToLive>0</TimeToLive>
        </DLQConfig>
    </MDBConfig>
</proxy-factory-config>

부수적으로 멀티로 invoker-proxy-binding 요소들을 지정할 수 있기 때문에, 하나의 MDB가 서로 다른 JMSProviderAdapterJNDI 설정을 통해 멀티 바인딩 설정을 함으로써 여러 서버상에서 동일한 큐/토픽을 수신할 수 있습니다.

다른 방법으로는 예제 6.16, “JCA를 통해 JMS 프로바이더 아답터에통합시키기 위한 jms-ds.xml 서술자” 에서 보여지는 것 처럼 JCA 환경설정을 사용하여 JMS 프로바이더에 통합시킬 수 있습니다.

예제 6.16. JCA를 통해 JMS 프로바이더 아답터에통합시키기 위한 jms-ds.xml 서술자

<tx-connection-factory>
    <jndi-name>RemoteJmsXA</jndi-name>
    <xa-transaction/>
    <adapter-display-name>JMS Adapter</adapter-display-name>
    <config-property name="JMSProviderAdapterJNDI"
                     type="java.lang.String">RemoteJMSProvider</config-property>
    <config-property name="SessionDefaultType"
                     type="java.lang.String">javax.jms.Topic</config-property>
                 
    <security-domain-and-application>JmsXARealm</security-domain-and-application>
</tx-connection-factory>

6.4.2. org.jboss.jms.asf.ServerSessionPoolLoader MBean

ServerSessionPoolLoader MBean 서비스는 메시지 구동 빈 컨테이너에서 사용되는 javax.jms.ServerSessionPool 객체를 위한 팩토리를 관리합니다. ServerSessionPoolLoader 서비스의 설정가능한 속성들은 다음과 같습니다:

  • PoolName: 세션 풀에 대한 고유한 이름. 이것은 ServerSessionPoolFactory 인스턴스를 JNDI의 java:/PoolName 아랫쪽으로 연결시킬 때 사용되어집니다.
  • PoolFactoryClass: 인스턴스를 생성하기위한 org.jboss.jms.asf.ServerSessionPoolFactory 인터페이스의 완전한 형태를 갖는 클래스 이름.
  • XidFactory: 2 phase 커밋이 필요하지 않은 로컬 트랜잭션을 위한 javax.transaction.xa.Xid 값을 생성시키는데 사용되는 서비스의 JMX ObjectName. XidFactory MBean은 org.jboss.tm.XidFactoryMBean 인스턴스를 반환해주는 Instance 오퍼레이션을 제공해야만 합니다.

6.4.3. 비-JBoss JMS 프로바이더의 통합

우리는 외부 구현으로 JBossMQ JMS 구현을 대체시킬 수 있다는 것을 말한적이 있습니다. 이런 대체를 위해 취할 수 있는 여러가지 방법에 대해 아래쪽에서 요약을 해보겠습니다:

  • 외부 JMS 프로바이더로 관리되는 객체와 커뮤니케이션을 위해 올바른 JNDI 컨텍스트를 초기화시키는 것으로 JMSProviderLoader JBossMQProvider 클래스를 대체합니다.
  • ExternalContext MBean을 사용하여 외부 JMS 프로바이더로 관리되는 객체와 연합하여 JBoss JNDI 트리쪽으로 등록합니다.
  • JBoss JNDI 트리쪽으로 외부 JMS 객체들을 초기화시키기 위해 MBean을 사용하기. 이러한 접근방법에 대한 예제는 Websphere MQ를 위한 정보를 담고있는 http://sourceforge.net/tracker/index.php?func=detail&aid=753022&group_id=22866&atid=376687에서 찾을 수 있습니다.