Spring Framrwork

Spring framework Future와 AsyncResult 를 이용한 Async 서비스 호출

gregorio 2018. 4. 23. 10:19

Spring framework Controller에서는 일반적으로 동기 방식으로 서비스를 호출한다.

그러나 요청된 파라미터의 값에 따라 동시에 여러 서비스를 호출하여 결과를 받을 때 순차적으로 서비스를 호출하지 않고  비동기로 서비스를 호출하여 결과를 Return 받으면 서비스의 수행 성능을 향상 시킬 수 있다.


Controller에서 비동기적으로 서비스를 호출하기 위해서는 Java 1.5에서부터 지원하는 Future 기능을 사용하고, 응답을 받을 경우 호출되는 서비스에서  AsyncResult를 이용하여 결과를 Return할 수 있다.


Funcure 기능과 AsyncResult를 이용하기 위해서는 task관련 Bean을 다음과 같이 정의한다.


■ context-baen.xml

<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"

        xmlns:mvc="http://www.springframework.org/schema/mvc"

        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd

                http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd

                http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd

                http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd">


<task:annotation-driven  executor="taskExecutor"/>

<!-- mvc:annotation-driven>

<mvc:async-support default-timeout="2500" task-executor="taskExecutor">

<mvc:callable-interceptors>

<bean class="org.springframework.web.servlet.config.MvcNamespaceTests.TestCallableProcessingInterceptor" />

</mvc:callable-interceptors>

<mvc:deferred-result-interceptors>

<bean class="org.springframework.web.servlet.config.MvcNamespaceTests.TestDeferredResultProcessingInterceptor" />

</mvc:deferred-result-interceptors>

</mvc:async-support>

</mvc:annotation-driven-->

<!-- Async Task -->

<!-- task:annotation-driven scheduler="scheduler" executor="taskExecutor"/>

<task:scheduler id="scheduler" pool-size="10"/-->

<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">

    <property name="corePoolSize" value="100"/>

    <property name="maxPoolSize" value="100"/>

    <property name="queueCapacity" value="2000000000"/>

    <property name="keepAliveSeconds" value="120"/>

</bean>  

</beans> 

비동기적으로 서비스를 호출하기 위해 <task:annotation-drive>을 설정하고, executor를 정의하여 Pool size와 Capacity를 정의한다.



다음은 비동기적으로 실행할 서비스를 생성한다.


■AsyncServiceImpl.java

import org.springframework.scheduling.annotation.Async;

import org.springframework.scheduling.annotation.AsyncResult;

import org.springframework.stereotype.Service;


@Service("asyncService")

public class AsyncServiceImpl extends BaseService implements AsyncService{


@Async

@Override

public Future<List<Map<String, Object>>> selectUserListAsync(NexacroMapDTO dto) throws Exception {

LOGGER.info("selectUserListAsync Start");

Map<String, Object> searchMap = getDataSet(dto, "searchMap");

Thread.sleep(2000);

List<Map<String, Object>> resultList = getMapper("sbMapper").selectList("user.selUser", searchMap);

return new AsyncResult<List<Map<String, Object>>>(resultList);

}


@Async

@Override

public Future<List<Map<String, Object>>> selectUserListAsync1(NexacroMapDTO dto) throws Exception {

LOGGER.info("selectUserListAsync1 Start");

Map<String, Object> searchMap = getDataSet(dto, "searchMap");

List<Map<String, Object>> resultList = getMapper("sbMapper").selectList("user.selUser", searchMap);

LOGGER.info("selectUserListAsync1 End");

return new AsyncResult<List<Map<String, Object>>>(resultList);

}


}


Service 메소드에 @Async Annotation을 설정하여 해당 메소드가 비동기적 수행을 정의한다.

@Async는 public 메소드에만 정의되고, 동일한 Class에서는 @Async 메소드를 호출할 수 없다. 즉 외부의  Class에서 @Async 메소드를 호출한다.


@Async로 정의된 메소드는 Return Type이 Future<?>이고 결과는 AsyncResult를 생성하여 결과를 Return한다.


다음은 서비스를 호출하는 Controller를 생성한다.


■AsyncController


@RequestMapping(value = "/sample/selectUserAsync.do")

public ModelAndView selectUserAsync(NexacroMapDTO dto) throws Exception {

ModelAndView modelAndView = NexacroUtil.getModelAndView(dto);


Future<List<Map<String,Object>>> future1 = asyncService.selectUserListAsync(dto);


Future<List<Map<String,Object>>> future2 = asyncService.selectUserListAsync1(dto);


// List<Map<String,Object>> resultList1 = future1.get();

// List<Map<String,Object>> resultList2 = future2.get();


               modelAndView.addObject("list1", resultList1);

               modelAndView.addObject("list2", resultList2);

return modelAndView;

}

 


Controller에서 Future에 서비스의 Async Method를 호출한 후 결과를 받기 위해 생성된 future의 get Method를 호출하여 결과를 받는다.


■ 수행결과


  INFO | createCompositeTransaction ( 300000 ): created new ROOT transaction with id 192.168.0.59.tm0000100063

 INFO | createCompositeTransaction ( 300000 ): created new ROOT transaction with id 192.168.0.59.tm0000200063

 INFO | selectUserListAsync1 Start

 INFO | selectUserListAsync Start

 INFO | AtomikosDataSoureBean 'xaSBDataSource': getConnection ( null )...

 INFO | AtomikosDataSoureBean 'xaSBDataSource': init...

 INFO | atomikos connection proxy for oracle.jdbc.driver.LogicalConnection@1c182f16: calling getMetaData...

 INFO | 1. Connection opened

 INFO | 1. Connection.new Connection returned 

 INFO | atomikos connection proxy for oracle.jdbc.driver.LogicalConnection@1c182f16: calling getAutoCommit...

 INFO | 1. Connection.getAutoCommit() returned true

 INFO | addParticipant ( XAResourceTransaction: 3139322E3136382E302E35392E746D30303030313030303633:3139322E3136382E302E35392E746D31 ) for transaction 192.168.0.59.tm0000100063

 INFO | XAResource.start ( 3139322E3136382E302E35392E746D30303030313030303633:3139322E3136382E302E35392E746D31 , XAResource.TMNOFLAGS ) on resource xaSBDataSource represented by XAResource instance oracle.jdbc.driver.T4CXAResource@5c7c0881

 INFO | registerSynchronization ( com.atomikos.jdbc.AtomikosConnectionProxy$JdbcRequeueSynchronization@48d3cd82 ) for transaction 192.168.0.59.tm0000100063

 INFO | atomikos connection proxy for oracle.jdbc.driver.LogicalConnection@1c182f16: calling prepareStatement(SELECT

ID,

NAME,

PASSWORD,

DESCRIPTION,

USE_YN,

REG_USER

FROM TB_USER

WHERE 1 = 1)...


결과를 확인하면 로그에 selectUserListAsync1,과 selectUserListAsync 출력됨을 확인할 수 있으며, 각각의 서비스가 순차적으로 호출되지 않고 병렬로 호출되었음을 확인할 수 있다.