Aspect-Oriented Programming
주요기능 외 부가기능들을 특정 지점에 삽입하는 프로그래밍 방법.
더욱 간결하고 효율적인 프로그래밍이 가능하게 해준다.
@Aspect //부가기능 주입을 위한 AOP클래스 선언
@Pointcut //주입 대상 지정
@Before // 대상 실행 이전에 수행
@After //대상 실행 후 수행
@AfterReturning // 대상 실행 - 정상 시 수행
@AfterThrowing // 대상 실행 - 예외 발생 실행 시 수행
@Around // 대상 실행 전후로 수행
댓글 서비스 입출력 값 확인을 위한 로깅과
특정 메소드의 수행 시간을 측정하는 AOP를 실습해보겠다.
public CommentDto create(Long articleId, CommentDto dto) {
log.info("입력값 =>{}",articleId);
log.info("입력값 =>{}",dto);
//게시글 조회 및 예외 처리
Article article = articleRepository.findById(articleId)
.orElseThrow(()-> new IllegalArgumentException("댓글 생성 실패 ! 대상 게시글이 없다."));
//댓글 엔티티 생성
Comment comment = Comment.createComment(dto,article);
//엔티티를 db에 저장
Comment created = commentRepository.save(comment);
//dto로 변경하여 반환
CommentDto createdDto = CommentDto.createCommentDto(created);
log.info("반환값 => {}",createdDto);
return createdDto;
}
이런식으로 코드에 직접 추가할 수 있다.
하지만 logging 과 같은 부가적인 코드가 많아지면 코드가 번거럽고 더러워진다.
이러한 문제점을 AOP로 해결할 수 있다!!
@Aspect //AOP 클래스 선언 : 부가 기능을 주입하는 클래스
@Component //IoC 컨테이너가 해당 객체를 생성 및 관리
@Slf4j //로깅 기능
public class DebuggingAspect {
//입력값 로깅 메소드
//어떤 메소드를 대상 메소드로 선택할지!
@Pointcut("execution(* com.example.firstproject.service.CommentService.*(..))")
private void cut() {}
//cut() 실행 이전에 수행 (실행 시점 설정)
@Before("cut()")
public void loggingArgs(JoinPoint joinPoint){
//joinPoint는 cut()의 대상 메소드를 둘러싼 결합 지점
//입력값 가져오기
Object[] args = joinPoint.getArgs();
//클래스명
String className = joinPoint.getTarget()
.getClass()
.getSimpleName();
//메소드명
String methodName = joinPoint.getSignature()
.getName();
//입력값 로깅하기
for(Object obj : args){
log.info("{}#{}의 입력값 => {}",className ,methodName, obj);
}
}
//반환값 로깅 메소드
@AfterReturning(value = "cut()", returning = "returnObj")
public void loggingReturnValue(JoinPoint joinPoint,
Object returnObj) {
//joinPoint는 cut()의 대상 메소드
//returnObj는 cut()의 리턴값
//클래스명
String className = joinPoint.getTarget()
.getClass()
.getSimpleName();
//메소드명
String methodName = joinPoint.getSignature()
.getName();
//반환값 로깅
log.info("{}#{}의 반환값 => {}",className ,methodName, returnObj);
}
}
예를 들어 create() 메소드 하나를 대상으로 할 수도 있고,
CommentService.java에 있는 모든 메소드를 대상으로 설정할 수도 있다.
"execution(* com.example.firstproject.service.CommentService.create(..))"
"execution(* com.example.firstproject.service.CommentService.*(..))"
댓글 서비스 삭제 기능의 수행 시간을 측정해보겠다.
어노테이션 자바 클래스인 RunningTime.java
@Target({ElementType.TYPE, ElementType.METHOD}) //어노테이션 적용 대상
@Retention(RetentionPolicy.RUNTIME) //어노테이션 유지 기간을 런타인까지 유지하겠다.
public @interface RunningTime {
}
PerformanceAspect.java
@Aspect
@Slf4j
@Component
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 {
//그 대상을 실행까지 할 수 있는 joinPoint
//메소드 수행 전에 시간 측정 시작
StopWatch stopWatch = new StopWatch();
stopWatch.start();
//메소드를 수행
Object returningObj = joinPoint.proceed();
//메소드명
String methodName = joinPoint.getSignature()
.getName();
//메소드 수행 완료 후 측정 종료 및 로깅
stopWatch.stop();
log.info("{}의 총 수행 시간 => {} sec",methodName,stopWatch.getTotalTimeSeconds());
}
}
CommentApiController.java
//댓글 삭제 메소드
@RunningTime
@DeleteMapping("/api/comments/{id}")
public ResponseEntity<CommentDto> delete(@PathVariable Long id){
CommentDto deletedDto= commentService.delete(id);
return ResponseEntity.status(HttpStatus.OK).body(deletedDto);
}
아니 근데 나는 왜 한글 출력이...아웅 귀차나
.
.
.
.
.
.
.
IntelliJ 한글 깨짐 수리
IntelliJ vmoptions 설정
/bin/idea64.exe.vmoptions
-Dfile.encoding=UTF-8 추가