Browse Author

HJ.Park

AOP와 프록시 그리고 스프링 컨테이너

안녕하세요. 가자고 시니어 개발자 HJ.Park 입니다.
이번 포스트에서는 Java 진영에서 가장 많이 사용하고 있는 Spring 프레임워크에서 우리가 자연스럽게 사용하고 있지만 범하기 쉬운 실수에 대해서 공유합니다.
경험에 대해 자연스럽게 공유하기 위해서 편하게(라고 쓰고 반말이라고 읽습니다.) 쓰도록 하겠습니다.

사건의 발단은 이랬다.

가자고의 상품 유형은 매우 다양했고 그 다양성 때문에 결제로직은 매우 복잡한 비지니스 로직을 가지고 있었다.
결제 모듈을 담당한 개발자(이하 A 개발자)가 복잡한 비지니스 로직을 풀기 위해서 팩토리 패턴과 필터 패턴을 사용했는데 A 개발자는 팩토리에서 새 필터 인스턴스(new로 생성한)를 반환하도록 설계했다.
아마 그 개발자는 일부러 그렇게 설계했었던 것 같다. 처음에 해당 로직은 이상없이 잘 돌아갔다. 하지만 문제는 유지보수 및 기능을 계속 추가하면서 발생했다.
해당 모듈을 다른(이하 B 개발자) 개발자가 인수인계를 받았으나 해당 부분에 대한 유의사항(팩토리에서 반환하는 인스턴스가 스프링의 인스턴스가 아니라는 사실을…)을 전달받지 못했다.

그리고 약 6개월 후 우리는 팩토리에서 반환되는 인스턴스 필터의 메서드에 @Transactional을 걸었다…
처음에는 @Transactional이 복잡한 비지니스 로직과 구조속에 깊이 감추어져 있었기 때문에 우리의 잘못을 바로 알아채지 못했다.

시간이 지나면서 우리의 예상과 다르게 롤백된 데이터들이 발견되기 시작했다.
처음엔 그냥 하이버네이트의 트랜젝션 매니저(우리는 하이버네이트를 사용중이다.)가 이상하게 동작한다고 생각하고 하이버네이트를 욕하고 넘어갔다. 🙁

그리고 약 일주일 후 필터 인스턴스에 AOP를 걸려고 했지만 AOP가 제대로 동작하지 않았다.(@Transactional도 AOP로 동작한다.)
그래서 디버깅한 결과 필터 인스턴스가 new로 반환된 인스턴스이기 때문에 AOP가 전혀 동작하고 있지 않았던 것이다.
(스프링 프레임워크에서 AOP를 사용하기 위해서는 인스턴스에 프록시가 걸려있어야 하고 스프링 컨테이너에 등록되어 있는 인스턴스는 프록시가 걸려있다.
반면 동일한 클래스라고 해도 new로 생성한 인스턴스는 프록시가 걸려있지 않다.)

스프링의 빈을 사용하는 규칙은 간단하지만 이러한 실수는 복잡한 비지니스로직과 구조… 그리고 여러 개발자를 거치다 보면 이러한 문제는 어느 프로젝트나 발생할 수 있는 문제다.
그렇기 때문에 우리는 스프링 컨테이너와 AOP 그리고 프록시에 대한 아래 기본적인 사항을 꼭 숙지해야한다.

스프링 컨테이너

스프링을 사용한다면 스프링 컨테이너에 대한 기본적인 이해가 필요하다.
우리는 스프링의 인젝션(@Inject, @Autowire, @Resource…)을 사용하면서도 이 인스턴스들이 어디에서 오는지 별로 신경을 쓰지 않는 사람들도 많다.
그래서 스프링의 인스턴스들이 싱글턴으로 관리되고 있음을 망각하고 new를 사용하는 경우도 가끔 있다.
물론 단순한 로직이나 구조에서는 잘 일어나지는 않지만 복잡한 비지니스로직과 구조가 만나면 누구라도 충분히 할 수 있는 실수다.
스프링의 org.springframework.stereotype 패키지에는 @Component, @Controller, @Repository, @Service등의 annotation(이하 애노테이션)이 있다. 이 애노테이션을 클래스에 걸고 스프링에 해당 패키지를 스캔하도록 설정하면 해당 클래스들은 스프링이 초기화될 때 스프링 컨테이너에 하나의 인스턴스가 들어가게 된다.
물론 @Bean으로 직접 인스턴스를 생성, 설정하고 return하는 방법이나 XML로 선언하는 방법 등이 있다.

AOP와 프록시

스프링의 AOP는 대상 인스턴스에 반드시 프록시가 걸려있어야 작동을 할 수 있다.
인스턴스에 프록시가 걸리기 위해서는 스프링 컨테이너에 빈을 등록해야하며 반드시 스프링 컨테이너에 등록된 빈을 사용해야만 한다.

예제 – 스프링 컨테이너에 등록된 빈과 새로 생성한 인스턴스 비교

소스코드: https://github.com/hyunjun19/spring-boot-sample-aop

스크린샷 2016-08-23 오전 12.52.34
위 이미지를 보면 springInstanceService와 newInstanceService는 동일한 HelloWorldService 클래스의 인스턴스이지만 hashcode가 2872, 2876으로 각기 다른 인스턴스임을 알수 있다.
또한 springInstanceService를 보면 CglibAopProxy가 여러개 걸려있는 모습을 볼수있는 반면에 newInstanceService는 아무런 프록시도 걸려있지 않은 모습을 볼수 있다.