스프링 부트 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 객체를 이용할 수 있습니다.
'웹개발 > SpringBoot' 카테고리의 다른 글
빌드하고 실행하기 (0) | 2023.03.18 |
---|---|
ObjectMapper (0) | 2023.03.14 |
댓글 수정, 삭제기능 추가하기 (API 호출, fetch) (1) | 2023.03.14 |
자바스크립트 fetch로 REST API 호출하기 (댓글등록) (0) | 2023.03.13 |
댓글 REST API 완성하기 (0) | 2023.03.13 |
댓글