본문 바로가기
웹개발/SpringBoot

AOP(Aspect Oriented Programming)

by 철없는민물장어 2023. 3. 14.
728x90
반응형

스프링 부트 AOP (Aspect-Oriented Programming)는 스프링 프레임워크에서 제공하는 기능으로, 비즈니스 로직과 같은 핵심 기능과 로깅, 보안, 트랜잭션 처리와 같은 부가 기능을 분리하여 개발할 수 있도록 지원합니다.

 

예를 들어,

    @Transactional //DB를 건들기때문에, 중간에 문제가 생기면 롤백되도록 트랜잭션으로 설정
    public CommentDto create(Long articleId, CommentDto dto) {
//        log.info("input => {}",articleId);
//        log.info("input => {}",dto);

        //게시글조회 및 예외발생
        Article article = articleRepository.findById(articleId)
                .orElseThrow(()-> new IllegalArgumentException("댓글 생성 실패: 대상 게시글이 없습니다"));
        //댓글 엔티티 생성
        Comment comment = Comment.createComment(dto,article);
        //댓글 엔티티를 DB로 저장

        Comment created = commentRepository.save(comment);

        CommentDto createdDto = CommentDto.createCommentDto(created);

        //log.info("output=> {}",createdDto);
        //DTO로 변환하여 저장
        return createdDto;
    }

CommentService의 한 메소드인 create()에서

input데이터와 output데이터를 로깅하고 싶다면,

 create()코드 내에 log.info()를 작성하여 로깅할 수 있습니다.

 

그러나, create()코드 내에 로깅 코드가 들어가게 되면

create()의 핵심 기능과 로깅 코드가 뒤섞여, 코드의 가독성이 떨어질 수도 있으며

유지보수에도 어려움을 줄 수 있습니다.

 

또한, 여러 메소드에 똑같은 로깅 코드를 적용하려면 

코드의 중복이 많이 일어나게 됩니다.

 

이러한 상황에서 로깅 등의 부가기능을 분리하여 사용할 수 있도록 하는것이 AOP입니다.

 

package com.example.firstproject.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect //부가 기능을 주입하는 클래스
@Component //Ioc 컨테이너가 해당 객체를 생성 및 관리
@Slf4j
public class DebuggingAspect {

    //대상메소드 선택: CommentService#create()
    @Pointcut("execution(* com.example.firstproject.service.CommentService.*(..))")
    private void cut(){}

    //실행 시점 설정: cut()의 대상이 수행되기 이전
    @Before("cut()")
    private void loggingArgs(JoinPoint joinPoint){
        //입력값 가져오기
        Object [] args = joinPoint.getArgs();
        //클래스명
        String className = joinPoint.getTarget()
                .getClass()
                .getSimpleName();
        //메소드명
        String methodName = joinPoint.getSignature()
                .getName();

        //입력값 로깅하기
        //CommentService#create()의 입력값 => 5
        for(Object obj: args){
            log.info("{}#{} input => {}",className,methodName,obj);
        }
    }

    @AfterReturning(value = "cut()",returning = "returnObj") //cut()에 지정된 대상 호출 성공 후 실행
    public void loggingReturnValue(JoinPoint joinPoint, Object returnObj){
        String className = joinPoint.getTarget()
                .getClass()
                .getSimpleName();
        //메소드명
        String methodName = joinPoint.getSignature()
                .getName();

        log.info("{}#{} output => {}",className,methodName,returnObj);
    }

}

서비스 실행시 input 데이터와 output데이터를 로깅하기 위해 AOP를 만듭니다.

aop패키지를 만들고, 내부에 DebuggingAspect 클래스를 생성하였습니다.

클래스에는 @Aspect 어노테이션을 붙여 AOP클래스를 선언하고

@Component 어노테이션을 붙여 Ioc컨테이너가 해당 객체를 생성 및 관리하도록 했습니다.

 

@PointCut() 으로 대상 메소드를 선택하고,

 

해당 메소드가 실행되기 직전에 실행할 내용을

@Before()에,

 

해당 메소드가 실행완료한 직후에 실행할 내용을

@After에 작성합니다.

 

파라미터에 JoinPoint를 넣어, 실행하는 메소드의 인자,클래스명,메소드명 등을 사용할 수 있습니다.

 


이번에는 서비스가 실행되는동안 얼만큼의 시간이 걸렸는지 측정하는 AOP를 만들어 보겠습니다.

이 AOP는 어노테이션으로 사용할 수 있게 하겠습니다.

package com.example.firstproject.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD}) //어노테이션 적용대상 지정
@Retention(RetentionPolicy.RUNTIME) //어노테이션 유지기간을 런타임시까지..
public @interface RunningTime {
}

annotation 패키지를 만들어 위와같은 어노테이션클래스를 만들고..

 

package com.example.firstproject.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

@Aspect
@Component
@Slf4j
public class PerformanceAspect {

    //특정 어노테이션을 대상 지정
    @Pointcut("@annotation(com.example.firstproject.annotation.RunningTime)")
    private void enableRunningTime(){}

    //기본 패키지의 모든 메소드
    @Pointcut("execution(* com.example.firstproject..*.*(..))")
    private void cut(){}

    //두 조건을 모두 만족하는 대상을 전후로 부가 기능을 삽입
    @Around("cut() && enableRunningTime()")
    public void loggingRunningTime(ProceedingJoinPoint joinPoint) throws Throwable {

        //메소드 수행 전, 측정 시작
        StopWatch stopWatch=new StopWatch();
        stopWatch.start();
        //메소드 수행
        Object returningObj = joinPoint.proceed();
        //메소드 수행 후, 측정 종료 및 로깅
        stopWatch.stop();

        String methodName = joinPoint.getSignature().getName();
        log.info("{} Running Time => {}sec",methodName,stopWatch.getTotalTimeSeconds());
    }
}

aop패키지 내에 performance Aspect를 생성했습니다.

 

위의 로깅 aspect와 유사한데,

이번에는 대상 메소드 전후로 부가기능을 삽입하기 위해 @Around어노테이션을 사용하였습니다.

시간 측정은 StopWatch 객체를 이용할 수 있습니다.

 

728x90
반응형

댓글