AOP는 여러 객체에 공통으로 적용할 수 있는 기능을 분리해서 재사용성을 높여주는 프로그래밍 기법이다. 스프링에서는 프록시 객체를 자동으로 만들어 공통 기능을 삽입하는데, 이때 프록시 객체를 어떻게 만드는지 살펴보자.
스프링에서 프록시가 생성되는 방식
스프링은 AOP를 위한 프록시 객체를 생성할 때 빈 객체가 인터페이스를 상속한다면 해당 인터페이스를 이용해서 프록시를 생성한다. 아래 그림처럼 프록시와 빈 대상 클래스가 하나의 인터페이스를 상속받는 관계인 것이다.
Calculator cal = ctx.getBean("calculator", Calculator.class); // 정상 동작
RecCalculator cal = ctx.getBean("calculator", RecCalculator.class); // 익셉션 발생
따라서 프록시를 RecCalculator 클래스 타입으로 지정하면 익셉션이 발생하게 된다. 인터페이스가 아닌 클래스를 이용해서 프록시를 생성하고 싶다면 다음과 같이 @EnableAspectJAutoProxy 애노테이션의 proxyTargetClass 속성을 true로 설정하면 된다.
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppCtx {
...
}
AOP 관련 애노테이션 설정
@Pointcut 애노테이션의 execution 속성 표현식
Aspect를 적용할 위치를 지정할 때 @Pointcut 애노테이션에 execution 속성을 이용한다. 이때 표현식 규칙은 아래와 같다.
execution(수식어패턴? 리턴타입패턴 클래스이름패턴?메서드이름패턴(파라미터패턴))
수식어패턴은 생략 가능하며 public, protected 등이 오는데 스프링 AOP는 public 메서드에만 적용할 수 있으므로 public만 의미가 있다. 각 패턴은 *을 이용하여 모든 값을 표현할 수 있고 ..을 이용하여 0개 이상이라는 표현을 할 수 있다.
@Pointcut("execution(public * chap07..*(..))")
이전 포스트에서 작성한 표현식을 해석해보자. chap07 패키지 및 하위 패키지에 있으며, 파라미터가 0개 이상인 메서드를 호출한다는 의미이다.
Aspect의 순서 설정
한 Pointcut 에는 여러 Advice를 적용할 수도 있다. 즉, 하나의 메서드에 여러 개의 공통 기능이 적용되는 것이다. 이 경우 어떤 Aspect가 먼저 적용될지는 스프링이나 자바 버전에 따라 달라질 수 있으므로 적용 순서가 중요하다면 직접 순서를 지정해야 한다. 이때 @Order 애노테이션을 사용한다. @Aspect 애노테이션과 함께 @Order 애노테이션을 클래스에 붙이면 지정한 값에 따라 적용 순서가 결정된다.
@Aspect
@Order(1)
public class ExeTimeAspect {
...
}
@Aspect
@Order(2)
public class CacheAspect {
...
}
@Around의 Pointcut 설정 및 @Pointcut의 재사용
@Around의 Pointcut 설정
@Around 애노테이션에 execution 명시자를 직접 지정할 수 있다. 예시 코드는 아래와 같다.
@Aspect
public class ExeTimeAspect {
@Around("execution(public * chap07..*(..))")
public Object measure(ProceedingJoinPoint joinPoint) throws Throwable{
...
}
}
@Pointcut의 재사용
같은 Pointcut을 여러 Advice가 함께 사용한다면 정의된 Pointcut을 재사용할 수 있다. 이전 포스트의 ExeTimeAspect 클래스에서 Pointcut인 publicTarget() 메서드를 정의한 뒤 Around에서 호출해 사용한 것처럼, 정의된 Pointcut을 원하는 곳에서 호출하여 사용하면 된다.
여러 Aspect에서 공통으로 사용하는 Pointcut이 있다면 별도 클래스에 Pointcut을 정의하고 각 Aspect 클래스에서 호출하여 사용하도록 구성하면 관리가 편해진다. 아래 코드는 Pointcut 재사용의 예시이다. @Pointcut을 설정한 CommonPointcut 클래스는 빈으로 등록할 필요가 없다.
public class CommonPointcut {
@Pointcut("execution(public * chap07..*(..))")
public void commonTarget() {
}
}
@Aspect
public class ExeTimeAspect {
@Around("CommonPointcut.commonTarget()")
public Object measure(ProceedingJoinPoint joinPoint) throws Throwable{
...
}
}
@Aspect
public class CacheAspect {
...
@Around("CommonPointcut.commonTarget()")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable{
...
}
}
참고 서적: <초보 웹 개발자를 위한 스프링 5 프로그래밍 입문>
이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
Spring Framework 시리즈
- 스프링 프로젝트 시작하기 (Maven)
- 스프링 컨테이너(Container) 의미
- Dependency, DI, Assembler (의존, 의존 주입, 주입기) 개념 정리
- 스프링에서의 의존 주입(DI)의 의미와 사용법
- 스프링 애노테이션을 사용한 의존 주입(DI)
- 의존 자동 주입(1) - @Autowired 애노테이션
- 의존 자동 주입(2) - 빈 이름과 한정사
- 의존 자동 주입(3) - @Autowired의 필수 여부, 자동 주입과 명시적 의존 주입
- 컴포넌트 스캔 - @Component, @ComponentScan 사용하기
- 빈 객체의 라이프사이클과 범위 (Life Cycle & Scope of Bean)
- AOP 프로그래밍(1) - 프록시와 AOP
- AOP 프로그래밍(2) - 스프링에서의 AOP
'Java > [스프링 5 프로그래밍 입문]' 카테고리의 다른 글
[Spring] DB 연동(1) - 스프링 프로젝트에 DB 연동하기 (0) | 2021.09.10 |
---|---|
[Spring] Tomcat JDBC DataSource 클래스의 주요 설정(프로퍼티) (0) | 2021.09.10 |
[Spring] AOP 프로그래밍(2) - 스프링에서의 AOP (0) | 2021.08.12 |
[Spring] AOP 프로그래밍(1) - 프록시와 AOP (0) | 2021.08.10 |
[Spring] 빈 객체의 라이프사이클과 범위 (Life Cycle & Scope of Bean) (0) | 2021.08.03 |
댓글