[Spring AOP] 간단한 AOP 적용 예제 (Logging)

Kim Dae Hyun·2021년 8월 22일
0

Spring-AOP

목록 보기
1/6
post-thumbnail

🔎 AOP ??..

AOP의 아주 작은 정의
1. (부가적인) 공통의 기능을 분리하자.
2. 한 개 모듈(메서드)는 자신의 비즈니스 로직에만 집중하도록 하자

상황을 가정해볼께요.
한 컨트롤러의 메서드로 들어오는 파라미터에 대해 로그를 남기고 싶습니다.
파라미터 뿐만 아니라 리턴되는 값에 대한 타입, 값 까지 모두 로그로 찍고 싶고 한 메서드가 아닌 컨트롤러 내의 모든 메서드에 적용하고 싶습니다.

@GetMapping("/api/test1")
public String test1(@RequestBody DataDto dto) {

    log.info("Arg dto.id = {}", dto.getId());
    log.info("Arg dto.content = {}", dto.getContent());
    
    [ 비즈니스로직 수행 ... ]
    [ 비즈니스로직 수행 ... ]
    
    log.info("Return dto.id = {}", dto.getId());
    log.info("Return dto.content = {}", dto.getContent());
    
    return dto;
}

@GetMapping("/api/test2")
public String test2(@RequestBody DataDto dto) {

    log.info("Arg dto.id = {}", dto.getId());
    log.info("Arg dto.content = {}", dto.getContent());
    
    [ 비즈니스로직 수행 ... ]
    [ 비즈니스로직 수행 ... ]
    
    log.info("Return dto.id = {}", dto.getId());
    log.info("Return dto.content = {}", dto.getContent());
    
    return dto;
}

반복되는 작업이 보입니다.
이런 반복되는 작업을 분리하여 전처리 혹은 후처리가 가능하도록 AOP를 적용해볼께요.


🔎 AOP 적용

당연히 spring boot을 사용할테니 start를 이용해서 AOP에 대한 의존성을 가져올께요.

[ Gradle ]
implementation 'org.springframework.boot:spring-boot-starter-aop'

AOP 적용전

    @GetMapping("/echo")
    public String echo(@RequestParam String message) {

        log.info("echo parameter = {}", message);
        
        // 비즈니스 로직 수행 ..

        log.info("echo return = {}", message);
        return message;
    }

    @PostMapping("/save")
    public DataDto save(@RequestBody DataDto dto) {

        log.info("save parameter = {}", dto.getId());
        log.info("save parameter = {}", dto.getContent());
        
	// 비즈니스 로직 수행 ..
    
        log.info("save return = {}", dto.getId());
        log.info("save return = {}", dto.getContent());
        return dto;
    }

AOP 클래스 작성

@Slf4j
@Aspect
@Component
public class SimpleLogAop {

    // com.aop.controller 이하 패키지의 모든 클래스 이하 모든 메서드에 적용
    @Pointcut("execution(* com.aop.controller..*.*(..))")
    private void cut(){}

    // Pointcut에 의해 필터링된 경로로 들어오는 경우 메서드 호출 전에 적용
    @Before("cut()")
    public void beforeParameterLog(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);
        }
    }

    // Poincut에 의해 필터링된 경로로 들어오는 경우 메서드 리턴 후에 적용
    @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);
    }

    // JoinPoint로 메서드 정보 가져오기
    private Method getMethod(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        return signature.getMethod();
    }
}
  • @Aspect
    AOP 설정 클래스
  • Pointcut
    특정 조건으로 JoinPoint를 필터링 (패키지 단위, 클래스 단위, 메서드 단위, 어노테이션 단위 ... )
    Aspect를 적용시킬 위치를 지정한다고 이해해도 될 것 같습니다.
  • JoinPoint
    Aspect를 적용시킬 수 있는 모든 지점입니다.
    PointcutJoinPoint를 특정 조건으로 걸러낸 것으로 부분집합 정도로 보면 됩니다.
    JoinPoint를 매개변수로 받는 경우 런타임의 메서드 정보를 알아낼 수 있습니다.
  • @Before
    호출 전 !
  • @AfterReturning
    예외 없이 정상적으로 리턴 후 !

🌡 테스트

컨트롤러의 log 부분을 모두 주석처리 한 후 해당 메서드를 호출해볼께요.

/echo?message=hello 호출

/save 호출

로그가 잘 찍혔네요 ㅎ

profile
좀 더 천천히 까먹기 위해 기록합니다. 🧐

0개의 댓글