2021 Spring Study

Spring 11. AOP 프로그래밍 (2) - 스프링 AOP 구현

Yerim Kim 2021. 1. 13. 18:03

이전글

앞에서 프록시 객체와 데코레이터 패턴에 대해서 알아봤는데, 스프링도 이를 이용해서 AOP를 구현하고 있다.

 

스프링 AOP를 사용해보자. @Aspect 어노테이션을 사용해서 구현해볼 것이다.

 

순서

1. @Aspect 가 붙은 클래스 만들기

2. 설정파일에 @EnableAspectJAutoProxy 붙이기

3. target object 사용하기

 

+ 여러개의 Advice 와 Pointcut 사용하기

 


1. @Aspect 가 붙은 클래스 만들기

이전에 만들었던 프록시 UpperCaseMessage 는 이제 필요없다. 대신 다음과 같이 @Aspect 가 붙은 클래스가 필요하다.

package aspects;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class Aspects {

    @Pointcut("execution(public String *..*.*(..))")
    private void upperCasePointcut() {}

    @Around("upperCasePointcut()")
    public String upperCaseAdvice(ProceedingJoinPoint joinPoint)
            throws Throwable {
        Object targetResult = joinPoint.proceed();

        validateString(targetResult);

        return ((String) targetResult).toUpperCase();
    }

    private void validateString(Object object) {
        if (!object.getClass().equals(String.class)) {
            throw new IllegalStateException(
                "리턴값이 String 이 아닌 메서드에 대해 이 advice 를 사용할 수 없습니다.");
        }
    }
}

 

부가기능(공통기능) 하나를 advice 라고 한다. 여기서는 "String 타입의 리턴값을 upper case로 바꾸는 것"이 advice이다.

코드로는 @Around 가 붙은 메서드, 즉 upperCaseAdvice가 advice에 해당한다.

Around Advice 는 Spring에서 구현 가능한 어드바이스 종류들 중 하나이다. 이 외에도 Before Advice, After Returning Advice, After Throwing Advice, AfterAdvice 가 있다.

 

@Pointcut("execution(public String *..*.*(..))")

이 부분이 뭔지 살펴보자.

 

앞서 advice에 대해 얘기했는데, advice는 각각 어디에 적용될지 정해질 필요가 있다.

이 정보를 담는것이 pointcut 이다.

예시에서는 아래 코드를 통해 포인트컷을 정의하고 있다.

@Pointcut("execution(public String *..*.*(..))") 
private void upperCasePointcut() {}

 

@Pointcut 어노테이션의 속성값 execution(public String *..*.*(..))

이건 뭘까?

"접근제어자가 public 이고 리턴 타입이 String인 모든 메서드" 라는 뜻의 포인트컷 표현식이다.

포인트컷 표현식 작성법은 다음 포스팅에서 다룰 예정

 

@Around 애너테이션의 속성값을 보면 "upperCasePointcut()" 이렇게 되어있다.

@Around("upperCasePointcut()")

upperCasePointcut() 에서정의하는 포인트컷에 upperCaseAdvice 를 적용하겠다는 것이다.

 

2. 설정파일에 @EnableAspectJAutoProxy 붙이기

package config;

import aspects.Aspects;
import message.GreetingMessage;
import message.LoveMessage;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
public class AppConfiguration {

    @Bean
    public Aspects aspects() {
        return new Aspects();
    }

    @Bean
    public GreetingMessage greetingMessage() {
        return new GreetingMessage();
    }

    @Bean
    public LoveMessage loveMessage() {
        return new LoveMessage();
    }
}

@Aspect 에노테이션을 붙인 클래스를 공통기능으로 적용하려면 @EnableAspectJAutoProxy 애너테이션을 설정 클래스에 붙여야한다. 이 애노테이션을 추가하면 스프링은 @Aspect 애너테이션이 붙은 Bean 객체를 찾아서 빈 객체의 @Pointcut 설정과 @Around 설정을 사용한다.

3. target object 사용하기

import config.AppConfiguration;
import message.Message;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;

public class Test {

    public static void main(String[] args) {
        AbstractApplicationContext applicationContext =
            new AnnotationConfigApplicationContext(AppConfiguration.class);

        Message greetingMessage = applicationContext.getBean("greetingMessage", Message.class);
        System.out.println(greetingMessage.getValue());

        Message loveMessage = applicationContext.getBean("loveMessage", Message.class);
        System.out.println(loveMessage.getValue());
    }
}

 

사용은 이런식으로 하면 된다.

 


+ 여러개의 어드바이스 및 포인트컷 사용하기

package aspects;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class Aspects {

    @Pointcut("execution(public String *..*.*(..))")
    private void upperCasePointcut() {}

    @Around("upperCasePointcut()")
    public String upperCaseAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        Object targetResult = joinPoint.proceed();

        validateString(targetResult);

        return ((String) targetResult).toUpperCase();
    }

    @Pointcut("execution(public String *..*.*(..))")
    private void startWithAsteriskPointcut() {}

    @Around("startWithAsteriskPointcut()")
    public String startWithAsteriskPointcutAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        Object targetResult = joinPoint.proceed();

        validateString(targetResult);

        return "*" + targetResult;
    }

    private void validateString(Object object) {
        if (!object.getClass().equals(String.class)) {
            throw new IllegalStateException("리턴값이 String 이 아닌 메서드에 대해 이 advice 를 사용할 수 없습니다.");
        }
    }
}