AOP(2)
이번엔
수행되는 메서드, 예외, 리턴값, 처리시간, 접속 ip를 로그로 찍어보는 기능을 구현해보자.
BeforeAspect라는 클래스를 생성해주고
Componetn, Aspect 어노테이션을 작성해주자 그리고 logger객체 생성성
@Component
@Aspect
public class BeforeAspect{
private Logger logger = LoggerFactory.getLogger(BeforeAspect.class);
// 로거는 sl4j를 import해주어야함.
}
다음은 serviceStart메서드를 만들어주고 매개변수로 JoinPoint jp를 넣어주는
여기서 JoinPoint란 인터페이스로 advice가 적용되는 Target Object (ServiceImpl)의 정보,
전달되는 매개변수, 메서드, 반환값, 예외 등을 얻을 수 있는 메서드를 제공한다
주의 사항은 JoinPoint 인터페이스는 항상 첫 번째 매개변수로 작성 되어야한다.
@Component
@Aspect
public class BeforeAspect{
private Logger logger = LoggerFactory.getLogger(BeforeAspect.class);
// 로거는 sl4j를 import해주어야함.
@Before("CommonPointcut.implPointcut()")
public void serviceStart(JoinPoin jp){
String str = "-------------------------------------------------------------\n";
String className = jp.getTarget().getClass().getSimpleName();
String methodName = jp.getSignature().getName();
String param = Arrays.toString( jp.getArgs() );
str += "Start : " + className + " - " + methodName + "\n";
str += "Parameter : " + param + "\n";
}
}
jp.getTarget()은 aop가 적용된 객체를 가져오는건데 여기선 ServiceImpl들이 되겠다
-> jp.getTarget().getClass().getSimpleName(); 은 간단한 클래스명이다(패키지명 제외)
jp.getSignature()는 수행되는 메서드 정보이다.
-> jp.getSignature().getName();
jp.getArgs()는 메서드 호출 시 전달된 매개 변수이다.
-> Arrays.toString( jp.getArgs() );
후에 str에 다 담았다.
다음엔 ip와 로그인 한 이메일을 표시하기.
@Component
@Aspect
public class BeforeAspect {
private Logger logger = LoggerFactory.getLogger(BeforeAspect.class);
@Before("CommonPointcut.implPointcut()")
public void serviceStart(JoinPoint jp) {
String str = "--------------------------------------------------\n";
String className = jp.getTarget().getClass().getSimpleName(); // 간단한 클래스명(패키지명 제외)
String methodName = jp.getSignature().getName();
String param = Arrays.toString( jp.getArgs() );
str += "Start : " + className + " - " + methodName + "\n";
str += "Parameter : " + param + "\n";
try {
HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
Member loginMember = (Member)req.getSession().getAttribute("loginMember");
// ip : xxx.xxx.xxx.xxx (email : test01@naver.com) server 더블 클릭후 세팅
str += "ip : " + getRemoteAddr(req);
if(loginMember != null) { // 로그인 상태인 경우
str += " (email : " + loginMember.getMemberEmail() + ")";
}
} catch( Exception e) {
str += "[스케줄러 동작]";
}
logger.info(str);
}
HttpServletRequest를 이용하여 loginMember를 가져오는데 현재는 스케줄러가 동작중이기 때문에 예외가 발생한다.
그래서 try catch 를 이용하는것.
다음은 ip주소를 가져오는 메소드를 만들어보자
public static String getRemoteAddr(HttpServletRequest request) {
String ip = null;
ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-RealIP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("REMOTE_ADDR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
여러가지 경우의 수를 통해 ip를 가져오도록 만들었다.
다음은 return값이 무엇인지 알아보기 위한 클래스를 만들어보자
여기선 @AfterReturning 을 사용할건데
@AfterReturning이란 기본 @After + 반환값 얻어오기 기능을 수행한다.
@Component
@Aspect
@Order(5)
public class AfterReturningAspect {
private Logger logger = LoggerFactory.getLogger(AfterReturningAspect.class);
@AfterReturning(pointcut = "CommonPointcut.implPointcut()", returning="returnObj")
public void serviceReturnValue(Object returnObj) {
logger.info("Return Value : " + returnObj);
}
}
여기서 returning="returnObj" 은 반환 값을 저장할 매개변수를 지정한것이다.
@Order(5)는 클래스 실행 순서를 나타낸 것인데 큰 숫자 순으로 먼저 실행된다.
다음은 AroundAspect이다
@Around는 전터리(@Before)와 후처리(@After)를 한번에 작성 가능한 advice이다
proceed()는 전 / 후 처리를 나누는 기준으로 이들을 이용해서 러닝타임을 출력하는 클래스를 만들어보자
@Component
@Aspect
@Order(3)
public class AroundAspect {
private Logger logger = LoggerFactory.getLogger(AroundAspect.class);
@Around("CommonPointcut.implPointcut()")
public Object runningTime(ProceedingJoinPoint jp) throws Throwable{
long startMs = System.currentTimeMillis();
Object obj = jp.proceed(); // 전/후 처리를 나누는 기준
long endMs = System.currentTimeMillis();
logger.info("Running Time : " + (endMs - startMs) + "ms");
return obj;
}
매개변수에 있는 ProceedingJoinPoint 인터페이스는 전/후 처리 관련 기능으로
값을 얻어올 수 있는 메소드 제공한다. 여기선 proceed를 사용하는 것이다.
proceed() 메소드 호출 전엔 @Before advice 작성하고 proceed() 호출 후엔 @After advice를 작성하면 되고
메소드 마제막에 proceed() 의 값을 리턴 해야한다
다음은 요청을 수행 한뒤에 메소드 정보와 파라미터를 보여주는 클래스를 만들어보자
@Component
@Aspect
@Order(1)
public class AfterAspect {
private Logger logger = LoggerFactory.getLogger(AfterAspect.class);
@After("CommonPointcut.implPointcut()")
public void serviceStart(JoinPoint jp) {
// jp.getTarget() : aop가 적용된 객체(각종 ServiceImpl)
String className = jp.getTarget().getClass().getSimpleName(); // 간단한 클래스명(패키지명 제외)
// jp.getSignature() : 수행되는 메서드 정보
String methodName = jp.getSignature().getName();
String param = Arrays.toString(jp.getArgs());
String str = "End : " + className + " - " + methodName + "\n";
str += "Parameter : " + param + "\n";
str += "----------------------------------------\n";
logger.info(str);
}
}
마지막으로 발생한 예외를 출력하는 클래스이다
@AfterThrowing을 사용할건데 이것인 기본 @After에 던져지는 예외를 얻어오는 기능을 한다.
@Component
@Aspect
public class AfterThrowingAspect {
private Logger logger = LoggerFactory.getLogger(AfterThrowingAspect.class);
@AfterThrowing(pointcut = "CommonPointcut.implPointcut()", throwing="exceptionObj")
public void serviceReturnValue(Exception exceptionObj) {
String str = "<<exception>> : " + exceptionObj.getStackTrace()[0];
str += " - " + exceptionObj.getMessage();
logger.error(str);
logger.info("exception : " + exceptionObj);
}
}
throwing="exceptionObj" 는 던져진 예외를 저장할 매개변수를 지정한다.
모두 작성하였으면 실행해보자
로그인을 하면
잘 작동이 되는것을 볼 수 있다.