본문 바로가기
Spring&SpringBoot/<토비의 스프링 3.1 Vol1.1>, 이일민

트랜잭션 코드 분리를 위한 개선 과정 : 프록시, 다이나믹 프록시, 팩토리 빈, 빈 후처리기, 포인트컷

by 민휘 2023. 4. 20.

목표

서비스의 트랜잭션 코드 분리. 확장 가능한 설계를 고려하여 서비스나 부가기능이 늘어나더라도 코드 변경을 최소화할 것

 

개선 과정

Step1 : 서비스에 데코레이터 적용

서비스의 트랜잭션 코드를 분리하기 위해 데코레이터 패턴을 적용하여 프록시를 만듦. 이때 프록시는 클라이언트와 타깃 사이에서 요청을 중개하며 기능 위임과 부가기능을 적용할 메소드를 선택하고 부가기능을 적용하는 역할을 한다. 프록시를 코드로 직접 생성하면 기능위임 코드를 일일이 작성해야하고, 부가기능 코드가 메소드와 타깃마다 반복된다.

 

 

Step2 : 다이나믹 프록시로 변경

프록시 클래스를 코드로 작성해서 발생한 문제이므로 런타임 시점에 동적으로 프록시 클래스를 만드는 방법을 적용한다. 다이나믹 프록시는 리플렉션으로 생성한다. 리플렉션을 사용하면 Method 인터페이스 invoke로 기능위임 코드를 추상화할 수 있다. 어떤 부가기능을 어떤 메소드에 적용할지는 개발자가 정보를 알려줘야하므로 handler에 이 책임을 넘겼다. 이때 클라이언트, 다이나믹 프록시, handler, 타겟 관계에서 책임이 할당된 양상을 보면 다이나믹 프록시는 클라이언트의 요청을 받아주고 handler에게 클라이언트의 요청 정보를 넘기는 요청 중개의 책임이 있다. Handler는 기능 위임, 부가기능 적용 메소드 선택, 부가기능 적용의 책임을 맡고 있다. 이때 handler는 책임을 수행하기 위한 상태 정보인 타깃 오브젝트에 의존한다.

 

 

Step3 : 팩토리 빈 도입

다이나믹 프록시를 스프링 컨텍스트로 DI하려면 기본 생성자로 만들고 세터로 주입하는 방식은 사용할 수 없다. 다이나믹 프록시는 기본 생성자를 사용할 수 없고, Proxy 클래스의 static 메소드를 사용하기 때문이다. 그래서 기본 생성자가 있으면서 다이나믹 프록시를 생성하는 역할을 가진 팩토리 빈을 스프링 빈으로 등록한다. 다이나믹 프록시 객체를 생성하는 시점에 필요한 의존성(target, handler의 의존성)을 명시적으로 주입하는 방법을 사용한다.

 

 

Step4 : 다이나믹 프록시 생성을 추상화한 스프링 다이나믹 프록시 팩토리 빈

기존 JDK 다이나믹 프록시를 사용하는 방법은 Handler가 너무 많은 책임을 가진다. 타깃이나 부가기능이 추가될 때마다 새로운 다이나믹 프록시를 필요로 하기 때문에 설정정보에 팩토리빈 중복, Handler 클래스도 중복되는 문제가 발생한다. 그래서 스프링은 다이나믹 프록시를 생성하는 방법을 추상화해서 부가기능을 여러 타깃에 재사용할 수 있도록 한다. 기능 위임은 Invoction 콜백으로 분리하고 템플릿인 부가기능 클래스에서 사용하도록 한다.(콜백은 프록시가 생성해줌) 부가기능 자체를 담은 어드바이스와 부가기능을 적용할 메소드를 선택하는 포인트컷을 순수한 코드로 분리해 빈으로 등록하고, 이 둘의 조합을 어드바이저로 묶어 빈으로 등록한다. 프록시 팩토리 빈을 등록할 때는 타깃과 어드바이저를 선택하면 된다. 어드바이저는 타깃에 의존하지 않으므로 프록시에 의해 공유될 수 있다.

 

 

Step5 : 빈 후처리로 자동 프록시 생성기 도입

프록시 팩토리 빈을 등록하는 설정 코드가 중복된다. 어드바이저는 동일한데, 타깃이 달라지면 비슷한 설정이 중복된다. 따라서 팩토리 빈을 명시적으로 빈으로 등록하지 말고, 프록시 자동 생성기 DefaultAdvisorAutoProxyCreator를 빈으로 등록한다. 이 클래스는 빈 후처리기인데, 스프링이 생성하는 모든 빈은 이 빈 후처리기로 전달되어 후작업이 들어간다. 프록시 등록을 위해 포인트컷을 적용하여 프록시 적용 여부를 확인하고, 만족한다면 프록시를 생성해 컨테이너에 등록하고 어드바이저에 연결한다. 이때 포인트컷은 aspectJ 표현식을 사용해 복잡한 설정을 간단하게 표현할 수 있다.