์ค์ผ์ค๋ฌ๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋ ์๋ ์กฐ๊ฑด์ ๋ค ๋ง์กฑํด์ผ ํ๋ค.
- Application ํด๋์ค์์
@EnableScheduling
ํ์ฑํ - ์ค์ผ์ค๋ฌ๋ฅผ ์ ๊ณตํ ํด๋์ค๋ฅผ ๋น์ผ๋ก ๋ฑ๋ก
- ์ค์ผ์ค๋ฌ์ @Scheduled ์ฌ์ฉ
@Slf4j
@Component
class TimeScheduler {
private val log = logger()
private var formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
/** 5์ด ๋ง๋ค ํ์ฌ ์๊ฐ์ ๋ก๊ทธ๋ก ์ฐ๋ ์ค์ผ์ค๋ฌ */
@Scheduled(fixedRate = 5000)
fun reportCurrentTime() {
log.info("The time is now {}", LocalDateTime.now().format(formatter))
}
}
@Scheduled ๋ ๋ค์ํ ์ต์ ๋ค์ ์ง์ํ๋ค. ํนํ ํน์ ๋ ์ง, ์๊ฐ์ ๋์ํ๊ฒ๋ ํ ์๋ ์๋ค.
@Scheduled("0 * * * * MON-FRI")
- @Scheduled cron options
- second
- minute
- hour
- day of month
- month
- day of week
- Spring Scheduler ๋
์ผ์
์ ๋ฐ๋ผ ๋ฌด์ธ๊ฐ๋ฅผ ์ค์ผ์คํธ๋ ์ด์ ํ๊ธฐ ์ํ ๊ฒ - Spring Batch ๋ ๋ณต์กํ ์ปดํจํ
๋ฌธ์ ๋ฅผ ๊ตฌ์ถํ๊ธฐ ์ํด ์ค๊ณ๋ ๊ฐ๋ ฅํ
์ผ๊ด ์ฒ๋ฆฌ
ํ๋ ์์ํฌ - Spring Batch ๋ ์์ ์ ์ค์ผ์คํธ๋ ์ด์ ์ ์ฒ๋ฆฌํ์ง ์๊ณ ๋น๋๋ง ์ฒ๋ฆฌ
- ์ํ๋ ๊ฒฝ์ฐ Spring Scheduler ๋ฅผ ์ฌ์ฉํ์ฌ Spring Batch ์์ ์ ์ค์ผ์คํธ๋ ์ด์ ํ ์ ์๋ค.
์คํ๋ง ๋ฐฐ์น๋ : http://wiki.gurubee.net/pages/viewpage.action?pageId=4949437
- @Async ๋ ๋น๋๊ธฐ ์์ ์ ์ด๋ ธํ ์ด์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์๊ฒ๋ ์ง์ํด์ฃผ๋ ๋ ์์ด๋ค.
- ๊ธฐ๋ณธ ์ ๋ต์ ๋น๋๊ธฐ ์์
๋ง๋ค ์ค๋ ๋๋ฅผ ์์ฑํ๋
SimpleAsyncTaskExecutor
๋ฅผ ์ฌ์ฉํ๋ค. - ์ค๋ ๋ ๊ด๋ฆฌ ์ ๋ต์
ThreadPoolTaskExecutor
๋ก ๋ฐ๊ฟ์ ์ค๋ ๋ํ์ ์ฌ์ฉํ๊ฒ๋ ํ ์ ์๋ค.
public class GreetingService {
static ExecutorService executorService = Executors.newFixedThreadPool(10);
public void method1(final String message) throws Exception {
executorService.submit(new Runnable() {
@Override
public void run() {
// do something
}
});
}
}
SimpleAsyncTaskExecutor ์ฌ์ฉ
@EnableAsync
@SpringBootApplication
public class Application {
...
}
public class GreetingService {
@Async
public void method1(String message) throws Exception {
// do something
}
}
๊ธฐ์กด์ Application ํด๋์ค์์ ์ ์ฉํ @EnableAsync ๋ ์ ๊ฑฐํด์ผ ํ๋ค.
@Configuration
@EnableAsync
public class SpringAsyncConfig {
@Bean(name = "threadPoolTaskExecutor")
public Executor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(3);
taskExecutor.setMaxPoolSize(30);
taskExecutor.setQueueCapacity(10);
taskExecutor.setThreadNamePrefix("Executor-");
taskExecutor.initialize();
return taskExecutor;
}
}
public class GreetingService {
@Async("threadPoolTaskExecutor")
public void method1() throws Exception {
// do something
}
}
์ค๋ ๋ ๊ด๋ฆฌ ์ ๋ต์ ์ฌ๋ฌ๊ฐ ๊ฐ์ ธ๊ฐ๋ค๋ฉด ์ ๋ ๊ฒ ๋น ์ด๋ฆ์ ์ง์ ํด์ฃผ๋ฉด ๋๋ค.
AsyncConfigurerSupport ํด๋์ค๋ฅผ ์์ ๋ฐ์์ ์ค๋ ๋ ๊ด๋ฆฌ ์ ๋ต์ ์ค์ ํ ์๋ ์๋ค.
@Configuration
@EnableAsync
public class AsyncConfig extends AsyncConfigurerSupport {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(30);
executor.setQueueCapacity(50);
executor.setThreadNamePrefix("DDAJA-ASYNC-");
executor.initialize();
return executor;
}
}
@EnableAsync
: Spring method ์์ ๋น๋๊ธฐ ๊ธฐ๋ฅ์ ์ฌ์ฉ๊ฐ๋ฅํ๊ฒ ํ์ฑํ ํ๋ค.CorePoolSize
: ๊ธฐ๋ณธ ์คํ ๋๊ธฐํ๋ Thread ์ ์MaxPoolSize
: ๋์ ๋์ํ๋ ์ต๋ Thread ์ ์QueueCapacity
: MaxPoolSize ์ด๊ณผ ์์ฒญ์์ Thread ์์ฑ ์์ฒญ์, ํด๋น ์์ฒญ์ Queue ์ ์ ์ฅํ๋๋ฐ ์ด๋ ์ต๋ ์์ฉ ๊ฐ๋ฅํ Queue ์ ์, Queue ์ ์ ์ฅ๋์ด์๋ค๊ฐ Thread ์ ์๋ฆฌ๊ฐ ์๊ธฐ๋ฉด ํ๋์ฉ ๋น ์ ธ๋๊ฐ ๋์ThreadNamePrefix
: ์์ฑ๋๋ Thread ์ ๋์ฌ ์ง์
- private method ๋ ์ฌ์ฉ ๋ถ๊ฐ, public method ๋ง ์ฌ์ฉ ๊ฐ๋ฅ
- self-invocation(์๊ฐ ํธ์ถ) ๋ถ๊ฐ, ์ฆ inner method๋ ์ฌ์ฉ ๋ถ๊ฐ
- QueueCapacity ์ด๊ณผ ์์ฒญ์ ๋ํ ๋น๋๊ธฐ method ํธ์ถ์ ๋ฐฉ์ด ์ฝ๋ ์์ฑ
- ์ต๋ ์์ฉ ๊ฐ๋ฅํ Thread Pool ์์ QueueCapacity ๊น์ง ์ด๊ณผ๋ ์์ฒญ์ด ๋ค์ด์ค๋ฉด
TaskRejectedException
์๋ฌ๊ฐ ๋ฐ์ํ๋ค. - ๋ฐ๋ผ์, TaskRejectedException ์๋ฌ์ ๋ํ ์ถ๊ฐ์ ์ธ ํธ๋ค๋ง์ด ํ์ํ๋ค.
- ์ต๋ ์์ฉ ๊ฐ๋ฅํ Thread Pool ์์ QueueCapacity ๊น์ง ์ด๊ณผ๋ ์์ฒญ์ด ๋ค์ด์ค๋ฉด
@Async ์ ๋์์ AOP ๊ฐ ์ ์ฉ๋์ด Spring Context ์์ ๋ฑ๋ก๋ Bean Object ์ method ๊ฐ ํธ์ถ ๋ ์์, Spring ์ด ํ์ธํ ์ ์๊ณ @Async ๊ฐ ์ ์ฉ๋ method ์ ๊ฒฝ์ฐ Spring ์ด method ๋ฅผ ๊ฐ๋ก์ฑ ๋ค๋ฅธ Thread ์์ ์คํ ์์ผ์ฃผ๋ ๋์ ๋ฐฉ์์ด๋ค. ์ด ๋๋ฌธ์ Spring ์ด ํด๋น @Async method ๋ฅผ ๊ฐ๋ก์ฑ ํ, ๋ค๋ฅธ Class ์์ ํธ์ถ์ด ๊ฐ๋ฅํด์ผ ํ๋ฏ๋ก, private method๋ ์ฌ์ฉํ ์ ์๋ ๊ฒ์ด๋ค.
๋ํ Spring Context์ ๋ฑ๋ก๋ Bean์ method์ ํธ์ถ์ด์ด์ผ Proxy ์ ์ฉ์ด ๊ฐ๋ฅํ๋ฏ๋ก, inner method ์ ํธ์ถ์ Proxy ์ํฅ์ ๋ฐ์ง ์๊ธฐ์ self-invocation ์ด ๋ถ๊ฐ๋ฅํ๋ค.
AsyncExecutionAspectSupport ํด๋์ค์ doSubmit() ๋ฉ์๋์ ์ํด์ @Async ์ด๋ ธํ ์ด์ ์ ๋ฌ๋ฉด ํด๋น ๋ฉ์๋๊ฐ ๋น๋๊ธฐ๋ก ๋์ํ ์ ์๋ ๊ฒ์ด๋ค.
- ๋ฉ์๋์ ๋ํ ์์ ์์ด ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํ๋ค.
- ๊ธฐ์กด์๋ ๋๊ธฐ/๋น๋๊ธฐ์ ๋ฐ๋ผ์ ๋ฉ์๋์ ๋ด์ฉ์ด ๋ฌ๋ผ์ก๋ค.
- ExecutorService ๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ Runnable ์ run() ์ ์ฌ๊ตฌํํด์ผ ํ๋ ๋ฑ ๋์ผํ ์์ ๋ค์ ๋ฐ๋ณต๋์๋๋ฐ, @Async ๋ฅผ ์ฌ์ฉํ๋ฉด ๊ทธ๋ฌํ ๋ฐ๋ณต์์ ์์ด ์ฝ๋๋ฅผ ๊น๋ํ๊ฒ ๊ฐ์ ธ๊ฐ ์ ์๋ค.
- ๋๊ธฐ/๋น๋๊ธฐ์ ๋ํ ๊ณ ๋ฏผ์์ด ๋ก์ง ๊ตฌํ ๊ณผ์ ์๋ง ์ง์คํ ์ ์๋ค.
- ๋ชจ๋ธ ์ ๋ณด๋ฅผ HTTP ์ธ์ ์ ์ ์ฅํด์ค๋ค.
- ์ด๊ฑฐ๋์ HttpSession ์ ์ด์ฉํด์ setAttribute ๋ก HTTP ์ธ์ ์ ์ ์ฅํ ์ ๋ ์๋ค.
- @SessionAttributes ๋ฅผ ์ฌ์ฉํ๋ฉด ์ด ์ด๋
ธํ
์ด์
์ ์ค์ ํ ์ด๋ฆ์ ํด๋นํ๋ ๋ชจ๋ธ ์ ๋ณด๋ฅผ ์๋์ผ๋ก ์ธ์
์ ๋ฃ์ด์ค๋ค.
- ์ฆ, @ModelAttribute๋ฅผ HTTP Session์ ๋ด๋๋ค๊ณ ์๊ฐํ๋ฉด ๋๋ค. (๋จ, ํค๊ฐ์ด ๊ฐ์๊ฒฝ์ฐ)
- @ModelAttribute ๋ ์ธ์ ์ ์๋ ๋ฐ์ดํฐ๋ ๋ฐ์ธ๋ฉํ๋ค.
- ์ฌ๋ฌ ํ๋ฉด์์ ์ฌ์ฉํด์ผํ๋ ๊ฐ์ฒด๋ฅผ ๊ณต์ ํ ๋ ์ฌ์ฉํ๋ค.
- ex) ์ฅ๋ฐ๊ตฌ๋
- ex) ํผ์์ ์ ๋ ฅํ๋ ๊ฐ์ด ๋ง์๊ฒฝ์ฐ ํ๋ฉด์ ๋๋๊ธฐ๋ํ๋๋ฐ, ๊ทธ๋ Aํ๋ฉด์์ input ๊ฐ a, b๋ฅผ ๋ฐ๊ณ ๊ทธ ๋ค์ ํ๋ฉด B์์ c, d๋ฅผ ๋ฐ์์ ์กฐํฉํ ์ ์๋ค.
- SessionStatus๋ฅผ ์ฌ์ฉํด์ ์ธ์ ์ฒ๋ฆฌ ์๋ฃ๋ฅผ ์๋ ค์ค ์ ์๋ค.
- ํผ ์ฒ๋ฆฌ ๋๋๊ณ ์ธ์ ๋น์ธ๋ ์ฌ์ฉํ๋ค.
HttpSession ์ ์ด์ฉํ๋ ๊ฒ์ low level ์ด๋ฉฐ @SessionAttributes ๋ ๋ ์ถ์ํ ๋ level ์ด๋ค.
@Controller
@SessionAttributes("event")
@RequestMapping("/events")
// @SessionAttributes({"event", "book"} ์ฌ๋ฌ๊ฐ๋ ์ง์ ๊ฐ๋ฅ
public class EventController {
/**
* @SessionAttributes ๋ฅผ ์ฌ์ฉํ์ฌ ๋ชจ๋ธ์ ๋ด๊ธด event๋ ์ด๋ฆ์ ๊ฐ์ง ๊ฐ์ฒด๋ฅผ ์ธ์
์ ๋ด์์ค๋ค.
*/
@GetMapping("/form")
public String eventsForm(Model model) {
model.addAttribute("event, newEvent);
return "/book/form";
}
/**
* SessionStatus๋ฅผ ์ฌ์ฉํ์ฌ ํผ ์ฒ๋ฆฌ๊ฐ ๋๋๊ณ ์ธ์
์ ์๋ฃ์ํฌ ์ ์๋ค.
*/
@PostMapping
public String createEvents(@Validated @ModelAttribute Event event,
BindingResult bindingResult,
SessionStatus sessionStatus) {
if(bindingResult.hasErrors()) {
return "/form";
}
sessionStatus.setComplete(); // ์ฆ, ์ธ์
์ ๋ด๊ธด๊ฐ์ ์ ๋ฆฌํ๋ค.
return "redirct:/list";
}
}
์ธ์ ์ ๋ด๊ธด attribute ๋ฅผ ๊บผ๋ด์ค๋ ค๋ฉด ์๋์ ๊ฐ์ด ํ๋ฉด ๋๋ค.
Object event = request.getSession().getAttribute("event");
- HTTP ์ธ์ ์ ๋ค์ด์๋ ๊ฐ์ ์ฐธ์กฐํ ๋ ์ฌ์ฉ
- HttpSession ์ ์ฌ์ฉํ ๋ ๋นํด ํ์ ์ปจ๋ฒ์ ์ ์๋์ผ๋ก ์ง์ ํ๊ธฐ ๋๋ฌธ์ ํธ๋ฆฌํจ
- HTTP ์ธ์ ์ ๊ฐ์ ๋ฃ๊ณ ๋นผ๊ณ ์ถ์ ๊ฒฝ์ฐ๋ HttpSession ์ ์ฌ์ฉ
- @SessionAttributes ์๋ ๋ค๋ฅด๋ค
- @SessionAttributes ๋ ํด๋น ์ปจํธ๋กค๋ฌ ๋ด์์๋ง ์๋
- ์ฆ, ํด๋น ์ปจํธ๋กค๋ฌ ์์์ ๋ค๋ฃจ๋ ํน์ ๋ชจ๋ธ ๊ฐ์ฒด๋ฅผ ์ธ์ ์ ๋ฃ๊ณ ๊ณต์ ํ ๋ ์ฌ์ฉ
- @SessionAttribute ๋ ์ปจํธ๋กค๋ฌ๋ฐ(์ธํฐ์ ํฐ, ํํฐ ๋ฑ)์์ ๋ง๋ค์ด์ค ์ธ์ ๋ฐ์ดํฐ์ ์ ๊ทผํ ๋ ์ฌ์ฉ
- @SessionAttributes ๋ ํด๋น ์ปจํธ๋กค๋ฌ ๋ด์์๋ง ์๋
์ฌ์ค @SessionAttribute ๋ฅผ ์ด์ฉํด์ @SessionAttributes ๋ก HTTP Session ์ ๋ฃ์ด์ค ๊ฐ์ ์ ๊ทผํ ์ ์์ง๋ง ์ฌ์ค ๊ทธ๋ฐ ์ฉ๋๋ ์๋๊ณ , ์ปจํธ๋กค๋ฌ ๋ด์์๋ @SessionAttributes, SessionStatus, @ModelAttribute ๋ฅผ ์ด์ฉํด์ ๋ค๋ฃฌ๋ค.
- ์น ์ฌ์ดํธ์ ๋ฐฉ๋ฌธํ์๋ ๋ฐฉ๋ฌธํ ์๊ฐ์ ๊ธฐ๋ก์ ์ด์ฉํ๋ฉด, ๊ฒ์์ผ ๊ฒฝ์ฐ 2์๊ฐ์ด์ ๊ณผ๋ํ ๊ฒ์ํ ๊ฒฝ์ฐ ๋ฉ์์ง๋ฅผ ๋ฟ๋ ค์ค๋ค๋์ง ๊ฐ๋ฅํ๋ค.
์๋ฅผ๋ค์ด ์น ์ฌ์ดํธ์ ๋ฐฉ๋ฌธํ์๋ ๋ฐฉ๋ฌธํ ์๊ฐ์ ๊ธฐ๋กํ๋ ์ธํฐ์ ํฐ VisitTimeInterceptor ๊ฐ ์๋ค๊ณ ๊ฐ์ ํ๊ฒ ๋ค.
public class VisitTimeInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletReqeust request, HttpServletResponse response) {
HttpSession session = request.getSession();
if(session.getAttribute("visitTime") == null) {
session.setAttribute("visitTime", LocalDateTime.now());
}
return true;
}
}
์ปจํธ๋กค๋ฌ์์๋ ์๋์ ๊ฐ์ด ์ธ์ ์ ๋ด๊ธด visitTime์ ๊ฐ์ ธ์ฌ ์ ์๋ค.
@GetMapping
public String getEvents(Model model, @SessionAttribute LocalateTime visitTime) {
}
HttpSession ์ ์ด์ฉํด์ ๊บผ๋ผ ์๋ ์๋๋ฐ ๋ฌธ์ ๋ LocalDateTime ์ด ์๋๋ผ Object ๋ก ๋์จ๋ค. ์ฆ, ๊ทธ๋ฌ๋ฉด Type Conversion
์ด ์ผ์ด๋๊ฒ๋๋ค. ํ์ง๋ง
@SessionAttribute๋ฅผ ์ด์ฉํ๋ฉด Type Conversion
์ ์๋์ผ๋ก ์ง์ํด์ค๋ค.
@GetMapping
public String getEvents(Model model, HttpSession httpSession) {
Object visitTime = httpSession.getAttribute("visitTime");
}