-
Notifications
You must be signed in to change notification settings - Fork 0
Description
스프링 AOP
자동 프록시 생성
이전까지의 작업으로 타깃코드는 깔끔한 채로 남아 있고, 부가기능은 한 번만 만들어 모든 타깃과 메소드에 재사용이 가능하고, 타깃의 적용 메소드를 선정하는 방식도 독립적으로 작성할 수 있도록 분리되어 있다.
그러나 프록시 팩토리 빈 방식의 접근 방법의 한계라고 생각했던 두 가지 문제중에 하나가 남았다. 부가기능이 타깃 오브젝트마다 새로 만들어지는 문제는 스프링 ProxyFactoryBean의 어드바이스를 통해 해결됐다. 나머지 하나는 부가기능의 적용이 필요한 타깃 오브젝트마다 거의 비슷한 내용의 ProxyFactoryBean 빈 설정정보를 추가해주는 부분이다. 설정은 매번 복사해서 붙이고 target 프로퍼티의 내용을 수정해줘야 한다.
중복 문제의 접근 방법
이 책의 초반부이긴 하지만 이전에는 전략 패턴과 DI를 적용했다. 그리고 또 다른방법으로는 프록시에서 반복적인 위임 코드가 필요한 프록시 클래스 코드를 적용하여 해결했다. 타깃 오브젝트로의 위임 코드와 부가기능 적용을 위한 코드가 프록시가 구현해야 하는 모든 인터페이스 메소드마다 반복적으로 필요했다. 그리고 이를 위해 JDK의 다이내믹 프록시를 이용하여 특정 인터페이스를 구현한 오브젝트에 대해서 프록시 역할을 해주는 클래스를 런타임 시 내부적으로 만들도록 하였다.
변하지 않는 타깃으로의 위임과 부가기능 적용 여부 판단이라는 부분은 코드 생성 기법을 이용하는 다이내믹 프록시 기술에 맡기고, 변하는 부가기능 코드는 별도로 만들어서 다이내믹 프록시 생성 팩토리에 DI로 제공하는 방법을 사용했다. 의미 있는 부가기능 로직인 트랜잭션 경계설정은 코드로 만들게 하고, 기계적인 코드인 타깃 인터페이스 구현과 위임, 부가기능 연동 부분은 자동생성하게 한 것이다.
그러나 지금 해결해야하는 것.. 나머지 하나는 부가기능의 적용이 필요한 타깃 오브젝트마다 거의 비슷한 내용의 ProxyFactoryBean 빈 설정정보를 추가해주는 부분.. 이것을 해결하기 위해서 빈 후처리기를 이용하면 된다.
빈 후처리기를 이용한 자동 프록시 생성기
빈 후처리기란?
- 이름 그대로 스프링 빈 오브젝트로 만들어지고 난 후에, 빈 오브젝트를 다시 가공할 수 있게 해준다.
스프링이 제공하는 DefaultAdvisorAutoProxyCreator는 어드바이저를 이용한 자동 프록시 생성기다. 빈 후처리기를 스프링에 적용하는 방법은 간단하다. 빈 후처리기 자체를 빈으로 등록하면 된다. 스프링은 빈 후처리기가 빈으로 등록되어 있으면 빈 오브젝트가 생성될 때마다 빈 후처리기에 보내서 후처리 작업을 요청한다. 빈 후처리기는 빈 오브젝트의 프로퍼티를 강제로 수정할 수도 있고 별도의 초기화 작업을 수행할 수도 있다. 만들어진 빈 오브젝트 자체를 바꿔치기할 수도 있다. 따라서 스프링이 설정을 참고해서 만든 오브젝트가 아닌 다른 오브젝트를 빈으로 등록시키는 것이 가능하다. 그래서 이를 잘 이용하면 스프링이 생성하는 빈 오브젝트의 일부를 프록시로 포장하고, 프록시를 빈으로 대신 등록할 수도 있다. 바로 이것이 자동 프록시 생성 빈 후처리기다.
위의 그림은 빈 후처리기를 이용한 자동 프록시 생성 방법을 설명한다.
DefaultAdvisorAutoProxyCreator빈 후처리기가 등록되어 있으면 스프링은 빈 오브젝트를 만들 때마다 후처리기에게 빈을 보낸다.DefaultAdvisorAutoProxyCreator는 빈으로 등록된 모든 어드바이저 내의 포인트컷을 이용해 전달받은 빈이 프록시 적용대상인지 확인한다.- 프록시 적용 대상이면 그때는 내장된 프록시 생성기에게 현재 빈에 대한 프록시를 만들게하고, 만들어진 프록시에 어드바이저를 연결해준다.
- 빈 후처리기는 프록시가 생성되면 원래 컨테이너가 전달해준 빈 오브젝트 대신 프록시 오브젝트를 컨테이너에게 돌려준다.
- 컨테이너는 최종적으로 빈 후처리기가 돌려준 오브젝트를 빈으로 등록하고 사용한다.
적용할 빈을 선정하는 로직이 추가된 포인트컷이 담긴 어드바이저를 등록하고 빈 후처리기를 사용하면 일일이 ProxyFactoryBean 빈을 등록하지 않아도 타깃 오브젝트에 자동으로 프록시가 적용되게 할 수 있다.
포인트 컷이 클래스 필터와 메소드 매처 두 가지를 돌려주는 메소드를 가지고 있다.
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
}포인트 컷 표현식
포인트 컷 표현식이 왜 쓰일까?
리플렉션 API를 통해서 할 수 있는데, 이 코드를 작성하는게 꽤나 번거로운 작업이다. 그래서 아주 간단한 문자열인 표현식 언어를 사용해서 포인트컷을 작성할 수 있다.
그래서 AOP란 무엇인가?
초반부터 지금까지 해온 작업을 하나씩 살펴보자
트랜잭션 서비스 추상화
- 트랜잭션 적용이라는 추상적인 작업 내용은 유지한 채로 구체적인 구현 방법을 자유롭게 바꿀 수 있도록 서비스 추상화 기법을 적용. 비즈니스 로직 코드는 트랜잭션을 어떻게 처리해야 한다는 구체적인 방법과 서버환경에서 종속되지 않는다.
프록시와 데코레이터 패턴
- DI를 이용해 데코레이터 패턴을 적용
- 인터페이스와 DI를 통해 접근하도록 설계하고, 데코레이터 패턴을 적용해서, 비즈니스 로직을 담은 클래스의 코드에는 전혀 영향을 주지 않으면서 트랜잭션이라는 부가기능을 자유롭게 부여할 수 있는 구조를 만들었다.
다이내믹 프록시와 프록시 팩토리 빈
- 프록시 클래스 없이도 프록시 오브젝트를 런타임 시에 만들어주는 JDK 다이내믹 프록시 기술을 적용
- 프록시 클래스 코드 작성의 부담도 덜고, 부가 기능 부여 코드가 여기저기 중복돼서 나타나는 문제도 일부 해결했다.
- 그러나 동일한 기능의 프록시를 여러 오브젝트에 적용할 경우 오브젝트 단위로 중복이 일어나는 문제는 해결하지 못했다.
자동 프록시 생성 방법과 포인트 컷
- 스프링 컨테이너의 빈 생성 후처리 기법을 활용해 컨테이너 초기화 시점에서 자동으로 프록시를 만들어주는 방법을 도입
- 트랜잭션 부가기능을 어디에 적용하는지에 대한 정보를 포인트컷이라는 독립적인 정보로 완전히 분리
부가기능의 모듈화
- 관심사가 같은 코드를 분리해 한데 모으는 것은 소프트웨어 개발의 가장 기본이 되는 원칙
- 그래서 많은 개발자는 핵심기능을 담당하는 코드 여기저기에 흩어져 나타나야 했던 이런 부가기능을 어떻게 독립적인 모듈로 만들 수 있을까를 고민
- DI, 데코레이터 패턴,다이내믹 프록시, 오브젝트 생성 후처리, 자동 프록시 생성, 포인트 컷과 같은 기법을 이용하여 이를 해결
- 독립적으로 모듈화되어 있기 떄문에 이 코드는 중복되지 않으며, 변경이 필요하면 한 곳만 수정하면 된다. 또한 포인트 컷이라는 방법을 통해 부가기능을 부여할 대상을 선정할 수 있다.
AOP: 애스펙트 지향 프로그래밍
부가기능 모듈화 작업은 기존의 객체지향 설계 패러다임과는 구분되는 새로운 특성이 있다고 생각했다. 그래서 이런 부가기능 모듈을 객체지향 기술에서 주로 사용하는 오브젝트와는 다르게 특별한 이름으로 부르기 시작했다. 그것이 바로 애스팩트(aspect)다. 애스팩트란 그 자체로 애플리케이션의 핵심기능을 담고 있지는 않지만, 애플리케이션을 구성하는 중요한 한 가지 요소이고, 핵심기능에 부가되어 의미를 갖는 특별한 모듈을 가리킨다.
애플리케이션의 핵심적인 기능에서 부가적인 기능을 분리해서 애스펙트라는 독특한 모듈로 만들어서 설계하는 개발하는 방법을 애스팩트 지향 프로그래밍(Aspect Oriented Programming) 또는 약자로 AOP라고 한다. AOP는 OOP를 돕는 보조적인 기술이지 OOP를 완전히 대처하는 새로운 개념은 아니다. AOP는 애스펙트를 분리함으로써 핵심기능을 설계하고 구현할 때 객체지향적인 가치를 지킬 수 있도록 도와주는 것이라고 보면 된다.
프록시를 이용한 AOP
스프링은 IoC/DI 컨테이너와 다이내믹 프록시, 데코레이터 패턴, 프록시 패턴, 자동 프록시 생성 기법, 빈 오브젝트의 후처리 조작 기법 등의 다양한 기술을 조합해 AOP를 지원하고 있다. 독립적으로 개발한 부가기능 모듈을 다양한 타깃 오브젝트의 메소드에 다이내믹하게 적용해주기 위해 가장 중요한 역할을 맡고 있는 게 바로 프록시인데, 스프링은 프록시를 사용하고 있는게 핵심이다. 그래서 스프링 AOP는 프록시 방식의 AOP이다.
바이트코드 생성과 조작을 통한 AOP
AspectJ는 프록시를 사용하지 않는 대표적인 AOP 기술이다. AspectJ는 프록시처럼 간접적인 방법이 아니라, 타깃 오브젝트를 뜯어고쳐서 부가기능을 직접 넣어주는 직접적인 방법을 사용한다. 부가기능을 넣는다고 타깃 오브젝트의 소스코드를 수정할 수는 없으니, 컴파일된 타깃의 클래스 파일 자체를 수정하거나 클래스가 JVM에 로딩되는 시점을 가로채서 바이트코드를 조작하는 복잡한 방법을 사용한다.
근데 왜 바이트 코드 조작과 같은 복잡한 방법을 사용할까?
- 바이트코드를 조작해서 타깃 오브젝트를 직접 수행해버리면 스프링과 같은
DI 컨테이너의 도움을 받아서 자동 프록시 생성 방식을 사용하지 않아도 AOP를 적용할 수 있기 때문이다. - 프록시 방식보다
훨씬 강력하고 유연한 AOP가 가능하기 때문이다.프록시를 AOP의 핵심 메커니즘으로 사용하면 부가기능을 부여할 대상은 클라이언트가 호출할 때 사용하는 메소드로 제한된다. 하지만 바이트코드를 직접 조작해서 AOP를 적용하면 오브젝트의 생성, 필드 값의 조회와 조작, 스태틱 초기화 등의 다양한 작업에 부가 기능을 부여해줄 수 있다. 타깃 오브젝트가 생성되는 순간 부가기능을 부여해주고 싶을 수도 있다. 하지만 프록시 방식에서는 이런 작업이 불가능하다. 그리고 프록시 적용이 불가능한 private 메소드의 호출, 스태틱 메소드 호출이나 초기화, 심지어 필드 입출력 등에 부가기능을 부여하려고 하면 클래스 바이트코드를 직접 조작해서 타깃 오브젝트나 호출 클라이언트의 내용을 수정하는 것밖에는 방법이 없다.
참고: Spring AOP Self-Invocation
스프링 AOP를 기본적으로 사용하면서 동시에 AspectJ를 이용할 수도 있다.
AOP 용어
타깃
- 부가기능을 부여할 대상
- 핵심기능을 담은 클래스일 수도 있지만 경우에 따라서는 다른 부가기능을 제공하는 프록시 오브젝트일 수도 있다.
어드바이스
- 타깃에게 제공할 부가기능을 담은 모듈
- 오브젝트로 정의하기도 하지만 메소드 레벨에서 정의할 수도 있다.
조인 포인트
- 어드바이스가 적용될 수 있는 위치를 말한다.
- 스프링의 프록시 AOP에서 조인 포인트는 메소드의 실행 단계뿐
- 타깃 오브젝트가 구현한 인터페이스의 모든 메소드는 조인 포인트가 된다.
포인트컷
- 어드바이스를 적용할 조인 포인트를 선별하는 작업 또는 그 기능을 정의한 모듈
- 스프링 AOP의 조인 포인트는 메소드의 실행이므로 스프링의 포인트컷은 메소드를 선정하는 기능을 갖고 있다.
프록시
- 클라이언트와 타깃 사이에 투명하게 존재하면서 부가기능을 제공하는 오브젝트
- DI를 통해 타깃 대신 클라이언트에게 주입되며, 클라이언트의 메소드 호출을 대신 받아서 타깃에 위임해주면서, 그 과정에서 부가기능을 부여한다.
어드바이저
- 포인트컷과 어드바이스를 하나씩 갖고 있는 오브젝트
- 어떤 부가기능(어드바이스)을 어디에(포인트컷) 전달할 것인가를 알고 있는 AOP의 가장 기본이 되는 모듈
애스팩트
- OOP의 클래스와 마찬가지로 애스팩트는 AOP의 기본 모듈
- 포인트컷과 어드바이스의 조합으로 만들어지며 보통 싱글톤 형태의 오브젝트로 존재
이후에 AOP 관련된 것은 스프링 과거 버전의 내용이라 생략하겠습니다.
트랜잭션 속성
트랜잭션
- ACID 원칙을 지키고 있는 더 이상 쪼갤 수 없는 최소 단위의 작업
- @Transactional
트랜잭션 전파
- 트랜잭션의 경계에서 이미 진행 중인 트랜잭션이 있을 때 또는 없을 때 어떻게 동작할 것인가를 결정하는 방식
격리수준
- 트랜잭션의 I 원칙과 관련된 내용으로 격리수준을 지키고 있어야한다.
제한시간
- 트랜잭션을 수행하는 제한시간을 설정할 수 있다. REQUIRED나 REQUIRES_NEW와 함께 사용해야만 의미가 있다.
읽기전용
- 읽기전용(read only)으로 설정해두면 트랜잭션 내에서 데이터를 조작하는 시도를 막아줄 수 있다. 또한 데이터 액세스 기술에 따라서 성능이 향상될 수도 있다.
트랜잭션 대체 정책
스프링은 @Transactional을 적용할 때 4단계의 대체 정책을 이용하게 해준다.
메소드의 속성을 확인할 때 타깃 메소드 > 타깃 클래스 > 선언 메소드 > 선언 타입
가장 먼저 발견되는 속성정보를 사용하게 하는 방법이다.
- 타깃 오브젝트의 메소드
- [5],[6]이
@Transactional이 위치할 수 있는 첫 번째 후보 - 여기서 애노테이션이 발견되면 바로 애노테이션의 속성을 가져다 해당 메소드의 트랜잭션 속성으로 사용
- 타깃 클래스
- [4]에
@Transactional이 존재하는지 확인 - 클래스에 부여되면 해당 클래스의 모든 메소드에 공통적으로 적용되는 속성이 된다.
- 특정 메소드만 공통 속성을 따르지 않는다면 해당 메소드에 추가로
@Transactional부여
- 인터페이스의 메소드
- [2],[3]에
@Transactional이 부여됐는지 확인하고 속성을 적용
- 인터페이스
- [1]에
@Transactional이 부여됐는지 확인 - 인터페이스에
@Transactional을 두면 구현 클래스가 바뀌더라도 트랜잭션 속성을 유지할 수 있다는 장점이 있다.



