안녕하세요 코이킹입니다.
이번 포스트는 AOP를 적용한 로그 출력에설정에 대한 내용입니다.
개념적인 내용은 거의 없으므로, 개념적인 내용을 원하신다면 AOP를 키워드로 구글 검색하셔서 다른 자료를 참고해주시면 감사하겠습니다.
1. 목표
- AOP라는 키워드를 알기
- 스프링 부트에서 AOP를 적용하여 로그를 출력하도록 설정할 수 있다.
2. AOP란?
AOP를 구글에서 검색해보시면 위키백과에 검색해보시면 다음과 같은 검색 결과를 얻을 수 있을 겁니다.
컴퓨팅에서 관점 지향 프로그래밍(aspect-oriented programming, AOP)은 횡단 관심사(cross-cutting concern)의 분리를 허용함으로써 모듈성을 증가시키는 것이 목적인 프로그래밍 패러다임이다.
저의 경우 동일한 처리(횡단 관심사)를 서로 다른 컴포넌트에서 실시할 경우에는 동일한 처리를 하나의 모듈로 묶어서 처리한다면 더 효율적인 구현이 가능할 수 있다고 이해했습니다.
스프링부트에서도 AOP개념을 쉽게 적용할 수 있는 기능을 제공하고 있기에, 스프링 부트를 의 기능 활용해서 AOP개념을 적용한 로그 출력을 구현해 봤습니다.
3. AOP개념을 적용한 로그출력 설정
1) AOP 로그출력 설정 : /template-springboot/src/main/java/com/sb/template/aop/CommonAopLog.java
package com.sb.template.aop;
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.aspectj.util.GenericSignature.ClassSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
@Aspect
public class CommonAopLog {
@Pointcut("execution(* com.sb.template.controller.*.*(..))")
public void pointCut() {}
@Pointcut("@annotation(com.sb.template.annotation.Timer)")
public void timerPointCut() {}
@Before("pointCut()")
public void beforeProcess(JoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
Object[] objs = joinPoint.getArgs();
log.info("Start-{} ", methodSignature.getDeclaringTypeName() + "." + method.getName());
log.info("Request Param : {} ", objs);
}
@AfterReturning(value = "pointCut()", returning = "returnValue")
public void afterReturning(JoinPoint joinPoint, Object returnValue) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
log.info("End-{} ", methodSignature.getDeclaringTypeName() + "." + method.getName());
log.info("Response Param : {} ", returnValue);
}
@Around("timerPointCut()")
public Object timerProcess(ProceedingJoinPoint proceedingJoinPoint) {
StopWatch stopWatch = new StopWatch();
Object res = new Object();
MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
Method method = methodSignature.getMethod();
Object[] objs = proceedingJoinPoint.getArgs();
log.info("Start-{} ", methodSignature.getDeclaringTypeName() + "." + method.getName());
log.info("Input : {} ", objs);
try {
stopWatch.start();
res = proceedingJoinPoint.proceed();
log.info("End-{} ", methodSignature.getDeclaringTypeName() + "." + method.getName());
log.info("Output : {} ", res);
stopWatch.stop();
log.info("Process execution time : {} ", stopWatch.getTotalTimeSeconds());
return res;
} catch(Throwable throwable) {
log.error("Process-Error-{} , Message : {} ",
method.getName(), throwable.getMessage());
return res;
}
}
}
- 21행 : 스프링부트에서 사용할 빈으로 등록- 22행 : AOP적용하겠다는 선언 - 25~29행 : 포인트 컷(CommonAopLog에서 로그를 출력할 곳을 지정한 것)을 설정했습니다. 25행의 경우 컨트롤러 클래스가 들어있는 패키지를 지정해서 모든 컨트롤러 클래스에서 AOP를 사용한 로그를 출력할 수 있도록 설정했으며, 28행의 경우 Timer라는 어노테이션이 선언되어 있는 곳에서 AOP를 사용한 로그를 출력할 수 있도록 설정했습니다.
- 31~38행 : 컨트롤러에서 받은 요청의 요청 파라미터와 바디의 데이터를 확인할 수 있는 로그를 출력하는 메서드로, @Before가 선언되어 있어 컨트롤러의 요청에 매칭 되는 메서드가 실행되기 전에 beforeProcess메서드가 실행 되게 됩니다.
- 40~46행 : 컨트롤러에서 받은 요청의 응답을 보낼때의 데이터를 확인할 수 있는 로그를 출력하는 메서드로, @AfterReturning가 선언되어 있어 컨트롤러의 요청에 매칭 되는 메서드가 실행된 후에 afterReturning메서드가 실행 되게 됩니다.
- 48~73행 : Timer어노테이션이 선언된 곳의 처리시간과 매개변수로 사용된 데이터, 리턴값을 출력하는 메서드입니다.
2) AOP적용지점을 선언하기 위한 커스텀 어노테이션 : /template-springboot/src/main/java/com/sb/template/annotation/Timer.java
package com.sb.template.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Timer {
}
3) 컨트롤러/template-springboot/src/main/java/com/sb/template/controller/BoardController.java
- 이전 Log4j설정 시에 테스트용으로 작성한 로그 출력 코드를 제거했습니다.
4) 서비스 : /template-springboot/src/main/java/com/sb/template/service/BoardService.java
package com.sb.template.service;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import com.sb.template.annotation.Timer;
import com.sb.template.entity.Board;
import com.sb.template.enums.BoardType;
import com.sb.template.repo.BoardRepository;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class BoardService {
@Autowired
private BoardRepository boardRepository;
@Timer
public List<Board> getAllBoard() {
List<Board> res = boardRepository.findAll();
if (res == null) return null;
log.debug("Data from DB : {}", res);
return res;
}
@Timer
public Board createBoard(Board board) {
return boardRepository.save(board);
}
@Timer
public Board getBoardOne(int boardNo) {
Optional<Board> board = boardRepository.findById(boardNo);
if (board.isEmpty()) {
return null;
}
return board.get();
}
@Timer
public void updateBoardForm(int boardNo, Model model) {
Optional<Board> board = boardRepository.findById(boardNo);
if (board.isEmpty()) {
return ;
}
model.addAttribute("boardTypes", BoardType.getBoardTypes());
model.addAttribute("board", board.get());
}
@Timer
@Transactional
public Board updateBoard(int boardNo, Board updatedBoard) {
Optional<Board> res = boardRepository.findById(boardNo);
if (res.isEmpty()) {
return null;
}
Board board = res.get();
board.setType(updatedBoard.getType());
board.setTitle(updatedBoard.getTitle());
board.setContents(updatedBoard.getContents());
Board endUpdatedBoard = boardRepository.save(board);
return endUpdatedBoard;
}
@Timer
public void deleteBoard(int boardNo) {
Optional<Board> res = boardRepository.findById(boardNo);
if (res.isEmpty()) {
return ;
}
boardRepository.delete(res.get());
}
}
- 25, 36, 42, 54, 66, 85행 : 커스텀 어노테이션을 사용하여 어노테이션이 사용된 메서드에 AOP 로그 출력을 적용했습니다.
4. 동작확인
※ 위의 캡처는 글 상세보기 요청 시의 로그입니다.
컨트롤러와 서비스에서 AOP를 사용한 로그가 제대로 출력되고 있는 것을 확인할 수 있습니다.
5. 전체 소스코드
https://github.com/leeyoungseung/template-springboot/tree/feature/setting_for_log4j2
AOP를 적용한 로그 출력에 대한 포스트는 이상으로 마치겠습니다.
다음 포스트는 유효성 검사에 대한 내용이 되겠습니다.
'프로그래밍 > Springboot-토이프로젝트' 카테고리의 다른 글
【게시판-번외06】예외처리 (0) | 2022.09.16 |
---|---|
【게시판-번외05】유효성 검사 (0) | 2022.09.14 |
【게시판-번외03】필수기능 설정 & Log4j설정 (0) | 2022.09.14 |
【게시판-06】글 삭제 (0) | 2022.09.13 |
【게시판-05】글 수정 (0) | 2022.09.11 |
댓글