JavaEE6 Tutorial - Java Message Service_sys4u

914 views

Published on

  • Be the first to comment

JavaEE6 Tutorial - Java Message Service_sys4u

  1. 1. - 1 - Java Messaging Service 원본 주소 : http://docs.oracle.com/javaee/6/tutorial/doc/bncdq.html 번역자 : 김형석 수석 번역일 : 2013 년 06 년 20 일 Chapter47. Java Message Service Concept 이 장에서는 자바 메시지 서비스(JMS) API 에 대해 소개한다. 이 API 를 이용하면 어플리케이션 내에서 신뢰할 수 있고, 비동기적이며, 느슨히 연결된(loosely coupled) 커뮤니케이션 방식을 이용하여 메시지를 생성, 전송하고 수신하고 읽을 수 있게 된다. 이 장에서는 다음 내용들을 다룬다.  JMS API 개요  기본 JMS API 개념  JMS API 프로그래밍 모델  견고한 JMS 어플리케이션 작성 방법  JavaEE 어플리케이션에서 JMS 를 사용하는 방법  JMS 에 대한 추가 정보 JMS API 개요 이 장에서는 메시징(Messaging)에 대한 개념을 정의하고, JMS API 에 대한 내용과 이를 이용할 수 있는 상황 대해 기술한다. 또, JavaEE 어플리케이션 내에서 JMS API 가 어떻게 동작하는지에 대해서도 설명할 것이다. 메시징이란 무엇인가? 메시징은 소프트웨어 컴포넌트나 어플리케이션 간에 정보를 주고 받을 수 있도록(Communication) 하는 방법이다. 보통 메시징 시스템은 P2P(peer to peer) 구조이다. 메시징 클라이언트는 메시지를 전송하기도 하고 다른 어떤 메시징 클라이언트가 보낸 메시지를 수신하기도 한다. 각각의 메시징 클라이언트들은 메시지를 생성(create)하고 전송(send)하고 수신하고(receive) 읽을(read)수 있는 기능을 제공하는 메시징 중개자(Messaging Agent)와 연결된다. 메시징을 이용하면 느슨하게 연결(loosely coupled)되는 분산된 커뮤니케이션을 할 수 있게 된다. 송신자는 목적지(destination)로 메시지를 보내고, 수신자(recipient)는 그 목적지로부터 메시지를 얻어올 수 있다. 하지만, 송신자(sender)와 수신자(receiver)들이
  2. 2. - 2 - 커뮤니케이션을 하기 위해 같은 시점에 모두 실행 중일 필요는 없다. 사실 송신자는 수신자에 대해 전혀 알지 못해도 무방하다. 마찬가지로 수신자도 송신자에 대해 알 필요가 없다. 송신자와 수신자가 알고 있어야 할 것은 메시지의 형식(format)과 목적지에 대한 정보가 전부이다. 이런 관점에서 메시징은 원격지 어플리케이션에 대한 정보를 알고 있어야 하는 RMI(Remote Method Invocation: 원격 메소드 호출)와 같은 견고하게 연결된(tightly coupled) 커뮤니케이션 방식과 구별된다고 할 수 있다. 메시징은 또 이메일과도 다르다고 할 수 있는데, 이메일은 사람과 사람 간에, 혹은 소프트웨어 어플리케이션과 사람 간에 커뮤니케이션을 하기 위해 이용되지만, 메시징은 소프트웨어 어플리케이션이나 소프트웨어 컴포넌트 간의 커뮤니케이션을 위해 사용된다. JMS API 는 무엇인가? Java Message Service 는 어플리케이션에서 메시지를 생성하고, 송신하고, 수신하고, 읽을 수 있도록 제공된 Java API 이다. 이 API 는 Sun 과 그 외 몇몇 파트너들이 설계하였는데, 여기에는 Java 언어를 이용하여 다른 메시징 구현체들과 커뮤니케이션 할 수 있도록 하는 일반적인 인터페이스들과 그와 관련된 내용들이 정의되어 있다. JMS API 에는, 프로그래머가 알아야 하는 내용은 최소화하면서도, 세련된 메시징 어플리케이션을 만들 수 있도록 하는 충분한 기능들이 포함되어 있다. 또, JMS 어플리케이션이 동일한 도메인 내에 있는 여러 JMS 프로바이더들과 연동할 수 있도록 이식성을 극대화하였다. JMS API 는 느슨한 연결의 커뮤니케이션을 가능하게 할 뿐만 아니라 다음 기능도 지원한다.  비동기성(Asynchronous): JMS 프로바이더는 메시지들이 도착하면 바로 클라이언트에게 메시지를 전달해준다. 클라이언트가 메시지를 얻기 위해 프로바이더에게 요청을 할 필요가 없다.  신뢰성(Reliable): JMS API 는 메시지가 한 번, 그리고 단 한 번만 전달되도록 보장한다. 메시지가 유실되거나 중복되는 등의 문제를 해결하는 낮은 레벨(low level)의 기능 구조를 갖추고 있다. 현재 JMS 명세의 버전은 1.1 버전이다. JMS 명세를 확인하고 싶으면 다음 URL 을 이용하길 바란다: http://www.oracle.com/technetwork/java/index-jsp-142945.html 어떤 상황에서 JMS API 를 이용할 수 있는가? 다음과 같은 상황에서 엔터프라이즈 어플리케이션들은 RPC(Remote Procedure Call)와 같은 견고한 연결방식 대신 메시징 API 를 선택하는 경향이 있다.  컴포넌트가 다른 컴포넌트의 인터페이스에 의존하지 말아야 할 때, 즉 컴포넌트들이 손쉽게 교체될 수 있어야 할 때
  3. 3. - 3 -  모든 컴포넌트 혹은 어플리케이션들이 같은 시점에 실행 중이 아니더라도 커뮤니케이션이 가능해야 할 때  어플리케이션의 비즈니스 모델이 전송한 메시지에 대한 처리 결과를 즉각적으로 응답 받을 필요가 없을 때 예를 들어보면, 자동차 공장에서 사용되는 엔터프라이즈 어플리케이션의 컴포넌트들은 다음과 같은 방식으로 JMS API 를 사용할 수 있다.  재고 컴포넌트는 어떤 제품의 재고 수준이 일정량 이하로 떨어지면 공장 컴포넌트에게 메시지를 보낼 수 있다. 그리하여 자동차를 더 많이 만들 수 있다.  공장 컴포넌트는 부품 컴포넌트들에게 메시지를 보내 필요한 부품들을 조립할 수 있다.  부품 컴포넌트는 재고 컴포넌트나 주문 컴포넌트로 메시지를 보내 재고 수량을 갱신하고 새로운 부품을 주문할 수 있다.  공장 컴포넌트와 부품 컴포넌트 모두 정산 컴포넌트로 메시지를 보내 자산을 갱신할 수 있다.  업무팀에서 갱신된 카탈로그를 판촉팀으로 발행할 수 있다. 이런 방식으로 메시징을 이용하면 각 컴포넌트들이 네트워크나 리소스를 과도하게 점유하지 않고도 효율적으로 상호작용할 수 있다. 그림 47-1 를 보면 위 예제를 쉽게 이해할 수 있을 것이다. 제조업은 JMS API 를 이용하는 엔터프라이즈 어플리케이션의 한 예일 뿐이다. 소매(retail) 어플리케이션, 금융 서비스 어플리케이션, 의료 서비스 어플리케이션 등 수많은 어플리케이션들이 메시징을 이용할 수 있다. JMS API 는 JavaEE 플랫폼에서 어떻게 동작하는가? 1998 년 JMS API 가 처음 소개되었을 때, 이것의 가장 중요한 목표는 Java 어플리케이션이 IBM 의 MQSeries 와 같은 기존에 존재하던 메시징 기반 미들웨어(MOM Messaging Oriented Middleware)에 접근할 수 있도록 하는 것이었다. 그 때 이후로, 수 많은 개발 업체들이 JMS API 에 적응하고 이를 구현해 왔으며, 그래서 현재의 JMS 제품들은 엔터프라이즈 환경을 위한 메시징 기능을 완벽하게 제공할 수 있게 되었다.
  4. 4. - 4 - Java EE 플랫폼의 1.3 버전 배포부터 시작된 JMS API 는 플랫폼의 핵심적인 부분이 되어 왔으며, 많은 어플리케이션 개발자들은 JavaEE 컴포넌트를 이용하여 메시징 기능을 구현할 수 있게 되었다. JavaEE 플랫폼 내에 탑재되어 있는 JMS API 에는 다음과 같은 기능들이 포함되어 있다.  어플리케이션 클라이언트, 엔터프라이즈 자바빈즈(EJB), 그리고 웹 컴포넌트들은 JMS 메시지를 전송하거나 동기화된 방식으로 수신할 수 있다. 추가적으로 어플리케이션 클라이언트는 JMS 메시지를 비동기적으로 수신할 수도 있다. (하지만, 애플릿은 JMS API 를 지원할 필요가 없다.)  엔터프라이즈 빈의 한 종류인 메시지 드리븐 빈(MDB, Message-Driven Bean)은 메시지 소비를 비동기적으로 수행할 수 있다. JMS 프로바이더는 메시지 드리븐 빈을 이용하여 메시지에 대한 동시 처리 기능을 부가적으로 구현할 수 있다.  메시지에 대한 송/수신 기능은 분산 트랙잭션에 참가할 수 있는데, 이는 JMS 에 대한 작업과 데이터베이스에 대한 작업이 단일 트랜잭션 내에서 처리될 수 있음을 의미한다. JMS API 는 엔터프라이즈 어플리케이션의 개발 방식을 단순화시키고, JavaEE 컴포넌트와 기존에 존재하던 메시징 시스템간에 느슨하고, 신뢰로우며, 비동기적인 상호작용을 가능케 함으로써 Java EE 플랫폼의 가치를 향상시켰다. 개발자들은 특정 비즈니스 이벤트에 대해 동작하는 메시지 드리븐 빈을 추가함으로써 기존에 작성되어 있던 비즈니스가 새로운 동작을 하도록 기능을 손쉽게 추가할 수 있다. 또, JavaEE 플랫폼은 분산 트랜잭션과 메시지에 대한 동시 처리 기능을 제공함으로써 JMS API 를 향상시켰다. 더 자세한 정보를 알고 싶다면 엔터프라이즈 자바빈즈 3.1 규격(Specification)를 참고하기 바란다. JMS 프로바이더는 JavaEE 커넥터 아키텍처를 사용하고 있는 어플리케이션 서버와 통합될 수 있다. 개발자들은 리소스 어댑터(resource adapter)를 통해 JMS 프로바이더에 접근할 수 있다. 이 기능은 JMS 프로바이더가 여러 개의 어플리케이션 서버에 플러그인 되는 것이 가능하도록 해 주며, 또 어플리케이션 서버들이 여러 개의 JMS 프로바이더와 연동할 수 있도록 해 준다. 이에 대한 자세한 정보는 JavaEE 커넥터 아키텍처 규격 v1.6 을 확인해 보기 바란다. JMS API 기본 개념 이 장에서는 JMS API 의 가장 기본적인 개념에 대해 다룰 것인데, 이 내용은 JMS API 를 이용하는 간단한 어플리케이션을 작성하기 위해 반드시 알아야 할 것이다. 이 다음 장에서는 JMS API 의 프로그래밍 모델에 대해 다룰 것이다. 그 뒤에 소개될 장 들에서는 메시지 드리븐 빈을 이용하는 어플리케이션을 작성하기 위해 알아야 하는 고급 개념을 소개할 것이다. JMS API 아키텍처
  5. 5. - 5 - JMS 어플리케이션은 다음 부분들로 구성된다.  JMS 프로바이더(JMS Provider)는 JMS 인터페이스를 구현하고 관리/제어 기능을 제공하는 메시징 시스템이다. JavaEE 플랫폼의 구현체(Platform Implementation)는 JMS 프로바이더를 포함한다.  JMS 클라이언트(JMS Client)는 Java 언어로 작성된 프로그램이거나 컴포넌트로 메시지를 생산하거나 소비한다. JavaEE 어플리케이션 컴포넌트라면 어떤 것이라도 JMS 클라이언트로서 동작할 수 있다.  메시지는 JMS 클라이언트 사이에 정보를 주고 받는 데에 사용되는 객체이다.  관리 대상 객체(Administered Object)는 사전에 설정되는 JMS 객체로, 클라이언트에서 사용하기 위해 관리자가 생성하는 객체이다. JMS 관리 객체에는 두 가지가 있는데, 하나는 목적지(destination)이고 다른 하나는 커넥션 팩토리 (connection factory)이다. 이에 대한 내용은 [JMS 관리 대상 객체] 장에서 설명될 것이다. 그림 47-2 는 이 세 부분이 어떻게 상호작용하는지를 보여준다. 관리 툴을 이용하면 JNDI 네임스페이스 내에 목적지와 커넥션 팩토리 정보를 바인드(bind)할 수 있다. 그렇게 되면, JMS 클라이언트가 해당 네임스페이스 내에 있는 관리 대상 객체에 접근하기 위해 자원 주입(resource injection)을 이용할 수 있게 된다. 그리고 그 후 JMS 프로바이더를 통해 동일한 객체에 논리적인 커넥션을 맺을 수 있게 되는 것이다. 메시징 도메인들(Messaging Domains) JMS API 가 소개되기 전의 대부분의 메시징 제품들은 점대점(point-to-point) 방식이나 발행/구독(publish/subscribe) 방식 중 둘 중 하나만 지원했었다. JMS 규격은 각각의 접근 방식을 위해 분리된 도메인을 제공하고, 각 도메인 별로 따라야 할 규칙들을 정의한다. 독립적으로 동작하는 (stand-alone) JMS 프로바이더는 한 가지 혹은 두 가지 방식 모두를 구현할 수 있다. JavaEE 프로바이더는 두 가지 도메인 모두를 구현해야 한다. 사실, 대부분의 JMS API 구현체들은 점대점 방식 도메인이나 발행/구독 방식 도메인 모두를 지원하고 있으며, 몇몇 JMS 클라이언트들은 단일 어플리케이션 내에서 두 가지 도메인 모두를 사용하기도 한다. 이런 방식을 이용하여 JMS API 는 메시징 제품들의 기능과 유연성을 키웠다고 할 수 있다. JMS 규격은 여기서 한 걸음 더 나아갔다: 양쪽 도메인에 접근하기 위해 단일 JMS API 를 이용할 수 있게 된 것이다. 다음 장에서는 위에서 소개한 두 가지의 메시징 도메인에 대한
  6. 6. - 6 - 내용과 이들을 이용하기 위한 공통 인터페이스(common interface)를 사용하는 방법을 다룰 것이다. 점대점 메시징 도메인(Point-to-Point Messaging Domain) 점대점 제품이나 어플리케이션은 메시지 큐(Message Queue), 송신자(sender), 수신자(receiver)의 개념을 기반으로 만들어진다. 개별 메시지들은 특정 큐에 전달되고, 수신 클라이언트들은 메시지를 담고 있기 위해 생성된 큐로부터 메시지를 추출한다. 큐는 메시지가 소비되거나 만료되기 전까지 모든 메시지를 보유하고 있게 된다. 그림 47-3 에 소개되어 있는 점대점 메시진 방식은 다음과 같은 특징을 지니고 있다.  개별 메시지들은 단 하나의 소비자에 의해 소비된다.  메시지의 송신자와 수신자는 시간에 구애되지 않고 메시지를 주고 받을 수 있다. 수신자는 언제든 메시지를 가져올 수 있으며, 송신자가 메시지를 송신하고 있을 때 실행 중이지 않아도 된다.  수신자는 메시지를 성공적으로 처리했는지를 고지한다. 점대점 메시징 방식은 전송된 메시지가 단 하나의 소비자에 의해 성공적으로 처리되어야 하는 경우에 이용할 수 있다. 발행/구독 메시징 도메인(Publish/Subscribe Messaging Domain) 발행/구독(pub/sub) 제품이나 어플리케이션은 메시지를 하나의 토픽(topic)에 전송하는데, 이는 게시판(Bulletin Board)과 비슷하게 작동한다고 볼 수 있다. 발행자(Publisher)와 구독자(Subscriber)들은 보통 익명(anonymous)이고, 동적으로 컨텐트 계층(content hierarchy)에 발행하거나 그것을 구독할 수 있다. 이 시스템은 여러 발행자가 전달한 토픽들을 여러 구독자에게 나누어(distributing) 주는 역할을 담당한다. 토픽들은 현재의 구독자들에게 메시지를 전달되는 동안만 메시지를 유지한다. 발행/구독 메시징에는 다음과 같은 특징이 있다.  각 메시지들은 여러 구독자에 의해 소비될 수 있다.
  7. 7. - 7 -  발행자와 구독자 사이에는 시간적 의존성(timing dependency)가 존재한다. 어떤 토픽을 구독하는 클라이언트는 구독하기 시작한 이후에 발행된 메시지만 소비할 수 있다. 그리고 메시지를 소비하기 위해 지속적으로 활성화(active)되어 있어야 한다. JMS API 는 이 시간적 의존성을 어느 정도 늘리기 위해 구독자가 지속 구독(durable subscriptions)을 할 수 있는 기능을 제공한다. 이 방식을 이용하면 구독자가 동작하고 있지 않은 시점에 전송된 메시지를 수신할 수 있게 된다. 지속 구독 방식은 큐의 유연성과 신뢰성을 부여하면서도 클라이언트가 여러 수신자에게 메시지를 전송할 수 있게 해 준다. 이에 대한 상세한 정보는 [지속 구독 생성하기] 장을 참고하기 바란다. 각 메시지가 여러 소비자에 전달되어 처리되어야 한다면 발행/구독 방식을 이용하면 된다. 그림 47-4 에서 발행/구독 방식이 설명되어 있다. 공통 인터페이스를 이용하여 프로그래밍하기 JMS API 버전 1.1 을 이용하여 작성된 코드는 점대점이나 발행/구독 도메인 둘 중 하나와만 연동할 수 있었다. 사용되는 목적지는 도메인에 따라 다르고, 어플리케이션의 동작 방식도 큐를 사용하냐 토픽을 사용하냐에 따라 다르기 때문이다. 하지만 코드 자체는 모든 도메인에 동일하게 사용될 수도 있고, 그렇게 되면 어플리케이션이 보다 유연하고 재사용 가능하도록 작성될 수 있다. 이 예제에서는 이 공통 인터페이스에 대해 언급하고 설명할 것이다. 메시지 소비 메시징 제품들은 태생적으로 비동기적으로 동작한다: 메시지를 생산하거나 소비하는 사이에 근원적인 시간적 의존성은 존재하지 않는다. 하지만, JMS 규격에서는 이 용어를 좀 더 엄밀하게 사용하고 있다. 메시지들은 다음 두 가지 방식으로 소비될 수 있다.  동기화 처리: 구독자나 수신자가 receive 메소드를 호출하여 목적지로부터 메시지를 명시적으로 가져간다. receive 메소드는 메시지가 도착할 때 까지 대기할 수도 있고, 지정된 일정 시간 한도 내에 메시지가 도착하지 않으면 타임아웃 처리할 수도 있다.  비동기화 처리: 클라이언트는 소비자에 메시지 리스너(message listener)를 등록할 수 있다. 메시지 리스너는 이벤트 리스너와 유사하다. 언제든 메시지가 목적지에 도착하면, JMS 프로바이더는 리스너의 onMessage 메소드를 호출하여 메시지를 전달하는데, 이 메소드는 메시지의 컨텐츠에 대해 동작한다.
  8. 8. - 8 - JMS API 기본 개념 JMS 어플리케이션의 기본적인 구성요소들은 다음과 같다.  관리 대상 객체: 커넥션 팩토리와 목적지  커넥션들(connections)  세션들(sessions)  메시지 생성자들(message producers)  메시지 소비자들(message consumers)  메시지들(messages) 그림 47-5 를 보면 위의 객체들이 어떻게 상호작용하는지를 확인할 수 있다. 이 장에서는 모든 객체들을 간략하게 설명할 것이고 이들을 사용하기 위한 명령어와 간단한 코드 조각을 보여줄 것이다. 그리고 마지막에는 JMS API 의 예외 처리 방법에 대해 간략히 설명할 것이다. 모든 객체들을 통합하는 예제는 이 장의 마지막에 추가되어 있다. 더 상세한 내용을 알고 싶으면 JavaEE API 문서 중 JMS API 문서를 확인하기 바란다. JMS 관리 대상 객체(JMS Administered Objects) JMS 의 두 부분인 목적지(destination)과 커넥션 팩토리(connection factories)는 프로그램적으로 관리되기 보다는 관리자에 의해 관리된다. 이 객체들을 위한 기술은 JMS API 의 구현 방식과는 완전히 달라 보일 것이다. 그러므로, 이 객체들에 대한 작업은 어떤 프로바이더를 사용하냐에 따라 달라질 수 있는 작업으로 관리자가 처리해야 하는 것이다.
  9. 9. - 9 - JMS 클라이언트들은 이 객체에 접근하기 위해 공통으로 사용될 수 있는 인터페이스를 이용하며, 그래서 클라이언트 어플리케이션은 코드의 수정이 없거나 아주 적은 수정만으로 하나 이상의 JMS API 구현체와 연동할 수 있게 된다. 보통 관리자는 관리 대상 객체들을 JNDI 네임스페이스 내에 설정하며, 그 뒤 JMS 클라이언트들이 자원 주입을 이용하여 이 객체들에 접근하게 된다. GlassFish 서버를 이용한다면, create-jms-resource 라는 명령어를 이용하거나 관리자 콘솔을 이용하여 커넥터 자원 폼에서 JMS 관리 대상 객체를 생성할 수 있다. 또, 직접 어플리케이션에 추가할 수 있는 glassfish-resource.xml 라는 이름의 파일에 필요한 자원을 추가할 수도 있다. NetBeans IDE 에는 GlassFish 서버에 JMS 자원들을 생성할 수 있는 마법사 기능이 추가되어 있다. 자세한 내용은 [NetBeans IDE 를 이용하여 JMS 자원 추가하기]장을 참고하기 바란다. JMS 커넥션 팩토리(JMS Connector Factories) 커넥션 팩토리는 클라이언트가 프로바이더에 접근하기 위해 사용하는 객체이다. 커넥션 팩토리는 관리자가 설정한 커넥션에 대한 설정 파라미터 값의 집합을 포함하고 있다. 각 커넥션 팩토리는 ConnectionFactory, QueueConnectionFactory 혹은 TopicConnectionFactory 의 인스턴스이다. 커넥션 팩토리를 생성하는 방법은 [NetBeans IDE 를 이용하여 JMS 자원 추가하기]장을 참고하기 바란다. 보통 JMS 클라이언트 프로그램의 앞 부분에서 커넥션 팩토리 자원을 ConnectionFactory 변수에 주입하여 사용하게 된다. 예를 들어 다음 코드는 JNDI 이름이 jms/ConnectionFactory 인 자원을 지정하여 그것이 ConnectionFactory 객체에 할당되게 하고 있다. @Request(lookup = “jms/ConnectionFactory”) private static ConnectionFactory connectionFactory; 위와 같이 JavaEE 어플리케이션에서는 JMS 관리 대상 객체들은 보통 jms 라는 하위 컨텍스트 명을 추가하여 사용한다. JMS 목적지(JMS Destination) 목적지는 클라이언트가 메시지를 전달하기 위해 사용하는 대상(target)이거나 메시지를 소비하기 위해 지정한 근원(source)이다. 점대점 메시징 도메인에서 목적지는 큐(queue)라고 불리고, 발행/구독 메시징 도메인에서는 토픽(topic)이라고 불린다. 하나의 JMS 어플리케이션은 여러 개의 큐와 토픽을 (둘 다) 사용할 수 있다. 목적지를 생성하는 방법에 대해 알고 싶으면 [NetBeans IDE 를 이용하여 JMS 자원 추가하기]장을 참고하기 바란다.
  10. 10. - 10 - GlassFish 서버를 사용하는 목적지를 생성하기 위해서는, 그 목적지를 위한 JNDI 이름을 정의한 JMS 목적지 자원(JMS destination resource)를 생성하여야 한다. GlassFish 서버에서 구현된 JMS 는 각 목적지 자원은 실제 물리적인 목적지를 참조한다. 관리자는 물리적인 목적지를 명시적으로 생성할 수 있는데, 만약 그러지 않았다면 어플리케이션 서버가 이것이 필요할 때 자동으로 생성하고 관리자가 목적지 자원을 제거할 때 제거하게 된다. 클라이언트 프로그램 내에 커넥션 팩토리를 주입하는 것과 마찬가지로, 보통 목적지 자원도 주입하여 사용한다. 커넥션 팩토리와는 달리, 목적지는 하나의 도메인에만 지정된다. 토픽과 큐 모두를 사용하는 어플리케이션을 작성해야 한다면 목적지를 Destination 객체에 할당하면 된다. 다음 코드는 두 개의 자원 하나의 큐와 하나의 토픽을 정의하고 있다. 각 자원의 이름은 JNDI 네임스페이스 내에 생성된 목적지 자원에 대응된다. @Resource (lookup = “jms/Queue”) private static Queue queue; @Resource (lookup = “jms/Topic”) private static Topic topic; 공통 인터페이스를 이용하면 커넥션 팩토리와 목적지를 섞거나 맞출 수도 있다. 이는 ConnectionFactory 인터페이스를 사용하는 대신에 QueueConnectionFactory 자원을 Topic 과 같이 사용할 수도 있고, TopicConnectionFactory 자원을 Queue 와 같이 사용할 수 있다는 말이다. 어플리케이션의 동작 방식은 어떤 목적지를 사용하냐에 따라 달라질 뿐 어떤 종류의 커넥션 팩토리를 이용하냐에 결정되지 않는다. JMS 커넥션(JMS Connections) 커넥션은 JMS 프로바이더와 가상의 연결을 캡슐화한다. 예를 들어 하나의 커넥션은 클라이언트와 프로바이더 서비스 데몬 간에 열려 있는 TCP/IP 소켓 정보를 표현할 수 있다. 물론 하나의 커넥션을 하나 혹은 여러 개의 세션을 생성하기 위해 사용할 수 있다. 참고 – JavaEE 어플리케이션 내에서 단일 커넥션으로부터 여러 개의 세션을 생성하는 능력은 어플리케이션 클라이언트에서만 사용할 수 있다. 웹이나 엔터프라이즈 빈 컴포넌트 내에서 하나의 커넥션은 오직 하나의 세션만을 생성할 수 있다. 커넥션들은 Connection 인터페이스를 구현한다. 커넥션 팩토리 객체를 가지고 있다면 다음과 같이 커넥션을 생성하는 데 사용할 수 있다. Connection connection = connectionFactory.createConnection (); 어플리케이션이 종료되기 전에 생성한 커넥션을 반드시 닫아주어야 한다. 커넥션을 닫는 데 실패하면 JMS 프로바이더에 의한 자원 해제가 실패할 수 있다. 커넥션을 닫으면 그것이 열어놓은 세션뿐만 아니라 메시지의 생산자와 소비자도 닫게 된다.
  11. 11. - 11 - connection.close (); 어플리케이션이 메시지를 소비할 수 있기 전에 반드시 커넥션의 start 메소드를 호출해야 한다: 자세한 내용은 [JMS 메시지 소비] 장을 참고하라. 만약 커넥션을 닫지 않고 메시지의 전달을 중지하고 싶다면 stop 메소드를 호출하면 된다. JMS 세션(JMS Sessions) 세션은 메시지를 생산하고 소비하기 위한 단일 스레드 컨텍스트(single-threaded context)이다. 세션은 다음 객체들을 생성할 때 사용할 수 있다.  메시지 생산자(Message Producer)  메시지 소비자(Message Consumer)  메시지(Message)  큐 탐색기(Queue Browser)  임시 큐/토픽([임시 목적지 생성하기]장 참조) 세션은 메시지 리스너의 실행을 직렬화한다: 이에 대한 상세한 내용은 [JMS 메시지 리스너] 장을 참조하라. 세션은 트랜잭션 컨텍스트를 제공하는데, 이를 이용하면 일련의 메시지 전송과 수신 작업들을 하나의 원자화된 작업으로 처리하도록 할 수 있다. 이에 대한 상세한 내용은 [JMS API 로컬 트랜잭션 이용하기] 장을 참조하라. 세션은 Session 인터페이스를 구현한다. 커넥션 객체를 생성한 뒤 그것을 이용하여 세션 객체를 생성할 수 있다. Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); 첫 번째 입력값은 트랜잭션 처리를 할 것인지 여부를 결정한다. 두 번째는 메시지가 성공적으로 전달되었을 때 세션이 메시지를 자동적으로 확인할 것인지를 결정한다. (이에 대한 자세한 내용은 [메시지 확인 제어하기] 장을 참조하라.) 트랜잭션 처리가 되는 세션을 생성하려면 다음 코드를 이용하면 된다. Session session = connection.createSession(true, 0); 여기서 첫 번째 입력값은 이 세션이 트랙잭션 처리가 될 것임을 의미한다. 두 번째는 트랜잭션 처리된 세션을 위해 메시지 확인 기능을 규정하지 않는다는 것을 의미한다. 트랜잭션에 대한 더 자세한 정보를 알고 싶으면, [JMS API 로컬 트랜잭션 이용하기] 장을 참조하라. JavaEE 어플리케이션 내에서 JMS 트랜잭션이 동작하는 방법에 대한 상세한 정보는 [JavaEE 어플리케이션 내에서 JMS API 사용하기] 장을 참조하라.
  12. 12. - 12 - JMS 메시지 생산자(JMS Message Producer) 메시지 생산자는 세션에 의해 생성된, 메시지를 목적지에 전송하기 위해 사용되는 객체이다. 이 객체는 MessageProducer 인터페이스를 구현한다. 어떤 목적지를 위한 MessageProducer 객체를 생성하기 위해서 Session 객체를 이용한다. 아래 코드는 Destination 객체나 Queue 객체나 Topic 객체를 위한 생산자를 만드는 방법을 보여주고 있다. MessageProducer producer = session.createProducer (dest); MessageProducer producer = session.createProducer (queue); MessageProducer producer = session.createProducer (topic); createProducer 메소드의 입력 값으로 null 을 입력하여 미확인 생산자(unidentified producer)를 만들 수도 있다. 미확인 생산자를 사용하게 되면 메시지를 전송할 때까지 목적지를 정의하지 않아도 된다. 메시지 생산자를 생성한 다음, send 메소드를 이용하여 메시지를 전송할 수 있다: producer.send (message); 메시지를 전송하기 위해 메시지를 먼저 생성해야 한다: 이에 대해서는 [JMS Message]장을 참고하라. 만약 미확인 생산자를 생성하였다면, 다음과 같이 중복 정의(overloaded)된 send 메소드를 이용하여 첫 번째 입력 값으로 목적지를 지정할 수 있다. MessageProducer anon_prod = session.createProducer (null); anon_prod.send (dest, message); JMS 메시지 소비자(JMS Message Consumer) 메시지 소비자는 세션에 의해 생성된 객체로 목적지로부터 전달된 메시지를 수신하는 데에 사용된다. 이 객체는 MessageConsumer 인터페이스를 구현한다. 메시지 소비자는 JMS 클라이언트가 JMS 프로바이더의 어떤 목적지에 관심이 있음을 등록할 수 있도록 한다. JMS 프로바이더는 목적지로부터 해당 목적지에 등록된 소비자까지 메시지가 전달되는 모든 과정을 관리한다. 예를 들어, 어떤 Destination 이나 Queue 나 Topic 객체를 위한 MessageConsumer 객체는 Session 객체를 이용하여 만들 수 있다. MessageConsumer consumer = session.createConsumer (dest); MessageConsumer consumer = session.createConsumer (queue); MessageConsumer consumer = session.createConsumer (topic);
  13. 13. - 13 - Session.createDurableSubscriber 메소드를 이용하면 지속형 토픽 구독자(durable topic subscriber)를 생성할 수 있다. 이 메소드는 목적지로 토픽을 이용할 때에만 유효하다. 자세한 내용은 [지속형 구독자 만들기]를 참조하라. 메시지 소비자가 생성되고 나면, 바로 활성화(active)되어 메시지를 수신할 수 있게 된다. MessageConsumer 의 close 메소드를 이용하면 이 소비자를 비활성화(inactive) 상태로 변경할 수 있다. 메시지의 전달은 생성된 커넥션의 start 메소드를 호출하기 전까지는 시작되지 않는다. (이 start 메소드를 실행하는 것을 잊어서는 안 된다. JMS 프로그램에서 발생하는 가장 흔한 오류 중 하나는 커넥션을 시작하는 것을 잊어버려서 발생한 것이다.) 이제 receive 메소드를 이용하면 메시지를 동기적(synchronously)으로 소비할 수 있다. 이 메소드는 커넥션을 시작한 이후라면 언제든 사용할 수 있다. connection.start (); Message m = consumer.receive (); connection.start (); Message m = consumer.receive (1000); // 1 초 후 타임아웃 비동기적으로 처리하고 싶을 때는 메시지 리스너를 이용하면 된다. 이 내용은 다음 장에서 다룰 것이다. JMS 메시지 리스너(JMS Message Listener) 메시지 리스너는 메시지를 위한 비동기적 이벤트 핸들러로서 동작하는 객체이다. 이 객체는 MessageListener 인터페이스를 구현하는데, 이 인터페이스에는 onMessage 라는 단 하나의 메소드만 포함되어 있다. onMessage 메소드 내에 메시지가 전달되었을 때 수행되어야 하는 동작을 정의하면 된다. 메시지 리스너는 특정 MessageConsumer 객체의 setMessageListener 메소드를 호출함으로써 등록할 수 있다. 예를 들어, MessageListener 인터페이스를 구현한 Listener 라는 클래스를 만들어 두었다면, 다음과 같은 방법을 이용하여 메시지 리스너를 등록할 수 있다. Listener myListener = new Listener (); consumer.setMessageListener (myListener); 참고 – JavaEE 플랫폼 내에서 MessageListener 는 어플리케이션 클라이언트에서만 사용할 수 있다. 웹 컴포넌트나 엔터프라이즈 빈 내에서는 사용할 수 없다. 메시지 리스너를 등록한 후, Connection 객체의 start 메소드를 호출하여 메시지 전달을 시작할 수 있다. (만약 메시지 리스너를 등록하기 전에 start 메소드를 호출하면 몇몇 메시지가 유실될 수도 있다.) 메시지가 전달되기 시작된 후, JMS 프로바이더는 메시지가 전달될 때 마다 자동적으로 메시지 리스너의 onMessage 메소드를 호출한다. onMessage 메소드는 Message 타입의
  14. 14. - 14 - 입력값 하나를 받는데, 이 메소드 내에서는 어떤 타입의 Message 타입으로도 변환(cast)할 수 있다. ([메시지 바디] 장을 참조하라) 메시지 리스너는 특정한 목적지 유형에 국한되지 않고 사용할 수 있다. 하나의 리스너는 큐나 토픽 둘 중 하나로부터 메시지를 수신할 수 있는데, 이는 메시지 소비자가 생성될 때 어떤 유형의 목적지를 설정했느냐에 의해 결정된다. 하지만 일반적으로 메시지 리스너는 특정한 메시지 유형이나 포맷에 맞게 동작한다. 작성된 onMessage 메소드는 모든 예외사항을 처리하여야 한다. 절대로 체크 예외(checked exception)을 뱉어서는 안 되며, RuntimeException 을 뱉는 것도 프로그래밍 오류로 간주된다. 메시지 소비자를 생성하는 데 사용되었던 세션은 함께 등록된 모든 이벤트 리스너의 실행을 직렬화한다. 어느 순간에서건 세션의 메시지 리스너는 하나만 실행된다. JavaEE 플랫폼 내에서 메시지 드리븐 빈은 메시지 리스너의 특별한 유형이다. 이에 대한 자세한 내용은 [메시지 드리븐 빈을 이용하여 비동기적으로 메시지 수신하기]장을 참조하라. JMS 메시지 셀렉터 (JMS Message Selectors) 만약 수신된 메시지를 필터링 할 필요가 있다면 JMS API 의 메시지 셀렉터를 이용하면 된다. 이것을 이용하면 메시지 소비자가 관심있는 메시지만 소비하게 할 수 있다. 메시지 셀렉터는 어플리케이션이 아니라 JMS 프로바이더에 메시지 필터 작업을 할당한다. 메시지 셀렉터를 사용하는 어플리케이션에 대한 예는 [JMS API 와 세션빈을 사용하는 어플리케이션]를 참고하라. 메시지 셀렉터는 어떤 표현법을 포함하고 있는 String 객체이다. 이 표현법은 SQL92 조건 표현 구문(SQL92 conditional expression syntax)의 부분집합을 기반으로 만들어졌다. 아래 예에서 사용된 메시지 셀렉터는 NewsType 의 값이 Sports 거나 Opinion 인 메시지들을 선택하게 된다. NewsType = ‘Sports’ OR NewsType = ‘Opinion’ createConsumer 메소드와 createDurableSubscriber 메소드는 메시지 소비자를 생성할 때 메시지 셀렉터를 설정할 수 있게 되어 있다. 메시지 셀렉터가 설정되고 나면, 메시지 소비자는 헤더 혹은 속성 값이 해당 메시지 셀렉터에 맞는 메시지만 수신하게 된다. ([메시지 헤더] 장과 [메시지 속성] 장을 확인하라.) 메시지 셀렉터는 메시지 바디의 내용에 기반한 필터링을 할 수는 없다. JMS 메시지 (JMS Message)
  15. 15. - 15 - JMS 어플리케이션의 궁극적인 목적은 다른 어플리케이션에서 사용될 수 있는 메시지를 생산하고 소비하는 것이다. JMS 메시지는 단순하면서도 아주 유연한 기본 형식으로 만들어져 있는데, 이기종 플랫폼에 설치된 비 JMS 어플리케이션에서 사용되는 메시지 형식도 생성할 수 있다. JMS 메시지는 세 부분으로 나뉘어진다; 헤더(header), 속성(properties), 그리고 바디(body)이다. 헤더만이 필수적으로 존재해야 한다. 이 장의 나머지 부분들은 이 세 가지 부분들에 대해 다룰 것이다. 메시지 헤더, 속성, 바디에 대한 상세한 내용은 Message 인터페이스의 API 문서를 확인하길 바란다. 메시지 헤더 (Message Headers) JMS 메시지 헤더에는 클라이언트와 프로바이더가 메시지를 확인하고 전달하는 데에 사용하는 미리 정의된 필드들과 그에 대한 값들이 입력된다. 표 47-1 은 JMS 메시지 헤더의 필드들과 그에 대한 값이 어디에서 설정되는지에 대한 내용을 보여준다. 예를 들어, 모든 메시지는 유일한 식별자(unique identifier)를 갖고 있는데, 이는 헤더 필드의 JMSMessageID 의 값으로 입력된다. 또 다른 필드인 JMSDestination 의 값은 메시지가 전달된 큐 혹은 토픽에 대한 정보를 나타낸다. 다를 필드들은 시간 정보나 우선 순위 등을 나타낸다. 각 헤더 필드들은 setter 와 getter 메소드들과 연관되어 있는데, 이에 대한 내용들은 Message 인터페이스의 문서에 기술되어 있다. 몇몇 헤더 필드들은 클라이언트에 의해 설정되도록 되어 있지만, 많은 경우 send 나 publish 메소드가 호출될 때 자동으로 설정되며, 이렇게 설정되는 값들은 클라이언트가 설정한 값을 덮어 쓰게 된다. 헤더 필드 설정 주체 JMSDestination send 혹은 publish 메소드 JMSDeliveryMode send 혹은 publish 메소드 JMSExpiration send 혹은 publish 메소드 JMSPriority send 혹은 publish 메소드 JMSMessageID send 혹은 publish 메소드 JMSTimestamp send 혹은 publish 메소드 JMSCorrelationID 클라이언트 JMSReplyTo 클라이언트 JMSType 클라이언트 JMSRedelivered JMS 프로바이더 표 47-1 JMS 메시지 헤더 필드 값들을 설정하는 곳
  16. 16. - 16 - 메시지 속성 (Message Properties) 헤더에 입력된 필드와 값 외에 추가적인 정보가 필요하다면 속성 값들을 이용할 수 있다. 다른 메시징 시스템과의 호환성을 위해 속성값들을 추가할 수도 있고, 혹은 메시지 셀렉터를 위해 속성 값을 추가할 수도 있다. ([JMS 메시지 셀렉터] 장을 참고하라.) 메시지 셀렉터에서 사용될 속성값을 설정하는 방법에 대해서는 [JMS API 와 세션빈을 사용하는 어플리케이션]장을 참고하라. JMS API 는 프로바이더가 지원할 수 있는 몇몇 사전에 정의된 속성 이름들을 제공하고 있다. 사전 정의 속성값이든 사용자 정의 속성값이든 모두 필수 항목은 아니다. 메시지 바디 (Message Bodies) JMS API 는 다섯 가지의 메시지 바디 형식- 혹은 메시지 유형을 정의해 두고 있는데, 이것들을 이용하면 다양한 형식의 데이터를 송/수신할 수 있으며, 이미 존재하고 있는 메시지 형식과도 호환될 수 있다. 표 47-2 에서 메시지 유형들을 확인할 수 있다. 메시지 유형 바디 내용 TextMessage java.lang.String 객체 (예를 들어 XML 파일의 내용) MapMessage 이름(name)-값(value) 쌍으로 이루어진 정보들. 이름은 String 객체이고 값은 Java 프로그래밍 언어의 원시 자료형이다. Enumerator 를 이용하여 순차적으로 접근할 수도 있고 이름에 근거하여 무작위로 접근할 수도 있다. 입력된 정보의 순서는 정해져 있지 않다. BytesMessage 해석되지 않은 바이트들의 스트림. 이 메시지 유형은 말 그대로 기존의 메시지 형식에 맞추기 위해 바디를 인코딩 한다. StreamMessage Java 프로그래밍 언어의 원시 자료형의 스트림. 순차적으로 입력되고 순차적으로 읽힌다. ObjectMessage Java 프로그래밍 언어에서 사용하는 Serializable 객체. Message 없음. 헤더 필드들과 속성들로만 이루어짐. 메시지 바디가 필요치 않은 경우에 유용하게 사용할 수 있다. 표 47-1 JMS 메시지 유형들 JMS API 에는 각 유형의 메시지를 생성하고 그 내용을 채울 수 있는 메소드들을 포함되어 있다. 예를 들어, TextMessage 를 생성하여 전송하려면, 다음과 같은 코드를 이용하여야 한다. TextMessage message = session.createTextMessage (); message.setText (msg_text); // msg_text 는 String 객체이다. producer.send (message); 메시지 소비가 완료되면, 메시지는 원형의(generic) Message 객체로서 도착하게 되는데, 적절한 메시지 유형으로 캐스팅하여 사용해야 한다. 메시지 컨텐츠를 추출하기 위해 한 개 혹은 몇 개의 getter 메소드를 이용할 수 있다. 다음 코드는 getText 메소드를 사용하는 예 이다.
  17. 17. - 17 - Message m = consumer.receive(); if (m instanceof TextMessage) { TextMessage message = (TextMessage) m; System.out.println("Reading message: " + message.getText()); } else { // Handle error } JMS QueueBrowser 큐로 전송된 메시지들은 해당 큐에 대한 메시지 소비자가 그 메시지들을 소비하기 전 까지 큐 안에 남아있게 된다. JMS API 는 큐 내에 있는 메시지들을 탐색하고 각 메시지들의 헤더 값들을 확인할 수 있는 QueueBrowser 객체를 제공한다. QueueBrowser 객체는 Session.createBrowser 메소드를 이용하여 생성할 수 있다. 예를 들면 다음과 같다. QueueBrowser browser = session.createBrowser (queue); QueueBrowser 를 사용하는 예제는 [Queue 내의 메시지를 탐색하는 간단한 예제] 장에서 찾아볼 수 있다. createBrowser 메소드는 QueueBrowser 를 생성할 때 두 번째 입력값으로 메시지 셀렉터를 지정할 수 있다. 메시지 셀렉터에 대한 자세한 내용은 [JMS 메시지 셀렉터] 장을 참고하라. JMS API 는 토픽을 탐색하기 위한 메커니즘은 제공하지 않는다. 토픽에 등록되는 메시지들은 보통 등록되자마자 사라진다. 메시지를 소비할 메시지 소비자들이 등록되어 있지 않다면, JMS 프로바이더가 알아서 메시지들을 제거한다. 지속 구독 방식을 이용하면 메시지 소비자 없이도 토픽 내에 메시지가 남아 있을 수 있지만, 여전히 메시지를 확인할 수 있는 방법은 존재하지 않는다. JMS 예외 처리 JMS API 메소드들에 의해 던져지는 예외의 최상위 클래스는 JMSException 이다. JMSException 를 catch 하면 JMS API 와 관련된 모든 예외 상황을 처리할 수 있다. API 문서에 기술되어 있는 JMSException 하위 클래스들은 다음과 같다.  IllegalStateException  InvalidClientIDException  InvalidDestinationException  InvalidSelectorException  JMSSecurityException  MessageEOFException  MessageFormatException  MessageNotReadableException
  18. 18. - 18 -  MessageNotWriteableException  ResourceAllocationException  TransactionInProgressException  TransactionRolledBackException 튜토리얼 내의 모든 예제들은, 그렇게 하는 것이 합당한 경우, JMSException 을 catch 하고 처리하고 있다. 견고한 JMS 어플리케이션 작성하기 이 장에서는 어플리케이션을 작성하는 데에 필요한 일정 수준의 안정성과 성능을 달성하기 위해 JMS API 를 사용하는 방법에 대해 다룰 것이다. 많은 사람들이 단 한번 확실하게 전달되어야 하는 메시지들이 유실되거나 중복 전송되는 문제 때문에 JMS 어플리케이션을 구현하여 사용한다. JMS API 가 이를 위한 기능들을 제공하기 때문이다. 메시지를 생성하는 데에 가장 신뢰할 수 있는 방법은 메시지를 트랜잭션 내에서 유지되도록(PERSISTENT) 하며 전송하는 것이다. JMS 메시지들은 기본적으로 PERSISTENT 이다. 트랜잭션이라는 것은 메시지를 전송하고 수신하는 것과 같이 일련의 작동을 묶어서 하나의 작업 단위로 만드는 것인데, 그렇게 함으로써 모든 작업들이 성공하거나 실패하도록 하는 것이다. 이에 대한 자세한 내용은 [메시지 지속성 지정하기] 장과 [JMS API 의 로컬 트랜잭션 이용하기] 장을 참고하라. 메시지를 소비하는 방법 중 가장 신뢰할 수 있는 방법은 큐나 토픽에 대한 지속 구독을 트랜잭션 내에서 처리하는 것이다. 이에 대한 상세한 내용은 [임시 목적지 만들기] 장과 [지속 구독 방법] 장, 그리고 [JMS API 의 로컬 트랜잭션 이용하기] 장을 참고하라. 좀 다른 경우에는 낮은 수준의 신뢰도를 이용하여 오버헤드를 줄이고 성능을 향상시킬 수도 있다. 메시지를 여러 우선순위로 나누어 보낼 수 있고([메시지 우선순위 정하기] 참조), 또 일정 시간이 지나면 메시지가 만료되도록 할 수도 있다.([메시지가 만료되도록 설정하기] 참조) JMS API 는 다양한 종류와 수준의 신뢰도를 얻을 수 있도록 여러 가지 방법들을 제공한다. 이 장은 JMS API 를 이용하여 신뢰도 높은 어플리케이션을 개발하기 위한 기본적인 방법과 좀 더 진보된 방법을 다루는 두 절로 나뉘어져 있다. 이제 소개될 내용은 JMS 클라이언트에 적용될 수 있는 특징들을 다루고 있다. 이들 중 몇몇은 JavaEE 어플리케이션에서는 좀 다르게 동작할 수도 있다. 그 차이점은 이 장과 더불어 [JavaEE 어플리케이션에서 JMS API 사용하기] 장에서 심도 있게 다룰 것이다. 안정성있는 메시징을 위한 기본 메커니즘 메시지 전달을 하는 데 안정성을 보장하기 위한 기본적인 메커니즘은 다음과 같다.
  19. 19. - 19 -  메시지 확인 제어하기: 메시지 확인을 다양한 수준으로 제어할 수 있다.  메시지 Persistence 지정하기: 메시지가 (파일이나 DB 내에) 지속되도록 지정할 수 있는데, 이 말은 프로바이더에 문제가 발생하더라도 메시지가 유실되지 않음을 의미한다.  메시지 우선순위 설정하기: 다양한 수준의 메시지 우선순위를 지정할 수 있는데, 결과적으로 메시지가 전달되는 순서에 영향을 줄 수 있다.  메시지 만료 허용하기: 메시지의 만료 시간을 지정할 수 있어 너무 오래된 메시지는 전달되지 않도록 할 수 있다.  임시 목적지 생성하기: 커넥션이 생성되어 사용되고 있는 시간 동안에만 유지되는 임시 저장소를 생성할 수 있다. 메시지 확인 제어하기(Controlling Message Acknowledgement) 확인되기 전 까지는 메시지에 대한 소비가 성공적이었다고 간주되지 않는다. 성공적인 메시지 소비는 보통 세 단계를 거쳐 일어난다. 1. 클라이언트가 메시지를 수신한다. 2. 클라이언트가 메시지를 처리한다. 3. 메시지가 확인되었다. 확인 처리는 세션에 지정된 메시지 확인 모드에 따라 JMS 프로바이더나 클라이언트 둘 중 하나에 의해 시작된다. 트랜잭션 내에서 처리되고 있는 세션([JMS API 로컬 트랜잭션 사용하기] 장 참조)에서는, 트랜잭션이 커밋 처리되면 자동적으로 확인 처리가 이루어진다. 트랜잭션이 롤백 처리되면 모든 메시지들은 재전송된다. 트랜잭션 내에서 처리되지 않는 세션은 createSession 메소드의 두 번째 입력값에 지정된 값에 따라 메시지 확인 처리가 이루어진다. 여기에는 세 가지 입력값이 사용될 수 있다.  Session.AUTO_ACKNOWLEDGE: 클라이언트의 receive 메소드가 성공적으로 리턴 되었거나, 메시지를 처리하기 위해 호출된 메시지 리스너가 성공적으로 리턴 되면 자동적으로 메시지 확인된다. AUTO_ACKNOWLEDGE 세션 내에서 동기화 수신(synchronous receive)을 이용하면 위에서 언급한 메시지 소비가 세 단계로 이루어진다는 규칙과는 다르게 동작한다. 이 경우에는, 메시지 주신과 확인이 한 단계에서 같이 발생하는데, 그 뒤에 메시지에 대한 처리가 이루어진다.  Session.CLIENT_ACKNOWLEDGE: 클라이언트가 메시지의 acknowledge 메소드를 직접 호출함으로써 확인 처리가 이루어진다. 이 모드에서는 확인이 세션 레벨에서 일어난다. 소비된 메시지에 대해 확인 처리를 하게 되면 자동적으로 그 세션에서 소비된 모든 메시지들의 수신에 대해 확인 처리가 된다. 예를 들어, 메시지 소비자가 열 개의 메시지를 소비하던 중 다섯 번째에서 확인 처리를 수행하면 열 개의 모든 메시지에 대해 확인 처리가 이루어진다. 참고 – JavaEE 플랫폼에서, CLIENT_ACKNOWLEDGE 세션은 어플리케이션 클라이언트에서만 사용할 수 있으며, 웹 컴포넌트나 엔터프라이즈 빈에서는 사용할 수 없다.
  20. 20. - 20 -  Session.DUPS_OK_ACKNOWLEDGE: 이 옵션을 이용하면 세션은 전달된 메시지에 대해 나중에 확인 처리를 하게 된다. 이것을 이용하면 JMS 프로바이더에 문제가 생기는 경우 메시지가 중복 발송되는 경우가 있는데, 그렇기 때문에 반드시 중복 메시지를 적절히 처리할 수 있는 클라이언트에서만 사용되어야 한다. (만약 JMS 프로바이더가 메시지를 재전송한다면, JMS 메시지 헤더의 JMSRedelivered 값이 true 로 설정되어야 한다.) 이 옵션은 메시지 중복 전송을 막기 위한 작업 오버헤드를 줄이는 데 도움을 준다. 큐로부터 메시지가 수신되었지만 세션이 끝날 때까지 확인 처리가 이루어지지 않으면, JMS 프로바이더가 이들을 보유하고 있다가 다음에 소비자가 큐에 접근할 때 메시지를 다시 전송하게 된다. 프로바이더는 지속형 TopicSubscriber 들에서 사용되는 세션이 종료된 경우에도 처리 확인되지 않은 메시지가 있으면 위와 같이 보유한다. ([지속 구독 생성하기]장 참조) 지속 구독이 아닌 TopicSubscriber 를 위한 메시지들이 처리가 확인되지 않은 채로 세션이 완료되게 되면 해당 메시지들은 버려지게 된다. 큐나 지속형 구독 방식을 사용하고 있는 경우, Session.recover 메소드를 이용하여 트랜잭션 처리되지 않는 세션을 종료하고 처리 확인되지 않은 메시지부터 다시 시작하도록 할 수 있다. 실제로 세션의 전송된 메시지들이 이전에 확인된 메시지 이후로는 전송되지 않은 것으로 리셋 된다. 그 후에 전송되는 메시지는 원래 전송되었던 상황과는 좀 다를 수 있는데, 메시지가 만료되었거나 혹은 높은 우선 순위의 메시지가 먼저 전달될 수도 있기 때문이다. 지속형이 아닌 TopicSubscriber 의 경우에는 세션이 recover 될 때 확인되지 않은 메시지들은 버려질 수도 있다. [메시지 처리 확인 예] 장에서 언급되는 예제 프로그램에서 메시지 처리가 완료되기 전 까지는 메시지 처리 확인을 하지 않도록 하는 두 가지 방법을 확인할 수 있다. 메시지 Persistence 지정하기(Specifying Message Persistence) JMS API 는 JMS 프로바이더에 문제가 발생하였을 경우 메시지가 버려질지 여부를 결정하는 두 가지 전송 모드를 지원한다. 이 모드들은 DeliveryMode 인터페이스의 필드로 정의되어 있다.  PERSISTENT 모드는, 기본적으로 이 모드가 사용되는데, JMS 프로바이더가 정지하거나 문제가 발생하는 경우에도 메시지가 유실되지 않도록 별도의 처리를 하도록 한다. 이 모드를 이용하여 전송되는 메시지들은 전송되는 과정에서 안정적인 저장 장치(storage)에 쌓이게 된다.  NON_PERSISTENT 모드는 JMS 프로바이더가 메시지를 저장하거나 프로바이더에 문제가 발생했을 때 메시지가 유실되지 않을 것을 보장할 것을 요구하지 않는다. 이 모드들은 두 가지 방법으로 지정될 수 있다.  메시지 생산자에 의해 전달되는 모든 메시지들에 동일한 전달 모드가 지정되도록 하기 위해 MessageProducer 인터페이스의 setDeliveryMode 메소드를 이용할 수 있다. 예를 들어, 다음 코드는 생산자의 전달 모드를 NON_PERSISTENT 로
  21. 21. - 21 - 지정한다. producer.setDeliveryMode (DeliveryMode.NON_PERSISTENT);  특정한 메시지에 대해 별도로 모드를 지정하기 위해 긴 형식의 publish 메소드를 이용할 수도 있다. 두 번째 입력값이 전달 모드이다. 예를 들어, 아래 send 메소드를 호출하는 코드는 message 가 NON_PERSISTENT 모드로 전달되도록 한다. producer.send (message, DeliveryMode.NON_PERSISTENT, 3, 10000); 세 번째와 네 번째 입력값들은 우선순위와 만료 시간인데, 다음 두 개 장에서 다뤄질 것이다. 전달 모드를 지정하지 않으면 기본적으로 PERSISTENT 모드가 사용된다. 성능을 높이고 저장소의 오버헤드를 줄이고자 한다면 NON_PERSISTENT 모드를 사용하는 것이 바람직하다. 하지만 메시지가 유실되어도 무방한 경우에만 사용해야 할 것이다. 메시지 우선 순위 설정하기(Setting Message Priority Levels) JMS 프로바이더가 긴급 메시지를 먼저 전달하도록 메시지의 우선 순위를 지정할 수 있다. 메시지 우선 순위를 정하는 방법에는 다음 두 가지가 있다.  MessageProducer 의 setPriority 메소드를 이용하여 메시지 생산자에 의해 전달되는 모든 메시지가 동일한 우선 순위를 갖도록 할 수 있다. 예를 들어, 아래 코드는 producer 의 우선 순위를 7 로 설정한다. producer.setPriority (7);  특정 메시지의 우선 순위를 지정하기 위해 긴 send 나 publish 메소드를 이용할 수 있다. 세 번째 입력값이 우선순위이다. 예를 들어, 아래 send 메소드 호출은 message 의 우선 순위를 3 으로 지정한다. producer.send (message, DeliveryMode.NON_PERSISTENT, 3, 10000); 0(가장 낮음)부터 9(가장 높음)까지 10 개의 단계를 지정할 수 있다. 우선 순위를 지정하지 않는다면 기본적으로 4 로 처리된다. JMS 프로바이더들은 우선 순위가 높은 메시지를 낮은 메시지보다 먼저 보내려고 시도할 것이다. 하지만 반드시 그 정확한 순서대로 전달되지는 않을 것이다. 메시지 만료 허용하기(Allowing Message Expiration) 기본적으로 메시지들은 절대로 만료되지 않는다. 하지만, 일정 기간이 지나면 쓸모 없어지는 메시지가 있다고 한다면, 그에 대해 만료 시간을 지정하고 싶을 것이다. 이 역시 두 가지 방법으로 설정할 수 있다.  MessageProducer 인터페이스의 setTimeToLive 메소드를 이용하여 특정 메시지 생산자에 의해 전달되는 모든 메시지의 만료 시간을 동일하게 지정할 수 있다. 예를 들어 아래 코드는 producer 가 전달하는 모든 메시지들의 수명을 1 분(=60000ms)로 지정할 수 있다. producer.setTimeToLive (60000);
  22. 22. - 22 -  send 혹은 publish 메소드를 이용하여 특정 메시지의 만료 시간을 지정할 수 있다. 네 번째 입력 값이 밀리 세컨드로 지정되는 만료 시간이다. 예를 들어 아래 코드는 메시지의 수명을 10 초로 제한한다. producer.send (message, DeliveryMode.NON_PERSISTENT, 3, 10000); timeToLive 의 값을 0 으로 설정하면 그 메시지는 만료되지 않는다. 메시지가 전달될 때, timeToLive 에 현재 시각에 만료 시간 값을 더한 값이 설정된다. 지정된 만료시간 이후로도 전송되지 않은 메시지는 모두 폐기된다. 쓸모 없는 메시지를 폐기함으로써 저장 공간과 계산량을 보전할 수 있게 된다. 임시 목적지 생성하기(Creating Temporary Destination) 보통 JMS 목적지(큐와 토픽)는 프로그램적으로 만들어지는 것이 아니라 관리자에 의해 만들어진다. 보통 JMS 프로바이더들은 목적지를 생성하거나 제거할 수 있는 도구를 제공하는데, 이런 툴들은 오랫동안 지속되는 목적지를 위해 사용된다. JMS API 는 이뿐만 아니라 특정한 커넥션이 생성된 후 살아있는 동안에만 유지되는 목적지(TemporaryQueue 나 TemporaryTopic)을 생성하는 기능도 제공한다. 이 임시 목적지들은 Session.createTemporaryQueue 메소드나 Session.createTemporaryTopic 메소드를 이용하여 만들 수 있다. 임시 저장소 내에 있는 메시지를 소비할 수 있는 메시지 소비자는 해당 저장소를 생성하기 위해 사용했던 커넥션을 이용하여 생성된 소비자들뿐이다. 모든 메시지 생산자들이 임시 목적지로 메시지를 전송할 수 있다. 임시 목적지가 속해 있는 커넥션을 닫으면(close), 임시 목적지 역시 닫히게 되고 그 안에 저장되어 있던 내용들은 모두 사라진다. 임시 목적지는 단순한 요청/응답 메커니즘을 구현하기 위해 사용될 수 있다. 임시 목적지를 생성하고, 메시지의 JMSReplyTo 헤더 값을 그 목적지로 지정한 뒤 전송하면, 그 메시지를 수신하여 소비한 메시지 소비자가 그 값을 이용하여 다시 그 임시 목적지로 응답 메시지를 전송할 수 있다. 이 때 메시지 소비자는 JMSCorrelationID 헤더 필드 값을 원래 전달되었던 메시지의 JMSMessageID 값으로 입력하여 원래 메시지를 참조하도록 할 수도 있다. 예를 들어, onMessage 메소드에서 session 을 하나 생성하여 메시지를 보낸 쪽으로 응답을 보낼 수 있다. 다음 코드에서와 같이 말이다. producer = session.createProducer (msg.getJMSReplyTo ()); replyMsg = session.createTextMessage ("Consumer " + "processed message: " + msg.getText ()); replyMsg.setJMSCorrelationID (msg.getJMSMessageID ()); producer.send (replyMsg); 더 상세한 내용은 [Java 메시지 서비스 예제] 장을 참고하길 바란다. 안정성을 위한 진보된 메커니즘
  23. 23. - 23 - 메시지 전달을 보다 안정적으로 하기 위한 고급 메커니즘은 다음과 같다.  지속 구독 생성하기: 위에서 이미 언급하였지만, 토픽에 대해 지속 구독(durable topic subscription)을 할 수 있는데, 이를 이용하면 구독자가 실행되고 있지 않은 상태에서 발행된 메시지도 수신할 수 있게 된다. 지속 구독 방식은 발행/구독 방식의 메시지 도메인에도 큐를 사용하는 정도의 안정성을 보장해 준다.  로컬 트랜잭션 이용하기: 로컬 트랜잭션을 이용하면 메시지를 전송하고 수신하는 일련의 작업들을 원자화된 하나의 작업으로 묶을 수 있다. 작업 중 하나가 실패하기만 하면 모든 작업이 롤백 된다. 지속 구독 생성하기(Creating Durable Subscriptions) 발행/구독 어플리케이션들이 모든 발행된 메시지들을 수신할 수 있도록 보장하기 위해서는, 발행자들에 PERSISTENT 전달 모드를, 그리고 구독자에게는 지속 구독을 사용하도록 하여야 한다. 토픽이 목적지로 설정되어 있는 경우에서는 Session.createComsumer 메소드는 지속되지 않는 구독자(nondurable subscriber)를 생성하게 된다. 지속되지 않는 구독자는 자신이 실행되고 있는 순간에 발행된 메시지만 수신할 수 있다. 좀 더 많은 자원을 사용하기는 하지만, Session.createDurableSubscriber 메소드를 이용하면 지속되는 구독자를 생성할 수 있다. 지속 구독을 이용할 경우에는 동일 시점에 실행되고 있는 구독자는 오직 하나여야 한다. 지속되는 구독자는 JMS 프로바이더에 유지되고 있는 유일한 식별자(unique identity)를 지정함으로써 지속 구독을 등록할 수 있다. 그 뒤에 오는 동일한 식별자를 가지고 있는 구독자는 이전 구독자가 작업하지 않고 남겨둔 내용을 이어서 구독하게 된다. 만약 지속 구독을 하고 있는 실행중인 구독자가 하나도 없다면, JMS 프로바이더는 구독자가 수신하거나 혹은 메시지가 완료될 때까지 구독자들의 메시지를 유지하게 된다. 지속 구독을 위한 유일한 식별자를 만드는 방법은 아래 항목을 설정하면 된다.  커넥션을 위한 클라이언트 ID  구독자를 위한 토픽의 이름과 구독명 특정 클라이언트에 대한 커넥션 팩토리를 위해 클라이언트 ID 는 명령 실행줄이나 관리자 콘솔을 이용하여 설정할 수 있다. 커넥션과 세션을 생성하기 위해 이 커넥션 팩토리를 이용한 다음, 두 개의 입력값을 갖는 createDurableSubscriber 메소드를 호출한다: 토픽과 구독명을 나타내는 String 값을 입력한다. String subName = "MySub"; MessageConsumer topicSubscriber = session.createDurableSubscriber (myTopic, subName);
  24. 24. - 24 - subscriber 는 Connection 객체나 TopicConnection 객체의 start 메소드를 호출하면 활성화된다. 나중에 이 subscriber 객체에 close 를 호출할 수 있다. topicSubscriber.close (); JMS 프로바이더는 큐에 전송된 메시지를 저장했던 것과 같이 토픽으로 전송되거나 발행된 메시지들을 저장한다. 만약 프로그램이나 다른 어플리케이션이 동일한 커넥션 팩토리, 클라이언트 ID, 토픽, 구독명을 이용하여 createDurableSubscriber 메소드를 호출하면, JMS 프로바이더는 그 구독자가 비활성 상태였을 때 발행되었던 모든 메시지를 전송하게 된다. 지속 구독을 삭제하려면, 먼저 구독자를 close 한 다음, 구독명을 이용하여 unsubscribe 메소드를 호출하면 된다. topicSubscriber.close (); session.unsubscribe (“MySub”); unsubscribe 메소드는 프로바디어가 해당 구독자를 위해 유지하고 있던 상태 정보를 삭제하도록 한다. 그림 47-6 과 47-7 은 비지속 구독자와 지속 구독자간의 차이점을 보여주고 있다. 보통의 경우-비지속 구독자의 경우에는, 구독자와 구독이 동일 시점에 시작되고 종료되며, 실제로 동일하다. 구독자가 닫히게 되었을 때, 구독 역시 끝나게 된다. 여기서 create 는 토픽에 대한 Session.createConsumer 를 의미하고 close 는 MessageConsumer.close 를 의미한다. 첫 번째 close 와 두 번째 create 사이에 발행된 메시지들은 어떠한 것도 구독자에 의해 소비되지 않는다. 그림 47-6 에서 구독자는 M1, M2, M5, M6 메시지들을 소비하지만 M3 과 M4 는 유실된다. 지속 구독의 경우, 구독자는 닫혔다가 다시 생성될 수 있으며 어플리케이션이 unsubscribe 메소드를 호출하기 전 까지는 구독이 지속되고 메시지들을 계속 유지하게 된다. 그림 47- 7 에서 create 는 Session.createDurableSubscriber 를 의미하고, close 는 MessageConsumer.close 를, 그리고 unsubscribe 는 Session.unsubscribe 를 의미한다. 구독자가 닫혀 있던 순간에 발행되었던 메시지들을 구독자가 다시 생성되면 전달되게 된다. 그래서 구독자가 닫혀 있는 상태에서 전달되었던 M2, M4, M5 메시지들도 유실되지 않는 것이다.
  25. 25. - 25 - 지속 구독을 이용하는 JavaEE 어플리케이션에 대한 예제는, [메시지 확인 예제], [지속 구독 예제], 그리고 [JMS API 와 세션빈을 사용하는 어플리케이션] 장을 참고하기 바란다. JMS API 로컬 트랜잭션 이용하기(Using JMS API Local Transaction) 여러 개의 일련의 작업들은 트랜잭션이라 불리는 하나의 원자화된 작업으로 묶일 수 있다. 만약 어떤 하나의 작업이 실패하게 되면 트랜잭션 내의 모든 작업이 롤백 되고, 모든 작업이 처음부터 다시 시작될 수 있다. 모든 작업들이 성공적으로 끝나면 트랜잭션은 커밋 된다. JMS 클라이언트 내에서도 메시지의 전송과 수신을 하나의 단위로 묶기 위해 로컬 트랜잭션을 이용할 수 있다. JMS API 의 Session 인터페이스에는 JMS 클라이언트에서 사용할 수 있는 commit 과 rollback 메소드가 정의되어 있다. 트랜잭션 commit 은 모든 메시지들이 전송되었고 모든 소비된 메시지들이 확인되었음을 의미한다. 트랜잭션 rollback 은 모든 메시지들이 파괴되고 모든 소비된 메시지들이 복구되어 만료되지 않는 이상 다시 전달되는 것을 의미한다. ([메시지 만료 허용하기]장 참조) 트랜잭션 처리되는 세션은 언제나 트랜잭션에 관여하게 된다. commit 이나 rollback 메소드가 호출되면, 그 즉시 실행 중이던 트랜잭션은 종료되고 새로운 트랜잭션이 시작된다. 트랜잭션 처리되고 있는 세션을 닫으면 아직 완료되지 않은 전송/수신 작업 모두를 포함한 현재 진행 중인 트랜잭션에 대해 롤백 처리를 하게 된다. 엔터프라이즈 자바빈 컴포넌트 내에서는 Session.commit 과 Session.rollback 메소드를 사용할 수 없다. 그 대신 분산 트랜잭션(distributed transaction)을 이용할 수 있는데, 그 내용은 [JavaEE 어플리케이션 내에서 JMS API 를 사용하기] 장을 참조하기 바란다. 물론 몇 개의 전송과 수신을 하나의 JMS API 로컬 트랜잭션 내에 묶을 수 있다. 그렇게 할 경우, 작업의 순서를 면밀히 살펴보아야 한다. 트랜잭션이 모든 전송과 모든 수신에 대해 잡혀 있거나 항상 전송보다 수신이 먼저 일어나게 된다면 아무런 문제가 없다. 하지만 요청/응답 메커니즘을 사용하려고 하는 경우에서는, 하나의 트랜잭션 내에서 메시지를 전송하고 응답을 받기 위해 기다리게 될 것인데, 그렇게 되면 트랜잭션이 커밋 될 때까지 전송 작업이 완료될 수 없으므로 프로그램이 멈춰버리게 된다.(program will hang) 아래 코드가 이 예를 보여주고 있다. // Don’t do this! outMsg.setJMSReplyTo (replyQueue);
  26. 26. - 26 - producer.send (outQueue, outMsg); consumer = session.createConsumer (replyQueue); inMsg = consumer.receive (); session.commit (); 트랜잭션이 커밋 되기 전에는 실제적으로 메시지 전송이 처리되지 않기 때문에 트랜잭션 내에는 어떠한 경우에도 메시지 전송에 의존하는 메시지 수신이 있어서는 안 된다. 추가적으로, 하나의 메시지에 대한 생산과 소비는 동일 트랜잭션의 부분이 될 수 없다. 트랜잭션이라는 것은 JMS 클라이언트와 JMS 프로바이더 간에 일어나는 것으로 메시지에 대한 생산과 소비 사이에 개입하기 때문이다. 그림 47-8 에서 이 상호작용을 볼 수 있다. Client1 에서 하나 혹은 그 이상의 메시지들을 하나 이상의 목적지로 전송하는 것은 단일 트랜잭션으로 묶일 수 있는데, 이는 하나의 세션을 이용하는 JMS 프로바이더에 대한 하나의 상호작용만 수행하기 때문이다. 비슷하게 Client2 가 하나 혹은 그 이상의 목적지로부터 하나 이상의 메시지를 수신하는 것 역시 하나의 트랜잭션으로 묶일 수 있다. 하지만 두 클라이언트 사이에 어떠한 직접적인 상호작용이 발생하지 않고, 사용하는 세션도 각각 다르기 때문에, 이 둘 간에는 트랜잭션이 일어날 수 없다. 다른 말로 설명하면, 하나의 세션에 대해 메시지를 생산하거나 소비하는 작업은 트랜잭션으로 처리될 수 있지만, 서로 다른 세션간에 메시지가 생산/소비되는 것은 트랜잭션으로 처리될 수 없다. 이는 메시징과 동기화된 프로세싱(synchronized processing) 간의 기본적인 차이점이다. 데이터를 주고 받기 위해 서로 단단하게 연결하는 대신, 메시지 생산자와 소비자는 JMS 프로바이더가 제공하는 필요한 메시지를 단 한번만 전달하도록 보장하는 안정적인 접근 방식을 이용하는 것이다. 세션을 생성할 때, 트랜잭션 처리를 할 것인지 아닌지를 결정할 수 있다. createSession 메소드의 첫 번째 입력값이 boolean 값인데, 이 값이 true 이면 이 세션이 트랜잭션 처리됨을 의미한다. 물론 false 이면 트랜잭션 처리되지 않음을 의미한다. 두 번째 입력값은 처리 확인 모드인데, 이는 트랜잭션 처리되지 않는 세션일 때에만 의미가 있다. ([메시지 확인 제어하기] 장 참조) 세션이 트랜잭션으로 지정되면, 두 번째 입력값은 무시되며, 그렇기 때문에 이런 경우에는 두 번째 입력 값을 0 으로 지정하는 것이 코드를 좀 더 읽기 쉽게 해 줄 것이다. 다음 예에서와 같이 말이다. session = connection.createSession (true, 0); 로컬 트랜잭션에 대한 commit 과 rollback 메소드는 해당 세션에 연관되어 있다. 여러 개의 작업을 수행한다 하더라도 하나의 세션만 이용한다면 큐와 토픽에 대한 작업들 역시 하나의
  27. 27. - 27 - 트랜잭션으로 묶일 수 있다. 예를 들어, 동일한 세션의 큐로부터 메시지를 수신하여 같은 세션의 토픽으로 메시지를 전송하는 작업과 같은 일련의 작업들도 트랜잭션으로 묶일 수 있다는 말이다. 또, 메시지 리스너의 생성자에 클라이언트 프로그램의 세션을 입력하여 메시지 생산자를 만드는 데 사용할 수도 있다. 이 방법을 이용하여 비동기 메시지 소비자 내에서 메시지를 수신하고 전송하는 데에 동일한 세션을 이용할 수 있다. [로컬 트랜잭션 예제] 장에서 JMS API 로컬 트랜잭션을 이용하는 예를 확인할 수 있다.

×