Skip to content

Latest commit

ย 

History

History
291 lines (223 loc) ยท 10.9 KB

@Scheduled, @Async, @SessionAttribute vs @SessionAttributes.md

File metadata and controls

291 lines (223 loc) ยท 10.9 KB

@Scheduled

์Šค์ผ€์ค„๋Ÿฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์•„๋ž˜ ์กฐ๊ฑด์„ ๋‹ค ๋งŒ์กฑํ•ด์•ผ ํ•œ๋‹ค.

  • 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

์Šคํ”„๋ง ๋ฐฐ์น˜ vs ์Šค์ผ€์ค„๋Ÿฌ

https://stackoverflow.com/questions/46729567/what-is-the-difference-between-spring-scheduled-tasks-and-spring-batch-jobs

  • Spring Scheduler ๋Š” ์ผ์ •์— ๋”ฐ๋ผ ๋ฌด์–ธ๊ฐ€๋ฅผ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ํ•˜๊ธฐ ์œ„ํ•œ ๊ฒƒ
  • Spring Batch ๋Š” ๋ณต์žกํ•œ ์ปดํ“จํŒ… ๋ฌธ์ œ๋ฅผ ๊ตฌ์ถ•ํ•˜๊ธฐ ์œ„ํ•ด ์„ค๊ณ„๋œ ๊ฐ•๋ ฅํ•œ ์ผ๊ด„ ์ฒ˜๋ฆฌ ํ”„๋ ˆ์ž„์›Œํฌ
  • Spring Batch ๋Š” ์ž‘์—…์˜ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜์„ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š๊ณ  ๋นŒ๋“œ๋งŒ ์ฒ˜๋ฆฌ
  • ์›ํ•˜๋Š” ๊ฒฝ์šฐ Spring Scheduler ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Spring Batch ์ž‘์—…์„ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ ํ•  ์ˆ˜ ์žˆ๋‹ค.

์Šคํ”„๋ง ๋ฐฐ์น˜๋ž€ : http://wiki.gurubee.net/pages/viewpage.action?pageId=4949437

@Async

  • @Async ๋Š” ๋น„๋™๊ธฐ ์ž‘์—…์„ ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ๋” ์ง€์›ํ•ด์ฃผ๋Š” ๋…€์„์ด๋‹ค.
  • ๊ธฐ๋ณธ ์ „๋žต์€ ๋น„๋™๊ธฐ ์ž‘์—…๋งˆ๋‹ค ์Šค๋ ˆ๋“œ๋ฅผ ์ƒ์„ฑํ•˜๋Š” SimpleAsyncTaskExecutor ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
  • ์Šค๋ ˆ๋“œ ๊ด€๋ฆฌ ์ „๋žต์„ ThreadPoolTaskExecutor ๋กœ ๋ฐ”๊ฟ”์„œ ์Šค๋ ˆ๋“œํ’€์„ ์‚ฌ์šฉํ•˜๊ฒŒ๋” ํ•  ์ˆ˜ ์žˆ๋‹ค.

@Async ์‚ฌ์šฉ ์ „

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
            }            
        });
    }
}

@Async ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ ์„ค์ •

SimpleAsyncTaskExecutor ์‚ฌ์šฉ

@EnableAsync
@SpringBootApplication
public class Application {
    ...
}

@Async ์‚ฌ์šฉ

public class GreetingService {

    @Async
    public void method1(String message) throws Exception {
        // do something
    }
}

ThreadPoolTaskExecutor ์‚ฌ์šฉ

๊ธฐ์กด์— 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 ์ ‘๋‘์‚ฌ ์ง€์ •

@Asycn ์‚ฌ์šฉ ์‹œ ์ฃผ์˜์ 

  • private method ๋Š” ์‚ฌ์šฉ ๋ถˆ๊ฐ€, public method ๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
  • self-invocation(์ž๊ฐ€ ํ˜ธ์ถœ) ๋ถˆ๊ฐ€, ์ฆ‰ inner method๋Š” ์‚ฌ์šฉ ๋ถˆ๊ฐ€
  • QueueCapacity ์ดˆ๊ณผ ์š”์ฒญ์— ๋Œ€ํ•œ ๋น„๋™๊ธฐ method ํ˜ธ์ถœ์‹œ ๋ฐฉ์–ด ์ฝ”๋“œ ์ž‘์„ฑ
    • ์ตœ๋Œ€ ์ˆ˜์šฉ ๊ฐ€๋Šฅํ•œ Thread Pool ์ˆ˜์™€ QueueCapacity ๊นŒ์ง€ ์ดˆ๊ณผ๋œ ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด TaskRejectedException ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.
    • ๋”ฐ๋ผ์„œ, TaskRejectedException ์—๋Ÿฌ์— ๋Œ€ํ•œ ์ถ”๊ฐ€์ ์ธ ํ•ธ๋“ค๋ง์ด ํ•„์š”ํ•˜๋‹ค.

@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 ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ทธ๋Ÿฌํ•œ ๋ฐ˜๋ณต์ž‘์—… ์—†์ด ์ฝ”๋“œ๋ฅผ ๊น”๋”ํ•˜๊ฒŒ ๊ฐ€์ ธ๊ฐˆ ์ˆ˜ ์žˆ๋‹ค.
  • ๋™๊ธฐ/๋น„๋™๊ธฐ์— ๋Œ€ํ•œ ๊ณ ๋ฏผ์—†์ด ๋กœ์ง ๊ตฌํ˜„ ๊ณผ์ •์—๋งŒ ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ๋‹ค.

@SessionAttributes

  • ๋ชจ๋ธ ์ •๋ณด๋ฅผ HTTP ์„ธ์…˜์— ์ €์žฅํ•ด์ค€๋‹ค.
  • ์ด๊ฑฐ๋Œ€์‹  HttpSession ์„ ์ด์šฉํ•ด์„œ setAttribute ๋กœ HTTP ์„ธ์…˜์— ์ €์žฅํ•  ์ˆ˜ ๋„ ์žˆ๋‹ค.
  • @SessionAttributes ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ด ์–ด๋…ธํ…Œ์ด์…˜์„ ์„ค์ •ํ•œ ์ด๋ฆ„์— ํ•ด๋‹นํ•˜๋Š” ๋ชจ๋ธ ์ •๋ณด๋ฅผ ์ž๋™์œผ๋กœ ์„ธ์…˜์— ๋„ฃ์–ด์ค€๋‹ค.
    • ์ฆ‰, @ModelAttribute๋ฅผ HTTP Session์— ๋‹ด๋Š”๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค. (๋‹จ, ํ‚ค๊ฐ’์ด ๊ฐ™์€๊ฒฝ์šฐ)
  • @ModelAttribute ๋Š” ์„ธ์…˜์— ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋„ ๋ฐ”์ธ๋”ฉํ•œ๋‹ค.
  • ์—ฌ๋Ÿฌ ํ™”๋ฉด์—์„œ ์‚ฌ์šฉํ•ด์•ผํ•˜๋Š” ๊ฐ์ฒด๋ฅผ ๊ณต์œ ํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.
    • ex) ์žฅ๋ฐ”๊ตฌ๋‹ˆ
    • ex) ํผ์—์„œ ์ž…๋ ฅํ•˜๋Š” ๊ฐ’์ด ๋งŽ์€๊ฒฝ์šฐ ํ™”๋ฉด์„ ๋‚˜๋ˆ„๊ธฐ๋„ํ•˜๋Š”๋ฐ, ๊ทธ๋•Œ Aํ™”๋ฉด์—์„œ input ๊ฐ’ a, b๋ฅผ ๋ฐ›๊ณ  ๊ทธ ๋‹ค์Œ ํ™”๋ฉด B์—์„œ c, d๋ฅผ ๋ฐ›์•„์„œ ์กฐํ•ฉํ•  ์ˆ˜ ์žˆ๋‹ค.

SessionStatus

  • SessionStatus๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์„ธ์…˜ ์ฒ˜๋ฆฌ ์™„๋ฃŒ๋ฅผ ์•Œ๋ ค์ค„ ์ˆ˜ ์žˆ๋‹ค.
  • ํผ ์ฒ˜๋ฆฌ ๋๋‚˜๊ณ  ์„ธ์…˜ ๋น„์šธ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.

HttpSession ์„ ์ด์šฉํ•˜๋Š” ๊ฒƒ์€ low level ์ด๋ฉฐ @SessionAttributes ๋Š” ๋” ์ถ”์ƒํ™” ๋œ level ์ด๋‹ค.

Example

@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");

@SessionAttribute

  • HTTP ์„ธ์…˜์— ๋“ค์–ด์žˆ๋Š” ๊ฐ’์„ ์ฐธ์กฐํ•  ๋•Œ ์‚ฌ์šฉ
  • HttpSession ์„ ์‚ฌ์šฉํ•  ๋•Œ ๋น„ํ•ด ํƒ€์ž… ์ปจ๋ฒ„์ „์„ ์ž๋™์œผ๋กœ ์ง€์› ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํŽธ๋ฆฌํ•จ
  • HTTP ์„ธ์…˜์— ๊ฐ’์„ ๋„ฃ๊ณ  ๋นผ๊ณ ์‹ถ์€ ๊ฒฝ์šฐ๋Š” HttpSession ์„ ์‚ฌ์šฉ
  • @SessionAttributes ์™€๋Š” ๋‹ค๋ฅด๋‹ค
    • @SessionAttributes ๋Š” ํ•ด๋‹น ์ปจํŠธ๋กค๋Ÿฌ ๋‚ด์—์„œ๋งŒ ์ž‘๋™
      • ์ฆ‰, ํ•ด๋‹น ์ปจํŠธ๋กค๋Ÿฌ ์•ˆ์—์„œ ๋‹ค๋ฃจ๋Š” ํŠน์ • ๋ชจ๋ธ ๊ฐ์ฒด๋ฅผ ์„ธ์…˜์— ๋„ฃ๊ณ  ๊ณต์œ ํ•  ๋•Œ ์‚ฌ์šฉ
    • @SessionAttribute ๋Š” ์ปจํŠธ๋กค๋Ÿฌ๋ฐ–(์ธํ„ฐ์…‰ํ„ฐ, ํ•„ํ„ฐ ๋“ฑ)์—์„œ ๋งŒ๋“ค์–ด์ค€ ์„ธ์…˜ ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•  ๋•Œ ์‚ฌ์šฉ

์‚ฌ์‹ค @SessionAttribute ๋ฅผ ์ด์šฉํ•ด์„œ @SessionAttributes ๋กœ HTTP Session ์— ๋„ฃ์–ด์ค€ ๊ฐ’์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ์‚ฌ์‹ค ๊ทธ๋Ÿฐ ์šฉ๋„๋Š” ์•„๋‹ˆ๊ณ , ์ปจํŠธ๋กค๋Ÿฌ ๋‚ด์—์„œ๋Š” @SessionAttributes, SessionStatus, @ModelAttribute ๋ฅผ ์ด์šฉํ•ด์„œ ๋‹ค๋ฃฌ๋‹ค.

์›น ์‚ฌ์ดํŠธ์— ๋ฐฉ๋ฌธํ–ˆ์„๋•Œ ๋ฐฉ๋ฌธํ•œ ์‹œ๊ฐ„์„ ๊ธฐ๋ก Example

  • ์›น ์‚ฌ์ดํŠธ์— ๋ฐฉ๋ฌธํ–ˆ์„๋•Œ ๋ฐฉ๋ฌธํ•œ ์‹œ๊ฐ„์„ ๊ธฐ๋ก์„ ์ด์šฉํ•˜๋ฉด, ๊ฒŒ์ž„์ผ ๊ฒฝ์šฐ 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");
}

References