[Spring] Spring Advice로 커스텀 어드바이스 만들기

반응형

이번 포스트부터는 Spring AOP 컴포넌트를 이용하여 실제 Advice를 만들어보고, 위빙하는 시간을 가져보겠습니다. 

 

 

 

Advice Interface

Advice에 대해 다시 복습해보자면 Advice는 특정 조인포인트 즉, 메서드가 호출되는 시점에 실행할 코드를 정의한 것입니다. ProxyFactory 클래스에서 addAdvice 메서드를 이용해 어드바이스를 직접 추가하는 방법과 Advisor 구현체를 addAdvice에 전달하여 간접적으로 추가하는 방법이 있었죠.

 

두 방법에 차이가 있다면 어드바이스와 달리 Advisor는 관련 PointCut과 함께 어드바이스를 전달한다는 점이 있습니다. PointCut은 실제 어드바이스를 언제 실행할지를 정하는 AOP 용어로 어드바이스를 실행할 지점을 상세하게 지정할 수 있다는 장점이 있습니다.

 

위 이미지는 Spring Advice의 계층 구조를 나타낸 것입니다. Spring Advice의 계층 구조는 표준 AOP 얼라이언스 인터페이스와 동일한 구조로 되어 있으며 ProxyFactory 클래스를 수정하지 않더라도 새로운 어드바이스 타입을 쉽게 만들 수 있는 구조로 되어 있습니다.

 

 

 

Before Advice

Before Advice는 이름 그대로 Target 클래스의 메서드가 호출되기 전에 발생할 이벤트 어드바이스를 Before Advice라 합니다. Spring Advice에서는 이를 MethodBeforeAdvice로 정의하여 사용하도록 권장하는데요.

 

예를 들면, 우리가 특정 메서드를 호출할 때 호출을 하는 대상(Caller)이 충분한 권한이 있는지 확인해야 한다면 이 Advice가 좋을 것입니다.

 

먼저 사용자를 식별할 수 있는 UserInfo 라는 클래스를 만들어줍니다.

 

현재 로그인 한 사용자에 대한 정보를 가지고 관리하는 SecurityManager 클래스를 만들어줍니다.

 

Advice를 적용할 Target 클래스를 만들어줍니다. 로그인 한 사용자에게만 메시지를 출력하는 메서드인 print를 만들어봤습니다.

 

Target 클래스에 적용할 Advice를 만들어줍니다. 이 때 인증 정보를 알 수 없는 유저에 대한 예외 처리를 추가합니다.

 

마지막으로 어드바이스가 잘 동작하는지 확인하기 위해 Bean 객체를 ProxyFactory 클래스를 이용해 프록시 객체로 만들고, Advice를 적용합니다. 

 

그러면 위와 같이 UserInfo가 있을 때와 없을 때 정확하게 잘 나오고 있음을 알 수 있습니다.

 

그런데, SecurityManager 인스턴스를 만들고 TargetBean 인스턴스를 생성할 때 또 다른 SecurityManager 인스턴스를 만들었습니다. 그러면 두 시나리오 모두UserInfo 데이터가 아예 NULL이 되어 있어야 하는 거 아닌가요?

 

흔히 많이들 생각하는 질문일 것입니다. 하지만 우리가 여기서 주의깊게 봐야할 것은 SecurityManager 클래스가 가지고 있는 UserInfo는 ThreadLocal이라는 점입니다. ThreadLocal은 하나의 스레드에서 실행되는 코드가 동일한 객체를 사용할 수 있도록 해주는데, 이 때는 new 연산자를 이용해 새로운 인스턴스를 생성한다 하더라도 같은 스레드에서 이뤄지는 코드는 항상 동일한 객체로 실행된다는 특징이 있습니다.

 

이에 대표적인 예시가 바로 Spring Security에서 제공하는 사용자 인증 정보(Principal)입니다.

 

이 외에도 BeforeAdvice는 클라이언트가 요청한 인수 등의 수정이나 데이터 정제에도 유용하게 쓸 수 있습니다.

 

 

 

AfterReturning Advice

AfterReturning Advice는 JoinPoint에서 메서드 호출의 결과를 반환했을 때만 동작하는 어드바이스입니다. Before Advice와 달리 메서드가 이미 실행하고 난 뒤 발생하기 때문에 인수를 변경하는 작업 등은 불가능합니다.

 

이 어드바이스를 가장 유용하게 쓸 수 있는 방법에는 Target 로직의 결괏값에 대한 검증에 사용해 볼 수 있습니다. 예를 들면 암호화 키 생성을 하는 로직이 있을 때 해당 암호화 키 길이가 짧으면 해당 키가 취약하다는 것을 알려주는 것이죠.

 

간단하게 3 seed를 주고 1이 나왔을 때 약한 키를 출력해주는 Target 클래스를 만들어줍니다.

 

이를 검사하는 Advice를 만들어줍니다. AfterAdvice를 만들 때는 Target 클래스에 해당하는 메서드가 약속된 것인지를 확인하는 것이 중요합니다. 그렇지 않은 곳에 Advice를 넣고 프록시 객체를 만들었을 경우 엉뚱한 값을 대조하여 예외를 발생시키기 때문입니다.

 

총 10번을 시도하여 어드바이스가 제대로 동작하는지 확인해보겠습니다.

 

보다시피 1이 나왔을 때는 보안에 취약한 키를 출력하는 SecurityException을 발생하고, 그렇지 않은 경우 STRONG_KEY가 잘 출력되는 것을 볼 수 있습니다.

 

 

 

AroundAdvice (MethodInterceptor)

Spring AOP에서 Around는 메서드가 실행되는 동안 발생하는 어드바이스입니다. Before + After를 조합한 어드바이스와 비슷한데, 다른 점이 있다면 반환 값을 수정할 수 있다는 점입니다. 뿐만 아니라 특정 조건을 통해 실제 객체의 메서드를 실행하지 않 도록 할 수도 있습니다.

 

즉, Around 어드바이스가 실제 객체 메서드를 좌지우지하며 전체 구현을 새로운 코드로 바꿔버리기도 합니다. 실제로 Spring 컴포넌트 대부분이 바로 이 Around Advice를 사용하는데요. 특히 원격 프록시 지원 기능이나 트랜잭션 관리 기능들이 여기에 포함됩니다.

 

여기에 대표적인 예시는 호출한 메서드의 성능을 측정할 수 있는 프로파일링이 있습니다.

 

WorkerBean에 어떤 특정한 알고리즘 함수를 정의하고 Spring Framework에서 제공하는 StopWatch 클래스를 사용해 메서드가 실행된 시간을 측정하는 코드입니다.

 

실제로 main 함수의 코드에는 Results만 출력하도록 했지만 ProfilingInterceptor에 의해 실행 시간과 Target 객체의 정보를 출력해주는 부분까지 실행 결과로 나오고 있습니다.

 

 

 

ThrowAdvice

마지막으로 ThrowAdvice는 항상 메서드를 호출하는 JoinPoint 이후에 실행된다는 점에서 AfterReturningAdvice와 유사하지만 메서드가 예외를 던질 때만 실행되는 어드바이스입니다.

 

ThrowAdvice는 AfterRetruningAdvice와 같이 AfterAdvice를 상속받고 있어 프로그램 실행을 거의 제어할 수 없습니다. 다만 예외는 확실하게 제어할 수 있는데, 프로그램에서 발생한 예외를 무시하고 예외의 타입만을 변경할 수 있습니다.

 

예를 들면 명확하게 정의되지 않은 배열 예외를 던지고 있는 API가 있다고 했을 때, ThrowAdvice를 사용하면 해당 API의 모든 클래스에 어드바이스를 적용해 예외 계층 구조를 좀 더 명확하고 관리하기 쉽게 다시 정의할 수 있습니다.

 

스로우 어드바이스가 다른 어드바이스와 차이점이 있다면 필수로 구현해야 할 메서드가 없다는 점입니다. 따라서 처음 이 인터페이스를 사용하게 되면 어떻게 구현해야 하는지 잘 모르는 경우가 있을 수 있습니다.

 

ThrowAdvice가 첫 번째로 찾는 것은 바로 하나 이상의 afterThrowing() public 메서드입니다. 이 메서드는 의미 있는 값을 반환할 수 없어 반환 타입으로 void를 사용하는 것이 가장 좋지만, 메서드의 반환 타입은 사실 중요하지 않습니다.

 

첫 번째 afterThrowing 메서드를 보면 Java의 모든 Exception 타입을 인수로 지정했는데, 이것은 모든 예외 케이스에 해당 메서드를 적용하겠다는 것입니다. 이 메서드의 가장 큰 우선 순위는 예외 타입을 인자로 받는 afterThrowing 메서드가 없을 때 동작합니다.

 

두 번쨰 afterThrowing 메서드를 보면 예외를 던진 메서드(method), 메서드에 전달된 인수(args), 메서드 호출 대상(target), 잡아낼 예외 타입(ex)의 4가지 인수를 선언했습니다. 위에서 말한대로 지정된 예외 타입을 인자로 받는 afterThrowing 메서드이기 때문에 IllegalArgumentException의 경우에는 첫 번쨰 메서드가 아닌 두 번쨰 메서드가 실행되는 것입니다. 

 

이 어드바이스를 가장 효율적으로 사용할 수 있는 방법에는 바로 Exception Logging인데요. 전체 Exception 타입만을 주고, logging하는 로직을 넣는다면 애플리케이션의 Exception Logging을 한 곳에서 관리할 수 있게 됩니다. 또한 애플리케이션 코드를 별도로 수정하지 않고도 Logging 코드를 추가하거나 수정할 수 있어 매우 유용하지요.

 

 

 

마치며...

Spring에서 다룰 수 있는 어드바이스를 알아보고, 간단히 사용법과 팁에 대해 알아봤습니다. 어드바이스의 종류를 선택할 때는 애플리케이션의 요구 사항에 따라 어드바이스 타입을 선택할 수도 있겠지만 자신의 필요에 따라서도 맞추는 것이 중요합니다. 단연 요구사항 충족만이 아닌 서비스의 유지보수 차원에서도 쓰일 수 있기 때문입니다.

 

정리하자면 BeforeAdvice만으로 해결할 수 있다면 굳이 AroundAdvice를 사용할 필요가 없습니다. 물론 차후를 대비하기 위해 AroundAdvice를 사용했다가 쉽게 AfterAdvice를 구현할 수 있도록 할 수 있겠지만 코드 리딩을 하는 다음 사람을 위해 이 코드가 어떤 역할인지 맹목적으로 알 수 있도록 필요 이상의 코드 구현은 자제해야 합니다.

 

가령 예를 들면 BeforeAdvice에서 메서드를 호출한 횟수를 세고자 할 때 BeforeAdvice만 사용한다면 Couter 코드만 작성하면 됩니다. 하지만 AroundAdvice를 사용한다면 메서드를 호출하고 호출자에게 값을 반환하는 코드를 추가해야 하는데, 결국 이러한 필요 이상의 코드는 불필요한 오류나 버그를 발생시키는 원인이 될 수도 있습니다.

 

따리서 어드바이스는 요구사항과 필요에 맞춰 적당히 쓰되 그 이상은 사용을 자제하는 것을 권장합니다.

 

반응형

Tistory Comments 0