메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

Message-Driven Bean

한빛미디어

|

2002-11-26

|

by HANBIT

10,902

「한빛 네트워크 기사 공모전」 우수작: 김대곤

아직도 EJB(Enterprise Java Beans)는 실제 비즈니스를 수행하는 애플리케이션을 개발하는 프로젝트에서 사용하기에는 부담스러운 기술인 것 같다. 자바를 사용하는 대부분의 프로젝트는 일반적으로 고객의 신기술에 대한 요구사항으로 EJB를 사용하려고 하나 실제는 JSP(Java Server Page)와 자바 빈즈(Java Beans)로 구성하고 최소한의 부분만 EJB로 구현하려고 하는 것 같다. 유지보수와 EJB 분야의 중고급 개발자를 구하는데 따르는 어려움 때문이다. 하지만 이런 경향은 좋다, 나쁘다라고 말할 수 있는 성질은 아닌 것 같다.

만약 최소한의 EJB를 구현해야 한다면 어떤 부분이 좋을까? 필자의 생각으로는 애플리케이션의 대부분을 차지하고 있는 동기 방식의 프로그램이 아닌 비동기 방식의 프로그램이 아닐까 한다. 즉, 세션 빈(Session Bean), 엔티티 빈(Entity Bean) 다음에 나온 메시지 중심의 MDB(Message-Driven Bean)이라고 생각한다. 실제 몇 분 또는 몇 시간씩 걸리는 작업을 브라우저에서 기다리는 것은 초인적인 인내력을 필요로 한다. 잘못해서 취소버튼이라도 누르는 날엔 기다림은 허사가 되고, 처음부터 시작해야 되는 경우도 있다. 본 기사는 MDB를 설명한다기보다 MDB의 예제를 제공하는 것을 목표로 하고 있다.

본 기사는 다음과 같은 구성으로 진행해 나갈 것이다. JMS(Java Message Service)의 개요

Message-Driven Bean의 예제를 제공하고, 그 이해를 돕는 수준에서 JMS를 설명해 보자. JMS는 J2EE 플랫폼의 한 부분으로 엔터프라이즈 메세징 시스템에 접근할 수 있도록 하는 벤더 독립적인 표준 API다. 이런 설명보다는 JMS는 JDBC와 비슷하다는 측면에서 설명해 보자. JDBC는 제품의 종류와 무관하게 여러 종류의 관계형 데이터베이스에 접근할 수 있도록 하는 표준 API이다. JMS는 데이터베이스가 아닌 메세징 시스템에 접근하는 API이다. JMS는 JMS Provider, JMS 클라이언트로 구성되며, JMS 클라이언트는 Publisher/Subscriber(또는 Sender/Receiver, Producer/Comsumer)로 나누어진다. 본 기사에서도 각각에 대한 설명과 예제를 제공할 것이고, Message-Driven Bean은 주로 JMS Subscriber의 역할을 담당하는 Bean이다.

JMS Server 설정(웹로직)

JMS Provider는 벤더에서, 즉 J2EE의 플랫폼을 제공하는 회사에서 만드는 것이다. JMS를 지원하는 메세징 시스템으로는 IBM MQSeries, BEA 웹로직 JMS 서비스, 썬의 Microsystems" iPlanet Message Queue 등이 있다. 여기서는 가장 널리 사용되는 웹로직 JMS 서버를 설정하는 방법에 대하여 살펴보도록 할 것이다.
  1. Persistent Message 저장과 Message Paging하기 위한 공간을 만든다. (파일 또는 데이터베이스)
    Persistent Message를 저장하기 위해 File이나 데이터베이스를 선택할 수 있고, Message Paging를 위해서는 파일이 권장된다.(선택사항) 파일 설정은 간단하게 이름과 파일을 저장할 디렉토리를 정하면 된다. 데이터베이스로 설정하는 방법은 weblogic 메뉴얼을 참고하기 바란다.
  2. JMS 서버 설정
    JMS서버의 설정을 위해서는 이전에 설정했던 저장장소가 필요하다.
  3. JMS Destination(Topic 또는 Queue)
    JMS Destination은 Topic이나 Queue로 설정할 수 있으며, 이름 및 JNDI이름을 설정이 주요한 속성이다. Topic은 모든 Consumer들이 메세지를 받는 방식이고, Queue는 각 메세지는 단 한 번 Consumer로 전달되는 방식이다. 즉, 하나의 메세지가 어떻게 처리되는가에 따라 결정해야 한다.
  4. TopicConnectionFactory 설정
    TopicConnectionFactory는 JNDI에 등록되어, 프로그램에서 이를 Factory객체를 사용하여 TopicConnection를 생성한다. 이름과 JNDI이름이 주요 속성이며, 다른 값들은 디폴트 값으로 사용해도 무방하다.
JMS 서버를 위한 설정에 대하여 간단하게 살펴보았다. 설정에 대한 자세한 사항은 웹로직 매뉴얼을 참조하기 바란다. 위에서 다룬 내용은 JMS 클라이언트 프로그램을 실행하기 위한 최소한의 작업이다.

JMS 클라이언트 프로그램

먼저, JMS 클라이언트 프로그램을 컴파일하고 실행하기 위한 Classpath에 대해 살펴보자. 필자의 시스템 환경(Windows 2000 Professional)에는 다음과 같이 CLASSPATH가 설정되어 있다.
   set CLASSPATH=.;d:\jdk1.3.1_02\jre\lib\rt.jar;d:\j2sdkee1.3.1\lib\j2ee.jar;
                   d:\bea\weblogic700\server\lib\weblogic.jar
자바파일을 컴파일하기 위해서 J2SE의 rt.jar 파일과 J2EE의 j2ee.jar 파일이 필요하다. 이때 weblogic.jar가 설정되어 있지 않으면 실행시 에러가 발생한다. 왜냐하면 weblogic.jndi.WLInitialContextFactory를 찾을 수 없기 때문이다. CLASSPATH를 설정하고 나면, 이제 프로그램을 위한 모든 준비는 끝난 셈이다.

먼저 Message를 받는 Consumer부터 살펴보자.
import javax.jms.Message;
import javax.jms.MapMessage;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicConnection;
import javax.jms.TopicSession;
import javax.jms.Topic;
import javax.jms.Session;
import javax.jms.TopicSubscriber;
import javax.jms.JMSException;
import javax.naming.InitialContext;
import javax.naming.Context;
import java.util.Hashtable;

public class MyReceiver implements javax.jms.MessageListener {

   public static void main(String[] args) throws Exception {

      new MyReceiver("my.jms.TopicFactory", "my.jms.topic");
                      
      while(true)  {
         Thread.sleep(10000);
      }

   }


   public MyReceiver(String factoryName, String topicName) throws Exception {

      InitialContext  jndiContext = getInitialContext();

      TopicConnectionFactory  factory  = (TopicConnectionFactory)jndiContext.lookup(factoryName);
      Topic topic = (Topic) jndiContext.lookup(topicName);

      TopicConnection   connect = factory.createTopicConnection();
      TopicSession   session  = connect.createTopicSession(false, 
Session.AUTO_ACKNOWLEDGE);

      TopicSubscriber  subscriber = session.createSubscriber(topic);
      subscriber.setMessageListener(this);

      connect.start();
   }

   public void onMessage(Message message) {

      try {

         MapMessage msg  = (MapMessage)message;
         String     text = (String)msg.getObject("name");
         System.out.println(text);

      } catch (JMSException jmsE) {
         jmsE.printStackTrace();
      }

   }

   public static InitialContext getInitialContext() throws Exception {

      Hashtable h = new Hashtable();
      h.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
      h.put(Context.PROVIDER_URL, "t3://localhost:7001");

      return new InitialContext(h);

   }

}
이제 Producer를 살펴보자.
import javax.jms.Message;
import javax.jms.MapMessage;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicConnection;
import javax.jms.TopicSession;
import javax.jms.Topic;
import javax.jms.Session;
import javax.jms.TopicPublisher;
import javax.jms.JMSException;
import javax.naming.InitialContext;
import java.util.Hashtable;
import javax.naming.Context;

public class MySender {

   public static void main(String[] args) throws Exception {

      InitialContext  jndiContext = getInitialContext();

      TopicConnectionFactory  factory  = (TopicConnectionFactory)jndiContext.lookup("my.jms.TopicFactory");

      Topic topic = (Topic) jndiContext.lookup("my.jms.topic");

      TopicConnection   connect = factory.createTopicConnection();

      TopicSession   session  = connect.createTopicSession(true, 0);

      TopicPublisher  publisher = session.createPublisher(topic);

      MapMessage   msg = session.createMapMessage();
      msg.setObject("name", new String("input"));

      publisher.publish(msg);

      connect.close();
   }
   

   public static InitialContext getInitialContext() throws Exception {
      
      Hashtable h = new Hashtable();
      h.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
      h.put(Context.PROVIDER_URL, "t3://localhost:7001");

      return new InitialContext(h);

   }

}
각 프로그램이 진행되는 순서는 InitialContext → TopicConnectionFactory → TopicConnection → TopicSession → TopicPublisher/Message 또는 TopicSubscriber이고 Topic은 InitialContext으로부터 lookup 메소드를 통하여 얻어온다. 이 때 InitialContext의 경우 얻어오는 코드는 각 제품에 따라 다르다. Consumer의 경우, Message가 받으면 onMessage 메소드가 자동 실행된다. 두 개의 프로그램은 약간 차이가 있지만 필자는 각 소스들이 자바에 대한 기본지식이 있는 개발자들에게 직관적으로 이해될 것이라고 믿는다. 또한 이러한 예제 제공 방식이 적합하다고 생각한다.

Message-Driven Bean

이제 우리의 목적지인 Message-Driven Bean에 대해 살펴보자.
import javax.jms.Message;
import javax.jms.MapMessage;
import javax.ejb.MessageDrivenBean;
import javax.jms.MessageListener;
import javax.naming.NamingException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.ejb.EJBException;
import javax.ejb.MessageDrivenContext;

public class MyBean implements javax.ejb.MessageDrivenBean, javax.jms.MessageListener  {

   MessageDrivenContext mdContext;
   Context              jndiContext;

   public void setMessageDrivenContext(MessageDrivenContext mdc) {
      mdContext = mdc;
      try {
         jndiContext = new InitialContext();
      } catch(NamingException ne) {
         throw new EJBException(ne);
      }
   }

   public void onMessage(Message msg) {
      try {
         MapMessage recMsg = (MapMessage)msg;
         String name = (String)recMsg.getObject("name");
      } catch(Exception e) {
         throw new EJBException(e);
      }
   }

   public void ejbCreate() {
   }

   public void ejbRemove() {
      try {
         jndiContext.close();
         mdContext = null;
      } catch(NamingException ne) {
      }
   }

}
Message-Driven Bean은 javax.ejb.MessageDrivenBean, javax.jms.MessageListener 인터페이스를 구현하여 작성한다. JMS Client에서 본 Consumer 예제와 onMessage 메소드를 제외하고는 다른 모습을 보이는 듯하다. 하지만 이러한 작업들은 모두 EJB 컨테이너에서 담당하고 있는 것이다. 이 Bean을 전개(Deploy)할 때 필요한 정보들, TopicConnectionFactory, Topic 또는 Queue가 모두 등록되어야 한다. 또한 메시지의 동시 처리를 위해서는 SessionPool도 설정해야 한다. Message-Driven Bean은 실제로 호출되어 생성되는 것이 아니기 때문에 ejbCreate 메소드는 사용되지 않는다. 그래서 항상 빈 메소드이다. OnMessage 메소드 안에서 다른 Enterprise Bean들을 호출할 수도 있고, 데이터베이스의 프로시저를 호출할 수도 있다. 또한 새로운 JMS 메세지를 보낼 수도 있다. 이것은 필요에 따라 작성하면 된다.

결어

지금까지 살펴보았듯이 Message-Driven Bean은 JMS Consumer 역할을 담당한다. Session Bean이나 Entity Bean를 JMS Consumer처럼 구현하면 굳이 Message-Driven Bean를 사용할 필요가 없을 것 같지만, 그럴 경우에는 Message에 도착하지 않을 경우 무한정 대기 상태가 될 수 있다.

비동기 통신은 그렇게 많이 사용되지는 않는 것 같다. 그럼에도 필요한 사람들에게는 어떤 방법으로든 구현해야 하는 종류의 것이다. 만약 EJB를 하나 이상 사용한다면, Message-Driven Bean은 좋은 해결책을 제시해 줄 것이다. 본 기사를 통해 좀 더 MDB에 익숙해지길 바란다.
TAG :
댓글 입력
자료실

최근 본 책0