Clean Code that Works.

private RedirectView handleSignIn(Connection connection, NativeWebRequest request) {
		List userIds = usersConnectionRepository.findUserIdsWithConnection(connection);
		if (userIds.size() == 0) {
			ProviderSignInAttempt signInAttempt = new ProviderSignInAttempt(connection, connectionFactoryLocator, usersConnectionRepository);
			request.setAttribute(ProviderSignInAttempt.SESSION_ATTRIBUTE, signInAttempt, RequestAttributes.SCOPE_SESSION);
			return redirect(signUpUrl);
		} else if (userIds.size() == 1) {
			usersConnectionRepository.createConnectionRepository(userIds.get(0)).updateConnection(connection);
			String originalUrl = signInAdapter.signIn(userIds.get(0), connection, request);
			return originalUrl != null ? redirect(originalUrl) : redirect(postSignInUrl);
		} else {
			return redirect(URIBuilder.fromUri(signInUrl).queryParam("error", "multiple_users").build().toString());
		}
	}

저기 보면 사용자 아이디를 조회 하는데 userConnectionRepository를 사용하는데, 구현체로 제공하는게 JdbcUsersConnectionRepository 이다.
neo4j를 사용하고 있는중이라 당연히!! 사용할 수가 없어서 간단하게 findUserIdsWithConnection 만 구현하는 Neo4jUsersConnectionRepository 만들어서 사용.



Neo4j cypher 쿼리 팁 들

Java2013. 10. 16. 09:52
RDB에서 table로 검색 하는것 처럼 검색 하고 싶을 때
start n=node:__types__(className="kr.lolstats.myapp.api.models.LeagueSummoner") return n;
-> __type__ 이라는 인덱스를 자동으로 생성해주니까 이걸 가지고 r검색 하면 된다.

NODE ID로 정렬하고 싶을때
ORDER BY ID(p) 
-> p는 정렬할 노드



http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/remoting.html#remoting-httpinvoker

요약하면, HTTP Invoker를 사용하면 직렬화를 이용해서 원격 호출을 지원할 수 있다.

클라이언트 쪽에서 사용하기 위해서는 당연히 원격 호출을 위한 인터페이스가 필요하다. 이 인터페이스를 포함한 파일들을 서비스를 노출하는 서버에서 제공 해야 된다.(maven으로 jar 파일로 묶어서 클라이언트에 배포, impl은 노출시킬 필요가 없고 필요한 인터페이스만 노출 하면 된다.) 

위에 설정에서 처럼 httpInvokerProxy가 example.AccountService 타입의 bean을 생성해 주기 때문에(프록시로) 클라이언트로 사용할 때는

@Autowired private AccountService service; 형식으로 일반 Spring bean을 사용하는것처럼 사용하면 된다.

물론 내부적으로는 HTTP POST를 통해서 사용되는 거기 때문에 이를 인지하고 있어야 되고, 세부적인 설정이 필요 하다면 commons HttpClient를 httpInvokerRequestExecutor로 지정한 후에 여러가지 세팅을 지정할수 있다(timeout, connection pooling, 인증 등)

스프링에서 제공하는 remoting 기능중에 HTTP invokers 를 사용하는 방법이 있다.(위 문서를 간단히 번역, 날림 번역)

Burlap 과 Hessian을 사용하여 remoting 기능을 사용할 수 있지만 이 방법은 표준이 아닌 가벼운 프로토콜을 사용하여 자기들만의 직렬화 방식을 사용한다.

Spring Http Invokers HTTP를 통해 서비스를 노출하기 위해 자바 표준 직렬화 기능을 사용한다. 이것은 파라미터나 리턴타입이 복잡하고 Hessian 이나 Burlap의 직렬화 기법이 동작하지 않을때 큰 이점이 있다. 

스프링은 J2SE에서 제공하는 표준 HTTP 호출이나 Commons HttpClient 를 사용한다. Commons HttpClient를 사용하는게 기능적으로 쉽고 좀 더 많은 이점이 있다. 이 링크를 참조 jakarta.apache.org/commons/httpclient 

서비스 오브젝트 노출하기.

스프링 HttpInvoker는 org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter를 제공한다. 

AccountService 를 스프링 웹 MVC DispatcherServlet을 통해 노출시키기 위해서는 dispatcher의 application context에 아래와 같은 설정을 추가 해야 된다.

<bean name="/AccountService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter"> <property name="service" ref="accountService"/>     <property name="serviceInterface" value="example.AccountService"/> </bean>

이 방식은 DispathcerServlet의 표준 매핑 방식에 따라 매핑된다.

다른 방법은 HttpInvokerServiceExporter를 root application에 만들 수 있다(e.g 'WEB-INF/applicationContext.xml')

<bean name="accountExporter" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">     <property name="service" ref="accountService"/>     <property name="serviceInterface" value="example.AccountService"/> </bean>

추가적으로 이것과 통신하기 위한 exporter를 'web.xml'에 정의 해야 하고, 서블릿의 이름은 exporter와 일치 해야 한다. 

<servlet>
    <servlet-name>accountExporter</servlet-name>
    <servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>accountExporter</servlet-name>
    <url-pattern>/remoting/AccountService</url-pattern>
</servlet-mapping>

만약 서블릿 컨테이너를 사용하지 않고 Sun's Java 6를 사용한다면 내장된 HTTP servier implementation을 사용할수 있다.(이런것도 있었나) SimpleHttpServerFactoryBean과 함계 SImpleHttpInvokerServiceExporter를 아래의 예제처럼 사용하면 된다.

<bean name="accountExporter"
    class="org.springframework.remoting.httpinvoker.SimpleHttpInvokerServiceExporter">
    <property name="service" ref="accountService"/>
    <property name="serviceInterface" value="example.AccountService"/>
</bean>

<bean id="httpServer"
        class="org.springframework.remoting.support.SimpleHttpServerFactoryBean">
    <property name="contexts">
        <util:map>
            <entry key="/remoting/AccountService" value-ref="accountExporter"/>
        </util:map>
    </property>
    <property name="port" value="8080" />
</bean>


서비스를 클라이언트와 연결하기.

프록시를 사용해서, 스프링은 당신의 요청을 HTTP POST requests로 변환하여 노출된 서비스를 가르키는 URL로 가게 해준다.

<bean id="httpInvokerProxy" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
    <property name="serviceUrl" value="http://remotehost:8080/remoting/AccountService"/>
    <property name="serviceInterface" value="example.AccountService"/>
</bean>

전에 언급한 바와 같이 HTTP client는 원하는 것을 사용할 수 있다. J2SE HTTP 를 사용하는 HttpInvokerProxy가 기본으로 지정되어 있지만 httpInvokerRequestExecuttor 속성에 Commons HttpClient를 세팅해서 사용할 수도 있다.

<property name="httpInvokerRequestExecutor">
    <bean class="org.springframework.remoting.httpinvoker.CommonsHttpInvokerRequestExecutor"/>
</property>


기술을 선택할때의 고려사항

여기에서 제시된 각각의 기술은 단점이 있다. 

RMI를 사용할때 RMI 트래픽을 터널링 하지 않는한  HTTP protocol을 통해 객체에 접근할수 없다. RMI는 완전한 객체 직렬화를 지원하는 상당히 무거운 프로토콜 이지만 정보를 통해 직렬화가 필요한 복합 데이터 모델을 사용할때 중요하다. 하지만, RMI-JRMPs는 Java 클라이언트에 종속적이 된다. 이것은 Java-to-Java간의 원격 호출 방식이다.

Spring에서 제공하는 HTTP invoker는 HTTP 기반의 원격호출을 사용하지만 자바 직렬화가 필요할때 사용할 수 있는 좋은 선택이다. 이것은 RMI invoker의 기본 구조를 공유하고 HTTP를 전송하기 위해서만 사용된다. HTTP invoker는 Java-to-Java 에 제한을 두진 않지만 클라이언트와 서버 둘다 스프링을 사용해야 된다.

Hessian 이나 Burlap은 이기종 환경에서 사용할때 큰 이점이 있다. 왜냐하면 이들은 명시적으로 Java Clinet가 아닌것도 사용가능하기 때문이다. 하지만 Java Clinet가 아닌것을 지원하는데는 여전히 제한이 있다. 알려진 이슈로는 늦은 초기화 콜렉션이 포함된 하이버네이트 객체를 직렬화 하는것에대한 문제가 있다. 만약 이런 구조의 데이터 모델을 가지고 있다면 Hessian의 대용으로 RMI나 HTTP invoker를 사용하는것을 고려할 수 있다.

JMS는 서비스들을 클러스터링하기에 용이하고 JMS 브로커를 통해 로드 벨런싱을 하게 하거나 자동 오류극복을 하게 할 수 있다. 기본적으로 JMS를 사용할때 자바의 직렬화를 사용하도록 되어 있지만 JMS provider는 XStream을 구현하거나 다른 방식을 구현한 것처럼 다른 메커니즘을 가진 방식을 사용할 수 있다.

마지막으로 가장 적게 사용되는건 아니지만 EJB는 RMI를 넘어서는 표준 role-based 인증민 인가, 원격 트랜잭션 전파를 지원하는 장점이 있다. 스프링의 핵심 기능에서 제공되지는 않지만 원격 보안 컨텍스트 전파를 지원하는 RMI Invoker나 HTTP Invoker를 통해 얻을 수 있다. 





스프링 시큐리티에선 패스워드 인코딩 방식을 몇가지 지원 하는데 

로그인 시도시 입력한 패스워드를 인코딩 하고, db에 저장된 인코딩된 패스워드와 비교를 한다.


이것을 수행하는 부분은

org.springframework.security.authentication.dao.DaoAuthenticationProvider.additionalAuthenticationChecks(UserDetails, UsernamePasswordAuthenticationToken)


이 부분으로 여기서 인코딩 하는데 사용된 패스워드 인코더를 가지고 패스워드를 확인한다.


참고 할 것.



jms 구현 예제

Java2013. 2. 15. 19:00

http://weblog.plexobject.com/?p=1420


참고참고

스프링 처럼 DI를 받을 수 있는데...

FBUserDetailService를 di 받아서 사용할 경우 일반적으로 
def fbUserDetailService 이런 형식의 카멜 표기법으로 정의하는것으로 생각한다.

하지만 되지 않아!!!!!

http://grails.org/doc/latest/guide/services.html#dependencyInjectionServices 

저기 문서의 가운데를 보면

NOTE: Normally the property name is generated by lower casing the first letter of the type. For example, an instance of the BookService class would map to a property named bookService.

To be consistent with standard JavaBean conventions, if the first 2 letters of the class name are upper case, the property name is the same as the class name. For example, the property name of theJDBCHelperService class would be JDBCHelperService, not jDBCHelperService or jdbcHelperService.

See section 8.8 of the JavaBean specification for more information on de-capitalization rules.



JDBCHelperService를 DI 받을려면 JDBCHelperService로 작명을 해주어야 한다고 한다.

뭐 그럼 나도 사용할 때

def FBUserDetailService 로 하면 됨 @_@

Grails 2.0에서 spring security 플러그인을 지원 하는데, 인증은 Fackbook을 통해서 하고 인증 정보는 spring security를 사용하는 인증 방법.

1. Grails Filter를 사용해서 인증 url을 필터링함.
2. 인증 url로 요청이 들어오고 인증이 안되어 있을 경우 Facebook을 통해 인증 하도록 함.

참고 URL
Grails Filter :http://grails.org/doc/latest/guide/theWebLayer.html#filters 
Facebook 인증 :  http://developers.facebook.com/docs/authentication/ 


FackbookAuthFilters의 코드(약간 자바 스타일 문법도 섞임)
쭉 보면 login으로 요청을 하는데 code 값이 없거나 미인증된 사용자면 Facebook을 통해서 인증을 하도록 한다.
Facebook에서 인증후 code값을 전달해 주는데, 이 code값을 다시 Fackbook을 통해서 accessToken을 받아 오도록 하고, 
이것을 가지고 사용자의 정보를 가져올 수 있다.

def filters = {

all(uri : '/login/**', uriExclude : '/logout/**') {

before = {

String code = params.code


SecurityContext context = SecurityContextHolder.context

String username = context.authentication.getName()


if (code == null && username.equals('anonymousUser')) {

redirect(url: "https://www.facebook.com/dialog/oauth?client_id=" + clientId + "&redirect_uri="+ redirectUri +"&scope=publish_stream,email")

return false

} else {

if (username.equals('anonymousUser')) {

String accessToken = null;

Integer expires = null;


def http = new HTTPBuilder( 'https://graph.facebook.com/oauth/' )


http.get( path: 'access_token',

query: [client_id: clientId, redirect_uri: redirectUri, client_secret: clientSecret, code: code] ) { resp, reader ->

String result = reader.text

String[] pairs = result.split("&")

for (String pair : pairs) {

String[] kv = pair.split("=")

if (kv.length != 2) {

throw new RuntimeException("Unexpected auth response")

} else {

if (kv[0].equals("access_token")) {

accessToken = kv[1]

}

if (kv[0].equals("expires")) {

expires = Integer.valueOf(kv[1])

}

}

}

}

if (checkAuthByAccessToken(accessToken, expires, request, response)) {

redirect(url: serviceUrl)

return false

}

}

}

}


 
Facebook을 통해서 인증 받은 사용자 정보가 서비스에 존재 하지 않은 사용자일경우 기본 롤로 생성을 하고 만약 있으면 조회를 한 후 스프링 시큐리티 세션에 넣어서 인증정보를 가지고 있도록 한다.

def boolean checkAuthByAccessToken(String accessToken, expires, request, response) {

if (accessToken != null && expires != null) {

def fb = new RESTClient('https://graph.facebook.com/')

def resp = fb.get(path: 'me', query: [ access_token: accessToken])

def email

def password = 'test'

ExampleUser user

if (resp.status == 200) {

email = resp.data.email

user = ExampleUser.findByUsername(email)

if (user == null) {

user = new ExampleUser(username: email, password : password, accountExpired: false, accountLocked: false, enabled: true ).save(flush : true)

Role role = Role.findByAuthority('ROLE_USER')

UserRole.create(user, role, true);

} else {

user = userDetailsService.loadUserByUsername(user.username, true)

}

}


UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(user, password)

request.getSession().setAttribute(UsernamePasswordAuthenticationFilter.SPRING_SECURITY_LAST_USERNAME_KEY, TextEscapeUtils.escapeEntities(user.username));

authRequest.setDetails(authenticationDetailsSource.buildDetails((HttpServletRequest) request))


SecurityContextHolder.context.authentication = authRequest


return true

} else {

throw new RuntimeException("Access token and expires not found")

}

}


http://stackoverflow.com/questions/4624250/where-to-put-default-servlet-handler-in-spring-mvc-configuration

저기를 보도록 하자.

기본적으로, 스프링은 HandlerMapping은 order 프로퍼티에서 낮은 숫자가 우선 순위가 높다. dispatcher에서 처음으로 설정들을 불러들일 때 DispatcherServlet은 이 order 값을 사용해서 HandlerMapping 들을 정렬한다.

만약 order값이 명시적으로 세팅되어있지 않을 경우에는 모든 핸들러들은 Integer.MAX_VALUE 값으로 동일하게 설정된다. 그러므로 정렬 뒤에 빈이 정의된 순서대로 정해지게 된다.

따라서 handler mapping의 값을 명시적으로 세팅해주어야 <mvc:default-servlet-handler /> 태그를 설정파일 아무 위치에다가 두어도 된다.


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가 메시징 인프라 구조 로직 보다 비즈니스 로직에 촛점을 맞출 수 있다. 이 클래스는 메시징 컨텍스트의 안이나 밖에서 사용될 수 있고, 메시징 프레임워크 바깥에서 테스트할 수 있다.