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

스프링 AOP

by 민휘 2023. 4. 20.

요약 : 부가기능은 핵심기능에 강하게 의존하므로 OOP의 객체 DI 방식으로는 중복을 완벽하게 제거할 수 없다. 그래서 관점(부가기능)을 중심으로 프로그래밍하는 AOP를 도입하여 부가기능을 완벽하게 모듈화했다. 스프링 AOP는 프록시를 사용하여 타깃 메소드가 호출되는 순간에만 부가기능을 적용하다록 만들었다. 프록시를 사용하면 별다른 설정 없이 스프링 컨테이너와 JDK 프록시만으로도 처리가 가능하기 때문이다.

5장부터 6장까지 트랜잭션 코드를 서비스 코드로부터 완벽하게 분리하는 방법을 알아보았다. 그 결과 트랜잭션 코드는 어떠한 서비스에도 중복 없이 적용될 수 있게 되었다. 이 과정에서 다이나믹 프록시, 빈 후처리기 등과 같은 다양한 기술을 사용했다.

 

목표는 중복 제거와 변경 유연성

목표는 핵심기능에 부여되는 부가기능을 효과적으로 모듈화하여 중복을 제거하고, 변경으로 인한 수정 범위를 제한하는 것이다. 여기까지는 OOP가 객체를 캡슐화해서 재사용성을 높이는 것과 동일하다. 그러나 부가 기능은 객체로 캡슐화하고 DI로 연결하는 것만으로는 충분히 캡슐화되지 않는데, 그 이유는 부가기능이 핵심기능에 강하게 결합되기 때문이다. 부가기능을 담은 객체는 코드의 분리만으로 자율적인 존재가 될 수 없다. 그래서 다이나믹 프록시나 빈 후처리기와 같이 런타임에 동작하는 코드 조작을 사용해 부가기능 모듈을 핵심기능으로부터 독립적으로 만들었다. 이제 핵심기능은 순수하게 자신의 로직에만 집중하고, 여러 기능에 반복적으로 나타나는 부가기능은 런타임에 만들어져 핵심기능과 함께 적용된다.

 

AOP vs OOP

OOP는 말 그대로 객체를 지향한다. 설계와 개발의 기준을 객체로 두고, 캡슐화하고 협력을 만들어서 애플리케이션 기능을 구현한다. 확장성 있는 설계를 위해 객체의 응집도를 높이고 중복을 제거해 변경으로 인한 부수 효과를 줄이는 것이 중요하다. 하지만 애플리케이션에 필요한 객체에서 부가기능이 반복적으로 적용되어야하는 경우, 해당 객체에 부가기능 코드를 중복해서 넣으면 응집도가 떨어지고 변경에 취약하다. 부가기능만을 담은 객체를 만들고 필요한 객체에 의존성을 주입하는 책임을 외부 객체에 할당하더라도, 부가기능과 타깃이 많아지면 그에 따른 중복이 발생했다. 이것은 OOP의 전통적인 중복 제거 방법만으로는 부가기능의 완전한 모듈화가 불가능하다는 것을 뜻한다. OOP만으로는 독립적인 모듈을 만들기가 어려우니 프록시, 빈 후처리, 어드바이스와 포인트컷 같은 방법을 도입했고 이것이 곧 AOP를 지원하기 위한 방법이 된다. AOP는 관점을 중심으로 프로그래밍하는 패러다임으로, 애플리케이션 전반에서 나타나는 특정 관점을 바라볼 수 있게 해준다. 설계, 개발 단계에서 부가기능은 완벽하게 모듈화되어 독립적으로 존재하지만, 런타임에서는 핵심로직에 동적으로 적용될 수 있다. 핵심은 OOP가 기존 방식으로 부가기능의 모듈화를 하기 어려우니 AOP를 도입하여 보완했다는 점이다. 둘은 상호보완적인 관계이다.

 

프록시 기반의 스프링 AOP

스프링 AOP는 특히 프록시를 사용하여 부가기능을 모듈화하고 핵심기능에 적용했다. 프록시를 사용하면 스프링 컨테이너와 자바의 기본 JDK 외에 특별한 환경을 필요로 하지 않는다는 장점이 있다. 서버 환경이라면 기초적인 서블릿 컨테이너로 충분하다.

스프링 AOP는 프록시를 사용하여 클라이언트의 타깃 메소드 호출을 인터셉트한다. 그래서 부가기능이 적용되는 대상은 오브젝트 메소드 뿐이며, 부가기능이 적용될 수 있는 때는 메소드 호출이 일어나는 경우 뿐이다.

 

바이트코드 조작 기반의 AspectJ

프록시 방식이 아닌 AOP 프레임워크로 AspectJ가 있다. AOP의 목표는 코드를 작성할 때는 부가기능을 모듈화하는 것이고, 런타임에는 부가기능이 선택된 타깃에 적용되도록 하는 것이다. 이 목표를 달성할 수 있다면 수단은 상관 없다. 스프링은 프록시를 선택했기 때문에 타깃 메소드 호출이 일어나는 시점에 부가기능을 적용한다. 반면 AspectJ는 바이트코드를 조작하여 클래스파일을 메모리에 올리기 때문에 훨씬 자유롭고 강력하게 부가기능을 적용할 수 있다. 타깃이 생성되는 순간에 부가기능 코드를 만들어줄 수도 있고, 프록시 적용이 불가능한 private이나 필드 입출력 등에도 부가기능을 적용할 수 있다. 이는 타깃 코드를 직접 수정하기 때문에 가능한 일이다. 이런 기능은 대부분 JVM 실행 옵션이나 별도의 바이트코드 컴파일러 혹은 클래스 로더를 사용해야하므로 설정이 번거롭다는 단점이 있다. 대부분의 부가기능 적용은 프록시 방식을 사용해 호출 시점에 부여하는 것으로도 충분하므로 스프링 AOP는 프록시를 선택한 것으로 보인다.

 

AOP 용어

  • 타깃 : 부가기능을 부여할 대상
  • 어드바이스 : 부가기능 로직을 담은 모듈
  • 조인 포인트 : 어드바이스가 적용되는 위치. 스프링 AOP의 조인 포인트는 메소드의 실행 단계 ㅜ뿐이다.
  • 포인트컷 : 어드바이스를 적용할 조인 포인트를 선별
  • 프록시 : DI로 타깃 대신 클라이언트에게 주입됨. 타깃을 위임하고 부가기능 부여함.
  • 어드바이저 : 포인트컷과 어드바이스를 하나씩 가지는 모듈. 스프링 AOP의 기본 모듈.
  • 애스펙트 : AOP의 기본 모듈. 부가기능을 나타냄. 한개 또는 그 이상의 포인트컷과 어드바이저 조합으로 만들어진다. 보통 싱글톤이다.

 

AOP 네임스페이스

AOP와 관련된 빈을 등록하는 설정은 다른 빈을 등록하는 것으로부터 의미를 분리하는 것이 좋다. 그래서 aop 네임스페이스를 선언하고, 포인트컷과 어드바이저, 컨피그 빈을 전용 태그를 사용하면 이해하기 쉽고 코드 양도 줄어든다. 포인트컷은 다른 어드바이저에서도 참조 가능하므로 재사용할 수 있다.

<beans xmlns="http://www.springframework.org/schema/beans"
			 <!-- 추가 -->     
			 xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<aop:config>
	<aop:pointcut id="transactionPointcut" expression="execution(* *..*ServiceImpl.upgrade*(..))"/>
	<aop:advisor advice-ref="transactionAdvice" pointcut-ref="transactionPointcut"/>
</aop:config>