토비의 스프링 - 6장 (3)
이전까지는 이야기가 계속되던 프록시와 프록시 기법을 사용하게 된 트랜잭션 처리와 같은 부가 기능에 대해서 알아보았습니다. 그래서 오늘은 이러한 부가 기능을 스프링에서는 AOP라고 부르는데 이에 대한 이야기를 해보려고 합니다.
먼저 이전의 ProxyFactoryBean 로 빈을 생성 했었습니다. 그런데 ProxyFactoryBean 은 타깃이 되는 빈을 설정해줘야 합니다. 이런 설정을 따로 해주지 않아도 타깃 빈의 목록을 제공하면 자동으로 타깃 빈에 대한 프록시를 만들어주는 기법 중 하나인 빈 후처리 기법이 있습니다. 책에서는 빈 후처리기 중 하나인 DefaultAdvisorAutoProxyCreator 을 기준으로 설명을 하게 됩니다.
빈 후처리기는 빈으로 등록이 되는데 빈 오브젝트가 생성될 때마다 빈 후처리기에 후처리 작업을 요청하면서 프록시를 생성하게 됩니다. 이때 빈 후처리기는 빈 오브젝트의 프로퍼티를 강제로 수정할 수 도 있고 별도의 초기화 작업도 수행할 수 있습니다.
후처리 작업을 좀 더 자세히 설명하면 스프링은 빈 오브젝트를 만들때마다 등록된 빈 후처리기에 빈 오브젝트를 보내게 되고 그때 빈 후처리기에 등록된 모든 어드바이저 내의 포인트컷이 전달받은 빈이 프록시 적용 대상인지를 확인하고 대상이라면 내장된 프록시 생성기에서 프록시를 만들어 어드바이저에 연결하고 생성된 프록시를 빈 오브젝트 대신 돌려주게 됩니다. 결과 ProxyFactoryBean 에서 생기는 설정 문제를 해결할 수 있게 됩니다.
이때 포인트 컷은 항상 타깃 오브젝트의 특정 메소드에 부가 기능을 부여했었는데 포인트컷의 2가지 기능 중 메소드 매처만 사용했기 때문에 그랬던 것이고 다른 기능인 클래스 필터로 오브젝트를 선별 할 수 있습니다.
추가로 알아두면 좋은 것 중엔 포인트컷 표현식이라는 게 있습니다.
이는 자바 RegEx 라는 정규식처럼 클래스와 메서드 선정 알고리즘을 표현할 수 있는 방식입니다. 이는 사실 AspectJ 라는 유명 프레임워크에서 제공하는 것을 일부 가져와 확정해서 사용하는 것입니다.
표현법은 아래와 같습니다.
execution([접근 제한자 패턴] 타입패턴 [타입패턴.]이름패턴 (타입패턴 | "...", ...))
실제 적용예를 보겠습니다.
public class Target implements TargetInterface {
public int add(int a, int b) { return 0; }
}
"execution(public int com.joung.sample.Target.add(int, int))"
포인트컷 표현식을 사용하면 로직이 짧은 문자열 안에 담기게 되는데 이는 런타임 전까진 검증과 기능확인이 되지 않는 단점이 있기 때문에 많은 시간을 투자해서 학습과 테스트를 충분히 할 수 있도록 하는게 중요합니다.
AOP 를 설명하기 전, 책에서 비지니스 로직을 담았던 UserService 에 트랜잭션이라는 부가기능을 부여하기 위해 작업했던 내용들을 되돌아 보려고 합니다.
제일 먼저 트랜잭션 경계설정을 비지니스 코드에 섞으면서 생겼던 문제가 특정 트랜잭션 기술에 종속되는 문제점, 로컬 트랜잭션 방식 코드를 글로벌/분산 트랜잭션 방식으로 바꿀 때 모든 트랜잭션 코드들을 수정해야 한다는 문제점이 생겼습니다. 이를 해결 하기 위해 트랜잭션 적용 이라는 추상적인 작업 내용을 유지하면서 구체적인 구현 방법을 자유롭게 바꿀 수 있도록 서비스 추상화 기법 을 적용해 런타임 시 필요한 트랜잭션 기술을 주입 받아 어떤 기술이든 사용할 수 있도록 구현했습니다.
이후에는 트랜잭션 코드와 비지니스 로직 코드가 같이 있어 두 코드가 영향을 줄 수 있는 상황에 오게 되었습니다. 하지만 비지니스 로직 코드에는 트랜잭션 코드가 필요했고 이를 노출해야 할 필요성이 있었습니다. 하지만 경계설정을 담당하는 코드의 특성 때문에 더이상 추상화와 메소드 추출로는 방법이 없어 DI 를 통한 데코레이터 패턴을 적용하게 되었습니다. 데코레이터 패턴을 통해 비지니스 로직에는 영향을 받지 않으면서 트랜잭션이라는 부가기능을 자유롭게 부여 할 수 있는 구조로 바뀌게 되었습니다.
그런데 이런 데코레이터 패턴을 사용해도 모든 비지니스 로직 인터페이스의 메소드마다 트랜잭션 기능을 부여하는 코드를 작성해 프록시 클래스를 만드는것과 트랜잭션 기능이 부여되지 않아도 되는 메서드에도 프록시로서의 위임 때문에 일일이 구현하는것은 오히려 부담이 되었습니다. 그래서 이런 프록시 클래스를 런타임시 만들어주는 JDK 다이나믹 프록시 기술을 적용해 기능 부여 코드를 메서드 단위로 적용 할 수 있게 되었습니다. 다만 동일한 기능의 프록시를 여러 오브젝트에 적용 할때의 중복 문제는 해결할 수 없었습니다. 그래서 스프링의 프록시 팩토리 빈을 사용해 다이나믹 프록시 생성 방법에 DI를 도입했습니다.
마지막으로 트랜잭션 적용 대상이 되는 빈마다 프록시 팩토리 빈을 설정해줘야 하는 부담을 줄이기 위해 스프링 컨테이너의 빈 후처리 기법을 이용해 자동으로 프록시를 만들어 주는 방법을 도입하게 되었습니다.
이런 부가기능은 모듈화 되기가 어려운데 이는 명칭대로 부가기능이기 때문에 독립적인 방식으로 존재하기 어렵고 이를 추가할 대상, 즉 타깃이 있어야만 의미가 있습니다. 개발자들은 이런 코드들을 어떻게 독립적인 모듈로 만들까를 고민해왔습니다.
지금까지 배웠던 내용이 이런 부가 기능을 모듈화 하는 작업의 일환이였는데 이 부가기능 모듈화 작업을 개발자들은 객체지향 설계 패러다임과 다른 새로운 특성을 가지고 있다고 생각했습니다. 그리고 이런 모듈화 된 부가기능 모듈을 객체지향 오브젝트와는 다르게 애스팩트(Aspect) 라는 이름으로 부르기 시작했습니다. 그리고 이런 프로그래밍을 관점지향 프로그래밍 (AOP) 라고 부르게 되었습니다.
이런 AOP는 여러 방식이 있는데 프록시를 이용하는 방식의 AOP를 스프링에서는 자주 사용합니다. 그 외에 AspectJ 처럼 타깃 오브젝트를 뜯어 부가적인 기능을 부여하는 AOP 도 있습니다.
AOP 에서 주로 사용하는 용어를 마지막으로 정리하고 끝내도록 하겠습니다.
- 타깃 : 부가기능을 부여할 대상
- 어드바이스 : 타깃에게 제공할 부가기능을 담은 모듈
- 포인트컷 : 어드바이스를 적용할 선별 작업 또는 그 기능을 정의한 모듈
- 어드바이저 : 어떤 어드바이스를 어떤 포인트컷으로 적용할지를 알고 있는 AOP 의 기본 모듈
- 애스펙트 : OOP 의 클래스와 마찬가지의 AOP 의 기본 모듈
- 조인 포인트 : 어드바이스가 적용되는 위치 (프록시 AOP 에서의 메소드의 실행 단계)
- 프록시 : 클라이언트와 타깃 사이의 부가기능을 제공하는 오브젝트
이런 AOP 를 사용하기 위해선 4개의 빈을 등록하면 됩니다.
- 자동 프록시 생성기 (빈 후처리기)
- 어드바이스
- 포인트컷
- 어드바이저