Framework/Spring

[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) 테스트

ex02.jsp

결과를 보면 메소드(입력) 실행 전 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) 테스트

 

참고자료
 

Spring : AOP with Java Config - Method Parameter 접근

1. 이 포스트는 Spring : AOP with Java Config 시리즈의 연속이다. 2. 종종 어떤 메소드를 실행 전후에 해당 메소드의 parameter에 접근해 데이터를 참조하거나 수정할 필요가 있다. 3. Apect 클래스에서 method.

kogle.tistory.com

 

Spring AOP AspectJ @AfterThrowing Example - HowToDoInJava

In this spring aop example, we will learn to use aspectj @AfterThrowing annotation. @AfterThrowing annotated methods run after the method (matching with pointcut expression) exits by throwing an exception.

howtodoinjava.com

 

스프링 AOP JoinPoint 객체

  스프링 AOP JoinPoint JoinPoint는 Spring AOP 혹은 AspectJ에서 AOP가 적용되는 지점을 뜻한다. 해당 지점을 JoinPoint 라는 인터페이스로 나타낸다. AOP 를 수행하는 메소드는 이 JoinPoint 인스턴스를 인자..

dlgkstjq623.tistory.com


전체 소스는 GitHub에서 확인하실 수 있습니다.

+피드백은 언제나 환영입니다 :)

반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.