Spring ๋์ ์์ ์คํ ThreadPoolTaskExecutor
์๊ฐ๋ณด๋ค ๊ฐ๋ฐ์ ์งํํ ๋ Runnable Thread๋ฅผ ์ด์ฉํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ง ์๋ค. ํนํ ์น๊ฐ๋ฐ์ด๋ ์๋ฒ๊ฐ๋ฐ์ ํ๋ค๋ณด๋ฉด, ๊ฒ์์ฒ๋ผ ๋์ ์ธ ๊ธฐ๋ฅ๋ณด๋ค ์ ์ ์ธ ๊ธฐ๋ฅ์ด ๋ง์ด ์๊ตฌ๋๋ค. ํ์๋ ํ๋ก์ ํธ ์งํ์ค, API์ ์ง์์ ์ธ ์ฐ๊ฒฐ์ ํ๋ฉด์ ๋ค๋ฅธ ์์ ๋ค์ ๋์์ ํ ์ ์๋ ๊ธฐ๋ฅ์ ์ฝ๋์์ผ๋ก๋ง ๊ตฌํํด์ผํ๋ ์ํฉ์์ Spring์์ ์ ๊ณตํด์ฃผ๋ ThreadPoolTaskExecutor๋ฅผ ์ฌ์ฉํ๋ค.
๊ฐ๋จํ ๋ฐ๋ชจ์นํ์ด์ง๋ฅผ ๋ง๋ค์ด ๋ฒํผ์ ํด๋ฆญํ์๋ ์์ ์ด ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์งํ์ด ๋๊ณ ๋ฒํผ์ disable์ํค๋ ์์ ์ ํด๋ณผ๊ฒ์ด๋ค.
ํ๋ก๊ทธ๋ ์ค๋ฐ๋ ์์ ์ ์งํ์ ๋ณด์ฌ์ฃผ๊ณ ์ถ์์ผ๋... ๊ฐ๋จํ ํ ์คํ ๋ชจ๋์ด๋ผ ์ต๋ํ ๊ฐ๋ณ๊ฒ ์๋ก๊ณ ์นจํ์๋ ๋ง๋ค ํ์ธ์ด ๊ฐ๋ํ๋๋ก ๊ตฌํํด๋ณผ๋ ค๊ณ ํ๋ค.
Package Tree
Background Thread ๊ธฐ๋ฅ์ ๊ตฌํํ๊ธฐ ์ํด 4๊ฐ์ ํด๋์ค๋ฅผ ๋ง๋ค์ด ์ฌ์ฉํ ๊ฒ์ด๋ค.
pom.xml
์ด๋ฒ๊ธ์์๋ ํฌ๊ฒ ์ฌ์ฉํ๋ ์ด์ ๊ฐ ์์ง๋ง, ์์ผ๋ก ajax ํต์ ํ ๋ ๋ฐ์ดํฐ๋ฅผ ํธํ๊ฒ ์ฃผ๊ณ ๋ฐ๊ธฐ ์ํด jackson databind ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ค. form ํํ์ ๋ฐ์ดํฐ๋ฅผ json ํํ๋ก ๋ฐฑ์๋์์ ๋ฐ์์ ์ฌ์ฉ์ด ๊ฐ๋ฅํ๋ค.
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0.pr4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.9.1</version>
</dependency>
</dependencies>
servlet context
Spring ํ๋ก์ ํธ๋ฅผ ์์ฑํ์๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ง๋ค์ด์ง๋ context.xml ํ์ผ์ jsonView ๋งคํ์ด ๋ค์ด์์๋ ์๋์ผ๋ก Object์ ๋งคํํด์ฃผ๋ ์ค์ ์ ํด์ค์ผํ๋ค.
์ฃผ์ ์ฌํญ์ผ๋ก BeanNameViewResolver order๋ฅผ 0์ผ๋ก ๋ณ๊ฒฝํด์ค๋ค.
์ค์ ์ ์ํด์ฃผ๋ฉด beanName์ด ์๋ view๋งคํ์ผ๋ก ๊ฐ์ฃผํ์ฌ 404์๋ฌ๊ฐ ๋ฐ์ํ ๊ฒ์ด๋ค.
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />
<context:component-scan base-package="test.*" />
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<beans:bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
<beans:property name="order" value="0"></beans:property>
</beans:bean>
<beans:bean id="jsonView" class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
</beans:beans>
AsyncConfig.java
Task ์คํ์ ์ํ ์ค์ ์ ํด์ฃผ๋ ํ์ผ์ด๋ค.
ํ์๋ ํด๋น Task๊ฐ ์คํ๋์์๋ ๊ฐ์ Task๋ฅผ ์คํํ์ง ๋ชปํ๊ฒ ์ต๋ Thread ์๋ฅผ 1๋ก ์ง์ ํ๋ค.
๊ธฐ๋ณธ Thread ์๊ฐ ์ต๋ Thread ์๋ฅผ ๋์๊ฐ๋ฉด ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
package test.async.mongo.async;
import java.util.concurrent.Executor;
import javax.annotation.Resource;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
/*
* @Configuration : bean ๊ฐ์ฒด ๋ฑ๋ก
* @EnableAsync : ๋น๋๊ธฐ ํ๋ก์ธ์ ์ฌ์ฉ์
*/
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer{
//๊ธฐ๋ณธ Thread ์
private static int TASK_CORE_POOL_SIZE = 1;
//์ต๋ Thread ์
private static int TASK_MAX_POOL_SIZE = 1;
//QUEUE ์
private static int TASK_QUEUE_CAPACITY = 0;
//Thread Bean Name
private final String EXECUTOR_BEAN_NAME = "executor1";
@Resource(name="executor1")
private ThreadPoolTaskExecutor executor1;
@Bean(name="executor1")
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(TASK_CORE_POOL_SIZE);
executor.setMaxPoolSize(TASK_MAX_POOL_SIZE);
executor.setQueueCapacity(TASK_QUEUE_CAPACITY);
executor.setBeanName(EXECUTOR_BEAN_NAME);
executor.setWaitForTasksToCompleteOnShutdown(false);
executor.initialize();
return executor;
}
/*
* Thread Process๋์ค ์๋ฌ ๋ฐ์์
*/
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new AsyncExceptionHandler();
}
/*
* task ์์ฑ์ ์ pool์ด ์ฐผ๋์ง๋ฅผ ์ฒดํฌ
*/
public boolean checkSampleTaskExecute() {
boolean result = true;
System.out.println("ํ์ฑ Task ์ :::: " + executor1.getActiveCount());
if(executor1.getActiveCount() >= (TASK_MAX_POOL_SIZE + TASK_QUEUE_CAPACITY)) {
result = false;
}
return result;
}
}
AsyncExceptionHandler.java
Task ์๋ฌ๊ฐ ๋ฐ์ํ์๋ ์คํ๋๋ ์ฝ๋๋ด์ฉ์ด๋ค.
package test.async.mongo.async;
import java.lang.reflect.Method;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler{
@Override
public void handleUncaughtException(Throwable throwable, Method method, Object... obj) {
System.out.println("Thread Error Exception");
System.out.println("exception Message :: " + throwable.getMessage());
System.out.println("method name :: " + method.getName());
for(Object param : obj) {
System.out.println("param Val ::: " + param);
}
}
}
AsyncTaskService.java
ํด๋น ํ์ผ์์ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์คํํ ๋ก์ง์ ๊ตฌํํ๋ค.
๊ฐ๋จํ ์์๋ก 0~29๊น์ง 1์ด์ ํ๋ฒ์ฉ ๋ก๊ทธ๊ฐ ์ฐํ๋ ์ฝ๋๋ฅผ ๊ตฌํํ๋ค.
package test.async.mongo.service;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service("asyncTaskService")
public class AsyncTaskService {
@Async
public void jobRunningInBackground(String temp) {
System.out.println("Thread Start");
for(int i=0; i < 30; i++) {
System.out.println(temp + " i ::::: " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Thread End");
}
}
HomeController.java
ableToRunThread ํ๋ผ๋ฏธํฐ์ ํ์ฌ ์ฐ๋ ๋๊ฐ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๋๊ณ ์๋์ง์ ์ฌ๋ถ๋ฅผ ๋ณด๋ด๊ณ ,
AJAX ํต์ ์ ์ํ POST request์ฝ๋๋ฅผ ์์ฑํ๋ค.
package test.async.mongo.web;
import javax.annotation.Resource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import test.async.mongo.async.AsyncConfig;
import test.async.mongo.service.AsyncTaskService;
@Controller
public class HomeController {
@Resource(name="asyncTaskService")
private AsyncTaskService asyncTaskService;
@Resource(name="asyncConfig")
private AsyncConfig asyncConfig;
@RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView home() {
ModelAndView mav = new ModelAndView("home");
mav.addObject("ableToRunThread",asyncConfig.checkSampleTaskExecute());
return mav;
}
@RequestMapping(value = "/testing", method = RequestMethod.POST)
public ModelAndView testing() {
ModelAndView mav = new ModelAndView("jsonView");
try {
//Task ์คํ๊ฐ๋ฅ ์ฌ๋ถ ํ
if(asyncConfig.checkSampleTaskExecute()) {
asyncTaskService.jobRunningInBackground("Jamong");
}else {
System.out.println("Thread ๊ฐ์ ์ด๊ณผ");
}
} catch (Exception e) {
System.out.println("Thread Err :: " + e.getMessage());
}
return mav;
}
}
home.jsp
ํด๋ผ์ด์ธํธ ๋ถ๋ถ์ ์ฝ๋์ด๋ค.
์ฐ๋ ๋๊ฐ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๋๊ณ ์๋์ง ํ์ธํ์ฌ, ์๋ก๊ณ ์นจ์ ๋๋ ์๋ ๋ฒํผ์ disable์ฌ๋ถ๋ก ์์ ์ ์งํ ์ฌ๋ถ๋ฅผ ํ์ธํ ์ ์๋๋ก ๊ตฌํํ๋ค.
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
<title>Home</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script type="text/javascript">
function testing(){
$.ajax({
url:"testing",
type:"POST",
cache:false,
data:$("#form1").serialize(),
async:false,
success:function(data){
$('#testingBtn').attr("disabled","disabled");
$('#testingBtn').val('testing');
},
error:function(e){
console.log("err : " + e);
}
});
}
</script>
</head>
<body>
<h1>
Hello world!
</h1>
<form name="form1" id="form1" method="post" onsubmit="return false;">
<input type="text" name="txt1" id="txt1"/>
</form>
<c:choose>
<c:when test="${ableToRunThread == false}">
<input id="testingBtn" type="button" onclick="testing()" value="testing" disabled="disabled"/>
</c:when>
<c:otherwise>
<input id="testingBtn" type="button" onclick="testing()" value="test Ready"/>
</c:otherwise>
</c:choose>
</body>
</html>
๋๊ธ