거북이-https://velog.io/@violet_evgadn 이전완료

AOP 실습 본문

웹 개발/Spring(활용)

AOP 실습

VioletEvgadn 2022. 8. 4. 19:11

실습에 앞서...

자, 아무 클래스나 말고 @Aspect 구문을 입력해보자.

해당 라이브러리가 존재하는가?

 

만약 존재한다면 바로 아래 파트로 넘어가면 된다.

하지만 @Aspect는 존재하지 않고 @EnableAspectJAutoProxy 어노테이션만 존재하는 경우가 있을 것이다.

이 경우 실습을 할 수가 없다!

 

그렇다면 어떻게 해야 실습을 할 수 있을까.

Spring의 간편한 점은 build.gradle을 통해 의존성을 주입만 해주면 Spring 측에서 의존성에 필요한 라이브러리들을 Repository에서 찾아 자동으로 다운로드한다는 것이다.

즉, AOP 의존성을 build.gradle에 주입해주기만 한다면 Spring은 자동으로 Repsitory에서 의존성을 위해 필요한 라이브러리를 찾아 다운로드한다.

 

◎ build.gradle에 추가해야 할 구문

implementation 'org.springframework.boot:spring-boot-starter-aop'

dependencies 부분에 위 구문을 추가하면 된다. 아래 사진의 가장 윗 줄에 추가되었음을 볼 수 있다

 

자 이렇게만 하면 바로 실습을 들어갈 수 있...지는 않다.

이렇게 build.gradle을 변경했다면 이를 "적용시켜야" 스프링 측에서 필요한 라이브러리를 설치할 것이다.

 

방법은 2가지가 존재한다

1. 버튼 클릭

build.gradle의 dependenceis에 의존성을 추가했다면 우측 상단에 위와 같은 버튼이 생성될 것이다.

여기에서 왼쪽 코끼리 버튼을 클릭하면 변경시킨 의존성이 적용되어 필요한 라이브러리를 설치하게 된다

 

2. 단축키 사용

사실 1번과 똑같은 방식이다. 1번의 코끼리 버튼에 대한 설명을 보면 위 사진과 같이 명시되어 있다.

즉, Gradle Project 구조가 변화되었기 때문에 Gradle의 변화를 Load 시켜 프로젝트를 Update 시키겠다는 것이다.

이를 위해 "Ctrl + Shift + O" 버튼으로 변경시킨 의존성을 적용시킬 수 있다.


Spring AOP Pointcut 표현식

◎ Spring AOP Pointcut 표현식이란?

AOP를 실습하기 앞서, 우리는 Aspect가 Advice와 Pointcut으로 이루어짐을 배웠다.

Advice는 실제 실행되는 코드이므로 POJO 기반으로 코드를 짜면 되지만, Pointcut은 어떻게 지정해야 할까?

 

Spring에서는 AOP Poincut을 Aspect 실행 시점을 지정해주는 어노테이션(@Before, @Around, @After 등)의 Parameter로 지정해준다. pointcut(혹은 value)의 인자 값으로 Advice를 적용할 메서드들의 위치를 정의하는데, 이때 AOP Poincut 표현식을 활용해서 부가 기능을 활용할 메서드들을 지정한다.

 

간단히 설명하자면, Spring에서는 Advice를 어떤 JoinPoint에 활용해야 할지 정해야 하며, 이를 위해 AOP Poincut 표현식으로 대상을 정해주는 것이다.

 

 Spring AOP Pointcut 표현식

execution([접근제어자] 리턴타입 [클래스이름].메서드이름(파라미터)

 

기본적인 pointcut 표현식으로 [ ]는 생략 가능함을 의미한다.

  • 접근제어자 : public, private 등 해당 접근 제어자를 가진 메서드에만 Advice를 적용
  • 리턴타입 : 해당 리턴 타입을 가지는 메서드에만 Advice 적용
  • [클래스이름].메서드이름 : Advice를 적용할 클래스 및 메서드 경로를 입력한다. 이때 클래스 이름은 패키지 경로를 모두 입력해야 하며 생략 가능하다.
  • Parameter : 메서드의 파라미터를 명시한다. Parameter의 타입과 개수까지 지정해 줄 수 있다.

 

그리고 Pointcut 표현식에서는 꼭 포괄적인 요소를 포함할 수 있는 특수 용어가 존재한다.

  • * : 모든 값
  • .. : 0개 이상

 

3가지 예시를 통해 알아보자.

execution(* com.example.aop.*.*(*))

먼저 접근제어자는 생략되었기 때문에 모든 접근제어자 메서드에 Advice를 적용할 것이다.

리턴타입은 *으로써 모든 Return Type을 허가한다.

패키지는 com.example.aop인데 여기서부터가 중요하다.

com.example.aop 패키지에 존재하는 모든 클래스에 적용할 수 있으며(.*) 해당 클래스 내에 존재하는 모든 메서드에 Advice를 적용한다는 것이다(.*)

즉, com.example.aop(패키지).*(패키지 내에 존재하는 모든 클래스).*(클래스 내에 존재하는 모든 메서드)라는 의미를 갖게 되는 것이다.

마지막으로 파라미터에는 *이 입력되었으므로 파라미터의 종류는 상관없고 개수는 1개인 메서드에 대해서만 Adivce를 적용한다.

 

최종적으로 com.example.aop 패키지에 존재하는 모든 메서드 중 파라미터가 1개인 것에 Advice를 적용한다.

 

exeuction(* com.example..*.get*(..))

패키지는 com.example이다. 이후 ..*로 입력되어 있는데 이게 참 신기한 것이다.

com.example이 패키지명이다. 이후 ..이 입력되어 있으므로 0개 이상의 값이 들어올 수 있다.

위에서는 com.example.aop.*에서 (com.example.aop) + *이였기 때문에 com.example.aop와 패키지에 존재하는 클래스에만 적용할 수 있고, 패키지 내의 하위 패키지에는 적용할 수가 없었다.

하지만 이번 예시는 (com.example) + (..) 인 것이다. com.example이라는 패키지만 가지고 있다면 0개 이상의 값이 아래에 입력될 수 있다는 의미이며 이는 "com.example 패키지와 그 아래 있는 패키지에 속해있는" 것들을 찾는다는 것을 의미한다.

이후 *가 입력되어 있으므로 클래스에 대한 제한 조건은 없으며 모든 클래스 중 get*, 즉 get으로 시작하는 메서드에만 Advice를 적용한다는 의미이다.

 

최종적으로 com.example 패키지와 하위 패키지에 속해있는 모든 클래스에서 이름이 get으로 시작하는 메서드에 파라미터 타입 관계없이 0개 이상인(즉 파라미터는 Advice 선택 기준이 되지 않음) 메서드에 Advice를 적용하는 것이다

 

execution(* com.example..*.*(..))

마지막으로 정리하자. AOP에서 모든 메서드에 부가 기능을 적용시킬 때 활용하는 Pointcut 표현식이다.

 

해석은 com.example 패키지 및 하위 패키지에 이는 모든 클래스를 찾고 모든 메서드에 대해 파라미터 관계없이(타입 관계없이 0개 이상 가지는 메서드) Advice를 적용하는 것이다

 

within(클래스이름)

within으로 클래스 경로를 입력하면 해당 경로에 존재하는 모든 메서드에 대해 Advice를 적용한다.

execution 명시자에서 접근제어자와 리턴타입, 파라미터를 고려하지 않는 간단하게 만든 버전이라고 생각하면 된다

 

bean(빈이름)

Bean으로 등록한 객체의 모든 메서드에 Advice를 적용하는 방식이다.

 

annotation(어노테이션명)

지정한 어노테이션을 가지는 모든 Class의 메서드에 Advice를 적용한다


Annotation 분석

◎ @Around, @Before, @After

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Around {

    String value();

    String argNames() default "";
}
  • value : Pointcut 표현식을 받는 인자. Parameter에 대한 명시 없이 Pointcut 표현식을 입력하면 자동으로 value값에 적용된다
  • argNames : 사용자가 어노테이션을 활용한 메서드에 파라미터 이름을 지정했다면 지정한 파라미터명을 입력하는 것이다. 하지만 argNames를 굳이 입력하지 않아도 정상적으로 작동되므로, 활용하지 않는 것을 추천한다

@Before, @After도 동일한 형식을 가진다.

argNames를 거의 활용하지 않기 때문에 Parameter를 명시하지 않고 바로 Pointcut 표현식만 입력하여 많이 활용한다

 

추가로 @Around 같은 경우 무조건 ProceedingJoinPoint를 Parameter로 가져야 하는데 이는 아래 실제 코드를 보면서 조금 더 알아보자

 

◎ @AfterThrowing

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterThrowing {

    String value() default "";

    String pointcut() default "";

    String throwing() default "";

    String argNames() default "";

}

value와 argNames는 위와 동일하다

  • pointcut : Pointcut 표현식을 받는 인자. value와 역할이 겹친다. 만약 value와 pointcut 2개에 모두 표현식을 입력한다면 "pointcut Parameter 인자값"을 최종 Pointcut으로 인지한다
  • throwing : argNames 역할과 유사하다. Target 메서드가 반환할 예외에 대한 Argument명을 지정해주는 Parameter로써 argNames와는 달리 명시하여 "어떤 Parameter가 Exception을 담당하는가"를 명시해주는 게 좋다.

argNames는 그냥 모든 Parameter의 Argument Name을 지정해줘야 한다. 따라서 argNames 값을 입력할 경우 코드가 지저분해질 우려가 있어 굳이 활용을 추천하지 않는다.

하지만 thorwing은 다르다.

AfterThrowing 같이 "특수한 Case"에 Advice가 발생시키려는 Annotation에서는 특수한 Case에서  나온 결괏값 또한 중요한 역할을 한다. 예를 들어 AfterThrowing은 "에러가 발생했을 때" 적용되는 Advice이므로 대부분의 로직에서는 에러 내용을 사용할 것이다(ex. 에러 내용을 로그로 저장)

따라서 "특수한 Case에서 반환하는 값"에 집중할 필요가 있거, throwing을 통해 "이 객체가 특수한 Case에서 반환되는 값입니다!"를 알려줄 수 있다.

 

따라서 @AfterThrwoing의 throwing이나 @AfterReturning의 returning은 입력해주는 것이 좋다.

 

◎ @AfterReturning

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterReturning {

    String value() default "";

    String pointcut() default "";

    String returning() default "";

    String argNames() default "";

}

@AfterThrowing과 거의 똑같고 thorwing 대신 returning 인자를 받는다는 차이만 존재한다.

returning에 "정상적인 메서드의 반환 값"에 대응하는 Argument명을 입력하면 된다


Aspect 코드

@Aspect
@Component
public class AopConfig {
    @Before("execution(* com.example.review..*.*(..))")
    public void doSomethingBefore() {
        System.out.println("프로그램이 실행하기 이전입니당!!");
    }

    @AfterReturning(
            pointcut = "execution(* com.example.review..*.*(..))",
            returning = "return_value"
    )
    public void success_after(int return_value){
        System.out.println("프로그램이 정상적으로 실행되었습니다."+return_value+"값이 반환됩니다");
    }
    // returning으로 설정한 "return_value" 값과 매개변수 Arugment 이름인 return_value가 동일하게 설정되었음
    
    @AfterThrowing(
            pointcut = "execution(* com.example.review..*.*(..))",
            throwing = "tx"
    )
    public void fail_after(Throwable tx){
        System.out.println(tx.getMessage()+"때문에 비정상적으로 종료되었습니다");
    }
    // thorwing으로 설정한 "tx" 값과 매개변수 Arugment 이름인 tx가 동일하게 설정되었음

    @Around("execution(* com.example.review..*.*(..))")
    public Object timeCheck(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        long start = System.currentTimeMillis();
        try{
            return proceedingJoinPoint.proceed();
        }
        finally{
            long finish = System.currentTimeMillis();
            long timeMs = finish - start;
            System.out.println("걸린 시간은 "+timeMs+" ms 입니다");
        }
    }
}

먼저 Aspect로 올리기 위해서는 Spring Bean에 등록되어 있어야 함을 위에서 설명했다.

따라서 부가 기능들을 담은 Class인 AopConfig 클래스는 @Component 어노테이션을 통해 먼저 Spring Bean 상에 올려야 한다.

이후 @Aspect 어노테이션을 활용해 이 클래스가 부가 기능에 관련된 메서드(Advice)들을 담아 관리하는 객체임을 명시한다.

AOP Pointcut은 "com.example.review..*.*(..))"로 지정함으로써 패키지 내에 존재하는 모든 클래스의 모든 메서드에 Advice가 적용되도록 만들었다.

 

이후 Controller에서 핵심 로직이 실행된다고 가정하고, 이 클래스 이름을 Controller로 지정하자.

@Component
public class Controller {
    public int function(){
        System.out.println("핵심 로직이 실행되고 있는중");
        return 1;
    }
    
    public Object function2(){
        return 1/0;
    }
}

 

Test 코드와 결과를 보며 어노테이션 별로 분석해보자.

@SpringBootTest
class ReviewApplicationTests {

	@Autowired
	Controller controller;
	@Test
	void contextLoads() {
		controller.function();
	}

}

먼저 @Before 어노테이션부터 확인하자.

@Before, @After, @Around 어노테이션을 사실상 pointcut 값만 설정해주면 되므로 Pointcut 경로에 대해서만 입력해주었다.

@Before이 적용된 "doSomethingBefore()" 메서드를 확인해보면 메서드가 실행되기 이전 "프로그램이 실행하기 이전입니당!!"이라는 문구를 출력하는 메서드이다. 

결과 사진을 보면 핵심 로직에서 출력되는 "핵심 로직이 실행되고 있는 중"이라는 문구보다 이전에 출력됨을 알 수 있다.

즉, @Before 어노테이션을 활용하면 핵심 로직 이전 해당 메서드(부가 기능)가 먼저 실행됨을 알 수 있다.

 

function() 핵심 로직은 에러를 발생시키지 않는다. 즉 @AfterReturning 어노테이션을 활용한 Advice가 부가 기능으로써 수행될 것이다.

"success_after" 메서드를 확인하자. @AfterReturning을 확인하는데, pointcut을 지정함과 동시에 returning 값을 지정했음을 알 수 있다. 핵심 로직이 실행된 이후 Return 된 값을 returning으로 설정된 "return_value"라는 이름에 저장함을 명시하였다. 실제로 코드상 success_after의 Parameter는 "int return_value"로 설정되어 있다.

핵심 로직 function()에서 int형 값인 1을 반환함을 알 수 있다. 그리고 success_after Advice는 return_value 값을 출력하도록 구현되었다. 결과 사진을 보면 return_value 값에 핵심 로직에서 반환한 값인 1이 적용되어 출력됨을 볼 수 있다.

 

@AfterThrowing 어노테이션을 활용한 메서드도 비슷하게 설정되었다. 단지 @AfterThrowing은 "에러가 발생했을 경우" 호출되는 부가 기능이기 때문에 매개변수로 Throwable, 혹은 Throwable 클래스를 상속받아 만들어진 Exception 종류 중 한 개가 Parameter로 들어가야 할 것이다. 위 코드에서는 Thorwable을 Parameter로 삼고 tx.getMessage()를 활용함으로써 "발생한 에러의 이름"을 출력하도록 하였다.

 

그렇다면 무조건 Error가 발생할 function2()를 실행시키고 결과를 확인해보자

@AfterThrowing의 tx.getMessage() 부분이 / by zero로 치환되었음을 알 수 있다.

이는 Throwable, 즉 에러가 발생한 이유를 알려주는 것으로써 "왜 메서드를 실행할 때 에러가 발생하였는가"를 알 수 있게 된다.

 

여기서 주의 깊게 봐야 할 점은 @Before, @Around 어노테이션의 부가 기능은 핵심 로직의 실행 성공 여부와 관계없이 무조건 실행된다는 것이다.(@After 또한 마찬가지이다)

또한 @Around Advice의 결과는 @AfterThrowing, @AfterReturning Advice 결괏값이 도출된 이후 출력됨을 알 수 있다. 즉 @Around Advice의 결괏값이 가장 나중에 출력된다.

(참고로 try ~ catch 구문을 활용해서 에러 처리를 Controller 측에서 해준다면 실제 코드 상에서는 에러가 발생하였지만 Controller 측에서 발생한 에러를 성공적으로 처리한 것으로 인지하여 "어찌 되었든 결과가 좋으니 성공적으로 처리되었다!"라고 판단하여 @AfterReturning Advice가 실행된다)

 

1가지 경우를 더 확인해보자.

만약 핵심 로직이 성공적으로 끝났는데, 이 메서드가 아무런 값도 Return 시키지 않는다면 어떻게 될까?

@AfterReturning Advice는 int형 값인 return_value를 인자로 받아야 하는데 핵심 로직에서 아무런 값도 반환하지 않으므로 Advice가 받을 수 있는 인자값도 없게 되는 것이다.

한 번 실행시켜보자

결과를 확인해보면 @AfterReturning Advice가 결괏값을 출력하지 않았음을 볼 수 있다. 우리는 이를 통해 "Pointcut이 제대로 적용되었어도 핵심 로직의 반환 값과 Advice의 Parameter의 개수, Type 등이 다르다면 이 Advice는 실행되지 않는다"라는 사실을 알 수 있다.

즉, "설정한 Parameter와 딱 맞는" 반환값을 내는 핵심 로직에만 Advice가 실행됨을 알 수 있다.

 

자, @Before(@After도 활용 방식은 동일), @AfterThrwoing, @AfterReturning은 모두 쉽게 구현할 수 있음을 볼 수 있다.

문제는 @Around이다. try ~ catch 구문도 활용하고 애초에 처음 보는 ProceedingJoinPoint라는 클래스도 활용해야 한다.

그렇다면 이 @Around Annotation에 대해 더 자세히 알아보자

 

◎ @Around

@Around 코드를 구현해보기 전에 @Around의 동작 방식부터 복습하자.

결국 @Around는 "핵심 로직 실행 이전" A라는 로직을 수행시키고, "핵심 로직이 완료된 이후" B라는 로직을 실행시켜 최종적으로 결괏값을 내는 것이다. 메서드(핵심 로직)의 실행시간을 구하는 부가 기능이 대표적일 것이다.

핵심 로직 실행 시간을 구하기 위해선 "핵심 로직 실행" 시점에서의 시간을 구하고 "핵심 로직 완료 이후" 시점의 시간을 구해 두 시점의 시간을 빼는 로직이 수행되어야 할 것이다.

 

@Around 어노테이션을 활용하기 위해선 핵심 로직 실행 이전 Advice의 어느 부분까지 로직이 수행되어야 하는지 명시해야 하며 동시에 진행 상황을 저장해야 할 것이다. 이후 핵심 로직이 완료되었다면 @Around 어노테이션을 활용한 Adivce에서 중단된 부분부터 실행을 재개해 Advice를 끝까지 실행시킴으로써 완벽한 부가 기능에 대한 로직이 실행될 것이다.

이제 눈치가 빠른 사람은 이해했을 것이다.

@Around에서 활용하는 "ProceedingJoinPoint"라는 클래스가 핵심 로직 실행 이전 결괏값을 보존하면서 Controller(핵심 로직) 측에 요청을 보내 핵심 로직을 실행시키고 핵심 로직이 종료되었을 때 다시 @Around Advice로 돌아와 나머지 부가 기능을 실행시키는 주체가 되는 것이다.

 

이전 AOP에서 설명했던 시퀀스 다이어그램을 다시 살펴보자

출처 : https://thalals.tistory.com/271

서블릿이 Proxy에 요청을 보내는 것까지는 설명했지만 이 @Around 부분은 제대로 설명하지 않고 넘어갔다.

위 설명을 토대로 시퀀스 다이어그램을 완벽히 파악해보자.

서블릿은 Proxy에 요청이 왔음을 알린다. 이후 Proxy는 @Around Advice에 "실행될 차례"임을 알린다. 요청이 오면 @Around Advice에서는 부가 기능에 대한 로직을 수행하게 된다. 부가 기능에 대한 로직을 수행하자 joinPoint.proceed() 명령어를 통해 Controller에 요청을 보내는 부분이 보일 것이다. 바로 이 부분이 "핵심 로직을 실행하기 이전 실행되어야 하는 Advice 로직"을 모두 수행하였음을 알리는 부분이다.

그리고 핵심 로직이 끝나면 핵심 로직은 완료했음을 @Around Advice에 알리게 된다. 그렇다면 @Around Advice는 중지되었었던 부가 기능 로직을 다시 재개해 Advice를 끝까지 수행시키고 부가 기능을 완료한다.

이후 결괏값(Result)을 담아 Proxy에 보냄으로써 핵심 로직의 결괏값을 전달해주는 것이다.

 

이론으로만 설명하자니 너무 어렵지 않은가? 쉬운 예시를 통해 빠르게 이해하자.

 

핵심 로직은 고객 A의 정보를 반환하는 것이며 부가 로직(@Around Advice)은 핵심 로직의 실행 시간을 재는 부가 기능이 구현되어 있다고 가정하자. 핵심 로직의 실행 시간을 잴 때는 "핵심 로직이 실행되기 이전 시간"과 "핵심 로직이 실행된 이후 시간"을 알아야 하는데, 이를 각각 S, E라고 하겠다.

 

먼저 서블릿 측에 HTTP Request가 오고, 서블릿은 요청이 왔음을 Proxy 측에 알린다. Proxy는 @Around Advice가 존재함을 인지하고 @Around Advice에 요청이 왔음을 알린다. @Around Advice에서는 먼저 S를 측정할 것이다. 이후 @Around Advice는 E를 측정해야 하는데, 이를 위해선 핵심 로직이 실행되어야 한다. 따라서 @Around Advice는 joinPoint.proceed()를 통해 핵심 로직이 실행되어야 함을 Controller 측에 알린다.

여기에서 중요한 점은 @Around Advice는 사라지지 않는다. 즉 S를 저장한 상태로 잠시 Advice 실행을 중지시키고 Controller 측에 핵심 로직을 실행시키라고 요청한 것이다.

 

Controller는 아무것도 모르고 그냥 핵심 로직을 실행시켜 고객 A의 정보를 찾아 반환할 것이다. 이렇게 반환된 값은 @Around Advice에 보내질 것이다. @Around Advice는 결괏값을 받음으로써 "핵심 로직이 완료되었구나!"라고 판단할 수 있을 것이다. 그렇다면 @Around Advice는 남은 로직(E를 측정하는 것)만 처리하면 자신의 역할을 다하게 되는 것이다.

 

joinPoint.proceed() 다음 부분에 존재하는 로직을 수행하게 될 텐데 먼저 E를 측정할 것이다. 여기서 중요한 점은 핵심 로직이 수행될 때 @Around Advice는 사라지지 않았다는 점이다. 즉 이전에 구했던 S가 저장되어 있는 것이다.

@Around Advice는 S와 E를 모두 활용해 "E-S" 로직을 실행시켜 핵심 로직의 실행 시간을 측정할 수 있게 된다

 

@Around Advice는 드디어 핵심 로직의 걸린 시간을 측정하며 부가 기능을 완료시켰다. 이제 @Around Advice는 Controller 측에서 반환한 핵심 로직 결괏값을 Proxy 측에 전달해준다. Proxy에서는 이걸 Controller에서 보냈든 @Around Advice에서 보냈든 별 관계없기 때문에 그냥 최종 결과라고 생각하며 서블릿에 전달함으로써 전체 과정이 완료되는 것이다.

 

그렇다면 이런 개념을 바탕으로 코드를 이해해보자

@Aspect
@Component
public class AopConfig {
    ...
    @Around("execution(* com.example.review..*.*(..))")
    public Object timeCheck(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        long start = System.currentTimeMillis();
        try{
            return proceedingJoinPoint.proceed();
        }
        finally{
            long finish = System.currentTimeMillis();
            long timeMs = finish - start;
            System.out.println("걸린 시간은 "+timeMs+" ms 입니다");
        }
    }
}

먼저 @Around 어노테이션에 Pointcut을 지정했다. 그리고 Controller 측에 핵심 로직이 실행될 시점임을 알리는 객체인 ProceedignJoinPoint 객체를 무조건 인자로 설정해야 한다. 만약 인자에 ProceedingJoinPoint 객체가 없다면 핵심 로직을 실행하라고 Controller측에 알릴 수 없기 때문에 @Around Advice를 활용하지 못할 것이다.

long start는 "핵심 로직이 실행되기 이전의 시간", 즉 S값이 된다.

 

이후 `proceedingJoinPoint.proceed()`를 통해 Controller측에 핵심 로직을 실행하라고 요청을 보낸다.

여기서 try구문을 활용하는 이유가 존재한다. 위에서 설명했듯 @Around Advice는 Target이 성공적으로 완료되었든 실행을 실패하였든 무조건 수행되어야 하는 메서드이다. 즉, Target이 에러를 발생시키는 상황도 고려해야 한다는 것이다

이를 위해 try 구문을 활용해서 예외 상황을 대비하고, @Around Advice는 에러 내용에 별 관심이 없기 때문에 "throws Throwable"로 에러를 다른 클래스에 떠맡김으로써 대충 처리하는 것이다.(에러는 @AfterThrowing Advice가 군침을 흘리며 처리를 원하고 있을 것이다)

"핵심 로직이 실행된 이후의 부가 기능 로직"은 Target이 성공했든 실패했든 무조건 실행되어야 하는 내용이다.

따라서 finally 구문에 부가 기능에 대한 코드를 담음으로써 Target 성공 여부에 관계없이 부가 기능을 수행하도록 수행한다.

 

우리가 필요한 로직은 "핵심 로직이 실행된 이후의 시간", 즉 E를 구하는 것이다. 따라서 long finish에 핵심 로직이 실행된 이후 시간을 저장하게 될 것이다. @Around Advice는 이전 상태를 저장해 놓았기 때문에 long start에도 S가 저장되어 있을 것이다.

따라서 E - S를 계산하고 이를 timeMs에 저장함으로써 "핵심 로직이 수행되는 데 걸리는 시간"을 측정할 수 있게 되는 것이다

(위에 존재하는 단위 테스트 결과 사진들에서 "걸린 시간은 xx ms 입니다"를 출력하는 것으로 @Around Advice가 성공적으로 동작함을 알 수 있다)

'웹 개발 > Spring(활용)' 카테고리의 다른 글

build.gradle 사용법  (0) 2022.11.27
Spring Web 메인 클래스  (0) 2022.08.05
DI 주입 실습  (0) 2022.08.04
스프링 빈 활용  (0) 2022.08.03
Comments