본문 바로가기
Spring/Advanced

스프링 AOP 구현

by JHyun0302 2023. 8. 21.
728x90

Aspect

@Slf4j
@Aspect
public class AspectV1 {

	//hello.aop.order 패키지와 하위 패키지
    @Around("execution(* hello.aop.order..*(..))")
    public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
    	log.info("[log] {}", joinPoint.getSignature()); //join point 시그니처
    	return joinPoint.proceed();
    }
}

 

※ Pointcut : execution(* hello.aop.order..*(..))

 hello.aop.order 패키지와 그 하위 패키지( .. ) 지정하는 AspectJ 포인트컷 표현식

 

Advice : doLog() 메서드

 

 

 

◎ 참고 : 스프링 빈으로 등록하는 방법

  1. @Bean 을 사용해서 직접 등록
  2. @Component 컴포넌트 스캔을 사용해서 자동 등록
  3. @Import 주로 설정 파일을 추가할 때 사용( @Configuration )
        → @Test 에서는 간단하게 @Import 기능을 사용

 

 

 

 

 

 

 

 

 

 


포인트컷 분리

 

 

 

 

Aspect

@Slf4j
@Aspect
public class AspectV2 {
   
    //hello.aop.order 패키지와 하위 패키지
    @Pointcut("execution(* hello.aop.order..*(..))") //pointcut expression 
    private void allOrder(){} //pointcut signature
    
    @Around("allOrder()")
    public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("[log] {}", joinPoint.getSignature());
        return joinPoint.proceed();
    }
}

 

@Pointcut - 포인트컷 표현식

포인트컷 시그니처(signature) : 메서드 이름 + 파라미터 → allOrder()

코드 내용은 비워둔다.

 

 

 

 

 

 

 


어드바이스 추가

 

 

트랜잭션 기능 동작 방법

  1. 핵심 로직 실행 직전에 트랜잭션을 시작
  2. 핵심 로직 실행
  3. 핵심 로직 실행에 문제가 없으면 커밋
  4. 핵심 로직 실행에 예외가 발생하면 롤백

 

 

 

Aspect

@Slf4j
@Aspect
public class AspectV3 {

    //hello.aop.order 패키지와 하위 패키지 
    @Pointcut("execution(* hello.aop.order..*(..))") 
    public void allOrder(){}

    //클래스 이름 패턴이 *Service 
    @Pointcut("execution(* *..*Service.*(..))") 
    private void allService(){}

    @Around("allOrder()")
    public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("[log] {}", joinPoint.getSignature());
        return joinPoint.proceed();
    }

    //hello.aop.order 패키지와 하위 패키지 이면서 클래스 이름 패턴이 *Service
    @Around("allOrder() && allService()")
    public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            log.info("[트랜잭션 시작] {}", joinPoint.getSignature()); Object result = joinPoint.proceed();
            log.info("[트랜잭션 커밋] {}", joinPoint.getSignature());
            return result;
        } catch (Exception e) {
            log.info("[트랜잭션 롤백] {}", joinPoint.getSignature());
            throw e;
        } finally {
        	log.info("[리소스 릴리즈] {}", joinPoint.getSignature()); 
        }
    } 
}

 

  • allOrder() 포인트컷은 hello.aop.order 패키지와 하위 패키지를 대상으로 한다.
  • allService() 포인트컷은 XxxService 처럼 Service 로 끝나는 것을 대상으로 한다.
  • @Around("allOrder() && allService()") : 포인트 컷 조합

 

 

포인트컷이 적용된 AOP 결과

  • orderService : doLog() , doTransaction() 어드바이스 적용
  • orderRepository : doLog() 어드바이스 적용



 

 

 

 

 

 

 

 

 


포인트컷 참조

 

 

→ 포인트컷을 공용으로 사용하기 위해 별도의 외부 클래스로 분리

 

 

 

Pointcuts

public class Pointcuts {

    //hello.springaop.app 패키지와 하위 패키지 
    @Pointcut("execution(* hello.aop.order..*(..))") 
    public void allOrder(){}

    //타입 패턴이 *Service
    @Pointcut("execution(* *..*Service.*(..))") 
    public void allService(){}
    
    //allOrder && allService
    @Pointcut("allOrder() && allService()")
    public void orderAndService(){}
}

 

 

 

 

 

AspectV4Pointcut

@Slf4j
@Aspect
public class AspectV4Pointcut {
    
    @Around("hello.aop.order.aop.Pointcuts.allOrder()")
    public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("[log] {}", joinPoint.getSignature());
        return joinPoint.proceed();
    }
    
    @Around("hello.aop.order.aop.Pointcuts.orderAndService()")
    public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            log.info("[트랜잭션 시작] {}", joinPoint.getSignature());
            Object result = joinPoint.proceed(); 
            log.info("[트랜잭션 커밋] {}", joinPoint.getSignature());
        	return result;
        } catch (Exception e) {
            log.info("[트랜잭션 롤백] {}", joinPoint.getSignature());
            throw e;
        } finally {
        	log.info("[리소스 릴리즈] {}", joinPoint.getSignature()); 
        }
    } 
}

 

 

 

 

 

 


어드바이스 순서

 

 

 

하나의 애스팩트에 여러 어드바이스가 있으면 순서를 보장 받을 수 없다.

애스펙트를 별도의 클래스로 분리

 

 

 

 

 

 

AspectV5Order

@Slf4j
public class AspectV5Order {
    
    @Aspect
    @Order(2)
    public static class LogAspect {
        @Around("hello.aop.order.aop.Pointcuts.allOrder()")
        public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable {
            turlog.info("[log] {}", joinPoint.getSignature());
            ren joinPoint.proceed();
        } 
    }
    
    @Aspect
    @Order(1)
    public static class TxAspect {
        @Around("hello.aop.order.aop.Pointcuts.orderAndService()")
        public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
            try {
                log.info("[트랜잭션 시작] {}", joinPoint.getSignature());
                Object result = joinPoint.proceed(); 
                log.info("[트랜잭션 커밋] {}", joinPoint.getSignature());
                return result;
            } catch (Exception e) {
                log.info("[트랜잭션 롤백] {}", joinPoint.getSignature());
                throw e;
            } finally {
                log.info("[리소스 릴리즈] {}", joinPoint.getSignature()); 
            }
        } 
    }
}

 

  • LogAspect , TxAspect 애스펙트로 각각 분리
  • 각 애스펙트에 @Order 애노테이션을 통해 실행 순서를 적용

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


어드바이스 종류

 

 

※ 어드바이스 종류

  • @Around : 메서드 호출 전후에 수행, 가장 강력한 어드바이스, 조인 포인트 실행 여부 선택, 반환 값 변환, 예외 변환 등이 가능
  • @Before : 조인 포인트 실행 이전에 실행
  • @AfterReturning : 조인 포인트가 정상 완료후 실행
  • @AfterThrowing : 메서드가 예외를 던지는 경우 실행
  • @After : 조인 포인트가 정상 또는 예외에 관계없이 실행(finally)

 

 

 

 

 

AspectV6Advice

@Slf4j
@Aspect
public class AspectV6Advice {
    @Around("hello.aop.order.aop.Pointcuts.orderAndService()")
    public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            //@Before
            log.info("[around][트랜잭션 시작] {}", joinPoint.getSignature()); 
            Object result = joinPoint.proceed();
            //@AfterReturning
            log.info("[around][트랜잭션 커밋] {}", joinPoint.getSignature());
            return result;
        } catch (Exception e) {
            //@AfterThrowing
            log.info("[around][트랜잭션 롤백] {}", joinPoint.getSignature());
            throw e;
        } finally {
            //@After
            log.info("[around][리소스 릴리즈] {}", joinPoint.getSignature()); 
        }
    }
    
    @Before("hello.aop.order.aop.Pointcuts.orderAndService()")
    public void doBefore(JoinPoint joinPoint) {
    	log.info("[before] {}", joinPoint.getSignature());
    }
    
    @AfterReturning(value = "hello.aop.order.aop.Pointcuts.orderAndService()", returning = "result")
    public void doReturn(JoinPoint joinPoint, Object result) {
    	log.info("[return] {} return={}", joinPoint.getSignature(), result);
    }
    
    @AfterThrowing(value = "hello.aop.order.aop.Pointcuts.orderAndService()", throwing = "ex")
    public void doThrowing(JoinPoint joinPoint, Exception ex) {
        log.info("[ex] {} message={}", joinPoint.getSignature(), ex.getMessage());
    }
    
    @After(value = "hello.aop.order.aop.Pointcuts.orderAndService()")
    public void doAfter(JoinPoint joinPoint) {
    	log.info("[after] {}", joinPoint.getSignature());
    }
}

 

 

@Before : ProceedingJoinPoint.proceed() 사용하지 않는다. 메서드 종료시 자동으로 다음 타켓 호출. 예외 발생시 다음 코드 호출 X.

 

@AfterReturning : returning 속성에 사용된 이름은 어드바이스 메서드의 매개변수 이름과 일치. 반환 객체 변경 불가. 조작은 가능.

 

@AfterThrowing : throwing 속성에 사용된 이름은 어드바이스 메서드의 매개변수 이름과 일치.

 

@After : 정상 및 예외 반환 조건을 모두 처리. 리소스 해제할 때 사용.

 

@Around : 가장 강력한 Advice. proceed() 를 통해 대상을 실행. 여러번 가능.

  • 조인 포인트 실행 여부 선택 joinPoint.proceed() 호출 여부 선택
  • 전달 값 변환: joinPoint.proceed(args[])
  • 반환 값 변환
  • 예외 변환
  • 트랜잭션 처럼 try ~ catch~ finally 모두 들어가는 구문 처리 가능

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

좋은 설계는 제약이 있는 것이다

  • @Around 가 가장 넓은 기능을 제공하는 것은 맞지만,  joinPoint.proceed() 호출해야만 한다.
  • 반면에 @Before , @After 는 작성한 의도가 명확하게 드러나고 코드도 단순하다.

 

반응형

'Spring > Advanced' 카테고리의 다른 글

스프링 AOP - 예제  (0) 2023.08.22
스프링 AOP - 포인트컷  (0) 2023.08.21
스프링 AOP 개념  (0) 2023.08.21
@Aspect AOP  (0) 2023.08.21
빈 후처리기  (0) 2023.08.17