Spring @Async로 비동기 처리 - 2

Chan Young Jeong·2023년 4월 10일
0

스프링

목록 보기
4/7

이 글은 해당 아티클을 참고 및 번역하였습니다.


이번에는 직접 지난 시간에 보았던 @Async annotation을 이용해서 실험을 해볼 것입니다. 코드는 간단하게 UI에서 back-end 컨트롤러로 정보를 전달하는 것입니다.

@RestController
public class GreetController {

@Autowired
private AsyncMailTrigger greeter;

@RequestMapping(value = "/greet", method = RequestMethod.GET)
public String greet(HttpServletRequest request) throws Exception {
    String name = request.getParameter("name");
    greeter.asyncGreet(request); // 비동기 코드
    System.out.println(Thread.currentThread() + " Says Name is " + name);
    System.out.println(Thread.currentThread().getName() + " Hashcode " + request.hashCode());
    return name;

    }
}
@Component
public class AsyncMailTrigger {

@Async
public void asyncGreet(HttpServletRequest request) throws Exception {
    System.out.println("Trigger mail in a New Thread :: "  + Thread.currentThread().getName());
    System.out.println(Thread.currentThread().getName() + " greets before sleep " + request.getParameter("name"));
    Thread.sleep(1000); // 비동기적으로 실행시키기 위해 일부로 sleep을 호출
    System.out.println(Thread.currentThread().getName() + " greets " + request.getParameter("name"));
    System.out.println(Thread.currentThread().getName() + " Hashcode " + request.hashCode());
} 

}

실행결과


실행 결과를 보면 비동기적으로 asynGreet 메서드도 내가 정의한 Executor 쓰레드 풀에서 잘 실행되었다. 하지만 sleep한 이후에 print하는 과정에서 에러가 발생했다. 그 이유에 대해서 알아보자

이유

이 문제를 이해하기 위해서는 request의 lifecycle에 대해서 알아야한다.
request는 servelet container에 의해 servlet service method call 전에 만들어진다. request는 dispatcher servlet에 의해 전달된다. dispatcher servelet에서 request mapping을 통해 controller를 확인하고 해당 method를 호출한다. 그리고 request가 served되면 , servlet container는 request object를 삭제하거나 그 상태를 reset한다. ( 이 것은 container implemetain에 따라 다르다.)

하지만 여기서 중요한 것은 request가 served되고 response가 보내지면 , container는 request object의 상태를 reset하거나 삭제한다는 것이다.

우리의 코드에서는 async 부분을 잘 봐보자. 우리는 request 객체를 바로 asyncGreet 메서드에 전달하고 request의 정보를 바로 추출하려고 한다. 하지만 이 코드는 비동기적이기 때문에 우리의 main 쓰레드는 비동기 메소드의 실행을 기다리지 않는다. 따라서 main 쓰레드는 자신의 흐름에 따라 print하고 response를 보내버린다.

그렇게 되면 async 쓰레드에서는 여전히 request 객체를 필요로 하는데 sleep하는 동안servelt container에 의해 request object가 없어진 것이다.

무엇을 배웠는가?

절대로 request object 혹은 request ,response 와 관련된 것들(header)을 async 메서드에 직접 넘기지 말아라!

Never pass a Request object or any object related to Request/Response (headers) directly while using async; you never know when your response will be committed and refresh the state. If you do, you will face an intermittent error.

어떻게 할 수 있는가?

만약 request로 부터 async 메소드에 정보를 넘기고 싶으면 새로운 객체를 만들어서 그 객체에 해당 정보를 set하고 넘겨줘라!

public class RequestVO {

String name;

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
} 

}
@Component
public class AsyncMailTrigger {

@Async
public void asyncGreet(RequestVO reqVO) throws Exception {
    System.out.println("Trigger mail in a New Thread :: "  + Thread.currentThread().getName());
    System.out.println(Thread.currentThread().getName() + " greets before sleep" + reqVO.getName());
    Thread.sleep(1000);
    System.out.println(Thread.currentThread().getName() + " greets" + reqVO.getName());

} 

}
@RestController
public class GreetController {

@Autowired
private AsyncMailTrigger greeter;

@RequestMapping(value = "/greet", method = RequestMethod.GET)
public String greet(HttpServletRequest request) throws Exception {
    String name = request.getParameter("name");

    RequestVO vo = new RequestVO();
    vo.setName(name);
    //greeter.asyncGreet(request);
    greeter.asyncGreet(vo);

    System.out.println(Thread.currentThread() + " Says Name is " + name);
    System.out.println(Thread.currentThread().getName() + " Hashcode" + request.hashCode());
    return name;
    }
}

이렇게 하면 오류 없이 잘 작동하는 걸 볼 수 있다.


https://stackoverflow.com/questions/26036459/what-is-a-life-of-httpservletrequest-object

0개의 댓글