[Spring] 관점지향프로그래밍 (AOP) 개념 및 사용법

koline·2023년 12월 19일

Spring

목록 보기
4/15

관점지향프로그래밍 (AOP)


관점지향 프로그래밍이란 Spring의 3대 요소중 하나로, 특정 관점을 기준으로 로직을 보고 그 로직을 모듈화하는 프로그래밍 방식을 말한다. 애플리케이션의 각 부분에 공통적으로 들어가는 내용을 독립적으로 모듈화하여 책임을 분리하고 코드를 깔끔하게 유지하기 위해 사용된다.

코드를 작성하다보면 여러 부분에서 반복적으로 사용되는 비즈니스 로직이 있는데 이것을 흩어진 관심사 (Crosscutting Concerns)라 부른다. 그리고 이 흩어진 관심사를 모아서 Aspect로 모듈화하고 핵심적인 비즈니스 로직에서 분리하여 재사용하겠다는 것이 AOP의 취지이다.




주요 개념


Aspect

위에서 설명한 흩어진 관심사를 모듈화 한 것으로 주로 부가기능을 모듈화하기 위해 사용된다.

Target

클래스, 메서드 등 모듈화된 Aspect를 적용하는 곳

Advice

실질적으로 어떤 일을 해야할 지에 대한 것으로 실질적인 부가기능을 담은 구현체이다.

JointPoint

Advice가 적용될 위치와 적용되는 시점이다. 메서드 진입 지점, 생성자 호출 시점, 필드에서 값을 꺼내올 때 등 다양한 시점에 적용가능하다.

PointCut

JointPoint의 상세한 스펙을 정의한 것으로 'A란 메서드의 진입 시점에 호출할 것'과 같이 더욱 구체적으로 Advice가 실행될 지점을 정할 수 있다.

Spring에서 사용하는 AOP는 프록시 패턴 기반의 AOP 구현체이다. 프록시 객체를 쓰는 이유는 접근 제어 및 부가기능을 추가하기 위해서이다. 스프링 AOP는 스프링 빈에만 적용이 가능하며, 모든 AOP 기능을 제공하는 것이 아닌 스프링 IoC와 연동하여 중복코드, 프록시 클래스 작성의 번거로움, 객체들 간 관계 복잡도 증가 등 엔터프라이즈 애플리케이션에서 가장 흔한 문제에 대한 해결책을 지원하는 것이 목적이다.

여기서 Aspect를 실행시키는 타이밍은 어노테이션을 통해 설정할 수 있다.

@Before (이전) : 어드바이스 타겟 메소드가 호출되기 전에 어드바이스 기능을 수행
@After (이후) : 타겟 메소드의 결과에 관계없이(즉 성공, 예외 관계없이) 타겟 메소드가 완료 되면 어드바이스 기능을 수행
@AfterReturning (정상적 반환 이후)타겟 메소드가 성공적으로 결과값을 반환 후에 어드바이스 기능을 수행
@AfterThrowing (예외 발생 이후) : 타겟 메소드가 수행 중 예외를 던지게 되면 어드바이스 기능을 수행
@Around (메소드 실행 전후) : 어드바이스가 타겟 메소드를 감싸서 타겟 메소드 호출전과 후에 어드바이스 기능을 수행




사용법


@Aspect
@Component
@Slf4j
public class LoggingAspect {

    @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
    public void onGetRequest() { }

    @Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
    public void onPostRequest() { }
     
    @Pointcut("execution(* com.test..*.SomeService.*(..))")
    public void onSomeServiceExecution() { }
    
    /**
     *  특정 서비스 호출시 Aspect 실행
     */
    @Before("onSomeServiceExecution()")
    public void beforeSomeServiceAction(JoinPoint joinPoint) {
        // 메서드 정보 받아오기
        Method method = getMethod(joinPoint);
        log.info("======= method name = {} =======", method.getName());

        // 파라미터 받아오기
        Object[] args = joinPoint.getArgs();
        if (args.length <= 0) log.info("no parameter");
        for (Object arg : args) {
            log.info("parameter type = {}", arg.getClass().getSimpleName());
            log.info("parameter value = {}", arg);
        }
    }

    /** 
     *  Pointcut 과 매칭되는 메서드의 실행 전, 후에 실행
     *  @Around advice 는 꼭 proceed()가 필요하다. 
     */
    @Around("onGetRequest() || onPostRequest()")
    public Object logAction(ProceedingJoinPoint joinPoint) throws Throwable{
        Object result = null;
        try {
            result = joinPoint.proceed(joinPoint.getArgs());
            return result;
        } finally {
            // HttpServletRequest 로 URI 및 파라미터 목록 출력
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
            log.info("{} {}", request.getMethod(), request.getRequestURI());
            Set<String> keySet = request.getParameterMap().keySet();
            log.info("================== parameter scan starting ==================");
            for(String key: keySet) {
                log.info("parameter key = {}", key);
                log.info("parameter value = {}", request.getParameter(key));
            }
            log.info("================== parameter scan finished ==================");
        }
    }
}

beforeSomeServiceActionlogAction 두가지 예시를 들었다. 이에서 설명 했듯이 @Around 어노테이션은 타겟 메소드를 감싸서 호출 전후로 실행되는 메소드이기 때문에 반드시 proceed()를 메소드 내에서 실행하여야 한다.

@Before 어노테이션에서 사용된 onSomeServiceExecution() Pointcut처럼 특정 클래스의 모든 메소드가 실행될 때 Advice를 실행할 수도 있고, 특정 어노테이션이 실행될 때, 또는 위 예시에서 사용되지는 않았지만 특정 Bean을 타겟으로 설정할 수도 있다.




참고


[Spring] 스프링 AOP (Spring AOP) 총정리 : 개념, 프록시 기반 AOP, @AOP

profile
개발공부를해보자

0개의 댓글