너와 나의 프로그래밍

Spring Framework - AOP JoinPoint와 Annotation 본문

Back-End/Spring Framework

Spring Framework - AOP JoinPoint와 Annotation

Whatever App's 2020. 8. 10. 08:15

 

 

[Spring Framework] AOP JoinPoint와 Annotation

 

 

 

Chpater1. Advice JointPoint & Bound Variable

 

어드바이스 메소드를 의미 있게 구현하려면 클라이언트가 호출한 비즈니스 메소드의 정보가 필요하다. 어떤 메소드에서 어떤 동작이 이루어졌는지 알면 굉장히 편하다. 스프링에서는 이런 정보들을 이용할 수 있도록 JoinPoint 인터페이스를 제공한다.

JoinPoint Interface

  1. Signature getSignature() : 클라이언트가 호출한 메소드의 시그니처(리턴타입, 이름, 매개변수) 정보가 저장된 Signature 객체 리턴
  2. Object getTarget() : 클라이언트가 호출한 비즈니스 메소드를 포함하는 비즈니스 객체 리턴
  3. Object[] getArgs() : 클라이언트가 메소드를 호출할 때 넘겨준 인자 목록을 Object 배열로 리턴

그 중에 1번 getSignature 메소드가 리턴하는 Signature 객체를 이용하면 호출되는 메소드의 대한 다양한 정보를 얻을 수 있다.

Signature가 제공하는 메소드들.

  1. String getName() : 클라이언트가 호출한 메소드의 이름 리턴
  2. String toLongString() : 클라리언트가 호출한 메소드의 리턴타입, 이름, 매개변수를 패키지 경로까지 포함해서 리턴
  3. String toShortString() : 클라이언트가 호출한 메소드 시그니처를 축약한 문자열로 리턴

JoinPoint 메소드는 어드바이스의 종류에 따라 사용방법이 다소 다르지만 기본적으로 어드바이스 메소드에 매개변수로 선언만 하면 된다.

그러면 클라이언트가 비즈니스 메소드를 호출 할 때, 스프링 컨테이너가 JoinPoint 객체를 생성한다.

그리고 메소드 호출과 관련된 모든 정보를 JoinPoint 객체에 저장하여 어드바이스 메소드를 호출할 때 인자로 넘겨준다.

public class BeforeAdvice { 	public void beforeLog(JoinPoint jp) { 		String method = jp.getSignature().getName(); 		Object[] args = jp.getArgs();  		System.out.println(생략...); 	} } 

위 예제는 가장 기본적인 JoinPoint 메소드를 활용하여 Before Advice가 동작할 때 클라이언트가 호출한 메소드의 이름과 호출할 때 넘겨준 인자 목록을 Object 형식으로 출력해 주는 예제다.

JoinPoint를 메소드를 사용하는데 있어 3가지의 Advice는 조금 사용하는 방법이 독특하다.

그 중 AfterThrowing와 AfterReturning은 JoinPoint 메소드와 Bound 변수를 동시에 사용한다.

Bound 변수는 비즈니스 메소드가 리턴한 결과값을 바인딩 할 목적으로 사용하며, 어떤 값이 리턴될 지 모르기 때문에 Object 타입으로 선언된다.

또 특이하게 Bound 변수를 사용한 어드바이스 메소드는 따로 스프링 설정 xml에 매핑 설정을 해줘야 한다.

<!-- after-returning의 대한 returning을 설정해 준다. --> <aop:after-returning returning="returnObj"></aop:after-returning> <!-- after-throwing의 대한 exception을 설정해 준다. --> <aop:after-throwing exception="exceptObj"></aop:after-throwing> 
// Bound 변수는 JoinPoint 변수를 선언한 것과 같이 매개변수로 선언해 준다. // xml에서 설정한 After-Returning의 대한 returnObj를 Bound 변수로 사용할 수 있다. public class afterReturningAdvice { 	public void afterReturning(JoinPoint jp, Object returnObj) { 		String method = jp.getSignature().getName(); 		Object[] args = jp.getArgs();  		System.out.println(생략...); 	} }  // xml에서 설정한 After-Throwing의 대한 exception을 Bound 변수로 사용할 수 있다. public class afterReturningAdvice { 	public void afterReturning(JoinPoint jp, Exception exceptObj) { 		String method = jp.getSignature().getName(); 		Object[] args = jp.getArgs();  		System.out.println(생략...); 	} } 

마지막으로 Around Advice는 다른 어드바이스와는 다르게 반드시 ProceedingJoinPoint 객체를 매개변수로 받아야한다.

ProceedingJoinPoint 객체는 비즈니스 메소드를 호출하는 proceed() 메소드를 가지고 있으며 JoinPoint를 상속했다.

public class AroundAdvice { 	public Object aroundLog(ProceedingJoinPoint jp) throws Throwable { 		String method = jp.getSignature().getName(); 		 		// 꼭 proceed 메소드를 사용하자. 		Object obj = jp.proceed();  		System.out.println(생략...); 		return obj; 	} } 

chapter2. Advice Annotation

 

사실 제일 편한건 역시 Annotation을 사용하는 것이 제일 편한 것 같다.

AOP는 Advice를 스프링 컨테이너에서 설정해주지 않아도 간단한 어노테이션을 이용하여 Advice를 주입하는 것이 가능하다.

~ 생략 ~ <aop:aspectj-autoproxy></aop:aspectj-autoproxy> 

스프링 설정 파일에 <aop:aspectj-autoproxy> 엘리먼트만 선언하면 스프링 컨테이너는 AOP 관련 어노테이션들을 인식하고 용도에 맞게 처리해준다.

AOP 관련 어노테이션들은 어드바이스 클래스에 설정한다. 그리고 스프링 컨테이너에서 처리하게 하려면 반드시 어드바이스 객체가 생성되어 있어야 한다.

즉, 어드바이스 클래스는 @Service 어노테이션을 사용하여 검색을 할 수 있게 하거나, <bean>으로 설정하여 등록해줘야 한다.

기존 포인트컷을 설정할 때는 xml파일에 <aop:pointcut> 엘리먼트를 사용하여 포인트컷을 등록했다면 어노테이션으로 사용할 수도 있다.

<!-- 기존 스프링 설정 xml파일에 작성하는 형식 --> <aop:pointcut id="allPointcut" expression="execution(생략...)"></aop:pointcut> 
<!-- Java 어노테이션으로 Pointcut 설정 --> @Service public void LogAdvice(){ 	@Pointcut("execution(생략...)"); 	public void allPointcut(){}; } 

둘의 차이점을 보면 xml 방식과 어노테이션의 expression에 execution을 하는 방법은 같다. 그리고 execution을 사용할 때의 필터 처리도 동일하다. 하지만 xml은 id에 Annotation은 항상 참조 메소드를 꼭 사용해야 한다는 점이다.

참조 메소드는 구현 로직이 없는 메소드를 말한다. 어떤 기능 처리를 목적으로 하지 않고 단순히 포인트컷을 식별하는 이름으로만 사용한다.

Advice는 AOP 엘리먼트를 사용했던 것과 동일하게 5가지의 옵션을 제공한다. 이때 무조건 어드바이스 메소드가 결합될 포인트컷을 참조해야 한다.

또한 AOP 기능에서 가장 중요한 Aspect 설정은 @Aspect 어노테이션을 사용하여 설정한다.

Aspect를 설정하기 위해서는 반드시 포인트컷+어드바이스 결합이 필요하다.

@Service @Aspect // Aspect 설정이 가장 중요하다!!! public void LogAdvice(){ 	@Pointcut("execution(생략...)"); 	public void allPointcut(){};  	//어노테이션으로 Advice를 설정하고 포인트컷을 참조해줬다. 	@before("allPointcut") 	public void printLog(){ 		~ 생략 ~ 	} } 

Advice에서의 5가지 종류 중 After-returning과 After-Throwing 어드바이스는 특이하게 포인트컷 지정을 pointcut 속성을 사용하여 포인트컷을 참조한다. 그 이유는 비즈니스 메소드 수행 결과를 받아내기 위해 바인드 변수를 지정해야 하기 때문이다.

// AfterReturning은 returning 바인드 변수를 통해 비즈니스 로직의 결과 값을 받을 수 있다. @AfterReturning(pointcut="getPointcut()", returning="returnObj") // AfterThrowing은 throwing 바인드 변수를 통해 예외 처리 객체의 값을 받을 수 있다. @AfterThrowing(pointcut="getPointcut()", throwing="exceptObj") 

어노테이션으로 Aspect를 설정할 때 가장 큰 문제점은 어드바이스 클래스마다 포인트컷 설정이 포함되면서, 비슷하거 같은 포인트컷이 반복 선언되는 재사용 불가의 문제가 발생한다. 그래서 스프링은 이런 문제를 해결하고자 포인트컷을 외부에 독립된 클래스에 따로 설정한다.

// 포인트컷을 독립적으로 관리하는 클래스를 설정. @Aspect public class PointCommon { 	@Pointcut("execution(생략...)") 	public void allPointcut(){}  	@Pointcut("execution(생략...)") 	public void allPointcut(){} } 
// 공통적으로 설정한 PointcutCommon의을 참조하여 포인트컷을 사용할 수 있다. @Service @Aspect public class BeforeAdvice { 	@Before("PointcutCommon.allPointcut()") 	public void beforeLog(){//생략...} } 

이런식으로 독립적으로 관리하는 것이 포인트컷을 사용하는데 있어 더욱 효율적이고 유지보수에도 용이할 것 같다.

 

 

 

 

참조: 스프링 퀵 스타트

반응형