Chapter 19. JMS

19.1. 소개

Spring은 JMS API의 사용을 단순화하고 JMS 1.0.2와 1.1 API사이의 차이점으로부터 사용자를 감싸는 JMS 추상 프레임워크를 제공한다.

JMS는 기능적으로 대략 두개의 영역(메시지 생산자(production)와 소비자(consumption))으로 분리될수 있다. JmsTemplate 클래스는 메시지 생산과 동기적인 메시지 수령을 위해 사용된다. 비동기적인 수령은 Java EE의 메시지-기반 bean스타일과 유사하다. Spring은 메시지-기반 POJO(MDPs)를 생성하기 위해 사용된 많은 수의 메시지 리스너 컨테이너를 제공한다.

패키지 org.springframework.jms.core 는 JMS를 사용하기 위한 핵심적인 기능을 제공한다. 이것은 JDBC를 위한 JdbcTemplate처럼 자원의 생성과 릴리즈를 다루어서 JMS의 사용을 단순화하는 JMS 템플릿 클래스를 포함한다. 사용자 구현 콜백 인터페이스를 위한 작업 처리의 기초를 위임하는 공통적인 작업을 수행하고 좀더 정교한 사용을 위한 헬퍼(helper) 메소드를 제공하는것이다. JMS 템플릿은 같은 디자인을 따른다. 클래스는 메시지 전달, 비동기적인 메시지 소비, 그리고 JMS세션과 사용자를 위한 메시지 생산자(producer)를 드러내기 위한 다양하고 편리한 메소드를 제공한다.

패키지 org.springframework.jms.support 는 JMSException 번역 기능을 제공한다. 번역은 체크된 JMSException구조를 체크되지 않은 예외의 반영된 구조로 형변환한다. 만약 어느 제공자(provider)가 체크된 javax.jms.JMSException의 하위클래스를 명시한다면 이 예외는 체크되지 않은 UncategorizedJmsException으로 포장된다.

패키지 org.springframework.jms.support.converter 는 자바객체와 JMS메시지 사이의 변환을 위한 MessageConverter 추상화를 제공한다.

패키지org.springframework.jms.support.destination 는 JNDI내 저장된 목적지(destination)를 위한 서비스 위치자(locator)를 제공하는것처럼 JMS목적지(destination)관리를 위한 다양한 전략을 제공한다.

마지막으로 패키지 org.springframework.jms.connection 는 독립형 애플리케이션내에서 사용하기 위해 적합한 ConnectionFactory의 구현물을 제공한다. 이것은 또한 JMS를 위한 Spring의 PlatformTransactionManager의 구현물(JmsTransactionManager라는 이름의)을 포함한다. 이것은 트랜잭션적인 자원에서 Spring의 트랜잭션 관리 기법까지처럼 JMS의 통합을 허용한다.

19.2. Using Spring JMS

19.2.1. JmsTemplate

JmsTemplate의 두가지 구현물이 제공된다. 클래스 JmsTemplate 은 JMS 1.1 API를 사용하고 하위클래스 JmsTemplate102 은 JMS 1.0.2 API를 사용한다.

콜백 인터페이스를 구현할 필요가 있는 JmsTemplate을 사용하는 코드는 그것들에게 명백하게 정의된 규칙을 부여한다. MessageCreator 콜백 인터페이스는 JmsTemplate으로 코드를 호출하여 제공된 세션을 부여하는 메시지를 생성한다. JMS API의 좀더 복잡한 사용법을 허용하기 위해 콜백 SessionCallback은 JMS 세션을 가진 사용자를 제공하고 콜백 ProducerCallback 은 Session과 MessageProducer 쌍을 드러낸다.

JMS API는 send메소드의 두가지 타입을 나타낸다. 하나는 배달(delivery)모드, 우선순위(priority), 그리고 서비스의 품질(QOS) 파라미터와 같은 살아있는 시간(time-to-live)를 가져오고 다른 하나는 디폴트 값을 사용하는 QOS파라미터를 가지지 않는다. JmsTemplate내에서 많은 send메소드를 가진 이후 QOS파라미터의 셋팅은 많은 수의 send메소드로 중복을 피하기 위한 bean프라퍼티처럼 드러낸다. 유사하게도 동기적인 receive호출을 위한 timeout값은 프라퍼티 setReceiveTimeout를 사용하여 셋팅한다.

몇몇 JMS 제공자(provider)는 ConnectionFactory의 설정을 통해 관리자적인 디폴트 QOA값의 셋팅을 허용한다. 이것은 MessageProducer's의 send메소드 send(Destination destination, Message message) 를 호출이 JMS스펙에 명시되는 것보다 QOS의 다른 디폴트 값을 사용할것에 영향을 끼친다. 그러므로 QOS값의 일관적인 관리를 제공하기 위해서 JmsTemplate은 boolean프라퍼티인 isExplicitQosEnabledtrue로 셋팅하여 이것 자체의 QOS값을 사용하는것이 구체적으로 가능해야만 한다.

19.2.2. Connection Factory

JmsTemplateConnectionFactory에 대한 참조를 요구한다. ConnectionFactory는 JMS 스펙의 일부이고 JMS를 사용하여 작동하기 위한 항목점(entry point)처럼 제공한다. 이것은 JMS 제공자로 connection을 생성하고 SSL설정 옵션처럼 업체가 명시하는 다양한 설정 파라미터를 분리하기(encapsulates) 위한 factory처럼 클라이언트 애플리케이션에 의해 사용된다.

EJB내부에서 JMS를 사용할때, 업체는 선언적인 트랜잭션에 참여하고 connection과 세션의 풀링을 수행할수 있도록 JMS인터페이스를 구현물을 제공한다. 이 구현물을 사용하기 위해 J2EE컨테이너는 전형적으로 EJB나 서블릿 배치 서술자 내부 resource-ref처럼 JMS connection factory를 선언하는것을 요구한다. EJB내부에서 JmsTemplate로 이러한 기능을 사용하는것을 확인하기 위해 클라이언트 애플리케이션은 ConnectionFactory의 관리 구현물을 참조하는것을 확인해야만 한다.

Spring은 ConnectionFactory 인터페이스인 SingleConnectionFactory 의 구현물을 제공한다. 그것은 모든 createConnection 호출에 같은 Connection을 반환하고 close을 호출하여 무시한다. 이것은 테스트와 같은 connection 많은 수의 트랜잭션에 걸쳐있는 다중 JmsTemplate호출을 위해 사용될수 있기 때문에 독립형 환경에 유용하다. SingleConnectionFactory는 JNDI에서 전형적으로 나오는 표준적인 ConnectionFactory에 대한 참조를 가져온다.

19.2.3. 목적지(Destination) 관리

ConnectionFactory와 같은 목적지(Destinations)는 JNDI에서 저장되고 가져올수 있는 객체를 처리하는 JMS이다. Spring 애플리케이션 컨텍스트를 설정할때 하나는 JMS목적지를 위한 당신의 객체 참조의 의존성 삽입을 수행하기 위한 JNDI factory클래스인 JndiObjectFactoryBean을 사용할수 있다. 어쨌든 종종 이 전략은 애플리케이션내 많은 수의 목적지가 있거나 JMS제공자(provider)를 위한 유일한 향상된 목적지 관리 기능을 가진다면 다루기가 어렵다. 이러한 향상된 목적지 관리의 예제는 목적지의 동적 생성이 되거나 목적지의 구조적인 이름공간(namespace)을 위한 지원이 될것이다. JmsTemplate은 인터페이스 DestinationResolver의 구현물을 위한 JMS 목적지 객체의 목적지 이름의 분석을 위임한다. DynamicDestinationResolverJmsTemplate에 의해 사용되는 디폴트 구현물이고 동적 목적지 분석을 조정한다. JndiDestinationResolver는 또한 JNDI내 포함된 목적지를 위한 서비스 위치자와 같이 작동하고 DynamicDestinationResolver내 포함된 행위를 위해 선택적으로 의지한다(fall back).

매우 종종 JMS애플리케이션내 사용되는 목적지는 오직 수행시에만 알려지기 때문에 애플리케이션이 배치될때 관리적으로 생성될수 없다. 이것은 종종 잘 알려진 명명규칙에 따라 수행시 목적지를 생성하는 시스템 콤포넌트들의 상호작용간에 애플리케이션 로직을 공유하기 때문이다. 비록 동적 목적지의 생성이 JMS스펙의 일부는 아닐지라도 대부분의 업체는 이 기능을 제공하고 있다. 동적 목적지는 임시 목적지로부터 그것들과 달리 작동하는 사용자에 의해 정의된 이름을 가지고 생성되고 JNDI내 종종 등록되지 않는다. API는 목적지와 함께 속한 프라퍼티가 업체가 명시한 이후 제공자(privider)에서 제공자(provider)로의 다양한 동적 목적지를 생성하기 위해 사용된다. 어쨌든 업체에 의해 때때로 만들어진 간단한 구현물 선택은 JMS스펙내 경고를 무시하는것이고 디폴트 목적지 프라퍼티를 가진 새로운 목적지를 생성하기 위한 TopicSession의 메소드 createTopic(String topicName)QueueSession의 메소드 createQueue(String queueName)를 사용하는것이다. 업체 구현물에 의존하여 DynamicDestinationResolver는 그 다음 하나를 분석하는 대신에 물리적인 목적지를 생성할수도 있다.

boolean타입의 프라퍼티인 PubSubDomain은 사용되는 JMS도메인이 무엇인지에 대해 아는 JmsTemplate를 설정하기 위해 사용된다. 디폴트에 의해 이 프라퍼티의 값은 false이고 사용될 점대점(point-to-point) 도메인, 큐(queue)를 표시한다. 1.0.2구현물에서 이 프라퍼티의 값은 만약 JmsTemplate의 send작업이 큐(queue)나 토픽(topic)으로 메시지를 전달할지 결정한다. 이 플래그(flag)는 1.1구현물을 위한 send작업에 영향을 주지 않는다. 어쨌든 두 구현물에서 이 프라퍼티는 DestinationResolver의 구현물을 통해 동적 목적지의 분석행위를 결정한다.

당신은 프라퍼티 defaultDestination을 통해 디폴트 목적지를 가진 JmsTemplate을 설정할수 있다. 디폴트 목적지는 특정 목적지를 참조하지 않는 send와 receive작업이 사용될수 있다.

19.2.4. 메시지 리스너 컨테이너

EJB에서 JMS메시지의 가장 공통적인 사용중 하나는 메시지-기반 bean(MDB)을 다루는 것이다. Spring은 사용자를 EJB컨테이너에 묶지 않는 방법으로 메시지-기반 POJO(MDP)를 생성하는 해결법을 제공한다(Spring의 MDP지원을 상세히 다루기 위해 Section 19.4.2, “비동기적인 수령 - 메시지-기반 POJO” 부분을 보라.)

AbstractMessageListenerContainer의 하위클래스는 JMS메시지 큐로부터 메시지를 받고 큐에 삽입될 MDP를 다루기 위해 사용된다. AbstractMessageListenerContainer는 메시지 수령의 모든 쓰레드를 다루고 처리하기 위한 MDP로 배치한다. 메시지 리스너 컨테이너는 MDP와 메시지 제공자간에 매개수단이고 메시지를 받기 위한 등록, 트랜잭션내 들어가기, 자원 획득과 제거, 예외 처리와 같은 것들을 다룬다. 이것은 당신에게 애플리케이션 개발자가 메시지를 받는것과 관련있는 비지니스 로직을 작성하는 것과 프레임워크에 관련된 JMS내부구조를 위임하는 것을 가능하게 한다.

여기에는 각각의 특별한 기능을 가지는 Spring에 패키지된 AbstractMessageListenerContainer의 3개의 하위클래스가 있다.

19.2.4.1. SimpleMessageListenerContainer

이 메시지 리스너 컨테이너는 3개의 하위클래스에서 가장 간단하다. 이것은 시작시 고정된 수의 JMS세션을 간단히 생성하고 컨테이너의 수명을 통해 그것들을 사용한다. 이 하위클래스는 런타임 요구에 동적인 적응을 허용하지 않고 메시지의 트랜잭션 성격을 지니는 수령에 참가한다. 어쨌든, JMS제공자에서 가장 적은 요구사항을 가진다. 이 하위클래스는 오직 간단한 JMS API행위를 요구한다.

19.2.4.2. DefaultMessageListenerContainer

이 메시지 리스너 컨테이너는 가장 많은 경우에 사용되는 것이다. SimpleMessageListenerContainer를 가지고, 이 하위클래스는 런타임 요구에 동적 적응을 허용하지 않는다. 어쨌든 이 종류는 트랜잭션내 참가한다. 각각 받아진 메시지는 XA 트랜잭션으로 등록되고 XA 트랜잭션 문법의 장점을 가질수 있다. 이 하위클래스는 JMS 제공자의 적은 요구사항과 트랜잭션 참가를 포함하는 좋은 기능사이에서 좋은 균형을 가진다.

19.2.4.3. ServerSessionMessageListenerContainer

이 하위클래스는 3개중 가장 강력한 것이다. JMS세션의 동적 관리를 허용하는 JMS ServerSessionPool SPI에 영향을 끼친다. 이 구현물 또한 트랜잭션에 참가한다. 다양한 메시지 리스너 컨테이너의 사용은 강력한 런타임 튜닝을 가능하게 하지만 사용되는 JMS제공자의 좀더 큰 요구사항(ServerSessionPool SPI)이 둔다. 런타임 성능 튜닝이 필요없다면, DefaultMessageListenerContainerSimpleMessageListenerContainer를 찾는다.

19.2.5. 트랜잭션 관리

Spring은 하나의 JMS ConnectionFactory를 위한 트랜잭션을 관리하는 JmsTransactionManager을 제공한다. 이것은 JMS 애플리케이션이 Chapter 9, 트랜잭션 관리에서 언급된것처럼 Spring의 관리 트랜잭션 기능에 영향을 끼치는 것을 허용한다. JmsTransactionManager는 쓰레드를 위한 명시된 ConnectionFactory로 부터 Connection/Session 짝을 묶는다. 어쨌든 J2EE환경내 ConnectionFactory는 connection과 세션을 풀링할것이다. 그래서 인스턴스는 풀링 행위에 의존하는 쓰레드를 묶는다. 독립형 환경에서 Spring의 SingleConnectionFactory을 사용하는것은 하나의 JMS Connection과 자체의 Session을 가지는 각각의 트랜잭션을 사용하는 결과를 낳는다. JmsTemplateJtaTransactionManager 와 분산 트랜잭션을 수행하기 위한 XA-성능의 JMS ConnectionFactory 을 함께 사용될수 있다.

괸리되고 관리되지 않는 트랜잭션적인 환경에서 코드를 재사용하는것은 Connection으로 부터 Session을 생성하기 위한 JMS API를 사용할때 구별되지 못할수도 있다. 이것은 JMS API가 Session을 생성하기 위한 오직 하나의 factory메소드를 가지고 트랜잭션과 승인(acknowledgement)모드를 위한 값을 요구하기 때문이다. 관리되는 환경에서 환경의 트랜잭션적인 구조의 책임내 이러한 값들을 셋팅한다. 그래서 이러한 값들은 업체가 JMS connection을 포장하여 무시된다. 관리되지 않는 환경내에서 JmsTemplate을 사용할때 당신은 프라퍼티 SessionTransactedSessionAcknowledgeMode의 사용을 통해 이러한 값들을 명시할수 있다. JmsTemplate 를 가지고 PlatformTransactionManager을 사용할때 템플릿은 언제나 주어진 트랜잭션적인 JMS세션이 될것이다.

19.3. 메시지 보내기

JmsTemplate은 메시지를 보내기 위한 많은 편리한 메소드를 포함한다. javax.jms.Destination객체를 사용하여 목적지를 명시하는 send메소드가 있고 JNDI룩업으로 사용하기 위한 문자열을 사용하여 목적지를 정의한다. send메소드는 디폴트 목적지를 사용하는 목적지 인자를 가지지 않는다. 이것은 1.0.2 구현물을 사용하여 큐(queue)에 메세지를 보내는 예제이다.

import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Queue;
import javax.jms.Session;

import org.springframework.jms.core.MessageCreator;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.JmsTemplate102;

public class JmsQueueSender {

  private JmsTemplate jmsTemplate;

  private Queue queue;

  public void setConnectionFactory(ConnectionFactory cf) {
    jt = new JmsTemplate102(cf, false);
  }

  public void setQueue(Queue q) {
    queue = q;
  }

  public void simpleSend() {
    this.jmsTemplate.send(this.queue, new MessageCreator() {
      public Message createMessage(Session session) throws JMSException {
        return session.createTextMessage("hello queue world");
      }
    });
  }
}
      

이 예제는 제공되는 Session객체로부터 텍스트 메시지를 생성하기 위한 MessageCreator 콜백과 ConnectionFactory의 참조를 전달하여 생성되는 JmsTemplate, 메시지 도메인을 명시하는 boolean값을 사용한다. 0(zero) 인자 생성자와 connectionFactory / queue bean프라퍼티는 인스턴스를 생성(BeanFactory나 명백한 자바코드를 사용하여)하기 위해 제공되고 사용될수 있다. 대안으로, JMS설정을 위한 미리 빌드된 bean프라퍼티를 제공하는 Spring의 JmsGatewaySupport 편리한 base클래스로 부터 끌어내보자.

애플리케이션 컨텍스트내 1.0.2를 설정할때 만약 큐(queue)나 토픽(topic)으로 전달하기를 원하는지 표시하기 위한 boolean프라퍼티인 pubSubDomain 프라퍼티의 값을 셋팅하는것을 기억하는것이 중요하다.

send(String destinationName, MessageCreator creator) 메소드는 목적지의 문자열이름을 사용하여 당신에게 메시지를 보내도록 한다. 만약 이러한 이름들이 JNDI에 등록이 도어 있다면 당신은 JndiDestinationResolver의 인스턴스를 위한 템플릿의 DestinationResolver 프라퍼티를 셋팅해야만 한다.

만약 당신이 JmsTemplate을 생성하고 디폴트 목적지를 명시한다면 send(MessageCreator c)는 그 목적지로 메시지를 보낸다.

19.3.1. 메시지 변환기(converter) 사용하기

도메인 모델 객체의 전송을 용이하게 하기 위해 JmsTemplate 은 메시지의 데이터내용을 위한 인자처럼 자바객체를 가지는 다양한 send메소드를 가진다. JmsTemplate 내 오버로드된 메소드인 convertAndSendreceiveAndConvertMessageConverter인터페이스의 인스턴스를 위한 변환처리를 위임한다. 이 인터페이스는 자바객체와 JMS메시지 사이의 변환을 위한 간단한 규칙을 정의한다. 디폴트 구현물인 SimpleMessageConverterStringTextMessage, byte[]BytesMesssage, java.util.MapMapMessage간의 전환을 지원한다. 변환기를 사용하여 당신의 애플리케이션 코드는 JMS를 통해 전달하고 받는 비지니스 객체에 집중할수 있고 JMS메시지처럼 표현되는 방식의 상세화에 괴로워하지 않게된다.

현재 모래상자(sandbox)는 자바빈과 MapMessage간의 변환을 위한 반영(reflection)을 사용하는 MapMessageConverter을 포함한다. 당신이 스스로 구현할수 있는 다른 인기있는 구현물에 대한 선택사항은 객체를 표현하는 TextMessage을 생성하기 위한 JAXB, Castor, XMLBeans, 또는 XStream과 같은 존재하는 XML마셜링(marshalling) 패키지를 못쓰게 만드는(bust) 변환기이다.

변환기 클래스내부에서 일반적으로 캡슐화할수 없는 메시지 프라퍼티, 헤더, 그리고 몸체(body)의 셋팅을 받아들이기 위해 MessagePostProcessor 인터페이스는 당신에게 이것이 보내기 전 변환된 후 메시지에 접근하도록 한다. 아래의 예제는 java.util.Map 이 메시지로 변환된 후 메시지 헤더와 프라퍼티를 변경하는 방법을 보여준다.

	public void sendWithConversion() {
	  Map m = new HashMap();
	  m.put("Name", "Mark");
	  m.put("Age", new Integer(35));
	  jt.convertAndSend("testQueue", m, new MessagePostProcessor() {
		public Message postProcessMessage(Message message) throws JMSException {
		  message.setIntProperty("AccountID", 1234);
		  message.setJMSCorrelationID("123-00001");
		  return message;
		}
	  });
	}
		  

이것은 이 형태의 메시지를 보여준다.

	MapMessage={ 
	  Header={ 
		... standard headers ...
		CorrelationID={123-00001} 
	  } 
	  Properties={ 
		AccountID={Integer:1234}
	  } 
	  Fields={ 
		Name={String:Mark} 
		Age={Integer:35} 
	  } 
	}
		  

19.3.2. SessionCallback 과 ProducerCallback

send작업이 많은 공통적인 사용 시나리오를 다루는 동안 당신이 JMS세션이나 MessageProducer에서 다중 작업을 수행하고자 원하는 때가 있다. SessionCallbackProducerCallback는 JMS세션과 Session/MessageProducer를 짝으로(pair) 드러낸다. JmsTemplate의 execute() 메소드는 이러한 콜백 메소드를 수행한다.

19.4. 메시지 받기

19.4.1. 동기적인 수령

JMS가 전형적으로 비동기적인 처리와 관련되어 있지만 동기적으로 메시지를 소비하는것도 가능하다. 오버로드된 receive 메소드는 이 기능을 제공한다. 동기적으로 받는동안 쓰레드를 호출하는것은 메시지가 사용가능할때까지 블럭된다. 이것은 쓰레드를 호출하는것이 잠재적으로 무기한 블럭이 될수 있기 때문에 위험한 작업이 될수 있다. receiveTimeout 프라퍼티는 메시지를 기다리는것을 중지하기 전에 수령자(receiver)가 얼마나 오래 기다릴지를 명시한다.

JMS가 대개 비동기적인 처리에 관련되는 동안, 메시지를 동기적으로 받는것이 가능하다. 오버로드된 receive(..) 메소드는 이 기능을 제공한다. 동기적으로 받는동안, 메시지가 사용가능할때까지 쓰레드 블럭을 호출한다. 이것은 호출 쓰레드가 막연히 블럭될수 있기 때문에 위험한 작업이 될수 있다. receiveTimeout 프라퍼티는 메시지 받는것을 포기하기 전에 리시버(receiver)가 얼마나 오래 기다려야 하는지 명시한다.

19.4.2. 비동기적인 수령 - 메시지-기반 POJO

EJB에서 메시지-기반 Bean(MDB)와 유사한 형태로, 메시지-기반 POJO(MDP)는 JMS메시지를 위한 리시버로 작동한다. MDP에서 하나의 제약(MessageListenerAdapter 클래스에 대한 논의를 위해 아래를 보라)은 javax.jms.MessageListener 인터페이스를 구현해야만 한다는 것이다. POJO가 다중 쓰레드에서 메시지를 받는 경우를 알아보라. 이것은 구현물이 쓰레드에 안전하다는것을 확인하는 것이 중요하다.

아래는 MDP의 간단한 구현물이다.

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

public class ExampleListener implements MessageListener {

  public void onMessage(Message message) {
    if (message instanceof TextMessage) {
      try {
        System.out.println(((TextMessage) message).getText());
      } catch (JMSException ex) {
        throw new RuntimeException(ex);
      }
    } else {
      throw new IllegalArgumentException("Message must be of type TextMessage");
    }
  }
}

MessageListener를 구현했을때가 메시지 리스너 컨테이너를 생성할 시간이다.

아래는 Spring에 포함된 메시지 리스너 컨테이너중 하나(이 경우 DefaultMessageListenerContainer)를 정의하고 설정하는 방법에 대한 예제이다.

<!-- this is the Message Driven POJO (MDP) -->
<bean id="messageListener" class="jmsexample.ExampleListener" />

<!-- and this is the attendant message listener container -->
<bean id="listenerContainer"
  class="org.springframework.jms.listener.DefaultMessageListenerContainer">
  <property name="concurrentConsumers" value="5"/>
  <property name="connectionFactory" ref="connectionFactory" />
  <property name="destination" ref="destination" />
  <property name="messageListener" ref="messageListener" />
</bean>

각각의 구현물에 의해 지원되는 기능에 대한 상세한 설명을 위해서 다양한 메시지 리스너 컨테이너의 Spring Javadoc를 참조하라.

19.4.3. SessionAwareMessageListener 인터페이스

SessionAwareMessageListener 인터페이스는 JMS MessageListener 인터페이스를 유사하게 축소한 Spring특유이 인터페이스이다. 하지만 받은 Message로부터 JMS Session에 접근하는 메시지 핸들링 메소드를 제공한다.

package org.springframework.jms.listener;

public interface SessionAwareMessageListener {

    void onMessage(Message message, Session session) throws JMSException;
}

받은 메시지에 응답하기 위한 MDP를 원한다면 이 인터페이스(표준 JMS MessageListener 인터페이스에 우선하는)를 구현하는 MDP를 가지도록 선택할수 있다(onMessage(Message, Session)에서 제공되는 Session을 사용하는). Spring을 가진 모든 메시지 리스너 컨테이너 구현물은 MessageListenerSessionAwareMessageListener 인터페이스를 구현하는 MDP를 지원한다. SessionAwareMessageListener를 구현하는 클래스는 caveat가 되고 인터페이스를 통해 Spring에 묶인다. 이것을 사용할지 말지에 대한 선택은 애플리케이션 개발자나 아키텍트와 같은 당신에게 남게된다.

SessionAwareMessageListener 인터페이스의 'onMessage(..)' 메소드가 JMSException가 던지는것을 알아두라. 표준 JMS MessageListener에 대해 대조적으로, SessionAwareMessageListener 인터페이스를 사용할때, 던져진 예외를 다루기 위한 클라이언트 코드의 책임이다.

19.4.4. MessageListenerAdapter

MessageListenerAdapter 클래스는 Spring의 비동기적인 메시지 지원내 마지막 컴포넌트이다. 간단히 말해, MDP처럼 대부분의 어떤 클래스를 나타내는것을 허용한다.

[Note]Note

JMS 1.0.2 API를 사용한다면, JMS 1.0.2 API를 위한것만을 제외하고 MessageListenerAdapter처럼 같은 기능과 값 추가를 제공하는 MessageListenerAdapter102 클래스를 사용하는 것을 원할것이다.

다음의 인터페이스 정의를 잘 보라. 비록 인터페이스가 MessageListener뿐 아니라 SessionAwareMessageListener 인터페이스를 확장하지 않는다면, MessageListenerAdapter 클래스의 사용을 통해 MDP로 사용될수 있다. 다양한 메시지 핸들링 메소드가 받고 다룰수 있는 다양한 Message타입의 내용에 따라 강력하게 타입화된다.

public interface MessageDelegate {

    void handleMessage(String message);

    void handleMessage(Map message);

    void handleMessage(byte[] message);

    void handleMessage(Serializable message);
}
public class DefaultMessageDelegate implements MessageDelegate {
    // implementation elided for clarity...
}

특별히, 위 MessageDelegate 인터페이스의 구현물(위 DefaultMessageDelegate클래스)이 JMS 의존성을 전혀 가지지 않는 방법을 노트하라. 우리가 다음의 설정을 통해 MDP로 만들 POJO이다.

<!-- this is the Message Driven POJO (MDP) -->
<bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
    <constructor-arg>
        <bean class="jmsexample.DefaultMessageDelegate"/>
    </constructor-arg>
</bean>

<!-- and this is the attendant message listener container... -->
<bean id="listenerContainer"
  class="org.springframework.jms.listener.DefaultMessageListenerContainer">
  <property name="concurrentConsumers" value="5"/>
  <property name="connectionFactory" ref="connectionFactory" />
  <property name="destination" ref="destination" />
  <property name="messageListener" ref="messageListener" />
</bean>

아래는 JMS TextMessage 메시지를 받는것을 다룰수 있는 다른 MDP의 예제이다. 메시지 핸들링 메소드가 실제로 호출된 'receive'(MessageListenerAdapter내 메시지 핸들링 메소드의 이름이 'handleMessage'가 디폴트)이지만 설정가능한 방법을 알라. 'receive(..)' 메소드가 오직 JMS TextMessage 메시지에만 받고 응답하기 위해 강력하게 타입화된 방법또한 알라.

public interface TextMessageDelegate {

    void receive(TextMessage message);
}
public class DefaultTextMessageDelegate implements TextMessageDelegate {
    // implementation elided for clarity...
}

MessageListenerAdapter의 설정은 다음처럼 보일것이다.

<bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
    <constructor-arg>
        <bean class="jmsexample.DefaultTextMessageDelegate"/>
    </constructor-arg>
    <property name="defaultListenerMethod" value="receive"/>
    <!-- we don't want automatic message context extraction -->
    <property name="messageConverter">
        <null/>
    </property>
</bean>

'messageListener'TextMessage와 다른 타입의 JMS Message를 받는다면, IllegalStateException가 던져질것이다.

MessageListenerAdapter의 다른 기능은 핸들러 메소드가 void가 아닌 다른 값을 반환한다면 응답 Message를 자동으로 돌려주는 것이다.

다음의 인터페이스와 구현물을 보라.

public interface ResponsiveTextMessageDelegate {

    // notice the return type...
    String receive(TextMessage message);
}
public class DefaultResponsiveTextMessageDelegate implements ResponsiveTextMessageDelegate {
    // implementation elided for clarity...
}

만약 위 DefaultResponsiveTextMessageDelegateMessageListenerAdapter와 함께 결합되어 사용된다면, 'receive(..)'메소드의 수행으로부터 반환되는 어느 null이 아닌 값이 TextMessage로 변환될것이다. 결과 TextMessage는 원래 Message의 JMS Reply-To 프라퍼티로 정의되거나 디폴트 DestinationMessageListenerAdapter에 셋팅된다면 Destination로 보내어질것이다. 만약 Destination이 발견되지 않는다면, InvalidDestinationException가 던져질것이다(이 예외는 삼켜지지 않을것이고 호출 스택을 전할것이다.).

19.4.5. 트랜잭션내 참여

트랜잭션내 참여는 오직 두개의 작은 변경만이 요구된다. 트랜잭션 관리자를 생성하고 트랜잭션내 참여할수 있는 하위클래스중 하나를 가지고 트랜잭션 관리자를 등록할 필요가 있을것이다(DefaultMessageListenerContainer 또는 ServerSessionMessageListenerContainer).

트랜잭션 관리자를 생성하기 위해, JmsTransactionManager의 인스턴스를 생성하고 XA트랜잭션 성능을 가지는 connection factory를 주길 원할것이다.

<bean id="transactionManager" class="org.springframework.jms.connection.JmsTransactionManager">
  <property name="connectionFactory" ref="connectionFactory" />
</bean>

그리고 나서 이전 컨테이너 설정에 다음을 추가할 필요가 있다. 컨테이너는 나머지를 다룰것이다.

<bean id="listenerContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
  <property name="concurrentConsumers" value="5" />
  <property name="connectionFactory" ref="connectionFactory" />
  <property name="destination" ref="destination" />
  <property name="messageListener" ref="messageListener" />
  <property name="transactionManager" ref="transactionManager" />
</bean>