자가 호출은 굉장히 빈번하게 일어나는 일이면서 별다른 문제가 없지만 스프링 빈 메서드에서 자가 호출이 일어나면 이야기가 다릅니다.
스프링의 빈 메서드에서 발생하는 자가 호출은 개발자의 의도를 벗어나는 결과를 만들 수 있습니다. 특히 자가 호출되는 메서드에 AOP 애너테이션이 지정돼 있을 경우 문제가 됩니다. 예를 들어, 다음과 같은 스프링 컴포넌트가 있다고 해봅시다.
@Controller
@RequiredArgsConstructor
public class MyController {
private final MyService myService;
@ResponseStatus(HttpStatus.OK)
@GetMapping
public Object doSomething() {
myService.doSomething1();
return null;
}
}
@Service
@RequiredArgsConstructor
public class MyService {
public void doSomething1() {
doSomething2();
}
@Transactional
public void doSomething2() {
// do something
}
}
코드의 완결성 보다는 MyController 컴포넌트가 MyService 컴포넌트의 doSomething1() 메서드를 호출하고 doSomething1() 메서드는 자가 호출로 doSomething2() 메서드를 호출하는 상황이라는 점에 주목합니다. 또한 MyService.doSomething2() 메서드에 @Transaction 애너테이션이 적용된 점도 주목합니다. 아마도 개발자는 doSomething2() 메서드가 트랜잭션 상태로 동작하길 원했던 것 같습니다.
하지만 애석하게도 이 상황에서 doSomething2() 메서드에 적용된 트랜잭션은 제대로 동작할 수 없습니다. 왜냐하면 자가 호출이 발생하면 호출되는 메서드에 적용된 AOP 애너테이션이 동작하지 않습니다.
다시 말해 doSomething2() 메서드는 doSomething1() 메서드의 자가 호출로 실행되므로 doSomething2() 메서드에 걸린 @Transaction 애너테이션의 부가 기능은 실행되지 않습니다.
이것은 스프링의 AOP가 포록시 기반으로 동작하기 때문입니다. 스프링 AOP는 프록시 객체를 만들어 추가 동작을 삽입하는 방식으로 AOP의 부가 기능이 동작하게됩니다. 그래서 메서드에 지정된 AOP 애너테이션이 수행되려면 반드시 이 프록시 객체를 통해 메서드가 실행되어야 합니다.
MyController | ----------------> | MyServiceProxy | ----------------> | MyService |
부가 기능이 있는 곳 | 자가 호출되는 상황 |
이러한 탓에 메서드를 자가 호출하는 상황에서는 프록시의 부가 기능이 실행되지 못합니다. 프록시를 거치지 않고 클래스에 정의된 메서드를 곧바로 호출하기 때문입니다.
자가 호출은 스프링을 개발하면서 자주 발생하는 비교적 잘 알려진 실수 유형입니다. @Transaction 과 같은 AOP 기능을 사용하기 위해서 자가 호출을 사용해서는 안됩니다.
'Spring, Springboot' 카테고리의 다른 글
타입 기반 주입 (0) | 2024.07.15 |
---|---|
서비스 ( Service ) (1) | 2024.07.10 |
Spring Cloud OpenFeign (1) | 2024.02.07 |
전역 에러 처리 (0) | 2024.01.30 |
CORS 이해와 설정 (0) | 2024.01.29 |