지난 번 포스팅에서는 xml에서 설정을 했었다면, 이번은 java파일 안에서 Annotation을 사용하여 AOP를 테스트해보겠습니다.

 

xml파일에서 aop를 설정했을 때, 태그 안에서 설정 된 자바 클래스와 메서드까지 지정해놓고, 프로그램 실행 시 설정된 조건을 충족할 때 메서드가 실행되는 방식이었습니다.

 

xml파일의 태그에서 설정을 따로 하는게 아닌, 해당 자바 클래스의 어노테이션을 이용하여 파일 내에서 설정까지 같이 해보는 시간을 갖도록 하겠습니다.

 

1. 포인트컷 설정

 

먼저 포인트컷을 설정해 advice가 적용되는 대상을 지정하겠습니다.

 

package com.spring.biz.common;

import org.aspectj.lang.annotation.Pointcut;

public class PointcutCommon {
	
	@Pointcut("execution(* com.spring.biz..*Impl.*(..))")
	public void allPointcut() {}
	
	@Pointcut("execution(* com.spring.biz..*Impl.get*(..))")
	public void getPointcut() {}
    
}

 

대상을 지정 할 때 패키지이름/클래스이름/메서드이름을 정규식으로 지정하여 사용해야합니다.

2. AfterAdvice

 

package com.spring.biz.common;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Service;

@Service
@Aspect
public class AfterAdvice {
	
	@After("PointcutCommon.getPointcut()")
	public void afterLog() {
		System.out.println("[사후처리-AftergAdvice.afterLog]"
				+ " 비즈니스 로직 수행 후 로그(언제나, 무조건)");
	}
	
}

 

먼저 @Aspect라는 어노테이션은 해당 클래스의 객체사 Aspect를 구현한 것임으로 나타내기 위해서 사용합니다. 또한 @Service는 AOP와는 관계는 없지만 스프링에서 빈으로 인식하기위해 설정해 놓은 것입니다.

@After를 보면 옆에 클래스.메서드가 보이는데, 이는 위에서 설정한 포인트컷입니다. 만약 설정해놓지 않았다면, 

"PointPointcutCommon.allPointcut()"을, "execution(* com.spring.biz..*impl.*(..))"로 설정해도 무관합니다.

 

위처럼 메서드 설정을 마치고 실행 한 뒤, 메서드가 실행되면 메서드 실행하고나서 콘솔창에 출력이 됩니다.

 

3. BeforeAdvice

 

package com.spring.biz.common;

import java.util.Arrays;

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.springframework.stereotype.Service;

@Service //객체(인스턴스) 생성
@Aspect //포인트컷 + 어드바이스 연결
public class BeforeAdvice {
	
	//어드바이스 메소드
	//어드바이스의 동작시점 설정 + 포인트컷 지정
	@Before("PointcutCommon.allPointcut()")
	public void beforeLog(JoinPoint jp) {
		String methodName = jp.getSignature().getName(); //실행 될 메소드 명
		Object[] args = jp.getArgs();
		System.out.println("args : " + Arrays.toString(args));
		
		String argsMsg = (args.length == 0) ? "없음" : args[0].toString();
		System.out.println("[사전처리] " + methodName + "()메소드, args 정보 : " + argsMsg + " - 비즈니스 로직 수행 전 로그");
	}
}

 

어노테이션은 위의 AfterAdvice와 동일합니다.

 

그리고, 메서드의 파라미터를 보면, JoinPoint라는게 보일텐데, 이는 인터페이스로 어드바이스 메소드를 의미있게 구현하기 위해서 해당 인터페이스가 제공하는 것들이 있습니다.

 

메소드(JoinPoint)

설명

Signature getSignature()

클라이언트가 호출한 메소드의 시그니처(리턴타입, 이름, 매개변수)정보가 저장된 Signature 객체 리턴

Object getTarget()

클라이언트가 호출한 비즈니스 메소드를 포함하는 비즈니스 객체 리턴

Object[] getArgs()

클라이언트가 메소드를 호출할 때 넘겨준 인자 목록을 Object 배열로 리턴

메소드(Signature)

설명

String getName()

클라이언트가 호출한 메소드 이름 리턴

String toLongString()

클라이언트가 호출한 메소드의 리턴타입, 이름, 매개변수(시그니처)를 패키지 경로까지 포함하여 리턴

String toShortString()

클라이언트가 호출한 메소드 시그니처를 축약한 문자열로 리턴

 

해당 인터페이스를 사용할 때 어드바이스 메소드의 매개변수로 선언해야 하는 특징이 있습니다. 이 때 인자는 스프링 컨테이너가 넘겨줍니다. ex) (JoinPoint jp)

이 때 Around 어드바이스만 다른 어드바이스와 다른데, ProceedingJoinPoint 객체를 인자로 선언해야합니다.

 

위를 보면 JoinPoint 타입 jp의 메소드를 이용하여 변수에 담고, 변수에 담은 내용들을 출력하는 형태로 전개됩니다.

 

4. AfterThrowingAdvice

 

package com.spring.biz.common;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Service;

@Service
@Aspect
public class AfterThrowingAdvice {

	@AfterThrowing(pointcut = "PointcutCommon.allPointcut()", throwing = "exceptObj")
	public void exceptionLog(JoinPoint jp, Exception exceptObj) {
		String methodName = jp.getSignature().getName();
		
		if(exceptObj instanceof IllegalArgumentException ) {
			System.out.println(">>> 부적합한 값이 전달되었습니다.");
		} else if (exceptObj instanceof NumberFormatException) {
			System.out.println(">>> 숫자형식이 아닙니다.");
		} else if (exceptObj instanceof Exception) {
			//else처리해도 무방하다.
			System.out.println(">>> 예외가 발생했습니다.");
		}
		System.out.println("[예외처리] " + methodName + "() 메소드 - 실행 중 예외 발생, 메시지 : " + exceptObj.getMessage());
		
	}
}

 

AfterThrowingAdivce는 메서드가 실행되고 나서 실행되는 것으로, 예외가 발생할 때만 실행되는 것이 특징입니다.

해당 메서드의 파라미터에 Exception클래스를 추가하여 전달되는 예외메세지의 값을 변수에 담았고, 어노테이션 @AfterThrowingAdvice의 포인트컷 옆에 throwing이라는 것을 추가해 전달 할 오류메세지를 설정했습니다.

 

5. AfterReturningAdvice

 

package com.spring.biz.common;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Service;

import com.spring.biz.user.UserVO;

@Service
@Aspect
public class AfterReturningAdvice {
	
	@AfterReturning(pointcut = "PointcutCommon.getPointcut()", returning = "returnObj")
	public void afterReturnLog(JoinPoint jp, Object returnObj) {
		String methodName = jp.getSignature().getName();
		
		//리턴받은 객체를 확인하고 필요한 작업 처리
		if(returnObj instanceof UserVO) {
			UserVO vo = (UserVO)returnObj;
			if("admin".equalsIgnoreCase(vo.getRole())) {
				System.out.println(vo.getName() + "로그인(Admin)");
			} else {
				System.out.println(vo.getName() + "로그인");
			}
		}
		
		System.out.println("[사후처리] " + methodName + "() 메소드 , 리턴값 : " + returnObj);
	}
}

 

AfterReturningAdvice는 메서드 실행 후 실행되는 것으로, 정상실행 될 때만 실행되는 것이 특징입니다.

메서드의 파라미터에서 Object클래스 타입의 변수 returnObj가 선언된 것을 확인 할 수 있습니다. 이는 @AfterReturningAdvice의 pointcut의 설정과 그 옆의 returning에서 사용되는 것을 볼 수 있는데, 리턴받은 객체를 확인하고 필요한 작업을 처리할 수 있도록 합니다.

 

6. AroundAdvice

 

package com.spring.biz.common;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;

@Service
@Aspect
public class AroundAdvice {

	@Around("PointcutCommon.allPointcut()")
	public Object aroundLog(ProceedingJoinPoint pjp) throws Throwable {
		String methodName = pjp.getSignature().getName();
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		
		System.out.println("[Around BEFORE] 비즈니스 로직 실행 전 처리");
		Object returnObj = pjp.proceed(); //실행 할 메소드 동작 시키기
		System.out.println("[Around AFTER] 비즈니스 로직 실행 후 처리");
		
		stopWatch.stop();
		
		System.out.println("[around] " + methodName + "()메소드 , 실행시간 : " + stopWatch.getTotalTimeMillis() + "초(ms)");
		
		return returnObj;
	}
}

 

AroundAdvice는 이전과 다르게 ProceedingJoinPoint를 사용했습니다. ProceedingJoinPoint는 JoinPoint의 하위 인터페이스로, 중간에 보이는 proceed()는 다음 Advice를 실행하거나, 실제 target객체의 메서드를 실행하는 기능을 가진 메서드입니다.

 

 

 

 

참고자료

sjh836.tistory.com/157

doublesprogramming.tistory.com/207

 

'코딩 > Spring' 카테고리의 다른 글

Spring 스프링을 사용하는 이유  (0) 2020.08.20
Spring Lombok  (0) 2020.08.18
Spring AOP 관점 지향 프로그래밍  (0) 2020.08.13
Spring 의존성 주입(Dependency Injection)  (0) 2020.08.12
Spring context:component-scan  (0) 2020.08.11
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기