일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- JavaScript
- Redux Toolkit
- useLayoutEffect
- Render Queue
- docker
- Sparkplug
- Headless 컴포넌트
- linux 배포판
- 암묵적 타입 변환
- useCallback
- 주니어개발자
- type assertion
- AJIT
- task queue
- prettier-plugin-tailwindcss
- 클라이언트 상태 관리 라이브러리
- react
- jotai
- Compound Component
- zustand
- 프로세스
- helm-chart
- Custom Hook
- CS
- 타입 단언
- TypeScript
- Microtask Queue
- Recoil
- 좋은 PR
- 명시적 타입 변환
- Today
- Total
구리
TIL_210618_어노테이션 기반 AOP 본문
목차
AOP 설정
1. 어노테이션 사용을 위한 스프링 설정
스프링 설정 파일에 <aop:aspectj-autoproxy> 엘리먼트 선언
해당 엘리먼트를 선언함으로써 스프링 컨테이너는 AOP 관련 어노테이션들을 인식하고 용도에 맞게 처리해줍니다.
2. 포인트컷 설정
package com.springbook.biz.common;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Service;
@Service
public class LogAdvice {
@Pointcut("execution(* com.springbook.biz..*Impl.*(..))")
public void allPointcut(){}
@Pointcut("execution(* com.springbook.biz..*Impl.get*(..))")
public void getPointcut(){}
}
3. 어드바이스 설정
어드바이스 메소드가 언제 동작할지 결정하여 관련된 어노테이션을 메소드 위에 설정하면 됩니다.
예시
package com.springbook.biz.common;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Service;
@Service
public class LogAdvice {
@Pointcut("execution(* com.springbook.biz..*Impl.*(..))")
public void allPointcut(){}
@Pointcut("execution(* com.springbook.biz..*Impl.get*(..))")
public void getPointcut(){}
@Before("allPointcut()")
public void printLog(){
System.out.println("[공통 로그] 비즈니스 로직 수행 전 동작");
}
}
위 설정은 allPointcut() 참조 메소드로 지정한 비즈니스 메소드가 호출될 때, 어드바이스 메소드인 printLog() 메소드가 Before 형태로 동작하도록 설정한 것입니다.
4. 애스팩트 설정
@Aspect가 설정된 애스팩트 객체에는 반드시 포인트컷과 어드바이스를 결합하는 설정이 있어야 합니다.
예시
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 // Aspect = Pointcut + Advice
public class LogAdvice {
@Pointcut("execution(* com.springbook.biz..*Impl.*(..))") // 포인트컷
public void allPointcut(){}
@Before("allPointcut()") // 어드바이스
public void printLog(){
System.out.println("[공통 로그] 비즈니스 로직 수행 전 동작");
}
}
@Aspect 어노테이션으로 인해 스프링 컨테이너는 LogAdvice 객체를 애스팩트 객체로 인식하고 @Pointcut, @Before 어노테이션에 의해 위빙이 처리됩니다.
위 소스는 allPointcut() 메소드로 지정한 포인트컷 메소드가 호출될 때, printLog()라는 어드바이스 메소드가 실행되도록 설정한 것입니다. 그리고 @Before가 설정되었으므로 printLog() 메소드는 사전 처리 형태로 동작합니다.
어드바이스 동작 시점
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 beforeLog(JoinPoint jp) {
String method = jp.getSignature().getName();
Object[] args = jp.getArgs();
System.out.println("[사전 처리] " + method + "() 메소드 ARGS 정보 : " + args[0].toString());
}
}
AfterReturning 어드바이스
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.board.UserVO;
@Service
@Aspect
public class AfterReturningAdvice {
@Pointcut("execution(* com.springbook.biz..*Impl.get*(..))")
public void getPointcut(){}
@AfterReturning(pointcut="getPointcut()", returning="returnObj")
public void afterLog(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());
}
}
getUser() 메소드 후 @AfterRetuning 어노테이션으로 인해 afterLog() 메소드가 실행된 결과로 @AfterRetuning 에 pointcut 속성을 추가하여 포인트컷을 참조한 이유는 비즈니스 메소드 수행 결과를 받아내기 위해 returning 속성을 이용해 바인드 변수를 지정했기 때문입니다.
AfterThrowing 어드바이스
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="exceptObj")
public void exceptionLog(JoinPoint jp, Exception exceptObj) {
String method = jp.getSignature().getName();
System.out.println(method + "() 메소드 수행 중 예외 발생!");
if(exceptObj instanceof IllegalArgumentException){
System.out.println("부적합한 값이 입력되었습니다");
}else if(exceptObj instanceof NumberFormatException){
System.out.println("숫자 형식의 값이 아닙니다");
}else if(exceptObj instanceof Exception){
System.out.println("문제가 발생했습니다");
}
}
}
package com.springbook.biz.board.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.springbook.biz.board.BoardService;
import com.springbook.biz.board.BoardVO;
@Service("boardSerivce")
public class BoardServiceImpl implements BoardService {
@Autowired
private BoardDAOSpring boardDAO;
public BoardServiceImpl() {
}
public void insertBoard(BoardVO vo) {
if(vo.getSeq()==0){
throw new IllegalArgumentException("0번 글은 등록할 수 없습니다");
}
boardDAO.insertBoard(vo);
}
public List<BoardVO> getBoardList(BoardVO vo) {
return boardDAO.getBoardList(vo);
}
}
위와 같이 BoardServiceImpl 클래스의 insertBoard() 에 임의로 예외가 발생하게 설정해놓은 후 xml 파일을 실행하면 위 사진처럼 출력됩니다. @AfterThrowing 도 마찬가지로 throwing 속성을 이용한 바인딩 변수를 사용해야 하기에 pointcut 속성을 사용하여 포인트컷을 지정하였습니다.
After 어드바이스
package com.springbook.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 {
@Pointcut("execution(* com.springbook.biz..*Impl.*(..))")
public void allPointcut(){}
@After("allPointcut()")
public void finallyLog() {
System.out.println("[사후 처리] 비즈니스 로직 수행 후 무조건 동작 ");
}
}
예외가 발생해도 finallyLog() 메소드가 실행되는 것을 볼 수 있습니다. 그리고 finallyLog() 메소드에 바인드 변수가 없으므로 @After 어노테이션은 포인트컷 메소드만 참조하면 됩니다.
Around 어드바이스
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;
import org.springframework.util.StopWatch;
@Service
@Aspect
public class AroundAdvice {
@Pointcut("execution(* com.springbook.biz..*Impl.*(..))")
public void allPointcut(){}
@Around("allPointcut()")
public Object aroundLog(ProceedingJoinPoint pjp) throws Throwable {
String method = pjp.getSignature().getName();
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object obj = pjp.proceed();
stopWatch.stop();
System.out.println(method + "() 메소드 수행에 걸린 시간 : " + stopWatch.getTotalTimeMillis() + "(ms)초");
return obj;
}
}
UserServiceClient 클래스에서 실행하게 되면 다음과 같은 결과를 얻게 되는데 클라이언트가 호출한 비즈니스 메소드 실행 전 후로 arondLog() 메소드가 실행된 것을 알 수 있습니다.
참고로 Around 어드바이스 메소드에서만 ProceedingJoinPoint 객체를 매개변수로 받아야 하는 것이 핵심입니다.
(proceed() 메소드를 이용해 클라이언트가 호출한 비즈니스 메소드를 실행할 수 있기 때문)
외부 Pointcut 참조하기
어노테이션으로 설정시 어드바이스 클래스마다 포인트컷 설정이 포함되기에, 중복되는 포인트컷이 반복 선언되는 문제가 생깁니다.
이를 해결하기 위해 모든 포인트컷을 특정 클래스에 설정 후 어드바이스 클래스에서 어노테이션 참조값을 변경합니다.
- 모든 포인트컷을 선언하는 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(){}
}
- 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 {
@Before("PointcutCommon.allPointcut()")
public void beforeLog(JoinPoint jp) {
String method = jp.getSignature().getName();
Object[] args = jp.getArgs();
System.out.println("[사전 처리] " + method + "() 메소드 ARGS 정보 : " + args[0].toString());
}
}
원래는 각 어드바이스 클래스에 포인트컷 메소드를 일일이 생성하였다면 이제는 한 클래스에 모든 포인트컷을 선언하고 각 어드바이스 클래스의 메소드에는 @어노테이션에 (포인트컷 클래스명.포인트컷 메소드명) 이와 같은 방식으로 명시하면 됩니다.
바인드 변수가 있을 때도 포인트컷 클래스의 메소드를 참조하는 것은 같습니다.
AfterReturning 어드바이스 코드 예시
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.board.UserVO;
@Service
@Aspect
public class AfterReturningAdvice {
@AfterReturning(pointcut="PointcutCommon.getPointcut()", returning="returnObj")
public void afterLog(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());
}
}
'SPRING FRAMEWORK' 카테고리의 다른 글
TIL_210622_Spring MVC_스프링 설정파일, 인코딩 설정 (0) | 2021.06.22 |
---|---|
TIL_210621_스프링 트랜잭션 처리 (0) | 2021.06.21 |
TIL_210618_AOP (0) | 2021.06.18 |
TIL_210617_AOP 정의, 적용하는 법, 용어, 엘리먼트 (0) | 2021.06.17 |
TIL_210617_SpringFramwork JDBC Template 클래스 사용하여 DB 연동 (0) | 2021.06.17 |