Clean Code that Works.

http://www.skill-guru.com/blog/2010/01/13/asynchronous-method-invocation-in-spring-3-0/

비동기 호출을 사용해야 하는 일이 발생해서, 알아봄. @_@ 

Spring 3.0에서 task scheduling 과 asynchronous method드 호출을 지원하기 위한 어노테이션들이 추가된다.
이중에서 @Aysnc에 대해서 알아보고 사용예를 살펴보자.

The @Async annotation

The @Async 어노테이션은 비동기적으로 메소드를 호출할 수 있도록 해준다. 즉 호출하는 쪽에서 호출을 즉시 반환한다.(비동기 이기 때문에 호출한후 기다리지 않고 바로 반환한다는 뜻인듯)

@Async 의 한가지 사용예에는 사용자가 회원가입과 가입 완료 메일을 보내는 예가 있고 아니면 이와 비슷한 사용자의 작업을 정지 시키지 않는 작업을 들 수 있다.

예를 살펴 봄으로서 좀더 명확하게 설명을 해보도록 하자. 

applicationContext.xml
 

<?xml version=”1.0″ encoding=”UTF-8″?>

<beans xmlns=”http://www.springframework.org/schema/beans” xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xmlns:p=”http://www.springframework.org/schema/p” xmlns:context=”http://www.springframework.org/schema/context”
xsi:schemaLocation=”
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd”>

<context:component-scan base-package=”cs”/>
</beans>  


테스트 코드를 살펴보면

TestService.java

import org.springframework.context.support.ClassPathXmlApplicationContext;
import cs.service.RegularService;

public class TestService {

public static void main(String args[]){

ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext(new String[] {

“applicationContext.xml”});

RegularService regService = (RegularService) appContext.getBean(“regularService”);

regService.registerUser(“Skill-Guru”);

}

}


ReqularService.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import cs.async.MailUtility;

@Service
public class RegularService {

@Autowired

private MailUtility mailUtility ;

public void registerUser(String userName){

System.out.println(” User registration for  “+userName +” complete”);

mailUtility.sendMail(userName);

System.out.println(” Registration Complete. Mail will be send after 5 seconds “);

}

}


MailUtility.java

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Component
public class MailUtility {

@Async
public void sendMail(String name){

System.out.println(” I Will be formatting html mail and sending it  “);

try {

Thread.sleep(5000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(” Asynchronous method call of send email — Complete “);

}

}


TestService.java 테스크를 실행시켜 보면 출력은 아래와 같은 것이다.

User registration for  Skill-Guru complete
I Will be formatting html mail and sending it
Asynchronous method call of send email — Complete
Registration Complete. Mail will be send after 5 seconds


잠시 생각해보면, 위의 결과는 비동기 호출로 보이지 않는다. 뭐가 잘 못된 것일까?
applicationContext.xml에서 <task:annontaion-driven /> 설정을 빼먹었다.

수정된 applicationContext.xml

<?xml version=”1.0″ encoding=”UTF-8″?>

<beans xmlns=”http://www.springframework.org/schema/beans” xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xmlns:p=”http://www.springframework.org/schema/p” xmlns:context=”http://www.springframework.org/schema/context”
xmlns:task=”http://www.springframework.org/schema/task”
xsi:schemaLocation=”
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd“>

<context:component-scan base-package=”cs”/>

<task:annotation-driven/>

</beans>


굵게 처리된 부분을 확인하자.

테스트를 한번도 돌려 보면 출력은 아래와 같다.

User registration for  Skill-Guru complete
Registration Complete. Mail will be send after 5 seconds
I Will be formatting html mail and sending it

보는것 처럼 5초 후에 프로그램이 종료되지 않을 것이다.

Asynchronous method call of send email — Complete

registerUser가 sendMail을 호출 한 후 바로 컨트롤을 넘겨주는것을 볼 수 있다.

참고!!
위 예제는 테스트 케이스가 아닌 테스트 클래스를 통해 진행 되었기 때문에 문제가 없다.
테스트 케이스로 해서 진행 할 경우에는 비동기의 특징인 반환을 바로 해버리기 때문에 sleep이후에 문자가 출력되지 않고테스트 케이스가 종료되는 것을 볼 수 있다.
이 경우에는 테스트 코드가 종료되고 별로도 sleep를 넣어 줘서, 테스트가 종료되지 않도록 하여 thread가 완료 될 수 있는것을 확인 하도록 하자.(말 그대로 확인만.. ci서버가 있을 경우에 테스트 수행 시간이 오래 걸릴 수 있다.) 


참고!!
@Sync가 적용된 메서드의 리턴 타입은 void 나 Feature 타입이어야 한다.
아니면 비동기로 동작하지 않는다.



이번에는 스프링에서 JMS를 어떻게 지원하고, 사용하는 방법에 대해서 알아보자.


위 이미지에서 보듯이 JMS API는 JDBC API 와 상당히 유사하다. 스프링에서 JDBC template을 제공 하는 것 처럼 JMS template도 제공한다.

JMS template 과 message listener container 는 스프링에서 JMS 메시징을 사용하는 핵심 컴포넌트 이다. Spring JMS template(JmsTeplate)는 동기적으로 메시지를 받거나 보내는데 사용한다. message listener container(DefaultMessageListenerContainer)는 MDP를 사용하여 비동기 메시지를 받는데 사용한다. 대부분의 자바 EE 애플리케이션 서버들과는 다르게(JBoss, WebSphere) 스프링은 자기 자신이 JMS 제공자가 되지 않는다. 이것은 외부의 JMS 제공자(ActiveMQ, JBoss Messaging, IBM WebSphereMQ)가 스프링에서 메시징을 하기 위해 필요한 것이다. JMS template 과 message listener container 의 목적은 개발자가 JMS 제공자에 대한 자세한 JMS 연결, JMS 세션 그리고 JMS message producer 와 message consumer를 만드는것에 연결정보를 필요로 하는 것을 격리한다.

고 레벨 아키텍쳐


스프링 JMS 템플릿(JmsTemplate)은 메시지를 동기적으로 주고 받기 위한 primary interface다. JNDI를 사용할 때, JmsTemplate 여러 스프링 오프젝트들이 JMS 제공자에 연결하는데 이것을을 포함하여 사용한다. JndiTemplate, JndiObjectFactoryBean, JndiDestinationResolver 와 CachingConnectionFactory(아니면 SingleConnectionFactory)
JndiTempate bean은 제공자 URL, 보안 사용자와 JMS 제공자에 연결하기 위한 보안 자격 증명의 팩토리를 초기화 하는데 사용한다. JndiObjectFactoryBean 을 경유하여 JMS 커넥션 팩토리를 정의 하는데 사용되고, JMS destination은 JndiDestinationResolver를 통해 사용된다. 
아래 그림은 Spring JNDI object와 JMS provider와 애플리케이션 사이의 관계와 협력 관계를 표시한다.

 
메시지를 비동기적으로 받기 위해서, 스프링은 message listener container(DefaultMessageListener or SimpleMessageListenerContainer)를 제공 한다. 이것은 MDP를 만들기 위해서 사용된다. 첫 눈에 보이듯이 MDP는 Java EE 명세에서 보이는 message-driven bean과 유사함을 알 수 있다. 하지만 스프링 MDP는 MDB 보다 조금 더 유연성을 제공한다. non-message-aware POJO를 통해 Spring MDP를 생성 할 수 있다. 반면에 MDS는 EJB 3 명세를 준수해서 만들어야 한다. 즉 오브젝트는 반드시 javax.jms.MessageListener interface를 구현해야 하고 onMessage메서드를 override 해야 하고 
destination type(e.g., javax.jms.Queue or javax.jms.Topic)을 포함 하는 @MessagDrieven 어노테이션이나 XML을 제공 해야 합니다. 그리고 JNDI destiname도 포함하여야 합니다.

JmsTemplate 처럼 message listener container는 JndiTemplate, JndiObjectFactoryBean, JndiDestinationResolver와 CachingConnectionFactory 사이에 JMS provider와 연결을 위해 사용되고 비동기 listener를 시작하는데 사용합니다. message-driven bean과는 달리 스프링은 MDP를 만들기 위한 3가지 다른 방법을 제공 합니다. 아래 그림은 비동기 메시지를 받기위한 오브젝트들의 관계를 표현합니다.


JmsTemplate 개요

JmsTemplate은 동기적으로 메세지를 주고 받는데 기본오브젝트이다(메시지를 받기위해 대기하는 동안 블록킹함). JMS 1.1(JmsTemplate) 과 JMS 1.0.2(JmsTemplate102) 버전이 있다. 대부분의 JMS 프로바이더와 JMS EE 서버들은 JMS 1.1을 지원 하므로 JMS1.1을 사용할 것이다. JmsTemplate 를 사용하면 개발에 들어가는 노력과 시간을 상당부분 줄여준다. JmsTemplate를 사용할 때 JMS 프로바이더와 연결할때, JMS session(Queue Session)을 만들때나 message producer(Queue Sender)를 만들거나 또는 JMS message를 만드는데 대한 걱정을 할 필요가 없다. JmsTemplate는 자동적으로 String 오브젝트, Byte[] Object, Java Object 나 java.util.Map 에 해당하는 JMS 메시지 오브젝트로 변환해준다. 또한 자신의 메시지 컨버터를 제공하여 복잡한 메시지나 기본 메시지 컨버터가 지원 하지 않는 것을 해결 할 수 있다. 
아래 코드는 스프링을 사용하여 간단한 텍스트 메시지를 전송하는 예제이다.

public class SimpleJMSSender {

public static void main(String[] args) {

try {

ApplicationContext ctx =

new ClassPathXmlApplicationContext("app-context.xml");

JmsTemplate jmsTemplate =

(JmsTemplate)ctx.getBean("jmsTemplate");

jmsTemplate.convertAndSend("This is easy!");

}

...

}

}




대충 생략

메시지 드리븐 POJOs(Message-Driven POJOs)
메시지를 비 동기 적으로 받는 다는 것은 특정 큐나 토픽에 대해 응답 개기중에 논 블록킹 프로세스를 가지고 있다는 것이다. 이 기술은 이벤트 드리븐 형태의 처리 방식이다. 메세지 리스너에서 메시지가 존재하는것을 알려준다. Message-driven beans는 비동기 리스너를 위한 Java EE 기술이다. 스프링 프레인 워크는 MDP를 사용한 비동기 리스너 역시 지원 한다.
스프링에서 비동기 메시지 리스너를 구성하기위한 3가지 방법이 있다. javax.jms.MessageListener interface, implementing Spring’s SessionAwareMessageListener를 구현 하거나 POJO 클래스를 Spring MessageListenerAdapter 클래스로 랩핑 하면 된다. 이 3가지 방법은 메시지 리스너 클래스가 어떻게 구성되어 있느야에 따라 다양하다. 아래 설명에서는 메시지 리스너 컨테이너에 대해 상세한 설명과 함께 3가지 message driven bean 기술을 배우게 된다.

The Spring Message Listener Container
Message-driven POJOs 는 메시지 리스너 컨테이너의 context에서 생성 된다. 메시지 리스너 컨테이너는 connection factory, JMS destination, JNDI destination resolver 와 message listener bean을 바인딩 한다. 스프링은 두가지 타입의 메시지 리스너컨테이너를 제공한다(DefaultMessageListenerContainer 와 SimpleMessageListenerContainer). 이러한 메시지 리스너 컨테이너 들은 비동기 리스너 스레드를 명시해 주어야 한다. 
DefaultMessageListenerContainer 만이 실행시에 동적으로 리스너 숫자를 조절 할 수 있다. 추가로 DefaultMessageListenerContainer 는 XA 트랜잭션과의 통합을 지원 하는 반면에 SimpleMessageListenerContainer는 지원하지 않는다.  로컬 트랜잭션 메니저를 사용 하고 쓰레드 및 세션, 부하조건 변화에 따라 연결 조정을 사용 하는 간단한 애플리케이션의 경우 SimpleMessageListenerContainer를 사용하면 된다. 외부 트랜잭션 매니저나 XA 트랜잭션을 사용하고, 튜닝이 필요한 메시지 애플리케이션의 경우는 DefaultMessageListenerContainer를 사용하면 된다. 

MDP Option1 : MessageListener 인터페이스 사용하기
message-driven POJO의 가장 간편한 형태는 javax.jms.MessageListener 인터페이스를 구현한 비동기 receiver를 사용하는 것이다. 이것은 EJB3 의 message0driven bean 과 비슷하다. DefaultMessageListenerContainer는 CachingConnectionFactory 와 JNDIDestinationResolver에 주입 된다.



MessageListener 인터페이스를 구현할 때, message listener 클래스에 onMessage를 반드시 오버라이드 하여야 한다. 이 방법을 사용할때에 XML 설정에서 바꿔야할 것은 없다. 아래 코드 예제를 보면, javax.jms.MessageListener를 구현하고 onMessage 메서드를 오버라이드하고 TextMessage를 받는 SimpleJMSReceiver 메시지 리스너이다.

public class SimpleJMSReceiver implements MessageListener {
public void onMessage(Message message) {
try {
if (message instanceof TextMessage) {
System.out.println(((TextMessage)message).getText());
} else {
throw new IllegalStateException("Message Type Not Supported");
}
} catch (JMSException e) {
e.printStackTrace();
}
}
}

MDP Option 2: SessionAwareMessageListener 인터페이스 사용하기

스프링 프레임 워크는 javax.jms.MessageListener를 확장한 SessionAwareMessageListener를 제공한다. javax.jms.MessageListener 처럼,
SessionAwareMessageListener는 listener클래스에서 onMessage  메서드를 오버라이드 해야 한다. 그러나 javax.jms.MessageListener는 다르게 Message 오브젝트에 추가되는게 있다. SessionAwareMessageListener는 JMS Session에 접근할수 있도록 해준다.

void onMessage(Message message, Session session) throws JMSException

아래 그림은
SessionAwareMessageListener의 사용방법이다. 설정방식의 관점에서 보면 이것은 javax.jms.Mes
sageListener를 사용할 때와 같음을 알 수 있다.


만약 비동기 메시지 리스너에서 JMS Session 오브젝트에 접근하여야 할 경우 SessionAwareMessageListener는 유용하게 사용된다. 이 방식의 일방적인 사용 방법중에 하나는 응답 메시지를 보내는 쪽에 전달해야 할 경우 이다. 다른 사용 방식은 Session에 트랜잭션을 적용할 경우 이다. 아래의 간단한 예제에 대하여 생각해보자. SimpleJMSReceiver 클래스가 보낸쪽에 메시지들 돌려주고 JMSReplyTo 헤더 속성에서 이 메시지를 처리 했을을 나타 낸다.

public class SimpleJMSReceiver implements SessionAwareMessageListener {
public void onMessage(Message message, Session session) throws JMSException {
if (message instanceof TextMessage) {
String text = ((TextMessage)message).getText();
System.out.println(text);
//send the response
MessageProducer sender =
session.createProducer(message.getJMSReplyTo());
TextMessage msg = session.createTextMessage();
msg.setJMSCorrelationID(message.getJMSMessageID());
msg.setText("Message " + message.getJMSMessageID() + " received");
sender.send(msg);
} else {
throw new IllegalStateException("Message type not supported");
}
}
}

메시지를 보낸 후에 Session 오브젝트를 스스로 clean up 할 필요가 없음을 기억하자. 스프링이 JmsTemplate에서 다 처리해 준다. 또한 SessionAwareMessageListener의 onMessage 리스너는 JMSException을 던진다.(javax.jms.MessageListener에서 하지 않는)

MDP Option 3 : MessageListenerAdapter 사용하기.
비동기 메시지 리스너를 만드는 3번째 방법은 스프링 MessageListenerAdapter 오브젝트로 POJO 오브젝트를 덮어 씌우는 방법이다. 이 방법이 다른 두가지 방법과 다른 이유는 POJO receiver 클래스가 어떤 message listener인터페이스도 구현하지 않아도 되고, javax.jms.Message 오브젝트의 어떤것도 포함하지 않아도 되서 이다. 아래 그림을 보면 POJO receiver 클랫는 스프링 MessageListenerAdapter에 주입 된다.
MessageListenerAdapter에를 사용해서 POJO receiver의 메서드를 구성하는 방법이 몇가지 있다. 기본 메시지를 핸들링 하는 메서드를 MessageListenerAdapter를 통해 사용하거나 별도의 메서드를 리스너 클래스에서 지정해서 리스너 메서드로 사용할 수 있다. 후자를 사용하는 경우, 자바 객체 타입의 메시지를 변환하는 컨버터를 사용하던지 직접 JMS message 객체를 사용하는 방식을 사용할 수 있다. 이 두가지 예제는 아래 섹션에서 살펴보도록 하자.


Default message handler method
기본적으로, MessageListenerAdaptor는 handlmessage 메서드를 JMS 메시지가 수신되고 이에 상응하는 
POJO에서 찾습니다.
아래의 목록은 자동 메시지 변환을 사용하는  handleMessage 메서드 목록이다.

//receive a converted TextMessage
public void handleMessage(String message) {...}
//receive a converted BytesMessage
public void handleMessage(byte[ ] message) {...}
//receive a converted MapMessage
public void handleMessage(Map message) {...}
//receive a converted ObjectMessage
public void handleMessage(Object message) {...}

기본 message listner handler method를 사용하기 위해 message-driven POJO(eg, SimpleJMSReceiver)를 MessageListenerAdapter 빈에다가 생성자 속성을 통해 주입해 주어야 한다.(아니면 변수의 프로퍼티를 통해)
<bean id="messageListener" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
<constructor-arg>
<bean class="SimpleJMSReceiver"/>
</constructor-arg>
</bean>
<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="queueConnectionFactory"/>
<property name="destinationResolver" ref="destinationResolver"/>
<property name="concurrentConsumers" value="3" />
<property name="destinationName" value="queue1"/>
<property name="messageListener" ref="messageListener" />
</bean>

message-driven POJO를 정의 할 때 간단하게 사용하고자 하는 JMS message 타입에 따라 handlerMessage 메서드를 정의 할 수 있습니다. 예를 들어 아래 코드는 JMS TextMessage를 받는다.

public class SimpleJMSReceiver {
public void handleMessage(String message) {
System.out.println(message);
}
}

SimpleJMSReceiver 클래스는 JMS API에 대한 어떠한 참조도 가지고 있지 않다는 것을 확인하자. 실제로, 이 예제 안에서 message-driven POJO는 심지어 컨텍스트 안에서 메시징이 사용되는지에 대해서 알고 있는것이 아무것도 없다. 모든 메시징 인프라 스트럭쳐는 MessageListenerAdapter 나 DefaultMessageListenerContainer를 통해 전달하도록 되어 있다. 당신이 할 일은 JMS 메시지가 수신되고 이 유형에 따라 필요한 POJO handleMessage 메서드를 작성 해야 한다.

만약 수신되는 JMS 메시지 타입이 확실하지 않거나 TextMessage 나 MapMessage 중에 하나를 받을 가능성이 있다면 어떻게 해야 할까? 이전 예제에서 처럼 JMS Message Object의 instance를 확인 하거나
Message Type에 따라 즉시 처리할수 있다.

public class SimpleJMSReceiver implements MessageListener {
public void onMessage(Message message) {
try {
if (message instanceof TextMessage) {
//process message text...
} else if (message instanceof MapMessage) {
//process map message...
} else {
throw new IllegalStateException("Message Type Not Supported");
}
} catch (JMSException e) {
e.printStackTrace();
}
}
}


그러나 default handleMessage 메서드에서의 인수가 이미 받을 타입에 대해 "캐스팅" 되어 있을 경우가 있다. 스프링은 몇가지 방법으로 이것을 처리 한다. 특정 메시지 유형에 대한 default handleMessage 메서드가 정의 되지 않는 경우, MessageListenerAdapter는 NoSuchMethodException을 표시하고 JMS 메시징 타입을 찾을수 없게 된다.
따라서 handleMessage 메서드에서 각각 타입에 대해서 받기 원하는 파라미터를 명시 해야 한다. 예를 들어 TextMessage 나 MapMessage 메시지 타입을 인수로 받을 려면 HandleMessage aptjemrk String 이나 Map 파라미터를 받을 수 있도록 하면 된다.

public class SimpleJMSReceiver {
public void handleMessage(String message) {
//process String message body
}
public void handleMessage(Map message) {
//process Map message body
}
}

방금 설명된 메시지 변환 방식의 한가지 이슈는 message handler 메서드를 통해서만 메시지가 전달 되어야 한다는 점이다. 따라서 메시지 헤더 프로퍼티나 메시지 애플리케이션 프로퍼티에는 접근하거나 수정 할 수 없다. 예를 들어 발신자가 애플리케이션 프로퍼티 섹션에 추가적인 메타 정보를 기입한 후 전송하려고 하거나 메시지 헤더 프로퍼티의 JMSReplyTo, JMSMessageID 속성에 접근해야 할 경우 사용 할 수 없다. 이 예제들에서 메시지들을 자동으로 변환하는 것들에 대한 것을 DefaultMessagelistenerContainer에 알려줄 수 있다. MessageListenerAdapter 빈에  messageConverter 프로퍼티에다가 값을 null로 세팅함으로써 쉽게 사용할 수 있다. JMS 메시지 개체를 인수로 받는 방법보다 해당 자바 객체 유형을 받아서 처리 할 수 있다.

<bean id="messageListener"
class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
<constructor-arg>
<bean class="SimpleJMSReceiver"/>
</constructor-arg>
<property name="messageConverter"><null/></property>
</bean>

message conversion 기능을 중지 시키면 MessageListenerAdaptor는 기본적으로 아래의 handleMessage 메서드들 중에 하나를 찾는다.

//receive a JMS TextMessage
public void handleMessage(TextMessage message) {...}

//receive a JMS BytesMessage
public void handleMessage(BytesMessage message) {...}

//receive a JMS MapMessage
public void handleMessage(MapMessage message) {...}

//receive a JMS ObjectMessage
public void handleMessage(ObjectMessage message) {...}

//receive a JMS StreamMessage
public void handleMessage(StreamMessage message) {...}

이 방법은 JMS Message 오브젝트에 접근할 수 있는 방법을 제공해준다. 이 방법을 통해 헤더나 애플리케이션 정보를 뽑아내거나 수정 할 수 있다.

public class SimpleJMSReceiver {
public void handleMessage(TextMessage message) {
String text = message.getText();
String username = message.getStringProperty("username");
String msgId = message.getJMSMessageID();
//process text message
}
}

Custom message handler method

당연히 default handlerMessage 메소드들에  POJO 메시지 리서너를 제한할 필요가 없다. 사실 POJO 메시지 리스너들 중에  JMS Message Type이나 메시지 컨버젼 오브젝트(String, byte[], Map, or Object)둘중에 한개의 파라미터를 포함한
아무 메서드나 listener handler 메서드가 될수 있다. 자신만의 메서드를 message handler 처럼 사용할려면 반드시 MessageListenerAdapter의 defaultListenerMethod 프로퍼티에 메시지 핸들러로 사용하기 위한 메서드명을 적어 줘야 한다.
또한 MessageLIstenerAdapter는 메시지 본문을 변환하거나 message handelr message에 JMS Message type을 처리 하기 위한 것을 명시해 줘야 한다. 예를 들어,
TradeOrderManager 클래스에서 XML 거래 주문을 포함한 String 오브젝트를 처리하기 위한 createTradeOrder 메서드를MessageListenerAdapter에 구성해야 할 경우, defaultListenerMothod 프로퍼티에 createTradeOrder를 명시해 주고 SimpleMessageConverter를 사용하면 된다.

<bean id="messageListener"
class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
<constructor-arg>
<bean class="TradeOrderManager"/>
</constructor-arg>
<property name="defaultListenerMethod" value="createTradeOrder"/>
</bean>

POJO 메시지 리스너에서 String Object 파라미터를 받는 createTradeOrder메서드는 아래와 같다.

public class TradeOrderManager {
public void createTradeOrder(String xml) {
//process trade order xml message
...
}
...
}

그냥 표시된 코드를 공부할 경우, 이 POJO가 메시징과 관련된 것이 하나도 없다는 것을 볼 수 있다. 이것은 message-driven POJOs 의 예제이다. 메시징과 커뮤니케이션 로직을 POJO로 부터 분리함으로써 코드를 추상화 할 수 있고 POJO가 메시징 인프라 구조 로직 보다 비즈니스 로직에 촛점을 맞출 수 있다. 이 클래스는 메시징 컨텍스트의 안이나 밖에서 사용될 수 있고, 메시징 프레임워크 바깥에서 테스트할 수 있다.

이 내용은 대부분 Java Message Service 책에서 가져온 것이고, 위키디피아 및 구글 검색을 통해 얻어진 것입니다.
허접하니 그냥 참조만...

이번에 설명 할 내용
1. What is Message ? - Message란 무엇이고 왜 사용 하는가
2. JMS Architecture - JMS의 전반적인 아키텍쳐와 사용 모델
3. JMS Element - JMS에서 사용하는 엘리먼트들에 대한 설명

그럼 대충 시작.

1. What is Message?
메시지은 느슨하게 결합된 분산 통신의 한 형태이다. 여기서 통신이란 용어는 소프트웨어 컴포넌트들 간의 구성요소들 사이에 메시지의 교환으로 이해할 수 있다. 메시지 지향 테크놀로지들은 강하게 결합된 통신(TCP network socekst, CORBA or RMI 같은)을 중간요소의 도입을 통해 완하하려고 시도한다.  후자의 접근 방식을 통해 소프트웨어 컴포넌트가 서로 '간접적으로' 통신 할 수 있다. 이러한 메시지의 장점은 보내는 쪽에서 받는 쪽에 대한 정확한 정보가 없어도 가능 하다는 점이다.

메시징의 장점은 이기종 플랫폼 간의 통합과 시스템의 병목현상을 감소 시키고, 확장성을 증가 시키며 변화에 신속하게 대응 할 수 있다.


안좋은 예


간단한 메시징 시스템의 예로 콜센터를 들 수 있다.

고객과 콜센터 직원이 라인을 통해 다이렉트로 통화를 할 경우, 고객이 늘어날 경우에 콜센터 직원이 통화가 가능 할 때까지 계속 다시 전화를 걸어서 통화가 가능한 라인을 찾아야 한다.


좋은 예


위 그림처럼 중간에 경유할 수 있는 Voice Mail Box 같은 장치를 두면, 고객의 요청은 box에 쌓이게 되고 직원은 이것을 확인하고 처리 하면 되기 때문에 고객은 계속 전화를 걸어 통화가 가능한 라인이 있는지 확인할 필요 없이 처리 할 수 있다.


2. JMS Architecture

비즈니스 시스템에 사용되는 응용프로그램들간의 메시징 시스템은 엔터프라이즈 메시징 시스템이나 메시지 지향 미들웨어(Message-Oriented Middleware, MOM)이라고 한다.

대부분의 기업들이 메시징 시스템의 데이터 교환을 destinations라 부르는 가상 채널을 사용한다. 메시지가 전송되고 나면 특정 응용프로그램이 아니라 destination으로(queue 나 topic)에 저장 된다. 아무 응용프로그램이 관심이 있는 destinatin에 등록하여 메시지를 받아 볼 수 있다. 이러한 바법을 통해 메시지를 주고 받는 응용프로그램간의 관계가 느슨하게 유지된다. 메시지를 보내는 쪽이나 받는 쪽이나 어떤식으로든 서로 얽매이게 되지 않고 메시지를 주고 받을 수 있다.


destination


JMS는 응용프로그램 개발자가 같은 API로 다른 시스템들에 사용할 수 있는 JDBC와 유사 하다. 만약 JMS를 준수하여 서비스를 할 경우, JMS API를 통해 공급자가 해당 업체에 메시지를 주고 받을 수 있다. 예를 들어 SonicMQ를 사용하여 메시지를 보내고 IBM WebSphere MQ를 통해 메시지를 처리 할 수 있다.


메시징의 장점
1. 이기종 시스템간의 통합
- 이기종 플랫폼간의 커뮤니케이션과 통합은 메시징의 오래된 사용 방법이다. 메시징을 사용하여 애플리케이션의 서비스를 호출할 수 있고 완전히 다른 플랫폼에서 구현하는 시스템으로 사용할 수 있다. 많은 오픈 소스 및 상용 메시지 시스템은 자바 사이의 매끄러운 연결을 제공하고 다른 언어 및 다른 플랫폼에도 제공한다.
역사적으로 이기종 시스템간의 통합 작업은 많은 방법이 존재 하였다. 정보를 공유하는 데이터베이스를 사용하여 두 이기종 시스템 또는 응용프로그램간에 접근하는 방식이 아직까지 널리 사용되고 있는 방법이다. Remote Procedure CALL은 이종 분산 시스템간의 데이터 및 기능 공유의 또다른 방법이다. 이러한 해결책은 각각 장점과 단점을 가지고 있다. 메시징을 사용함으로써 데이터 및 기능을 애플리케이션이나 서브 시스템을 통해 공유함으로서 결합도를 낮추어 사용한다. 최근 웹 서비스가 또 다른 이기종 시스템 통합을 위한 솔루션으로 등장하였지만 아직 신뢰성이 부족하므로 메시징이 더 좋은 선택이다.(최근에는 웹 서비스도 많이 늘어 나는 추세)

2. 시스템 병목 현상을 감소
- 일반적으로 작업을 처리 하기 위해 많은 수의 프로세스를 만들고 해당 프로세스의 요청 속도를 유지 하기 위해 시스템과 애플리케이션의 병목현상이 발생한다. 가장 많이 볼 수 있는 예로는 튜닝 수준이 낮은 데이터베이스에 연결될 때 까지 기다리는 애플리케이션과 프로세스를 들 수 있다. 이러한 시점에서 응답시간은 악화되고 타이밍이 어긋나게 된다.
한정된 시간내에 처리할 수 있는 용량은 제한되어 있으므로, 이를 처리하기 위해서는 많은 시간이 추가된다.

3. 확장성을 향상
- 병목현상을 감소하는것과 같은 방법으로, 시스템의 전반적인 확장성 및 처리량을 증가 시킬 수 있다.

4. 최종 사용자의 생산성을 높임
- 비동기 메시징을 사용함으로서 생산성을 향상 시킬 수 있다. 동기적으로 처리할 경우 요청을 보내고 처리를 기다리는 동안 많은 시간이 소비 되는데, 비동기 메시징을 사용함으로써 이 시간을 감소 시키고 유용하게 사용 할 수 있다.

5. 아키텍쳐의 유연성 및 민첩성
- 아키텍쳐의 민첩성은 끊임없이 변화하는 환경에 신속하게 대응하는 능력이다. 추상화되고 연결성이 낮은 컴포넌트들에 메시징을 사용하게 되면 소프트웨어, 하드웨어, 비즈니스로직의 변경에 까지 빠른시간에 대응할 수 있다. 메시징을 통해 메시지 공급자나 클라이언트 컴포넌트는 프로그래밍 언어나 플랫폼에 종속족이지 않기 때문에 유연하고 민첩한 대응을 할 수 있다.

2.1 메시징 모델
JMS는 두가지 타입의 메시징 모델을 지원한다.
Point-to-point, publish-and-subscribe
이 메시징 모델은 메시징 도메인이라고 불리기도 한다. 줄여서 p2p와 pub/sub 각각 불리운다.
가장 간단한 의미로 pub/sub는 one-to-many 브로드 캐스트 메시징을 위한거고 p2p는 one-to-one 딜리버리 메시징이다.


JMS 관전에서 메시징 클라이언트들은 JMS 클라이언트라고 부르고 메시징 시스템은 JMS 프로바이더로 불리운다. JMS 애플리케이션은 많은 수의 JMS 클라이언트와 일반적으로 하나의 JMS 프로바이더로 구성된다.
추라고, 메시지를 제공하는 JMS 클라이언트는 message producer라고 하고, 메시지를 받는 쪽을 message consumer 라고 부른다. JMS 클라이언트가 각각 consumer나 producer가 될 수 있다. consumer 와 producer라는 용어를 사용할 때 JMS 클라이언트가 각각 메시지를 consume 하거나 produce하는걸 의미 한다.

2.1.1 Point-to-Point
- p2p 메시징 모델은 JMS 클라이언트가 메시지를 동기 및 비동기 방식으로 queue라고 불리우는 가상 채널을 경유하여 전송하거나 받을 수 있다. p2p모델에서 message producer는 sender, message consumer 는 receiver로 불리운다. p2p 메시징 모델은 Pull 기반이거나 클라이언트에게 자동으로 push되는 Polling 기반 모델이다. p2p 메시징 모델의 구분되는 특징중에 하나는 메시지는 queue에 전송되고 다른 수 많은 receiver가 같은 메시지를 받기위해 대기하고 있어도, 하나의 receiver에 하나씩 전송 된다는 점이다.
Point-to-point 메시징은 "발생하고 잊어 버리는" 비동기 메시징을 지원하는것 뿐만 아니라 동기정 요청/응답 메시징을 지원한다. Point-to-point 메시징은 publish-and-subscribe 모델보다 좀 더 결합할려는 경향이 있다. 보내는 쪽에서 메시지가 어떻게 사용되기 위해서 전송되는지 알고 누가 메시지를 받는지 알기 때문이다. 예를 들어, 전송하는 쪽에서 주식 거래를 하기 위해 큐에 내용을 전송하고 거래 확인 번호를 포함한 내용을 기다릴 때 이다. 이 예제에서 메시지를 보내는쪽은 메시지를 받는쪽이 거래요청을 처리할 것이라는 것을 알고있다. 다른 예는 시간이 오래걸리는 레포트 생성을 비동기적으로 요청할 때다. 보내는 쪽에서 레포트를 위한 요청을 만들고 레포트가 준비 되면 알림 메시지가 보내는쪽에게 전송된다. 이 예에서 센더는 보내는 쪽은 받는쪽에서 메시지를 보고 레포트를 생성하러 갈 것이라는 것을 알고있다.
Point-to-Point 모델을 로드밸런싱을 지원한다. 다중 리시버가 같은 큐에서 수신 대기 하고 있고 따라서 부하가 분산된다. 그림 1-4처럼 JMS provider는 메시징을 큐에서 관리한다. 각각 메시지가 한번에 한개씩 리시버 그룹에서 처리가 가능한 리시버가 처리하는것을 보장한다. JMS 스펙은 분산 메시징 처리를 강요하지 않는다. 비록 몇몇 JMS 벤더들이 이 로드밸런시을 구현하고 있지만. Point-to-point은 메시지가 읽혀지기 전에 대기열의 내용을 볼 수 있는 대기열 브라우저 같은 다른 기능을 제공 한다. 이 브라우저 컨셉은 pubish-and-subscribe 모델에서는 지원되지 않는다.

2.2.2 Publish-and-Subscribe
pub/sub 모델에서 메시지는 topic이라고 불리는 가상 채널을 통해 공급된다. Message producers 는 publishers, message consumers는 subscribers라고 불리운다. p2p모델과는 달리 topic을 통해 공급되는 메시지는 여러 subscribe에게 전달 될 수 있다. 이 방법은 가끔지 메시지를 브로드캐스팅하기위해 추천하는 방법이다. 모든 subscriber는 복제된 같은 메시지를 받는다. pub/sub 메시징 모델은 대형 푸쉬 기반 모델이다. 메시지는 consumer들에게 새로운 메시지 확인을 위해 요청및 poll을 할 필요 없이 자동적으로 broadcast 되는 모델이다.
pub/sub 모델은 p2p모델 보다는 결합도가 낮다. publisher는 많은 subscribers들이 있고, 이들이 메시지를 가지고 무엇을 할지 알지 못한다. 


3. JMS Element
기본적인 아키텍쳐 설명은 이정도 까지만 하고, 자주 사용되는 용어에 대해서 정리해보자.
이 내용은 http://en.wikipedia.org/wiki/Java_Message_Service에 잘 정리 되어 있다.

  • JMS provider : Message Oriented Middledware(MOM)을 위한 JMS 구현체. Java JMS의 구현체 이거나 이기종 MOM의 아답터 역할도 한다.
  • JMS client : 메시지를 전송하거나 받는 애플리케이션
  • JMS producer/publisher : 메시지를 만들고 전송하는 클라이언트
  • JMS consumer/subscriber : 메시지를 받는 클라이언트
  • JMS message : JMS 클라이언트 사이에 전송되는 오브젝트
  • JMS queue : 메시지를 전거나 메시지를 읽기 위해서 메시지가 대기중인 영역. 메시지의 전송 순서는 보장 하지 않고, 오직 한번 읽는 다는 것만 보장한다.
  • JMS topic : 다양한 subscriber들에게 메시지를 전송하기 위한 방법.


비밀번호 조합에서, 꼭 영문/숫자를 혼용하도록 해야 하는 요구사항이 있었다.
정규식으로 어찌저찌 해보고 @Pattern 을 사용해서 해볼려고 했지만.. 정규식으로는 안되는것 같았다.

하여, 그냥 만들어 보기로 결정.
JSR 303 구현체중 하나인 Hibernate Validator 를 사용 하는 중인데.
http://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#validator-customconstraints
여길 보면 자세히 설명이 되어 있다.

위 것을 참조 하여 일단 custom anontation을 만들어 보면, 아래와 같은 코드가 탄생..

@ConstraintComposition(CompositionType.AND)
@Target({ METHOD, FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = AlphabetAndNumberValidator.class)
public @interface AlphabetAndNumber {
     public abstract String message() default "{AlphabetAndNumber.message}";
     public abstract Class<?>[] groups() default { };
     public abstract Class<? extends Payload>[] payload() default { };
}

여기서 @Constraint 쪽에 제약 조건을 체크하는 클래스를 지정해 주어야 한다.

하여 AlphabetAndNumberValidator를 살펴 보면, 아래와 같은데.
isValid에서 제약 조건을 체크 하는 것을 볼 수 있다.
이 경우엔 영문을 가지고있는지, 숫자를 가지고 있는지 확인하고 이를 둘 다 가지고 있어야 통과 하도록.

public class AlphabetAndNumberValidator implements ConstraintValidator<AlphabetAndNumber, String> {

    @Override
    public void initialize(AlphabetAndNumber constraintAnnotation) {
    }
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if ( value == null) {
            return false;
        }
        Matcher matcher = Pattern.compile("[a-zA-Z]+").matcher(value);
        boolean isExistAlphabet = matcher.find();
        matcher = Pattern.compile("[0-9]+").matcher(value);
        boolean isExistNumber = matcher.find();
       
        if ( isExistAlphabet && isExistNumber) {
            return true;
        } else {
            return false;
        }
    }
}

이렇게 구현 한 후에 도메인 클래스나 기타 method 및 field에 @AlphabetAndNumber 를 붙여주고 valid 체크를 하게 되면 적용 된다.


아래 블로그에도 잘 정리 되어 있는데, 여기서 만든 custome validator는 Spring bean으로도 등록이 된다고 하니 다른 빈들을 참조 해서 좀 더 복잡한 제약 조건을 걸도록 만들수도 있다.
http://chanwook.tistory.com/855

하이버네이트 페이지에 설명이 상당히 잘 되어 있기 때문에, 아래 url 참고 해서 test 케이스 작성도 잊지 말도록 하자. :)
http://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#Using%20the%20constraint



리소스 지향 아키텍쳐(Resource Oriented Architecture, ROA) 란?
REST한 아키텍쳐를 구현하기 위한 한가지 방법.
http://en.wikipedia.org/wiki/Resource-oriented_architecture 

헐.. 위키 주소가 정말 RESTFul 한 URI 구나. !!

아무튼..

1. 웹 서비스에서 제공할 데이터를 특정한다. 
2. 데이터를 리소스로 나눈다.
3. 리소스에 URI로 이름을 부여한다.
4. 클라이언트에 제공할 리소스의 표현을 설계한다.
5. 링크와 폼을 이용해 리소스와 리소스를 연결한다.
6. 이벤트의 표준적인 코스를 검토한다.
7. 에러에 대해 검토한다.



 from. 웹을 지탱하는 기술.

 

JMS with spring - 1

Java2011. 6. 17. 17:47

1. JMS
수년간 시스템들은 복잡도가 올라가고 더 세련되어져 갔습니다. 시스템들은 과거의 시스템들에 비해 믿을 수 있고, 확장성이 증가하고, 더 많은 유연성이 필요하게 되었습니다. 이러한 요구들의 증가에 대응하여 이 빠른 시스템, 아키텍쳐, 디자이너 및 개발자들은 이들 복잡성을 해결하는 한가지 방법으로 메시징을 활용했습니다.

메시징이 사용되고 나서  Java Message Service(JMS) API 는 1999년 도입 이후 크게 변경되지 않았습니다. 메시징은 유연성과 확장성 문제의 해결을 위해 폭넓게 사용되었지만 많은 비지니스적 문제와 다른 애플리케이션의 문제에서도 사용 되었습니다.

 이기종간의 통합은 메시징이 중요한 역할을 하는 기본 영역중 하나 입니다. 

또한 메시징은 비동기 요청을 처리할 수 있는 기능 및 아키텍쳐 와 개발자들이 시스템 병목 현상을 감소 또는 제거를 위한 솔루션을 제공하고, 최종 사용자의 생산성 및 전반적인 시스템 확장성을 제공 합니다. 이기종 시스템 간에 높은 수준의 약한 결합 및 메시징을 활용하는 시스템에 높은 수준의 구조적인 유연성과 민첩성을 제공 합니다.
 
비즈니스 시스템에 사용되는 애플리케이션간의 메시징 시스템은 엔터프라이즈 메시징 시스템이나 메시지 지향 미들웨어(Message-Oriented Middleware, MOM)이라고 합니다.  

엔터프라이즈 메시징 업체들은 서로 다른 메시지 포맷과 네트워크 프로토콜을 사용하여 메시지를 교환하지만, 기본적인 의미는 동일합니다. API는 새 메시지 생성, 애플리케이션데이터로딩, 라우팅 정보 지정과 메시지를 보냅니다. 이 동일한 API를 사용하여 다른 애플리케이션에서 만든 메시지를 수신합니다. 

대부분 기업들이 메시징 시스템, 응용 프로그램 교환을 destinations라 부르는 가상 채널을 사용합니다. 메시지가 전송되면, 그것은 특정 애플리케이션이 아닌 대상 destination으로(queue or topic) 기록 됩니다. 아무 애플리케이션이나 관심있는 destination에 등록을 하여 메시지를 받아 볼 수 있습니다. 이러한 방법으로 메시지를 주고 받는 애플리케이션의 약한 관계가 성립됩니다. 메시지를 보내는 쪽이나 받는 쪽이나 어떤식으로든 서로 얽매이지 되지 않고 메세지를 주고 받을 수 있습니다.

JMS는 애플리케이션 개발자가 같은 API로 다른 시스템들에 사용할 수 있는 JDBC와 유사하다. 만약 JMS를 준수하여 서비스를 할 경우, JMS API를 통해 공급자가 해당 업체에 메시지를 주고 받을 수 있다. 예를 들어 SonicMQ를 사용하여 메시지를 보내고 IBM WebSphere MQ를 통해 메시지를 처리 할 수 있다. 

2. The advantages of Messaging
이기종 시스템간의 통합
이기종 플랫폼의 커뮤니케이션과 통합은 아마도 메시징을 위한 오래된 유스케이스입니다. 메시징을 사용하여 애플리케이션의 서비스를 호출할 수 있고 완전히 다른 플랫폼에서 구현하는 시스템으로 사용할 수 있습니다. 많은 오픈 소스 및 상용 메시징 시스템은 자바 사이의 매끄러운 연결을 제공하고 다른 언어 및 다른 플랫폼에도 제공 합니다. 예를 들어 ActiveMQ(open source) 와 IBM WebSphere MQ(commercial)이 있습니다. 이 두 메시징 시스템은 JMS를 지원 하고 또한 자바 메시징 클라이언트가 사용하는 기본 API를 지원 합니다(C, C++). 벤더에 의존하지 않고 non-java 시스템과 non-jms 메시징 클라이언트를 사용할 수 있습니다.
역사적으로, 이기종 시스템간의 통합 작업은 많은 방법이 존재 하였습니다. 정보를 공유하는 데이터베이스를 사용하여 두 이기종 시스템 또는 응용 프로그램간에 접근하는 방식이 아직 까지 널리 사용되고 있는 방법입니다. Remote Procedure Call, or RPC 는 이종 분산 시스템간의 데이터 및 기능 공유의 또 다른 방법입니다. 이러한 해결책은 각각 장점과 단점을 가지고 있고 메시징은 데이터 및 기능을 애플리케이션이나 서브 시스템을 통해 굥유할수 있는 진정한 결합도가 낮은 방법입니다. 최근 웹 서비스가 또 다른 이기종 시스템 통합을 위한 솔루션으로 등장했습니다. 그러나 웹서비스는 아직 신뢰성이 부족하므로 메시징이 더 좋은 선택 입니다.

시스템 병목 현상을 감소
많은 수의 프로세스를 만들고 해당 프로세스의 요청 속도를 유지 하기 위해 시스템과 애플리케이션의 병목현상이 발생 합니다. 고전적인 시스템 병목현상의 예는 저조한 튜딩이된 데이터 베이스에 연결될 때 까지 기다리는 애플리케이션과 프로세스를 들 수 있습니다. 시스템이 백업되는 시점에서 응답시간은 악화되고 결국 타이밍이 어긋 납니다.
시스템 병목 현상의 좋은 비유는 깔때기에 물을 붓는 것입니다. 한정된 시간내에 처리 할 수 있는 용량은 제한되어 있으므로 이를 처리 하기 위해서는 많은 시간이 추가 됩니다. 하지만 깔때기에 입구를 추가 하게 되면 이것을 해결 할 수 있습니다. 이 예는 Point -to-Point 모델로 해결이 가능하고, 병목현상이 감소 또는 어떤 경우에는 완전히 제거 됩니다.

확장성을 향상
많은 메시징 시스템 병목 현상을 감소하는 것과 같은 방법으로, 시스템의 전반적인 확장성 및 처리량을 증가 시킬수 있을 뿐만 아니라 효과적으로 응답시간을 감소 시킬수 있습니다. 

최종 사용자의 생산성을 높임
비동기 메시징의 사용은 최종 사용자의 생산성 역시 향상시킬 수 있습니다. 

아키텍쳐의 유연성 및 민첩성
아키텍쳐의 민첩성은 끊임없이 변화하는 환경에 신속하게 대응하는 능력입니다. 추상화되고 연결성이 낮은 컴포넌트들에 메시징을 사용하게 되면 소프트웨어, 하드웨어, 비즈니스로직의 변경에 까지 빠른시간에 대응할 수 있습니다. 메시징을 통해 메시지 공급자나 클라이언트 콤포넌트는 프로그래밍 언어나 플랫폼에 종속적이지 않기 때문에 유연하고 민첩한 대응응 할 수 있습니다.

엔터프라이즈 메시징
엔터프라이즈 메시징은 새로은 컨셉이 아닙니다. 메시징은 IBM Web-Sphere MQ, SonicMQ, Microsoft Message Queuing(MSMQ), and TIBCO Rendezvous 등이 수년간 존재해 왔습니다. 최근에 오픈 소스인 ActiveMQ가 참여하였습니다. 
엔터프라이즈 메시징의 핵심 컨셉은 메시지가 비동기적으로 한시스템에서 네트워크를 통해 다른 시스템으로 전달 되는 구조이다. 메시지가 비동기적으로 전달 된다는 말은 수신하는 쪽에서 메시지를 받기위해 대기하거나 처리를 하기 위해서 대기할 필요가 없다는 뜻이다. 



client 라든 용어에 대해서 정의해 보면, 메시징 시스템은 메시징 클라이언트와 몇몇 종류의 메시징 미들웨어 서버로 구성되어 있다. 클라이언트들은 메시징을 메시징 서버로 보낸 다음 메시지들은 다른 클라이언트들에 배포 된다. 클라이언트 비즈니스 애플리케이션이나 컴포넌트들이 이를 위해 메시징 API를 사용한다(우리의 경우엔 JMS)

중앙 아키텍쳐
엔터프라이즈 메시징 시스템들은 중앙 아키텍처를 사용하는 메시지 서버에 의존하는 하고 사용한다. 메시지 서버는 메시지 라우터나 브로커로 블리우고 메시지들을 메시징 클라이언트로 부터 다른 메시징 클라이언트로 전달하는 책임을 가지고 있다. 메시징 서버는 메시지를 보내는 클라이언트와 받는 클라이언트들로부터 분리되어 있다. 클라이언트들은 오직 메시징 서버만 바라보고 다른 클라이언트들은 신경쓰지 않는다. 전체 시스템에 영향을 미치지 않고 클라이언트들을 추가 및 수정할 수 있습니다. 
요약하면, 중앙 아키텍쳐는 hub-and-spoke topology에 사용된다. 간단한 예를 들면, 중앙 메시지 서버가 있고 모든 클랑이언트들은 이곳에 연결한다. 아래의 그림처럼,
 hub-and-spoke 아키텍쳐는 네트워크의 최소한 연결을 허용하고 다른 파트의 시스템간 최소한의 커뮤니케이션을 허용합니다. 

실제로, 중앙 메시지서버는 분산서버의 클러스터 수의 논리 단위로 운영된다.

분산 아키텍쳐
모든 분산 아키텍쳐는 현재의 네트워크 레벨에서 IP 멀티 캐스트를 사용합니다. 메시징 시스템은 멀티캐스팅을 바탕으로 중앙서버가 없습니다. 몇몇 서버들은 기능적으로 클라이언트 내의 일부로 내장 되어 있는 동안 메시지 라우팅은 IP 멀티 캐스트 프로토콜을 사용하여 네트워크 계층에 위임합니다.
IP 멀티캐스트는 애플리케이션이 한개 이상의 IP 멀티캐스트 그룹에 참여 하도록 허락합니다. 각 그룹은 IP 네트워크 주소를 사용하고 모든 회원들은 어떤 메시지든 간에 그 그룹안에서 재배포를 받습니다. 이러한 방법으로, 애플리케이션은 IP 멀티케스트 주소로 메시지를 전송할 수 있고 네트워크 레이어에서 메시지가 적절하게 재배포되는것을 기대할 수 있습니다(Figure 1-3 참조)
중앙 아키텍처와는 달리, 분산 아키텍쳐는 메시지 라우팅을 목적으로한 서버를 필요로 하지 않는다.(네트워크가 자동적으로 라우팅을 해줌). 그러나 기능적으로 각 클라이언트에 포함되어 , 이러한 메시지의 영속성 및 메시지 배달의 의미는 once-and-only-once 배달과 같습니다.




메시징 모델
JMS는 두가지 타입의 메시징 모델을 지원한다: point-to-point 와 publish-and-subscribe.
이 메시지 모델은 때로는 메시징 도메인이라고 한다. Point-to-point 메시징과 publish-and-subscribe 메시징은 줄여서 p2p 와 pub/sub로 각각 불리웁니다. 
가장 간단한 의미로 publish-and-subscribe는 one-to-many 브로드 캐스트 메시징을 위한거고 point-to-point 는 one-to-one 딜리버리 메시징입니다.


JMS 관점에서 메시징 클라이언들은 JMS 클라이언트라고 부르고 메시징 시스템은 JMS 프로바이더로 불리웁니다. JMS 애플리케이션은 많은 수의 JMS 클라이언트와 일반적으로는 하나의 JMS 프로바이더로 구성됩니다.
추가로, 메시지를 제공하는 JMS 클라이언는 message consumer로 불리웁니다. JMS 클라이언트는 message producer 와 message consumer가 될 수 있습니다. consumer 와 producer라는 용어를 사용할 때 JMS 클라이언트가 각각 consumes messages 나 produces message 하는걸 의미 합니다.

Point-to-Point
Point-to-Point 메시징 모델은 JMS 클라이언트가 메시지를 동기 및 비동기 방식으로 queue라고 부르는 가상 채널을 경유하여 전송하거나 받을 수 있습니다. point-to-point 모델에서, message producers 는 sender(보내는사람), message consumers 는 receivers(받는사람)으로 불립니다.
 

http://stackoverflow.com/questions/1787557/apache-proxy-to-tomcat-keep-alive-confusion
 

keep-alive 옵션에 대한 설명.


There are (at least) four “keep-alive”s.

  1. HTTP layer keep-alive between client browser and Apache. (Enables multiple client requests in a HTTP/TCP connection. “KeepAlive” directive to configure.)

  2. TCP layer keep-alive between client browser and Apache. (To avoid connection to be closed by firewalls, send empty packet periodically (around 2 hours by default in Linux). I don't know how to configure in Apache.)

  3. AJP layer keep-alive between Apache and Tomcat. (Enables multiple Apache request in a AJP/TCP connection. “max” and “smax” options for “ProxyPass” to configure.)

  4. TCP layer keep-alive between Apache and Tomcat. (Same as 2, but for firewall between Apache and Tomcat. “keepalive” option for “ProxyPass” directive to configure.)

JMS with spring

Spring2011. 5. 16. 18:23
http://blog.springsource.com/2011/01/25/green-beans-getting-started-with-enterprise-messaging-and-spring/

오랜만에 스프링 블로그 보니깐 재미지네

activeMQ : http://activemq.apache.org/

Producer -> message -> Broker(ActiveMQ) -> message -> Consumer

사내 발표용으로 잠시 만들어 본 PPT



 Vi 명령어 : 
http://manpage.co.kr/senario/viedit.htm
 
ftp 명령어 : http://ttongfly.net/zbxe/?document_srl=45527
tar - GNU 버전 tar 저장 도우미

이 설명서는 tarfile 이라고 알려진 저장 파일을 묶거나 풀 수 있도록 만들어 진 GNU 버전 tar 저장 프로그램에 대한 설명이다. tarfile 은 테이프 드라이브에 저장할 수도 있고, tarfile 을 일반적인 보통 파일로 쓸 수도 있다. tar 의 첫번째 인수로는 반드시 Acdrtux 중 하나의 옵션이 들어가야 하고, 다른 선택적인 기능이 덧붙여진다. tar 의 마지막 인수로는 압축될 파일이나 디렉토리의 이름이 오게 된다. 디렉토리 이름이 사용될 경우 언제나 하위 디렉토리가 함께 저장된다.

가장 많이 사용하는 일반적인 옵션

[압축할 때] tar cvzf 파일명.tar.gz <디렉토리> 또는 파일
[압축 해제] tar xvzf 파일명.tar.gz

[예제]
tar -xvvf foo.tar : foo.tar 파일을 푼다.
tar -xvvzf foo.tar.gz : gzip으로 압축된 foo.tar.gz 파일을 푼다.
tar -cvvf foo.tar foo/ : foo 디렉토리에 있는 내용물을 foo.tar 파일로 묶는다.

기능 옵션
반드시 아래 옵션들 중 하나가 들어가야 한다.
-A, --catenate, --concatenate : 저장 파일에 tar 파일을 추가한다.
-c, --create : 새 저장 파일을 만든다.
-d, --diff, --compare : 저장 파일 혹은 파일 시스템 간의 다른 점을 찾는다.
--delete : 저장 파일에서 지운다. (자기 테이프에는 쓰면 안됨!)
-r, --append : 저장 파일의 끝에 파일을 덧붙인다.
-t, --list : 저장 파일의 내용 목록을 보여준다.
-u, --update : 저장 파일에 저장된 사본보다 새로운 파일만을 덧붙인다.
-x, --extract, --get : 저장된 것에서 풀어낸다.

부가적인 옵션
--atime-preserve : 덤프된 파일의 접근 시간을 바꾸지 않는다.
-b, --block-size N : 블럭 크기를 N x 512 바이트로 정한다. (기본값 N = 20)
-B, --read-full-blocks : 읽은 만큼 블럭을 재지정한다. (4.2BSD 파이프를 읽기 위함)
-C, --directory DIR : DIR 디렉토리로 바꾸고 작업을 한다.
--checkpoint : 저장 파일을 읽는 동안 디렉토리 이름을 출력한다.
-f, --file [HOSTNAME:]F : 저장 파일 혹은 장치 파일 F에 저장한다. (기본 "-", 표준입/출력을 나타낸다.)
--force-local : colon 문자가 있더라도 저장 파일을 지역 파일로 처리한다.
-F, --info-script F --new-volume-script F : run script at end of each tape (implies -M) 테이프의 끝에 도달하면 스크립트를 실행한다. (-M 이 포함된다.)
-G, --incremental : 이전 GNU 형식으로 incremental 백업을 만들거나 목록을 보거나 풀어낸다.
-g, --listed-incremental F : 새로운 GNU 형식으로 incremental 백업을 만들거나 목록을 보거나 풀어낸다.
-h, --dereference : 심볼릭 링크를 묶지 않는다. 그것이 가리키는 파일을 묶는다.
-i, --ignore-zeros : 크기가 0인 것은 무시한다. (보통 EOF를 의미한다.)
-j, --bzip2 : bzip2 필터를 사용하여 .bz2 파일을 푼다.
--ignore-failed-read : 읽을 수 없는 파일이 있더라도 종료 코드 0을 출력하지 않는다.
-k, --keep-old-files : 기존에 있는 파일을 유지한다. 파일이 있으면 덮어쓰지 않는다.
-K, --starting-file F : 저장 파일에 있는 파일 F에서부터 시작한다.
-l, --one-file-system : 저장 파일을 만들 때 로컬 파일 시스템 안의 놓는다.
-L, --tape-length N : N * 1024 바이트를 쓴 다음 테이프를 바꾼다.
-m, --modification-time : 파일의 변경 시간 정보를 유지하지 않는다.
-M, --multi-volume : 여러 개로 나눠진 저장 파일로 만들거나 목록을 보거나 풀어낸다.
-N, --after-date DATE, --newer DATE : 주어진 DATE 보다 새로운 파일만 저장한다.
-o, --old-archive, --portability : ANSI 형식 대신 V7 형식으로 저장한다.
-O, --to-stdout : 표준 출력으로 파일들을 풀어낸다.
-p, --same-permissions, --preserve-permissions : 모든 퍼미션 정보를 유지한다.
-P, --absolute-paths : 파일 이름의 맨 앞 `/' 문자를 버리지 않는다.
--preserve : -p 옵션과 -s 옵션을 함께 사용한 것과 같다.
-R, --record-number : 저장 파일의 레코드 번호를 각각의 메시지로 보여준다.
--remove-files : 파일을 저장 파일에 덧붙인 다음 파일을 지운다.
-s, --same-order, --preserve-order : 저장 파일 목록과 똑같은 순서로 풀어낸다.
--same-owner : 같은 사용자 소유권으로 파일들을 풀어낸다.
--numeric-owner : user/group 이름으로 항상 숫자를 사용한다.
-S, --sparse : 듬성한 파일을 효율적으로 다룬다.
-T, --files-from F : 파일 F에서 목록을 읽어 추출하거나 만든다.
--null : -T reads null-terminated names, disable -C -C를 비활성화하고, -T로 읽을 때 null로 끝나는 이름을 읽는다.
--totals : --create로 만들어진 바이트 총합을 출력한다.
-v, --verbose : 처리중인 파일을 자세하게 보여준다.
-V, --label NAME : 저장 파일의 볼륨 이름을 NAME으로 한다.
--version : tar 프로그램의 버전 정보를 출력한다.
-w, --interactive, --confirmation : 각각을 처리할 때 마다 물어본다.
-W, --verify : attempt to verify the archive after writing it 저장 파일을 쓴 후에 저장 파일을 점검한다.
--exclude=FILE : FILE을 제외한다.
-X, --exclude-from FILE : FILE 목록에 있는 것을 제외한다.
-Z, --compress, --uncompress
compress로 압축하거나 푼다.
-z, --gzip, --ungzip : gzip으로 압축하거나 푼다.
--use-compress-program PROG : PROG로 저장 파일을 다시 처리한다. (PROG은 반드시 -d를 처리해야 한다.)
--block-compress : 테이프에 저장할 때 압축 프로그램의 출력을 막는다.
--rsh-command=CMD : `rsh' 대신 원격 COMMAND를 사용한다. 이 옵션은 표준 `rsh' 대신 원격 장치에 접근할 수 있는 다른 것(예를 들어, Kerberized `rsh')을 사용하는 사람들을 위해 필요하다.
-[0-7][lmh] : 드라이브와 밀도를 지정한다.

http://sinsungcampus.wo.to
담당자 : 유명홍 대표전화 :0505-555-1334, 02-6403-1334
E-mail : myunghong72@empal.com 

2.1.7 버전에 있는 JobLauncherTestUtils를 활용하여, 테스트를 하자!!
http://static.springsource.org/spring-batch/reference/html-single/index.html#creatingUnitTestClass

굿굿굿.

잡을 테스트 하거나 스텝을 테스트 할 수 있다.
Reader 및 Writer 테스트는 좀 더 살펴 봐야 하고..

문서에 있는 것 처럼 사용할려면
 JobLauncherTestUtils를 빈으로 만들어 주고, job, jobLauncher, jobRepository를 셋팅해주고 Inject 받아서 사용 하면 됨!!

테스트 클래스에 @Transactional 안적어저도 됨!! 적어 주면 오류!!
Rollback은 어디서 하는지 확인해 봐야함!!!