Spring

AOP(2)

불도정 2023. 5. 16. 13:34

이번엔 

수행되는 메서드, 예외, 리턴값, 처리시간, 접속 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" 는 던져진 예외를 저장할 매개변수를 지정한다.

 

모두 작성하였으면 실행해보자

로그인을 하면

 

잘 작동이 되는것을 볼 수 있다.