pom.xml파일에 AOP 라이브러리를 추가한다. (두번째 dependency만 추가하면 됨)
<!-- AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.8</version>
</dependency>
applicationContext.xml의 Namespaces 탭에서 aop를 클릭하고 저장한다.
이전에 작성한 advice 클래스를 스프링 설정 파일에 bean으로 등록하고 AOP 설정을 추가하자.
LogAdvice 클래스
package com.springbook.biz.common;
public class LogAdvice {
public void printLog() {
System.out.println("[공통 로드] 비즈니스 로직 수행 전 동작");
}
}
스프링 설정 파일 (applicationContext.xml)
<bean id="log" class="com.springbook.biz.common.LogAdvice"></bean>
<aop:config>
<aop:pointcut id="allPointcut" expression="execution(* com.springbook.biz..*Impl.*(..))"/>
<aop:aspect ref="log">
<aop:before pointcut-ref="allPointcut" method="printLog" />
</aop:aspect>
</aop:config>
조인포인트(Joinpoint)
클라이언트가 호출하는 모든 비즈니스 메소드. BoardServiceImpl이나 UserServiceImpl클래스의 모든 메소드를 조인포인트라고 생각하면 된다(여기서는 CRUD를 구현한 메소드들이다). 조인포인트를 다음에 설명할 '포인트컷 대상' 또는 '포인트컷 후보'라고도 하는데, 조인포인트 중에서 포인트컷이 선택되기 때문이다.
포인트컷(Pointcut)
포인트컷은 필터링된 조인포인트를 의미한다. 예를 들어 트랜잭션을 처리하는 공통 기능을 만들었다고 가정하자. 이 횡단 관심 기능은 등록, 수정, 삭제 기능의 비즈니스 메소드에 대해서는 당연히 동작해야 하지만, 검색 기능의 메소드에 대해서는 트랜잭션과 무관하므로 동작할 필요가 없다. 이렇게 수많은 조인포인트 중에서 우리가 원하는 특정 메소드에서만 횡단 관심에 해당하는 공통 기능을 수행하도록 하기 위해서 포인트컷이 필요하다. 포인트컷으로 메소드가 포함된 클래스와 패키지, 메소드 시그니처까지 지정할 수 있다.
스프링 설정 파일에 포인트컷을 하나 더 추가해보자.
<aop:pointcut id="getPointcut" expression="execution(* com.springbook.biz..*Impl.get*(..))"></aop:pointcut>
expression 속성에는 포인트컷 표현식을 작성한다. 이 값에 따라 메소드가 필터링된다.
*는 리턴 타입을 작성하는 자리 - 리턴 타입을 무시한다는 의미.com.springbook.biz..는 해당 값으로 시작하는 패키지 경로. *Impl은 이름이 Impl로 끝나는 클래스.get*(..)은 get으로 시작하는 메소드.그리고 <aop:before> 태그의 pointcut-ref속성에 getPointcut을 대신 넣어주면, BoardServiceClient프로그램을 실행했을 때 insertBoard()에는 반응하지 않고 getBoardList() 호출에만 반응하는 것을 확인할 수 있다.
Advice 어드바이스
어드바이스는 횡단 관심에 해당하는 공통 기능의 코드. 독립된 클래스의 메소드로 작성된다.
어드바이스로 구현된 메소드가 언제 동작할지 스프링 설정 파일을 통해 지정할 수 있다.
예를 들어 트랜잭션 관리 기능의 어드바이스 메소드 - 커밋, 롤백 등 - 가 있다고 가정하자. 이 메소드는 당연히 비즈니스 로직 수행 후에 처리되어야 한다.
스프링에서 어드바이스 동작 시점
- before
- after
- after-returning
- after-throwing
- around
LogAdvice 클래스의 printLog() 메소드가 비즈니스 로직 실행 후 동작하도록 설정을 변경해보자.
이전
<aop:before pointcut-ref="allPointcut" method="printLog" />
이후
<aop:after pointcut-ref="allPointcut" method="printLog" />
위빙 Weaving
위빙은 포인트컷으로 지정한 핵심 관심 메소드가 호출될 때, 어드바이스에 해당하는 횡단 관심 메소드가 삽입되는 과정을 의미한다. 이 위빙을 통해서 비즈니스 메소드를 수정하지 않고도 횡단 관심에 해당하는 기능을 추가하거나 변경할 수 있다.
스프링에서는 런타임 위빙 방식을 지원한다.
애스팩트Aspect 또는 어드바이저Advisor
AOP의 핵심은 Aspect다. 애스팩트는 포인트컷과 어드바이스의 결합으로서, 어떤 포인트컷 메소드에 대해서 어떤 어드바이스 메소드를 실행할지 결정한다.
LogAdvice.java
public class LogAdvice {
public void printLog() {
System.out.println("[공통 로드] 비즈니스 로직 수행 전 동작");
}
}
applicationContext.xml
<aop:config>
<aop:pointcut id="getPointcut" expression="execution(* com.springbook.biz..*Impl.get*(..))"></aop:pointcut>
<aop:aspect ref="log">
<aop:after pointcut-ref="getPointcut" method="printLog" />
</aop:aspect>
</aop:config>
우리는 핵심 관심 메소드(비즈니스 메소드)들 중 get으로 시작하는 메소드들이 호출될 때, 해당 메소드 실행 이후에 횡단 관심 메소드(트랜잭션 처리)인 printLog()가 실행되도록 하고 싶다. 그런데 get으로 시작하는 메소드들에 코드로 집어넣기에는 관심이 분리되지 않아 유지보수가 어려워질 것이다. 따라서 다음과 같이 AOP 설정을 한다.
aop:pointcut태그에서 어떤 핵심 관심 메소드 이후에 printLog()를 실행할 것인지를 정한다. id속성을 통해 참조할 이름을 정해주고, expression에서 핵심 관심 메소드의 조건을 구체적으로 정해준다. 여기서는 aop설정에서 getPointcut이란 이름으로 핵심 관심 메소드를 참조할 수 있도록 하고, 그 메소드가 될 조건은 com.springbook.biz로 시작하는 패키지에서 이름이 Impl로 끝나는 클래스에 작성된 이름이 get으로 시작하는 메소드여야 한다는 것이다.
aop:after태그에서 참조한 getPointcut으로 설정한 포인트컷 메소드가 호출되면, aop:aspect태그가 참조하는 log라는 어드바이스 객체의 printLog() 메소드가 실행된다. 참조한 log는 LogAdvice클래스의 bean 태그 id값이다. 해당 클래스를 id값으로 참조하고 있다.
이때 printLog()가 실행되는 시점이 바로 aop:before이다.