[Spring Framework]AOP를 이용한 Log 기록하기
이전 글에 이어 이번엔 aop를 이용해 로그를 기록하는 방법을 포스팅합니다.
테스트 코드 구조는 이전 글에서 참고해주세요.(Controller, Service, DAO, VO 등)
AOP에서 공통 기능 모듈을 Aspect라고 정의합니다.
Aspect는 기능을 정의한 Advice와 Advice가 적용될 Pointcut을 통해 실행됩니다. Advice가 실질적으로 기능이 구현된 구현체인 것이며, Advice는 Aspect가 Advice의 기능을 언제 실행할 지를 정의합니다. 그리고 기능이 적용될 대상을 Pointcut으로 설정합니다. 테스트 케이스는 데이터 저장 전과 후로 나누어 진행했습니다.
1. 데이터 저장 전
1) Advice를 정의할 클래스를 생성합니다.
package com.study.util;
import org.aspectj.lang.JoinPoint;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.study.transaction.vo.TransactionVO;
@Aspect
public class LogAdvice {
private static final Logger logger = LoggerFactory.getLogger(LogAdvice.class);
@Pointcut("execution(* com.study.transaction.service.impl.*Impl.*(..))")
private static void advicePoint() {}
@Before("advicePoint()")
public void logBefore(JoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
logger.info("### Before - Method : " + methodSignature.toString());
for(Object arg : joinPoint.getArgs()) {
if(arg instanceof TransactionVO) {
TransactionVO transactionVO = (TransactionVO) arg;
logger.info("VO/DTO => " + transactionVO);
logger.info("testno => " + transactionVO.getTestno());
logger.info("testname => " + transactionVO.getTestname());
} else {
logger.info(arg.toString());
}
}
}
}
@Pointcut 어노테이션은 기능이 적용될 대상을 정의한 것입니다. service.impl 패키지 하위에 있는 Impl로 끝나는 모든 클래스의 메소드에 대해 적용되도록 했습니다.
@Before 어노테이션은 적용 대상에 대해 기능을 언제 실행할 것인지에 대한 정의입니다. Before 뜻대로 메소드의 실행전에 logBefore 메소드가 실행됩니다.
실행 시점은 다음과 같이 정의할 수 있습니다.
- @Around : 타겟 메소드 실행 전과 후
- @Before : 타겟 메소드 실행 전
- @After : 타겟 메소드 실행 후
- @AfterReturning : 타겟 메소드 호출이 정상적으로 종료된 후
- @AfterThrowing : 타겟 메소드의 예외가 발생한 경우
앞서 대상 객체의 정보나 호출되는 메소드에 전달되는 파라미터 정보도 알 수 있다고 했는데 바로 JoinPoint라는 인터페이스 덕분입니다.
getArgs() 메소드는 메소드가 호출될 때 넘어온 인자 목록을 Object 배열로 리턴합니다. 이 방법을 이용해 리턴받은 배열을 루프 돌려 타입이 TransactionVO인 경우에만 값을 가져오도록 했습니다.
2) 생성한 LogAdvice 클래스를 Bean 객체로 등록합니다.
servlet-context.xml
<!-- Log 기록 -->
<beans:bean id="logAdvice" class="com.study.util.LogAdvice" />
3) 테스트 페이지 생성
ex02.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
<title>Spring Transaction Example</title>
<%@ include file="/WEB-INF/views/include/source.jsp" %>
<script>
function testTransaction() {
var params = {
testno : $("#testno").val(),
testname : $("#testname").val()
};
$.ajax({
type : "POST",
async : false,
url : "/transaction/testLogBefore.do",
cache : false,
dataType : "json",
data : JSON.stringify(params),
contentType: "application/json; charset=utf-8",
success: function(data) {
var msg = data.msg;
$("#result").html(msg);
},
error : function(request, status, error) {
alert("code : " + request.status + "\n" + "message : " + request.responseText + "\n" + "error : " + error);
}
});
}
$(document).ready(function() {
});
</script>
</head>
<body>
<form id="testForm">
<div class="container">
<div class="row border-bottom mb-3">
<div class="col-12">
<p class="h1">EX02. AOP를 이용해 Log 기록하기</p>
</div>
</div>
<div class="row">
<div class="col-4">
<div class="input-group mb-3">
<span class="input-group-text" id="basic-addon1">TESTNO</span>
<input type="text" class="form-control" id="testno" name="testno">
</div>
<div class="input-group mb-3">
<span class="input-group-text" id="basic-addon1">TESTNAME</span>
<input type="text" class="form-control" id="testname" name="testname">
</div>
</div>
</div>
<div class="row">
<div class="col-4">
<button type="button" class="btn btn-primary mb-3" onclick="testTransaction()">전송</button>
</div>
</div>
<div class="row">
<div class="col-auto">
결과 : <span id="result"></span>
</div>
</div>
</div>
<%@ include file="/WEB-INF/views/common/bottom.jsp" %>
</form>
</body>
</html>
*아래는 기존 코드에서 추가된 부분
TransactionController.java
@RequestMapping(value = "/transaction/testLogBefore.do", method = { RequestMethod.POST })
public @ResponseBody JSONObject testLogBefore(@RequestBody String jsonParam) throws Exception {
Gson gson = new Gson();
JSONObject result = new JSONObject();
TransactionVO transactionVO = new TransactionVO();
try {
transactionVO = gson.fromJson(jsonParam, TransactionVO.class);
transactionService.insertEx02(transactionVO);
result.put("msg", "SUCCESS");
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
throw new CustomSqlException("ERROR");
}
return result;
}
TransactionService.java
public String insertEx02(TransactionVO transactionVO) throws Exception;
TransactionServiceImpl.java
@Override
public String insertEx02(TransactionVO transactionVO) throws Exception {
transactionDAO.insertData(transactionVO); // Success
return "OK";
}
4) 테스트
결과를 보면 메소드(입력) 실행 전 Advice가 실행된 것을 확인할 수 있습니다.
2. 데이터 저장 후
1) LogAdvice에 @After 어노테이션을 사용한 메소드를 추가합니다.
@After(value="advicePoint()")
public void logAfter(JoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
logger.info("### After - Method : " + methodSignature.toString());
for(Object arg : joinPoint.getArgs()) {
if(arg instanceof TransactionVO) {
TransactionVO transactionVO = (TransactionVO) arg;
logger.info("VO/DTO => " + transactionVO);
logger.info("testno => " + transactionVO.getTestno());
logger.info("testname => " + transactionVO.getTestname());
} else {
logger.info(arg.toString());
}
}
}
2) 테스트
이번에는 메소드(입력) 실행 후 Advice가 실행된 것을 확인할 수 있습니다.
마지막으로 타겟 메소드에서 리턴하는 값을 가져오는 법도 알아보겠습니다.
3) LogAdvice에 @AfterReturning 어노테이션을 사용한 메소드를 추가합니다.
@AfterReturning(value="advicePoint()", returning="msg")
public void logAfterReturn(JoinPoint joinPoint, String msg) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
logger.info("### AfterReturning - Method : " + methodSignature.toString());
for(Object arg : joinPoint.getArgs()) {
if(arg instanceof TransactionVO) {
TransactionVO transactionVO = (TransactionVO) arg;
logger.info("VO/DTO => " + transactionVO);
logger.info("testno => " + transactionVO.getTestno());
logger.info("testname => " + transactionVO.getTestname());
logger.info("return msg => " + msg);
} else {
logger.info(arg.toString());
}
}
}
returning 옵션을 통해 타겟 메소드에서 리턴하는 값에 접근할 수 있습니다.
참고로 타겟 메소드(위 소스 TransactionServiceImpl.java 참고)에서는 "OK"라는 문자열을 리턴하고 있습니다.
4) 테스트
참고자료
전체 소스는 GitHub에서 확인하실 수 있습니다.
+피드백은 언제나 환영입니다 :)