[Spring] 스프링 AOP 개념 이해 및 적용 방법
AOP(Aspect Oriented Programming)
Spring은 Spring Triangle이라고 부르는 세 가지 개념을 제공해준다. 각각 IoC, AOP, PSA를 일컫는다.
AOP는 Aspect Oriented Programming의 약자로 “측면/양상 지향적인 프로그래밍”이라는 의미이다.
“측면/양상 지향적인 프로그래밍”이 무엇을 의미하는가?
class A {
method a() {
AAAA
method a가 하는 일들
BBBB
}
method b() {
AAAA
method b가 하는 일들
BBBB
}
}
class B {
method c() {
AAAA
method c가 하는 일들
BBBB
}
}
위와 같이 동일한 일을 하는 코드 AAAA, BBBB가 여기 저기서 사용되고 이렇게 흩어져 있으면 코드 변경이 필요한 경우 일일이 다 찾아서 바꿔야한다.
AOP는 그렇게 하지않고 여러군데서 사용되는 중복되는 코드를 떼어내서 분리하고, method a, b, c 는 자신이 해야할 작업만 갖고있자는 개념이다.
여기서 ‘여러곳에서 사용되는 중복되는 코드’가 AOP에서 말하는 ‘Aspect’ 라고 이해하면 된다.
프록시 패턴
Spring AOP는 프록시 패턴이라는 디자인 패턴을 사용해서 AOP 효과를 낸다.
프록시 패턴을 사용하면 어떤 기능을 추가하려 할 때, 기존 코드를 변경하지 않고 기능을 추가할 수 있다.
어떤 클래스가 Spring AOP의 대상이라면 그 기존 클래스의 빈이 만들어질 때 Spring AOP가 프록시(기능이 추가 된 클래스)를 자동으로 만들고 원본 클래스 대신 프록시를 빈으로 등록한다.
그리고 원본 클래스가 사용되는 지점에서 프록시를 대신 사용한다.
Payment 라는 interface가 있고
public interface Payment {
void pay(int amount);
}
Payment 를 implements 한 Cash 와 Card가 있다고 하면,
@Service
public class Cash implements Payment {
@Override
public void pay(int amount) {
System.out.println(amount + " 현금결제");
}
}
// Proxy 역할을 하는 Card 클래스
@Service
public class Card implements Payment {
Payment cash = new Cash();
@Override
public void pay(int amount) {
if(amount < 1000)
cash.pay(amount)
else
System.out.println(amount + " 신용카드");
}
}
이를 사용하는 클라이언트 에서는 코드 변경을 하지 않아도 된다.
단지 어떤 Payment를 사용할지만 알려주면 된다.
public class Store {
Payment payment;
public Store(Payment payment) {
this.payment = payment;
}
public void buy(int amount) {
payment.pay(amount);
}
}
public class StoreTest {
public static void main(String[] args) {
Payment card = new Card();
Store store = new Store(card);
store.buy(999); // 1000 미만 일때는 card(proxy) 클래스에서 cash 클래스로 바꿔치기 된다.
}
}
Spring AOP
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Sample {
}
@Target(ElementType.METHOD)
어노테이션을 메소드에 사용할 것 이라고 선언한다.
@Retention(RetentionPolicy.RUNTIME)
어노테이션이 RUNTIME까지 유지되도록 설정한다.
어떤 메소드에 AOP를 적용할건지 알려준느 어노테이션을 정의하여 해당 메소드에 어떤 기능을 추가할 것인지를 알려주는 실제 Aspect를 구현해야 한다.
@Component
@Aspect
@Slf4j
public class SampleAspect {
@Around("@Annotation(Sample)")
public Object sample(proceedingJoinPoint joinPoint) throws Throwable {
logger.info("sample Start");
Object proceed = joinPoint.proceed();
logger.info("sample End");
return proceed;
}
}
@Around(“@annotation(Sample)”)
이 어노테이션을 붙인 메소드에서는 ProceedingJoinPoint 파리미터를 받을 수 있다.
어노테이션의 value를 “@annotation(Sample)” 로 지정함으로써 joinPoint는 @Sample를 붙인 타겟 메소드를 의미하게 된다.
Object proceed = joinPoint.proceed();
타겟 메소드를 실행한다. 이 라인 앞 뒤로 logger를 찍어준다.
@Sample
public void sample(){
logger.info("test!!!!");
}
실제로 타겟에는 어노테이션 외에는 아무런 코드가 추가되지 않는다.
이것이 Aspect이며, Spring이 제공해주는 어노테이션 기반의 AOP이다.
이렇게 적용한 AOP는 내부적으로 프록시 패턴 기반으로 동작한다.