스터디/[토비의 스프링 3.1 Vol 1] (2025.03)

[토비의 스프링 3.1 Vol 1] 6장. AOP (6.5 스프링 AOP)

ttoance 2025. 4. 27. 10:00
반응형

6.5 스프링 AOP 

6.5.1 자동 프록시 생성 

  • 분리해낸 트랜잭션 코드는 투명한 부가기능 형태로 제공돼야 한다. 
    • 투명하다는 건 부가기능을 적용한 후에도 기존 설계와 코드에 영향을 주지 않아야 한다는 것이다. 
    • 투명한 부가기능을 적용하는 과정에서 발견했던 거의 대부분의 문제는 해결했다. 
      • 타깃 코드는 여전히 깔끔한 채로 남아 있고 
      • 부가기능은 한 번만 만들어 모든 타깃과 메소드에 재사용 가능하고 
      • 타깃의 적용 메소드를 선정하는 방식도 독립적으로 작성할 수 있도록 분리되어 있다. 
  • 프록시 팩토리 빈 방식의 접근 방법의 두 가지 문제중 
    • 부가기능이 타깃 오브젝트마다 새로 만들어지는 문제는 ProxyFactoryBean의 어드바이스 통해 해결했다. 
    • 남은 것은 부가기능의 적용이 필요한 타깃 오브젝트마다 거의 비슷한 내용의 ProxyFactoryBean 설정을 추가해주는 부분이다. 

 

빈 후처리기를 이용한 자동 프록시 생성 

  • DefaultAdvisorAutoProxyCreator은 어드바이저 이용한 자동 프록시 생성기다. 
    • 빈 후 처리기를 스프링에 적용하는 방법은 빈 후 처리기 자체를 빈으로 등록하면 된다. 
    • 스프링은 빈 후 처리기가 빈으로 등록되어 있으면 빈 오브젝트가 생성될 때마다 빈 후처리기에 보내서 후처리 작업을 요청한다. 
    • 빈 후 처리기는 빈 오브젝트의 프로퍼티를 강제로 수정할 수 있고 별도의 초기화 작업을 수행할 수 있다. 

 

  • 스프링은 빈 오브젝트를 만들 때마다 후처리기에게 빈을 보낸다. 
  • DefaultAdvisorAutoProxyCreator는 빈으로 등록된 모든 어드바이저 내의 포인트컷을 이용해 전달받은 빈이 프록시 적용 대상인지 확인한다. 
  • 프록시 적용 대상이면 그때는 내장된 프록시 생성기에게 현재 빈에 대한 프록시를 만들게 하고, 만들어진 프록시에 어드바이저를 연결해준다. 
  • 빈 후처리기는 프록시가 생성되면 원래 컨테이너가 전달해준 빈 오브젝트 대신 프록시 오브젝트를 컨테이너에게 돌려준다. 
  • 컨테이너는 최종적으로 빈 후처기가 돌려준 오브젝트를 빈으로 등록하고 사용한다. 

 

확장된 포인트컷 

  • 포인트컷은 클래스필터와 메소드 매처 두 가지를 돌려주는 메소드를 가지고 있다. 

 

6.5.2 DefaultAdvisorAutoProxyCreator의 적용 

Vol1-30/Ch6/6.5.2/src/springbook/user/service/NameMatchClassMethodPointcut.java

package springbook.user.service;

import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.NameMatchMethodPointcut;
import org.springframework.util.PatternMatchUtils;

public class NameMatchClassMethodPointcut extends NameMatchMethodPointcut {
	public void setMappedClassName(String mappedClassName) {
		this.setClassFilter(new SimpleClassFilter(mappedClassName));
	}
	
	static class SimpleClassFilter implements ClassFilter {
		String mappedName;
		
		private SimpleClassFilter(String mappedName) {
			this.mappedName = mappedName;
		}

		public boolean matches(Class<?> clazz) {
			return PatternMatchUtils.simpleMatch(mappedName, clazz.getSimpleName());
		}
	}
}

 

ServiceImpl로 이름이 끝나는 클래스와 upgrade로 시작하는 메소드를 선정하는 포인트컷 등록 

<bean id="transactionPointcut" class="springbook.user.service.NameMatchClassMethodPointcut">
		<property name="mappedClassName" value="*ServiceImpl" />
		<property name="mappedName" value="upgrade*" />
</bean>

 

 

어드바이스와 어드바이저 

<bean id="transactionAdvice" class="springbook.user.service.TransactionAdvice">
    <property name="transactionManager" ref="transactionManager" />
</bean>


<bean id="transactionAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
    <property name="advice" ref="transactionAdvice" />
    <property name="pointcut" ref="transactionPointcut" />
</bean>

 

 

 

6.5.3 포인트컷 표현식을 이용한 포인트컷 

  • 단순히 이름을 비교하는 일에서 더 나아가 포인트컷 표현식을 제공한다. 

포인트컷 표현식 

  • 포인트컷 표현식을 지원하는 포인트컷을 적용하려면 AspectJExpressionPointcut 클래스를 사용하면 된다. 
  • AspectJExpressionPointCut은 클래스와 메소드의 선정 알고리즘을 포인트컷 표현식을 이용해 한 번에 지정할 수 있게 한다. 

 

포인트컷 표현식 문법 

  • 포인트컷 지시자를 이용해 작성한다. 
  • 포인트컷 지시자 중에서 가장 대표적으로 사용되는 것은 execution()이다. 

  • 스프링에서 사용될 때의 빈의 이름을 비교하는 bean()도 있다. 

  • 포인트컷 표현식을 사용하면 로직이 짧은 문자열에 담기기 때문에 클래스나 코드 추가할 필요 없이 단순해진다. 
  • 반면에 문자열로 된 표현식이므로 런타임 시점까지 문법의 검증이나 기능 확인이 되지 않는다는 단점이 있다. 

 

6.5.4 AOP란 무엇인가?

트랜잭션 서비스 추상화 

  • 트랜잭션 경계설정 코드를 비즈니스 로직을 담은 코드에 넣으면서 생긴 문제는 특정 트랜잭션 기술에 종속되는 코드가 돼버린다는 점이었다. 
    • JDBC나 JTA를 이용한 방식으로 바꾸려면 모든 코드를 수정해야 한다. 
  • 그래서 트랜잭션 적용이라는 추상적인 작업은 유지한 채로 구체적인 구현 방법을 적용할 수 있도록 서비스 추상화 기법을 적용했다. 
  • 트랜잭션 추상화란 결국 인터페이스와 DI 통해 무엇을 하는지는 남기고, 그것을 어떻게 하는지를 분리한 것이다. 

 

프록시와 데코레이터 패턴 

  • 여전히 비즈니스 로직 코드에는 트랜잭션을 적용하고 있다. 
  • 그래서 도입한 방법이 바로 DI를 이용해 데코레이터 패턴을 적용하는 방법이었다. 
  • 클라이언트가 인터페이스와 DI 통해 접근하도록 설계하고, 데코레이터 패턴을 적용해서 비즈니스 로직을 담은 클래스의 코드에는 전혀 영향을 주지 않으면서 트랜잭션이라는 부가기능을 자유롭게 부여할 수 있는 구조를 만들었다. 

 

다이내믹 프록시와 프록시 팩토리 빈

  • 비즈니스 로직 인터페이스의 모든 메소드마다 트랜잭션 기능을 부여하는 코드를 넣어 프록시 클래스를 만드는 작업이 오히려 큰 작업이 되었다. 
    • 트랜잭션 기능을 부여하지 않아도 되는 메소드조차 프록시로서 위임 기능이 필요해 일일이 구현해야 한다. 
  • 프록시 오브젝트를 런타임 시에 만들어주는 JDK 다이내믹 프록시 기술을 적용했다. 
  • 그 덕분에 프록시 클래스 코드 작성의 부담도 덜고, 부가기능 부여 코드가 여기저기 중복해서 나타내는 문제도 해결했다. 
  • 하지만 동일한 기능의 프록시를 여러 오브젝트에 적용할 경우 오브젝트는 단위로는 중복이 일어나는 문제는 해결하지 못했다. 
  • 이를 해결하기 위해 다이내믹 프록시를 도입하여 내부적으로 템플릿/콜백 패턴 활용하는 스프링의 프록시 팩토리 빈 덕분에 부가기능을 담은 어드바이스와 부가기능 선정 알고리즘을 담은 포인트컷은 프록시에서 분리될 수 있었고 여러 프록시에서 공유해서 사용할 수 있게 됐다. 

자동 프록시 생성 방법과 포인트컷 

  • 트랜잭션 적용 대상이 되는 빈마다 일일이 프록시 팩토리 빈을 설정해줘야 한다. 
  • 스프링 컨테이너의 빈 생성 후 처리 기법을 활용해 컨테이너 초기화 시점에서 자동으로 프록시 만들어주는 방법을 도입했다. 
    • 프록시 적용할 대상 일일이 지쩡하지 않고 패턴 이용해 자동으로 선정할 수 있도록 했다. 

 

부가기능의 모듈화 

  • 관심사가 같은 코드를 분리해 한데 모으는 것은 소프트웨어 개발의 가장 기본이 되는 원칙이다. 
  • 그렇게 관심사가 같은 코드를 객체지향 설계 원칙에 따라 분리하고, 서로 낮은 결합도를 가진 채로 독립적이고 유연하게 확장할 수 있는 모듈로 만드는 것이 지금까지 해온 작업이다. 
  • 하지만 이 트랜잭션 적용에는 독립된 모듈로 만들 수가 없다. 왜냐하면 트랜잭션 경계설정 기능은 다른 모듈의 코드에 부가적으로 부여되는 기능이라는 특징이 있기 때문이다. 
  • 많은 개발자는 핵심기능을 담당하는 코드 여기저기에 흩어져 나타나야 했던 이런 부가기능을 어떻게 독립적인 모듈로 만들지 고민했고 지금까지 살펴본 DI, 데코레이터 패턴, 다이내믹 프록시, 오브젝트 생성 후처리, 자동 프록시 생성, 포인트컷과 같은 기법은 이런 문제를 해결하기 위해 적용한 방법이다. 

 

AOP: 애스팩트 지향 프로그래밍 

  • 부가기능을 어떻게 모듈화할까 연구해온 사람은, 기존 객체지향 설계 패러다임과 구분되는 새로운 특성이 있다고 생각했다. 
    • 그래서 이런 부가기능 모듈을 객체지향 기술에서 주로 사용하는 오브젝트와 다른 이름으로 부르기 시작했다. 
    • 이를 애스팩트 라고한다. 
  • 애스펙트란 그 자체로 애플리케이션의 핵심 기능을 담고 있지는 않지만, 애플리케이션을 구성하는 중요한 한 가지 요소이고, 핵심기능에 부가되어 의미를 갖는 특별한 모듈을 가리킨다. 
  • 애스펙트는 부가될 기능을 정의한 코드인 어드바이스와 어드바이스를 어디에 적용할지 결정하는 포인트컷을 가지고 있다. 
  • 지금 사용하고 있는 어드바이저는 아주 단순한 형태의 에스펙트라고 볼 수 있다. 

 

애스팩트 지향 프로그래밍

  • 애플리케이션의 핵심적인 기능에서 부가적인 기능을 분리해서 애스펙트라는 독특한 모듈로 만들어서 설계하고 개발하는 방법을 애스펙트 지향 프로그래밍 Aspect Oriented Programming (또는 AOP) 라고 한다. 
  • AOP는 OOP를 돕는 보조적인 기술이지 OOP를 완전히 대체하는 개념은 아니다. 
  • AOP는 애플리케이션을 다양한 측면에서 독립적으로 모델링하고, 설계하고, 개발할 수 있도록 만들어주는 것이다. 
  • 트랜잭션 기술의 적용에만 주목하고 싶다면 TransactionAdvice에만 집중하면 된다.  그리고 그 대상을 결정해주는 transactionPointCut 빈의 설정만 신경써주면 된다. 
  • 이렇게 애플리케이션을 특정한 관점을 기준으로 바라볼 수 있게 해준다는 의미에서 AOP를 관점 지향 프로그래밍이라고 한다. 

 

6.5.5 AOP 적용 기술 

프록시 이용한 AOP 

  • 프록시로 만들어서 DI로 연결된 빈 사이에 적용할 타깃의 메소드 호출 과정에 참여해서 부가기능을 제공해주도록 만들었다. 
  • 스프링 AOP는 자바의 기본 JDK와 스프링 컨테이너 외에는 특별한 기술이나 환경을 요구하지 않는다.
  • 어드바이스가 구현하는 MethodInterceptor 인터페이스는 다이내믹 프록시의 InvocationHandler와 마찬가지로 프록시로부터 메소드 요청정보를 전달받아서 타깃 오브젝트의 메소드를 호출한다. 
  • 독립적으로 개발한 부가기능 모듈을 다양한 타깃 오브젝트의 메소드에 다이나믹하게 적용해주기 위해 가장 중요한 역할을 하고 있는게 프록시이므로 스프링 AOP는 프록시 방식의 AOP라고 할 수 있다. 

 

바이트코드 생성과 조작을 통한 AOP 

  • 프록시 방식이 아닌 AOP도 있다. 
  • 가장 강력한 AOP프레임워크로 꼽히는 AspectJ는 프록시 사용하지 않는다. 
    • 타깃 오브젝트를 뜯어고쳐서 부가기능을 직접 넣어주는 직접적인 방법을 사용한다. 
    • 컴파일된 타깃의 클래스 파일 자체를 수정하거나 클래스가 JVM에 로딩되는 시점을 가로채서 바이트코드를 조작하는 방법을 사용한다. 
  • 프록시를 사용하지 않은 이유는
    • 바이트코드 조작해서 타깃 오브젝트 수정하면 스프링 같은 DI 컨테이너 도움 받아서 자동 프록시 생성 방식 사용하지 않아도 AOP 적용 가능하다. 
    • 프록시 방식보다 훨씬 강력하고 유연하다. 

 

6.5.6 AOP의 용어 

  • 타깃 : 부가기능을 부여할 대상
  • 어드바이스 : 타깃에서 제공할 부가기능 담은 모듈 
  • 조인 포인트 : 어드바이스가 적용될 수 있는 위치 (스프링에서는 메소드의 실행단계)
  • 포인트컷 : 어드바이스 적용할 조인 포인트 선별하는 작업 
  • 프록시 : 클라이언트와 타깃 사이 투명하게 존재하면서 부가기능 수행 
  • 어드바이저 : 포인트컷과 어드바이스 하나씩 가지고 있는 오브젝트 (스프링 AOP에서만 사용되는 용어)
  • 에스펙트 : AOP의 기본 모듈, 한 개 또는 그 이상의 포인트컷과 어드바이스 조합으로 만들어진다. 

 

 

6.5.7 AOP의 네임스페이스 

스프링의 프록시 방식 AOP 적용하려면 최소한 네 가지 빈을 등록해야 한다. 

  • 자동 프록시 생성기 : 스프링의 DefaultAdvisorAutoPorxyCreator클래스를 빈으로 등록한다. 
    • 빈으로 등록된 어드바이저 이용해 프록시 자동으로 생성 
  • 어드바이스 : 부가기능 구현한 클래스를 빈으로 등록 
  • 포인트컷 : 스프링의 AspecJExpressionPointcut을 빈으로 등록하고 expression 프로퍼티에 포인트컷 표현식을 넣어주면 된다. 
  • 어드바이저 : 스프링의 DefaultPointcutAdvisor 클랫를 빈으로 등록해서 사용한다. 

 

네임스페이스 이용해 AOP 빈 설정 

반응형