본문 바로가기
👨‍💻 2. 웹개발_Back end/2-6 Spring

[Spring] 5장 어노테이션 기반 AOP - 기존의 XML 설정을 어노테이션 사용하여 설정하기 및 어드바이스 동작 시점 별 어노테이션 참조 방법

by 달님🌙 2021. 10. 27.
반응형

 

실습 코드 참조

 

moonhy7/SpringFramework: Spring Framework 실습 코드 정리 (github.com)

 

GitHub - moonhy7/SpringFramework: Spring Framework 실습 코드 정리

Spring Framework 실습 코드 정리. Contribute to moonhy7/SpringFramework development by creating an account on GitHub.

github.com

 

 

5.1절 어노테이션 기반 AOP 설정

 

1. 어노테이션 사용을 위한 스프링 설정 ( _037_BoardWeb_AOP_Annotation )

 

1) xml 설정

- 스프링 IoC에서 XML 설정과 어노테이션 설정 모두 사용하는 걸 배움

- 스프링 AOP도 마찬가지로 어노테이션 설정을 지원함

- AOP 어노테이션으로 설정하려면 가장 먼저 스프링 설정 파일에 autoproxy 엘리먼트 선언하기

- 그러면 스프링 컨테이너가 AOP 관련 어노테이션들을 인식하고 용도에 맞게 처리해줌

<context:component-scan base-package="com.springbook.biz"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

 

 

2) 어노테이션 설정

- AOP 관련 어노테이션들은 어드바이스 클래스에 설정한다.

- 어드바이스 클래스에 선언된 어노테이션들을 스프링 컨테이너가 처리하려면 반드시 어노테이션 객체가 생성되어 있어야함

- 스프링설정 파일에 <bean> 등록하기 or @Service 어노테이션 사용하기 둘 중 하나로 컴포넌트가 검색될 수 있도록 해줄 것

 

2. 포인트컷 설정

 

1) XML 설정 방식

- pointcut 엘리먼트 사용하여 선언된 여러 포인트컷을 식별하기 위한 유일한 아이디를 지정

<aop:config>
  <aop:pointcut expression="execution(* com.springbook.biz..*Impl.*(..))" id="allPointcut"/>
  <aop:pointcut expression="execution(* com.springbook.biz..*Impl.get*(..))" id="getPointcut"/>

  <aop:aspect ref="before">
    <aop:before pointcut-ref="allPointcut" method="beforeAdvice"/>
  </aop:aspect>
</aop:config>

 

2) 어노테이션 설정 방식

- @Pointcut 사용하여 하나의 어드바이스 클래스 안에서 여러 개의 포인트컷 선언 가능

- 여러 개의 포인트컷을 식별할 떄는 참조 메소드를 이용하여 단순히 포인트컷을 식별하는 이름으로만 사용함

package com.springbook.biz.common;

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

@Aspect
public class LogAdvice {
	
	@Pointcut("execution(* com.springbook.biz..*Impl.*(..))")
	public void allPointcut() {}
	
	@Pointcut("execution(* com.springbook.biz..*Impl.get*(..))")
	public void getPointcut() {}
}

 

 

3. 어드바이스 설정

 

- 어드바이스 클래스 안에 횡단 관심에 해당되는 어드바이스 메소드가 구현되어있음

- 메소드의 동작 시점은 메소드 위에 어노테이션을 설정하면 된다.(XML 설정과 마찬가지로 다섯 가지가 제공됨)

- 이 때 반드시 어노테이션 뒤에 포인트컷 참조 메소드를 지정해야함

- 아래 설정을 보면 allPointcut() 참조 메소드로 지정한 비즈니스 메소드가 호출될 때 어드바이스 메소드인 printLog() 메소드가 Before 형태로 동작하도록 한 것을 확인

@Service
public class LogAdvice {

	//advice 어노테이션 : advice의 동작시점 어노테이션을 사용한다.
	@Before("allPointcut()")
	public void printLog() {
		System.out.println("[공통 로그] 비즈니스 로직 수행 전 동작");
	}
}

 

- 어드바이스 동작 시점과 관련된 어노테이션 참고

 

4. 애스팩트 설정

 

- 애스팩트 = 포인트컷 + 어드바이스

- @Aspect 설정을 해줌으로써 LogAdvice 객체를 애스팩트 객체로 인식함

- LogAdvice에 선언된 포인트컷 메소드(allPointcut())와 어드바이스 메소드(printLog())들 위에 설정된 어노테이션에 의해 위빙이 처리됨 

package com.springbook.biz.common;

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 LogAdvice {
	
	@Pointcut("execution(* com.springbook.biz..*Impl.*(..))")
	public void allPointcut() {}
	
	@Pointcut("execution(* com.springbook.biz..*Impl.get*(..))")
	public void getPointcut() {}
	
	//advice 어노테이션 : advice의 동작시점 어노테이션을 사용한다.
	@Before("allPointcut()")
	public void printLog() {
		System.out.println("[공통 로그] 비즈니스 로직 수행 전 동작");
	}
}

 

 

5.2절 어드바이스 동작 시점

 

1. Before 어드바이스 ( _038_BoardWeb_AOP_Annotation_Before )

 

1) 어드바이스 클래스에 어노테이션 추가

- @Service와 @Aspect를 추가하여 BefoerAdvice 클래스가 컴포넌트에 스캔되어 애스팩트 객체로 인식되도록 함

- @Before("allPointcut()") : allPointcut()으로 지정한 메소드가 호출될 떄 beforeAdvice() 메소드가 Before 형태로 동작

package com.springbook.biz.common;

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 {
	@Pointcut("execution(* com.springbook.biz..*Impl.*(..))")
	public void allPointcut() {}
	
	@Before("allPointcut()")
	public void beforeAdvice(JoinPoint jp) {
		String method = jp.getSignature().getName();
		Object[] args = jp.getArgs();
		
		System.out.println("[사전 처리] " + method + "() 메소드 ARGS 정보 : " + args[0].toString());
	}
}

 

2) 실행 결과

 

2. After Returning 어드바이스 ( _039_BoardWeb_AOP_Annotation_AfterReturning )

 

1) 어드바이스 클래스에 어노테이션 추가

- pointcut 속성을 이용하여 포인트컷을 참조하고 returning 속성을 사용하여 바인드 변수 지정

package com.springbook.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.springbook.biz.user.UserVO;

@Service
@Aspect
public class AfterReturningAdvice {
	
	@Pointcut("execution(* com.springbook.biz..*Impl.get*(..))")
	public void getPointcut() {}
	
	//returnObj : 포인트컷 메소드 실행 후 리턴 값을 받을 객체
	//@Before("getPointcut()")
	//JoinPoint 객체를 제외한 매개변수가 존재할때는 속성값을 따로 지정해줘야함
	@AfterReturning(pointcut="getPointcut()", returning = "returnObj")
	public void afterReturningAdvice(JoinPoint jp, Object returnObj) {
		String method = jp.getSignature().getName();
		if(returnObj instanceof UserVO) {
			UserVO user = (UserVO) returnObj;
			if(user.getRole().equals("Admin")) {
				System.out.println(user.getName() + " 로그인(Admin)");
			}
		}
		
		System.out.println("[사후 처리] " + method + "() 메소드 리턴값 : " + returnObj.toString());
	}
}

 

2) 실행 결과

 

3. After Throwing 어드바이스 ( _040_BoardWeb_AOP_Annotation_AfterThrowing )

 

1) 어드바이스 클래스에 어노테이션 추가

- pointcut 속성을 이용하여 포인트컷을 참조하고 throwing 속성을 사용하여 바인드 변수 지정

package com.springbook.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 {
	@Pointcut("execution(* com.springbook.biz..*Impl.*(..))")
	public void allPointcut() {}
	
	@AfterThrowing(pointcut="allPointcut()", throwing = "exceptionObj")
	public void afterThrowingAdvice(JoinPoint jp, Exception exceptionObj) {
		String method = jp.getSignature().getName();
		
		System.out.println("[예외 처리] " + method + "() 메소드 수행 중 발생한 예외 메시지" + exceptionObj.getMessage());
	}
}

 

2) 주석 해제

- 예외가 발생해야 AfterThrowingAdvice가 동작하는 것을 확인할 수 있음

@Service("boardService")
public class BoardServiceImpl implements BoardService{
	@Autowired
	BoardDAO boardDAO;
	
	public void insertBoard(BoardVO vo) {
		//객체 생성 시에 필드 변수의 int 타입들은 0으로 초기화 됨
		if(vo.getSeq() == 0) {
			throw new IllegalArgumentException("0번 글은 등록할 수 없습니다.");
		}
		boardDAO.insertBoard(vo);
	}
}

 

3) 실행 결과

 

4. After 어드바이스 ( _041_BoardWeb_AOP_Annotation_After )

 

1) 어드바이스 클래스에 어노테이션 추가

- afterAdvice() 메소드는 바인드 변수가 없으므로 포인트컷 메소드만 참조하면 됨

package com.springbook.biz.common;

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

@Service
@Aspect
public class AfterAdvice {
	@Pointcut("execution(* com.springbook.biz..*Impl.*(..))")
	public void allPointcut() {}
	
	@After("allPointcut()")
	public void afterAdvice() {
		System.out.println("[사후 처리] 비즈니스 로직 수행 후 무조건 동작");
	}
}

 

2) 실행 결과

 

 

5. Around 어드바이스 ( _042_BoardWeb_AOP_Annotation_Around )

 

1) 어드바이스 클래스에 어노테이션 추가

package com.springbook.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;

@Service
@Aspect
public class AroundAdvice {
	@Pointcut("execution(* com.springbook.biz..*Impl.*(..))")
	public void allPointcut() {}

	@Around("allPointcut()")
	public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
		System.out.println("[BEFORE] : 비즈니스 로직 수행 전 처리할 내용...");
		Object returnObj = pjp.proceed();
		System.out.println("[AFTER] : 비즈니스 로직 수행 후 처리할 내용...");
		return returnObj;
	}
}

 

2) 실행 결과

- aroundAdvice() 메소드는 바인드 변수가 없으므로 포인트컷 메소드만 참조하면 됨

- 어드바이스 메소드 중에서 유일하게 Around 메소드에서만 JoinPoint가 아닌 ProceedJoinPoint 객체를 매개변수로 받음

- 그래야 proceed() 메소드를 이용하여 클라이언트가 호출한 비즈니스 메소드를 실행할 수 있기 때문

 

 

6. 외부 Pointcut 참조하기

 

1) XML 설정으로 포인트컷 관리

 - XML 설정으로 포인트컷 관리한 경우 포인트컷을 여러 개 등록함

- 그 후 애스팩트를 설정할 때 pointcut-ref 속성으로 특정 포인트컷을 참조함

<aop:config>
  <aop:pointcut expression="execution(* com.springbook.biz..*Impl.*(..))" id="allPointcut"/>
  <aop:pointcut expression="execution(* com.springbook.biz..*Impl.get*(..))" id="getPointcut"/>

  <aop:aspect ref="before">
    <aop:before pointcut-ref="allPointcut" method="beforeAdvice"/>
  </aop:aspect>
</aop:config>

 

2) 어노테이션 설정

- 문제점 : 어드바이스 클래스마다 포인트컷 설정이 포함되면서 비슷한 포인트컷이 반복 선언되는 문제 발생

- 해결책 : 포인트컷을 외부에 독립된 클래스에 따로 설정 (PointcutCommon 클래스 생성)

package com.springbook.biz.common;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

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

 

3) Before 어드바이스 클래스 수정

- 포인트컷 소스는 삭제되고 @Before 어노테이션에서만 PointCommon 클래스의 allPointcut() 메소드를 참조

package com.springbook.biz.common;

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 {
//	public void allPointcut() {}
	
	@Before("PointcutCommon.allPointcut()")
	public void beforeAdvice(JoinPoint jp) {
		String method = jp.getSignature().getName();
		Object[] args = jp.getArgs();
		
		System.out.println("[사전 처리] " + method + "() 메소드 ARGS 정보 : " + args[0].toString());
	}
}

 

4) AfterReturning 어드바이스 클래스 수정

- 포인트컷 소스는 삭제되고 @AfterReturning 어노테이션에서만 PointCommon 클래스의 getPointcut() 메소드를 참조

- JoinPoint 객체를 제외한 매개변수가 또 존재할 경우 속성 값을 따로 지정

package com.springbook.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.springbook.biz.user.UserVO;

@Service
@Aspect
public class AfterReturningAdvice {
	
//	@Pointcut("execution(* com.springbook.biz..*Impl.get*(..))")
//	public void getPointcut() {}
	
	//returnObj : 포인트컷 메소드 실행 후 리턴 값을 받을 객체
	//@Before("getPointcut()")
	//JoinPoint 객체를 제외한 매개변수가 존재할때는 속성값을 따로 지정해줘야함
	@AfterReturning(pointcut="PointcutCommon.getPointcut()", returning = "returnObj")
	public void afterReturningAdvice(JoinPoint jp, Object returnObj) {
		String method = jp.getSignature().getName();
		if(returnObj instanceof UserVO) {
			UserVO user = (UserVO) returnObj;
			if(user.getRole().equals("Admin")) {
				System.out.println(user.getName() + " 로그인(Admin)");
			}
		}
		
		System.out.println("[사후 처리] " + method + "() 메소드 리턴값 : " + returnObj.toString());
	}
}

 

 

반응형

댓글