Spring AOP는 앞서 설명한 것과 같이 Aspect를 모듈화하여 객체 지향 프로그래밍을 보완하는 역할이다. 순수 자바로 구현되어 특별한 컴파일 과정이 필요하지 않고, Spring IoC 컨테이너에 의존한다. AOP 주입을 메서드 전후로만 지원하고, Spring Framework 에서 선언적 트랜잭션 관리에 중점적으로 사용된다.
Spring은 Spring AOP의 목표는 완벽한 AOP의 구현보다 Spring IoC와 매끄럽게 통합하여 문제를 해결하는데 도움이 되는 것이라고 한다. 따라서 세분화된 객체(도메인 객체)에 AOP를 적용하고 싶다면, AspectJ를 활용하라고 제안하고 있다. 즉, Spring은 AspectJ와 Spring AOP의 모든 기능을 활용하여 Spring 기반 애플리케이션에서 모든 AOP 기능을 사용할 수 있도록 지원하고 있다.
Proxy 패턴: 어떤 객체에 대한 접근을 제어하기 위한 용도로, 실제 객체의 메서드를 호출하면 그 호출을 중간에 가로채는 패턴
스프링에서는 다이내믹 프록시를 생성해서 Bean 객체로 등록하게 해주는 ProxyFactoryBean
을 제공한다. ProxyFactoryBean
은 순수하게 프록시를 생성하는 작업만을 담당하고, 프록시를 통해 제공해줄 Apsect는 별도의 빈에 구현할 수 있다. 그리고 생성하는 프록시에서 사용할 부가기능은 MethodInterceptor
인터페이스를 구현하여 만드는데, 해당 인터페이스의 invoke()
메서드는 ProxyFactoryBean
으로부터 타깃 오브젝트에 대한 정보를 함께 제공받아 타깃이 다른 여러 프록시에서 함께 사용할 수 있다. (타깃이 달라지면 새로운 InvocationHandler
를 구현해야 하는 다이내믹 프록시의 단점을 보완)
ProxyFactory
를 통해 생성된 프록시는 클라이언트로부터 요청을 받으면 메서드 선정 알고리즘을 담은 오브젝트인 Pointcut
에 부가기능을 부여할 메서드인지 확인 요청을 보낸다. 이때, 포인트컷 오브젝트는 Pointcut
인터페이스를 구현해서 만들 수 있다. 포인트컷에서 부가기능을 적용할 대상 메서드임을 확인 받으면, MethodInteceptor
타입의 Advice
객체를 호출하여 부가기능을 제공할 수 있도록 한다. 이때, Advice
객체에서 타깃의 메서드 호출이 필요할 시, 프록시로부터 전달받은 MethodInvocation
타입의 콜백 객체의 proceed()
메서드를 호출하여 타깃의 메서드를 호출하여 타깃에 의존하지 않도록 템플릿/콜백 구조로 되어 있다.
그렇다면 Advisor
왜 필요할까? Advisor
는 Pointcut
과 Advice
로 이루어진 객체이다. 포인트컷과 어드바이스는 별개의 객체로 등록할 수 있지만 어드바이저에 묶어 구현하는 이유는 애플리케이션에는 여러 개의 포인트컷과 어드바이스가 계속 추가될 수 있기 때문이다. 포인트컷과 어드바이스를 조합해 Advisor
에 등록함으로써 어떤 어드바이스에 어떤 포인트컷을 적용할지 명확하게 한다.
먼저, 아래와 같이 transaction 부가기능이 구현된 Advice 객체를 생성한다. 트랜잭션 Aspect를 적용할 메서드 선정을 위한 포인트컷 빈은 스프링이 제공하는 포인트컷 클래스를 사용한다.
package springbook.user.service;
public class TransactionAdvice implements MethodInterceptor {
PlatformTransactionManager transactionManager;
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
// 프록시에서 callback 객체를 인자로 받음
public Object invoke(MethodInvocation invocation) throws Throwable {
TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
Object ret = invocation.proceed(); // callback 호출(타깃 메소드 실행)
this.transactionManager.commit(status);
return ret;
} catch (RuntimeException e) {
this.transactionManager.rollback(status);
throw e;
}
}
}
Advice 클래스 작성 후, XML 설정파일에 <aop:config>
태그를 이용하여 Aspect를 설정한다. 이 때, AspectJ의 포인트컷 표현식을 사용하여 포인트컷을 설정할 수 있다.
<?xml version="1.0" encoding="UTF-8"?>
<!-- aop namespace 선언 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
...
<!-- aop class bean 등록 -->
<aop:config>
<!-- ServiceImpl로 끝나는 모든 클래스의 upgrade로 시작하는 메서드를 선정 -->
<aop:pointcut id="transactionPointcut" expression="execution(* *..*ServiceImpl.updgrade*(..))" />
<aop:advisor advice-ref="transactionAdvice" pointcut-ref="transactionPointcut" />
</aop:config>
</beans>
또한 아래와 같이 <aop:advisor>
태그를 사용할 수도 있다. 다만 하나의 포인트컷을 여러 개의 어드바이저에서 공유하려고 하는 경우에는 독립적인 포인트컷 태그로 등록해야 한다.
<aop:config>
<aop:advisor advice-ref="transactionAdvice" pointcut="execution(* *..*ServiceImple.updgrade*(..))" />
</aop:config>
@Aspect
어노테이션 이용@Aspect
어노테이션을 적용한 Aspect 클래스에 Advice를 구현하는 메서드와 Pointcut을 작성 후, XML 설정파일에 <aop:aspectj-autoproxy/>
를 설정하면 해당 클래스를 Aspect로 사용 가능하다. 먼저 AOP와 관련된 어노테이션은 아래와 같다.
@Aspect
: 어노테이션이 적용된 class를 Advisor (Pointcut + Advice)로 만듦@Around
: 메서드 전구역에 AOP 주입@Before
: 메서드 시작 전에 AOP 주입@After
: 메서드 종료 후에 AOP 주입@AfterReturning
: 메서드 정상 종료 후 AOP 주입@AfterThrowing
: 메서드에서 예외가 발생하며 종료된 후 AOP 주입@Pointcut
: Pointcut 지시자를 미리 설정특정 기능을 사용했을 때 Logging을 위한 Aspect를 생성해보자. 먼저 @Aspect
태그가 적용된 클래스를 생성하고 주입할 부가기능이 작성된 메서드를 생성하여 Advice 어노테이션을 적용한다. Advice 어노테이션을 적용할 때, AspectJ의 pointcut 표현식을 사용하여 어드바이스에 적용할 포인트컷을 정의할 수 있다. 마지막으로 XML 설정파일에 <aop:aspectj-autoproxy/>
태그를 추가하고 해당 클래스를 Bean으로 등록하면 된다.
<beans ...>
<aop:aspectj-autoproxy/>
<bean id="loggingSomething" class="springbook.user.aspect.LoggingSomething"/>
</beans>
package springbook.user.aspect;
...
@Aspect
public class LoggingSomething {
@Before("execution(* runSomething(..))")
public void before (JoinPoint joinPoint) {
//joinPoint 객체를 통해 타겟의 정보를 알 수 있음
System.out.println("@Before [" + joinPoint.getSignature().getName() + "] method.");
}
}
스프링 입문을 위한 자바 객체지향의 원리와 이해, 김종민, 위키북스
토비의 스프링 3.1 Vol. 1 스프링의 이해와 원리, 이일민, 에이콘출판
AOP 입문자를 위한 개념 이해하기 - Tecoble
Aspect Oriented Programming with Spring - Spring Framework Docs
Aspect Oriented Programming - Catsbi's DLog
[SpringBoot] @Aspect 어노테이션 (tistory.com)
Spring AOP[3] @Aspect 어노테이션을 이용한 AOP 구현, Advice 정의하는 어노테이션 종류, Aspect 클래스 작성 및 테스트 (tistory.com)
프록시 패턴(Proxy pattern) - 꿈꾸는 지구별 개발자, Phang's IT Blog