[Spring] 어노테이션을 통한 AOP(관점지향 프로그래밍)

[Spring] 어노테이션을 통한 AOP(관점지향 프로그래밍)

들어가기에 앞서

- 채규태 저자님의 '스프링 퀵 스타트' 를 참고하여 작성하였습니다.

- 저자님의 요청에 따라 언제든지 게시물이 비공개로 전환될 수 있습니다.

- 이해한 내용을 바탕으로 작성하였기 때문에 틀린 부분이 있을 수 있습니다.

세팅

AOP를 어노테이션을 통해 구현하려면 스프링 설정파일에 엘리먼트를 선언해야 한다.

물론 어노테이션 스캔 및 객체 생성을 위해 엘리먼트도 필요하다.

service 클래스는 다음과 같은데, 횡단 관심 코드를 지운 상태이다.

당장 BoardDAO의 로직을 알아야할 필요는 없고, DB 연동을 통해 게시판 데이터를 다루는 비즈니스 메소드들 이라고 알아두기만 하자.

package com.springbook.biz.board.impl; import java.util.List; import com.springbook.biz.board.BoardService; import com.springbook.biz.board.BoardVO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service("boardService") public class BoardServiceImpl implements BoardService { @Autowired private BoardDAO boardDAO; public void insertBoard(BoardVO vo) { boardDAO.insertBoard(vo); } public void updateBoard(BoardVO vo) { boardDAO.updateBoard(vo); } public void deleteBoard(BoardVO vo) { boardDAO.insertBoard(vo); } public BoardVO getBoard(BoardVO vo) { return boardDAO.getBoard(vo); } public List getBoardList(BoardVO vo) { return boardDAO.getBoardList(vo); } }

또한 위 클래스의 비즈니스 메소드들 중에서 포인트컷을 설정하여 그 포인트컷에 위빙할 어드바이스들을 생성만 해놓자.

이제부터 어노테이션을 통해 모든 어드바이스 동작 시점(Before, After Returning, After Throwing, After, Around)에 대해 위빙을 해보도록 하자.

어노테이션 사용

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

우선, 어드바이스 메소드를 사용하려면 당연히 어드바이스 객체가 필요하기 때문에 클래스 위에 @Service 어노테이션을 작성한다. 물론 @Component 로 써줘도 객체 생성은 되지만, 어드바이스도 service의 비즈니스 로직중 하나이기 때문에 @Service를 써주도록 하자.

package com.springbook.biz.common; import org.springframework.stereotype.Service; @Service("beforeAdvice") public class BeforeAdvice { }

다른 어드바이스 클래스들에 대해서도 마찬가지로 @Service 설정을 해주도록 하자.

애스팩트 설정

컨테이너가 어드바이스 클래스를 애스팩트 클래스로 인식하려면 @Aspect 어노테이션을 작성해야 한다.

package com.springbook.biz.common; import org.springframework.stereotype.Service; @Service("beforeAdvice") @Aspect public class BeforeAdvice { }

'애스팩트' 는 포인트컷과 어드바이스의 결합이라고 할 수 있다. 따라서 @Aspect 어노테이션이 붙은 클래스에서는 포인트컷과 어드바이스에 관련한 코드가 존재해야 한다.

다른 어드바이스 클래스 위에도 @Aspect를 작성하도록 하자.

포인트컷 선언

위빙을 하려면 포인트컷이 있어야 한다. 포인트컷을 선언하려면 @Pointcut 어노테이션을 사용해야 한다.

@Pointcut("포인트컷 표현식") 꼴로 작성되며, 이와 관련한 참조 메소드(구현 로직이 없는 메소드)위에 붙는다.

참조 메소드는 포인트컷을 식별하는 이름으로 사용된다.

service 클래스에 있는 모든 비즈니스 메소드들을 포인트컷이라고 하면

표현식은 execution(* com.springbook.biz..*Impl.*(..)) 으로 할 수 있다.

포인트컷 표현식에 대해 잘 모른다면 https://juwonkim.tistory.com/125 을 참고하면 된다.

따라서 참조메소드 선언은 다음과 같다.

package com.springbook.biz.common; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Service; @Service("beforeAdvice") @Aspect public class BeforeAdvice { //모든 포인트컷 @Pointcut("execution(* com.springbook.biz..*Impl.*(..))") public void allPointCut(){}; }

만약 포인트컷을 여러개 선언하고 싶으면 그만큼 참조메소드를 더 선언하면 된다.

package com.springbook.biz.common; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Service; @Service("beforeAdvice") @Aspect public class BeforeAdvice { //모든 포인트컷 @Pointcut("execution(* com.springbook.biz..*Impl.*(..))") public void allPointCut(){}; //insert로 시작하는 이름의 포인트컷 @Pointcut("execution(* com.springbook.biz..*Impl.insert*(..))") public void insertPointCut(){}; //get으로 시작하는 이름의 포인트컷 @Pointcut("execution(* com.springbook.biz..*Impl.get*(..))") public void getPointcut(){}; }

포인트컷을 설정하였으면, 마지막으로 어드바이스 메소드를 구현해야 한다.

다른 어드바이스들오 마찬가지로 코드를 작성하면 된다.

어드바이스 메소드 구현

이제 각 어드바이스에 적합한 메소드를 구현하면 된다.

포인트컷은 편의상 모든 포인트컷으로 정하자.

Before

Before 어드바이스 메소드를 구현하려면, 메소드 위에 @Before 어노테이션이 선언되어야 한다.

@Before("참조 메소드") 꼴로 작성하여 포인트컷을 설정한다.

그리고 구현 로직을 괄호안에 작성한다.

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("beforeAdvice") @Aspect public class BeforeAdvice { //모든 포인트컷 @Pointcut("execution(* com.springbook.biz..*Impl.*(..))") public void allPointCut(){}; @Before("allPointCut()") public beforeLog() { ... ... } }

또한 JoinPoint를 매개변수로 선언하여 적절히 사용해도 좋다.

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("beforeAdvice") @Aspect public class BeforeAdvice { //모든 포인트컷 @Pointcut("execution(* com.springbook.biz..*Impl.*(..))") public void allPointCut(){}; @Before("allPointCut()") public void beforeLog(JoinPoint joinPoint) { //JoinPoint를 이용하여 비즈니스 메소드 정보를 얻어냄 String businessMethodName = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); ... 로직 ... } }

After Returning

After Returning 어드바이스 메소드를 구현하려면, 메소드 위에 @AfterReturning 어노테이션이 선언되어야 한다.

비즈니스 메소드 수행 후 리턴되는 객체를 바인드 변수를 통해 얻어내야 하기 때문에

@AfterReturning(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; @Service("afterReturningAdvice") @Aspect public class AfterReturningAdvice { @Pointcut("execution(* com.springbook.biz..*Impl.get*(..))") public void allPointcut(){}; @AfterReturning(pointcut = "allPointcut()", returning = "returnObj") public void afterLog(JoinPoint joinPoint, Object returnObj) { ... ... } }

리턴 객체를 이용하여 로직을 구현하면 된다.

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; @Service("afterReturningAdvice") @Aspect public class AfterReturningAdvice { @Pointcut("execution(* com.springbook.biz..*Impl.get*(..))") public void allPointcut(){}; @AfterReturning(pointcut = "allPointcut()", returning = "returnObj") public void afterLog(JoinPoint joinPoint, Object returnObj) { String businessMethodName = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); //비즈니스 메소드의 리턴 객체 활용 String information = returnObj.toString(); ... 로직 ... } }

After Throwing

After Throwing 어드바이스 메소드를 구현하려면, 메소드 위에 @AfterThrowing 어노테이션이 선언되어야 한다.

비즈니스 메소드 실행 도중 발생하는 예외를 객체를 받아내야 하기 때문에

@AfterThrowing(pointcut = "참조 메소드", returning = "바인드 변수") 꼴로 작성하고, 매개변수로 선언해야 한다.

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("afterThrowingAdvice") @Aspect public class AfterThrowingAdvice { @Pointcut("execution(* com.springbook.biz..*Impl.get*(..))") public void allPointCut(){} @AfterThrowing(pointcut = "allPointCut()", throwing = "exceptObj") public void exceptionLog(JoinPoint joinPoint, Exception exceptObj) { ... ... } }

예외 객체를 이용하여 로직을 구현하면 된다.

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("afterThrowingAdvice") @Aspect public class AfterThrowingAdvice { @Pointcut("execution(* com.springbook.biz..*Impl.get*(..))") public void allPointCut(){} @AfterThrowing(pointcut = "allPointCut()", throwing = "exceptObj") public void exceptionLog(JoinPoint joinPoint, Exception exceptObj) { String businessMethodName = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); //예외 객체 활용 System.out.println(exceptObj.getMessage()); exceptObj.printStackTrace(); ... 로직 ... } }

After

After 어드바이스 메소드를 구현하려면, 메소드 위에 @After 어노테이션이 선언되어야 한다.

비즈니스 메소드 실행 후 무조건 실행되기 때문에 바인드 변수는 필요없다.

@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("afterAdvice") @Aspect public class AfterAdvice { @Pointcut("execution(* com.springbook.biz..*Impl.get*(..))") public void allPointCut(){}; @After("allPointCut()") public void finallyLog() { ... 로직 ... } }

Around

Around 어드바이스 메소드를 구현하려면, 메소드 위에 @Around 어노테이션이 필요하다.

@Around("참조 메소드") 꼴로 작성하면 되며,

비즈니스 메소드를 가로채고 앞뒤로 로직을 구현해야 하기 때문에 ProceedingJoinPoint 변수를 매개변수로 선언하고 proceed() 메소드를 사용해야 한다.

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("aroundAdvice") @Aspect public class AroundAdvice { @Pointcut("execution(* com.springbook.biz..*Impl.*(..))") public void allPointCut(){}; @Around("allPointCut()") public Object aroundLog(ProceedingJoinPoint pjp) throws Throwable { ... 비즈니스 메소드 실행 전 로직 ... Object returnObj = pjp.proceed(); ... 비즈니스 메소드 실행 후 로직 ... return returnObj; } }

from http://juwonkim.tistory.com/126 by ccl(A) rewrite - 2020-03-12 16:20:25

댓글

이 블로그의 인기 게시물

2020 LCK 롤챔스 Spring 경기 재개 및 일정

데이터 바인딩 추상화 - propertyEditor

Spring Web Form