본문 바로가기
스프링 (Spring)

AOP에 대해 알아보자

by codeyaki 2023. 8. 4.
반응형

스프링의 중요 개념 중 하나인 AOP (Aspect-Oriented Programming)에 대해서 알아보는 시간을 가져보려고 한다.

AOP란?

  • AOP란 개발을 하면서 중복코드를 제거하고 모듈성을 향상하기 위해서 사용되는 프로그래밍 패러다임이다.
  • spirng은 이러한 AOP를 지원하고 있다.
  • AOP의 핵심은 “횡단 관심사”를 분리하는 것이다. 즉, 애플리케이션의 주요 로직과는 별도로 존재하는 공통된 기능들을 한 곳에 모아서 관리하는 것 이러한 공통 기능을 “Aspect”라고 하며, 이를 원하는 부분에 주입하여 적용한다!

AOP 장점

  • 모듈성 향상
  • : 핵심 비즈니스 로직과 공통 기능들을 분리하여 코드의 가독성과 유지보수성을 개선한다.
  • 중복 코드 제거
  • : 공통 기능이 여러 곳에서 필요한 경우, AOP를 사용하여 중복 코드를 제거하고 코드 베이스를 간결하게 유지할 수 있습니다.
  • 관점 지향
  • : 관심사를 명확하게 분리하여 개발자가 핵심 비즈니스 로직에만 집중
  • 코드 재사용성
  • : 여러 모듈에서 같은 공통 기능을 사용해야 할 때, AOP를 이용하여 해당 기능을 한 번 작성하고 재사용 가능

AOP 주요 용어

  • Aspect(관점)
    • 공통 관심사(Concern)를 모듈화 한 단위, 횡단 관심사(Cross-cutting Concerns)라고도 함
    • 로깅, 트랜잭션, 보안 등 애플리케이션 내 여러 모듈에서 반복적으로 사용되는 기능들을 Aspect로 추상화
    • Aspect는 Advice와 Pointcut의 결합체, 메서드 실행 시점과 관련된 로직(Adive)과 어떤 Joinpoint를 선택할지를 정의하는 표현식(Pointcut)이 함께 포함된다.
  • Joinpoint
    • Aspect가 적용될 수 있는 실행 시점
    • 메서드 호출, 메서드 실행, 필드 접근 등 애플리케이션 내에서 특정시점에서 일어나는 지점을 의미한다
    • 각 Advice를 적용할 수 있는 지점, 조인포인트들 중에서 선택한 지점에 advice 적용
    • 각 Advice에 파라미터로 Joinpoint를 넣게 되면 접근할 수 있게 된다.
  • Advie
    • Aspect의 실제 로직을 정의하는 부분, Jointpoint에서 실행되는 행위
    • 어떤 시점에 어떤 로직을 수행할지 결정
    • 주요 Advice의 유형
      • Before: 메서드 실행 전에 실행되는 Advice
      • @Before("execution(* com.example.myapp.service.*.*(..))") public void beforeServiceMethods() { // 메서드 실행 전에 수행할 로직 }
      • After: 메서드 실행 후에 실행되는 Advice
      • @After("execution(* com.example.myapp.service.*.*(..))") public void afterServiceMethods() { // 메서드 실행 후에 수행할 로직 }
      • AfterReturning: 메서드 정상 실행 후에 실행되는 Advice
      • @AfterReturning(pointcut = "execution(* com.example.myapp.service.*.*(..))", returning = "result") public void afterReturningServiceMethods(Object result) { // 메서드가 정상적으로 반환된 후에 수행할 로직, result에 반환값이 들어옵니다. }
      • AfterThrowing: 메서드에서 예외 발생 시 실행되는 Advice
      • @AfterThrowing(pointcut = "execution(* com.example.myapp.service.*.*(..))", throwing = "ex") public void afterThrowingServiceMethods(Exception ex) { // 메서드실행 중 예외 발생 후에 수행할 로직, ex에 예외 객체가 들어옵니다. }
      • Around: 메서드 실행 전후에 실행되며, 메서드 실행 여부를 제어할 수 있는 가장 강력한 Advice
      • @Around("execution(* com.example.myapp.service.*.*(..))") public Object aroundServiceMethods(ProceedingJoinPoint joinPoint) throws Throwable { // 메서드 실행 전에 수행할 로직 Object result = joinPoint.proceed(); // 원래 메서드 실행 // 메서드 실행 후에 수행할 로직 return result; }
  • Pointcut
    • 어떤 Jointpoint를 선택할지 정의하는 표현식
    • Aspect가 어떤 Joinpoint에서 Advice를 적용할지 결정하는 데 사용된다.
    • Pointcut 표현식은 메서드 실행 시점, 패키지 내의 메서드, 어노테이션이 적용된 메서드 등 다양한 기준으로 작성할 수 있다.
    • 여러 Advice에서 같은 Pointcut을 사용할 경우 하나의 메서드를 만들어 둔 뒤 Advice에서 해당 메서드를 value로 넣는 방법도 가능하다.
    • @Slf4j @Aspect @Component public class SimpleAop { @Pointcut("execution(* com.posco.web.controller..*.*(..))") private void cut() { } @Before("cut()") // @Before("execution(* com.posco.web.controller..*.*(..))") public void beforeParamLog(JoinPoint joinPoint) { } @AfterReturning(value = "cut()", returning = "returnObj") // @AfterReturning(value = "execution(* com.posco.web.controller..*.*(..))", returning = "returnObj") public void afterReturnLog(JoinPoint joinPoint, Object returnObj) { } }
  • weaving
    • Aspect를 핵심 비즈니스 로직에 적용하는 과정
    • Aspect가 적용되어 있는 코드를 핵심 비즈니스 로직과 결합하여 실제 실행 가능한 코드로 만드는 과정
    • 컴파일 타임, 로드 타임, 런타임 등 다양한 시점에 Weaving이 수행되고, Spring AOP는 주로 프록시 기반의 런타임 Weaving을 사용한다.

사용 방법

(예시 - 컨트롤러의 파라미터 로깅 남기기)

package com.example.web.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Slf4j
@Aspect
@Component
public class SimpleAop {

    @Pointcut("execution(* com.example.web.controller..*.*(..))")
    private void cut() {
    }

    @Before("cut()")
    public void beforeParamLog(JoinPoint joinPoint) {
        Method method = getMethod(joinPoint);
        log.info(">>>>> method name = {} >>>>", method.getName());

        Object[] args = joinPoint.getArgs();
        if (args.length <= 0) log.info("no Parameters");
        for (int i = 0; i < args.length; i++) {
            log.info("({}) parameter type = {}", i, args[i].getClass().getSimpleName());
            log.info("({}) parameter value = {}", i, args[i]);
        }
        log.info("==============================================");

    }

    @AfterReturning(value = "cut()", returning = "returnObj")
    public void afterReturnLog(JoinPoint joinPoint, Object returnObj) {
        Method method = getMethod(joinPoint);
        log.info("<<<<<< method name = {} <<<<", method.getName());

        log.info("return type = {}", returnObj.getClass().getSimpleName());
        log.info("return value = {}", returnObj);
        log.info("=============================================");
    }

    private Method getMethod(JoinPoint joinPoint) {

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        return signature.getMethod();
    }
}
  1. Aspect 클래스 작성하기
    • @Aspect 어노테이션을 사용하여 Aspect클래스 선언
    • @Component도 선언하거나 Aspect를 Bean으로 등록해야 한다.
  2. Pointcut 정의
    • @Pointcut 어노테이션을 사용하여 Pointcut 표현식 정의
    • Pointcut은 어느 Joinpoint를 선택할지 지정하는 표현식
  3. Advice 정의
    • @Before, @After, @Around 등 Advice 어노테이션을 사용하여 특정 JoinPoint에서 실행될 로직 정의 ⇒ 주요 Advice의 유형 참고
    • Advice 메서드의 실행 시점을 지정하여 로직 적용
  4. @EnableAspectJAutoProxy 어노테이션 추가
    • AOP가 동작하도록 하려면 @EnableAspectJAutoProxy 어노테이션을 설정 클래스에 추가 등록
    • 만약 spring boot에서 사용 중이라면 의존성에 spring-boot-starter-aop를 추가하면 자동으로 해당 어노테이션도 포함된다.
    @SpringBootApplication
    @EnableAspectJAutoProxy
    public class PoscoApplication extends SpringBootServletInitializer {
    
    	public static void main(String[] args) {
    		SpringApplication.run(PoscoApplication.class, args);
    	}
    }
    
반응형