본문 바로가기
Spring|Spring-boot/Spring AOP

Spring AOP (2)

by oncerun 2022. 1. 29.
반응형

 

설정 사항

- org.aspectj 패키지 관련 기능은 aspectjweaver.jar 라이브러리가 제공하는 기능이다. 따라서 해당 라이브러리 의존관계의 추가가 필요하며 spring-boot인 경우 spring-boot-starter-aop를 포함하면 스프링 AOP 기능 + asppecthweaver.jar도 함께 사용할 수 있도록 의존관계에 포함된다. 

 

 

구성요소

  • 포인트 컷을 별도의 클래스로 관리하여 public으로 사용할 수 있도록 구성할 것이다. 
    import org.aspectj.lang.annotation.Pointcut;
    
    public class Pointcuts {
    
        @Pointcut("execution(* hello.aop.example..*(..))")
        public void allExample(){}   // point cut signature
    
        @Pointcut("execution(* *..*Service.*(..))")
        public void allService(){}   // point cut signature
    
        @Pointcut(" allExample() && allService()")
        public void exampleAndService(){}
    }
  • @Aspect가 붙은 클래스에서는 advice만 작성할 것이다. 
    외부 포인트 컷을 사용하기 위해선 포인트컷 클래스의  패키 지명과 클래스명까지 포함해야 한다.
    포인트 컷 클래스에는 public으로 접근 지시자를 설정하며 반환 타입은 void 함수의 몸체는 비워야 한다.
    @Slf4j
    @Aspect
    public class AspectExample {
    
    
        @Around("hello.aop.example.aop.Pointcuts.allExample()")
        public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable{
            log.info("[log] {}", joinPoint.getSignature());  
            return joinPoint.proceed();
        }
        
        @Around("hello.aop.example.aop.Pointcuts.exampleAndService()")
        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());
            }
        }
    }

스프링 AOP를 활용하여 포인트 컷이 중복되는 메서드에서 advice가 적용되는 순서를 지정하려고 한다. 스프링이 제공해 주는 @Order를 통해 어드바이스 단위에 붙여 사용했지만 순서가 적용되지 않는다.

org.springframework.core.annotation.@Order 애노테이션은 어드바이스 단위로 적용할 수 없고 클래스 단위로 적용해야 한다는 점이다. 따라서 하나의 애스펙트에 여러 어드바이스가 있는 경우에 별도의 순서를 지정하기 위해선 애스팩트를 별도의 클래스로 분리해야 한다.

 

@Slf4j
public class AspectExample {

    @Order(2)
    @Aspect
    public static class LogAspect{
        @Around("hello.aop.example.aop.Pointcuts.allExample()")
        public Object doLog(ProceedingJoinPoint joinPoint) throws Throwable{
            log.info("[log] {}", joinPoint.getSignature()); 
            return joinPoint.proceed();
        }
    }

    @Order(1)
    @Aspect
    public static class TxAspect{
        @Around("hello.aop.example.aop.Pointcuts.exampleAndService()")
        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());
            }
        }
    }



}

 

 

별도의 클래스로 분리해도 되지만 편의상 내부 클래스를 만들었다. 

 

AspectExample 클래스는 껍데기로 활용하고, 내부에서 각 적용될 애스펙트를 생성해 @Order를 적용시켜주면 된다.

 

 

스프링 AOP 어드바이스 종류

 

스프링 AOP는 프락시를 사용하는 AOP를 차용했기 때문에 메서드에만 AOP를 적용 시킬 수 있다. 

그래서 메서드에 적용시킬 수 있는 여러 어드바이스를 제공하는 여러가지 종류가 있다.

 

  • @Around : 메소드 호출 전후에 수행
  • @Before : 조인 포인트 실행 이전에 실행
  • @After Returning : 조인 포인트가 정상 완료 후 실행
  • @After Throwing : 메서드가 예외를 던지는 경우 실행
  • @After : 조인 포인트가 정상 또는 예외에 관계없이 실행(finally)

 

@Around 애노테이션은 메서드의 모든 부분을 통제한다. 실제 타깃이 호출되기 전, 후, 예외 발생 시, finally처럼 전반적으로 모든 코드를 작성할 수 있다. 나머지 @Before, @After 등등은 이 @Around를 부분 부분 잘라낸 것이라고 생각하면 된다. 

 

@Around("hello.aop.example.aop.Pointcuts.exampleAndService()")
public Object doTransaction(ProceedingJoinPoint joinPoint) throws Throwable{

    try {
    
    	[@Before 영역]
        log.info("[트랜잭션 시작] {}", joinPoint.getSignature());
        
        [타깃 호출]
        Object result = joinPoint.proceed();
        
        [@AfterReturning 영역]
        log.info("[트랜잭션 커밋] {}", joinPoint.getSignature());
        return result;
    } catch (Exception e) {
    
    	[@AfterThrowing 영역]
        log.info("[트랜잭션 롤백]", joinPoint.getSignature());
        throw e;
        
    } finally{
    
    	[@After 영역]
        log.info("[리소스 릴리즈] {}", joinPoint.getSignature());
        
    }
}

* Tip

 모든 어드바이스는 org.aspectj.lang.JoinPoint를  첫 번째 파라미터에 사용할 수 있다.

단 @Around는 ProceedingJointPoint를 사용해야 한다.

 

JoinPoint 인터페이스의 주요 기능

  • getArgs() : 메서드 인수를 반환합니다.
  • getThis() : 프락시 객체를 반환합니다.
  • getTarget() : 대상 객체를 반환합니다.
  • getSignature() : advice가 적용되는 메서드에 대한 설명을 반환합니다.
  • toString() : dvice가 적용되는 방법에 대한 유용한 설명을 인쇄합니다.

 

ProceedingJoinPoint 인터페이스의 주요 기능

  • proceed() : 다음 어드바이스나 타깃을 호출한다.

 

@Before - 조인 포인트 실행 전

@Before("hello.aop.example.aop.Pointcuts.exampleAndService()")
public void doBefore(JoinPoint joinPoint) {
    log.info("[before] {}", joinPoint.getSignature());
}

작업의 흐름을 변경할 수 없다. 메서드의 종료 시 자동으로 다음  타깃이 호출되며, 예외 발생 시에는 다음 코드가 호출되지 않는다. 

 

 

@AfterReturning - 메서드 실행이 정상적으로 반환된 경우

@AfterReturning(value = "hello.aop.example.aop.Pointcuts.exampleAndService()",returning = "result")
public void doReturn(JoinPoint joinPoint, Object result) {
    log.info("[return] {} return={}", joinPoint.getSignature(), result);
}

returing 속성에 사용된 이름은 어드바이스 메서드의 매개변수 이름과 일치해야 한다.

returning절에 지정된 타입의 값을 반환하는 메서드만 대상으로 실행되며 부모 타입 지정 시 모든 자식 타입은 인정된다.

 

* 반환되는 객체를 변경할 수 없다. 다만 조작은 가능하다.

 

 

@AfterThrowing - 메서드 실행이 예외를 던져서 종료될 경우

@AfterThrowing(value = "hello.aop.example.aop.Pointcuts.exampleAndService()",throwing = "ex")
public void doThrowing(JoinPoint joinPoint, Exception ex) {
    log.info("[ex] {} message={}", joinPoint.getSignature(),ex.getMessage());

}

 

@After - 메서드 실행이 종료될 때 실행된다.

@After(value = "hello.aop.example.aop.Pointcuts.exampleAndService()")
public void doAfter(JoinPoint joinPoint) {
    log.info("[after] {}", joinPoint.getSignature());
}

 

 

 

반응형

'Spring|Spring-boot > Spring AOP' 카테고리의 다른 글

Pointcut - within, args  (0) 2022.01.29
Pointcut - execution  (0) 2022.01.29
Spring AOP  (0) 2022.01.27
[Spring boot] AOP 활용예제  (0) 2021.06.09
[Spring] AOP(Aspect Oriented Programming) 방법론  (0) 2020.06.22

댓글