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
를 적용해볼께요.
당연히 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();
}
}
Pointcut
은 JoinPoint
를 특정 조건으로 걸러낸 것으로 부분집합 정도로 보면 됩니다.JoinPoint
를 매개변수로 받는 경우 런타임의 메서드 정보를 알아낼 수 있습니다.컨트롤러의 log 부분을 모두 주석처리 한 후 해당 메서드를 호출해볼께요.
/echo?message=hello 호출
/save 호출
로그가 잘 찍혔네요 ㅎ