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 타입이어야 한다.
아니면 비동기로 동작하지 않는다.