6.2.2 Spring의 advice 유형 by ys

Spring은 여러 가지 advice 타입을 제공하며 임의의 advice 타입을 지원하기 위해 확장 가능하다. 이 절에서는 기본 개념과 표준 조언 유형에 대해 설명합니다.

interception around advice

Spring에서 가장 근본적인 조언 유형은 interception around advice입니다.

스프링은 메서드 인터셉션을 사용해서 around advice의 AOP Alliance interface와 호환성이 있다. around advice를 구현한 MethodInterceptor는 다음 인터페이스를 구현해야 한다.

public interface MethodInterceptor extends Interceptor {

    Object invoke(MethodInvocation invocation) throws Throwable;
}

invoke()메서드의 MethodInvocation 인자는 호출되는 메서드, 대상 조인 포인트, AOP 프록시, 메서드의 인자를 노출한다.invoke()메서드는 호출의 결과(조인포인트(join point)의 반환값)를 반환해야 한다.

다음 예제는 간단한 MethodInterceptor구현을 보여줍니다 .

public class DebugInterceptor implements MethodInterceptor {

    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("Before: invocation=[" + invocation + "]");
        Object rval = invocation.proceed();
        System.out.println("Invocation returned");
        return rval;
    }
}

MethodInvocationproceed() 메서드의 호출을 봐라. 이는 인터셉터 체인을 따라 조인포인트로 진행한다. 대부분의 인터셉터는 이 메서드를 호출하고 그 반환 값을 반환할 것이다. 하지만 다른 around advice처럼 MethodInterceptor는 다른 값을 반환하거나 proceed 메서드를 호출하지 않고 예외를 던질 수 있다. 그렇지만 그럴듯한 이유가 없다면 이렇게 할 이유가 없다!

NOTE

MethodInterceptor는 다른 AOP Alliance 호환 AOP 구현체와 상호운용성이 있다. 이번 장의 뒷부분에 나오는 어드바이스 타입들은 공통의 AOP 개념을 구현했지만, 스프링에 특화된 방법으로 구현했다. 가장 구체적인 어드바이스 타입을 사용하는 이점을 누리면서 다른 AOP 프레임워크의 관점을 사용하고자 한다면 MethodInterceptor around advice를 사용해라. 포인트컷은 현재 프레임워크간의 상호운용성이 없고 AOP Alliance는 포인트컷 인터페이스를 정의하지 않았다.

Before advice

더 간단한 어드바이스 타입은 before advice다. 이는 메서드에 진입하기 전에만 호출되므로 MethodInvocation 객체가 필요없다.

before advice의 주요 이점은 proceed() 메서드를 호출할 필요가 없으므로 인터셉터 체인을 따라가다가 실패할 가능성이 없다는 점이다.

MethodBeforeAdvice 인터페이스가 아래 나와 있다.

public interface MethodBeforeAdvice extends BeforeAdvice {

    void before(Method m, Object[] args, Object target) throws Throwable;
}

(일반적인 객체가 필드 가로채기를 할 수 있고 스프링이 이를 구현할 것 같지는 않지만, Spring의 API 디자인은 필드 before advice를 허용 할 것이다.)

반환타입은 void이다. before advice는 조인포인트를 실행하기 전에 임의의 동작을 추가할 수 있지만 반환 값은 바꿀 수 없다. before advice가 예외를 던지면 인터셉터 체인을 추가로 실행하지 않는다. 예외는 인터셉터 체인을 다시 전파합니다. 예외가 unchecked 예외이면(아니면 호출된 메서드의 시그니처에서) 클라이언트에게 직접 전달될 것이다. 그렇지 않으면 unchecked 예외에서 AOP 프락시가 감쌀 것이다.

다음은 스프링에서 모든 메서드의 호출 횟수를 세는 before advice 예시이다.

public class CountingBeforeAdvice implements MethodBeforeAdvice {

    private int count;

    public void before(Method m, Object[] args, Object target) throws Throwable {
        ++count;
    }

    public int getCount() {
        return count;
    }
}

TIP

before advice를 어떤 포인트컷과도 사용할 수 있다.

Throws advice

Throws advice는 조인 포인트가 예외를 던지면 조인포인트의 반환 후에 호출된다. 스프링은 타입이 있는 throws advice를 제공한다. 이는 org.springframework.aop.ThrowsAdvice인터페이스가 아무런 메서드도 가지지 않는 다는 것을 의미한다. 주어진 객체가 하나 이상의 타입이 있는 throws advice 메서드를 구현했는지 구별하는 태그 인터페이스이다. 이는 다음과 같은 형식이 되어야 한다.

afterThrowing([Method, args, target], subclassOfThrowable)

마지막 인자만 필수값이다. 어드바이스 메서드가 메서드와 인자에 관심있냐에 따라 메서드 시그니처는 한 인자나 네 개의 인자를 가질 수 있다. 다음 클래스는 throws advice의 예시이다.

RemoteException가 던져질 때(하위 클래스를 포함해서) 아래 어드바이스를 호출한다.

public class RemoteThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with remote exception
    }
}

ServletException가 던져질 때 다음 어드바이스를 호출한다. 앞의 어드바이스와는 달리 4개의 인자를 선언했으므로 호출한 메서드, 메서드 인자, 대상 객체에 접근한다

public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {

    public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
        // Do something with all arguments
    }
}

마지막 예제에서 RemoteExceptionServletException를 모두 처리하는 한 클래스에서 이 두 메서드를 어떻게 사용할 수 있는지 설명한다. 하나의 클래스에 다수의 throws advice 메서드를 합칠 수 있다.

public static class CombinedThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with remote exception
    }

    public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
        // Do something with all arguments
    }
}

NOTE

throws advice 메서드가 예외를 던진다면 원래의 예외를 오버라이드할 것이다.(예: 사용자에게 던지는 예외를 바꾼다.) 보통 오버라이딩된 예외는 RuntimeException가 될 것이고 이는 어떤 메서드 시그니처와도 호환성을 가진다. 하지만 throws advice 메서드가 체크드 예외를 던진다면 대상 메서드에서 선언된 예외와 일치해야 하므로 특정 대상 메서드 시그니처와 결합이 생길 것이다. 대상 메서드 시그니처와 호환되지 않고 선언되지 않은 체크드 예외를 던지지 마라!

TIP

Throws advice는 임의의 pointcut과 함께 사용될 수 있습니다.

After Returning advice

Spring에서 after returning advice는 다음 목록과 같은 org.springframework.aop.AfterReturningAdvice인터페이스 를 구현해야한다 .

public interface AfterReturningAdvice extends Advice {

    void afterReturning(Object returnValue, Method m, Object[] args, Object target)
            throws Throwable;
}

after returning advice는 리턴 값 (수정할 수없는), 호출 된 메소드, 메소드의 인수 및 대상에 대한 액세스 권한을가집니다.

다음 after returning advice는 예외를 던지지 않고 성공적으로 이뤄진 모든 메서드 호출의 횟수를 센다.

public class CountingAfterReturningAdvice implements AfterReturningAdvice {

    private int count;

    public void afterReturning(Object returnValue, Method m, Object[] args, Object target)
            throws Throwable {
        ++count;
    }

    public int getCount() {
        return count;
    }
}

이 어드바이스는 실행경로를 바꾸지 않는다. 이 어드바이스가 예외를 던진다면 값을 반환하는 대신 인터셉터 체인에 예외를 던질 것이다.

TIP

After returning advice는 어떤 포인트컷과도 사용할 수 있다.

인트로덕션(introduction) 어드바이스

스프링은 introduction advice를 특별한 종류의 인터셉트 어드바이스로 다룬다.

Introduction은 IntroductionAdvisor와 다음 인터페이스를 구현하는 IntroductionInterceptor를 필요로 한다.

public interface IntroductionInterceptor extends MethodInterceptor {

    boolean implementsInterface(Class intf);
}

AOP Alliance MethodInterceptor 인터페이스에서 상속받은 invoke()메서드는 introduction을 반드시 구현해야 한다. 이 말은 호출된 메서드가 인트로덕션이 적용된 인터페이스에 있을 때 인트로덕션 인터셉터가 메서드 호출을 다루게 된다는 의미이다. 이는 proceed()를 호출할 수 없다.

인트로덕션 어드바이스는 어떤 포인트컷과도 사용할 없고 메서드가 아니라 클래스 수준에만 적용된다. 인트로덕션 어드바이스는 다음 메서드를 가지는 IntroductionAdvisor만 함께 사용할 수 있다.

public interface IntroductionAdvisor extends Advisor, IntroductionInfo {

    ClassFilter getClassFilter();

    void validateInterfaces() throws IllegalArgumentException;
}

public interface IntroductionInfo {

    Class[] getInterfaces();
}

MethodMatcher가 없으므로 인트로덕션 어드바이스와 관련된 Pointcut도 없다. 클래스 필터링만 적합하다.

getInterfaces() 메서드는 이 어드바이저가 인트로덕션한 인터페이스를 반환한다.

구성한 IntroductionInterceptor가 구현할 수 있는 인트로덕션이 된 인터페이스인지 아닌지를 보기 위해 내부적으로 validateInterfaces() 메서드를 사용한다.

스프링 테스트 슈트의 간단한 예시를 보자. 다음 인터페이스를 하나 이상의 객체에 인트로덕션하려고 한다고 가정해보자.

public interface Lockable {
    void lock();
    void unlock();
    boolean locked();
}

이는 mixin을 설명한다. 어드바이즈가 적용된 객체를 Lockable(류의 타입)로 캐스팅해서 lock, unlock 메서드를 호출할 수 있기를 원한다. lock()메서드를 호출하면 모든 setter 메서드가 LockedException를 던지기를 원한다. 그래서 객체에 대해서 전혀 모르더라도 객체를 불변상태로 만들 수 있는 관점을 추가할 수 있다. 이는 AOP의 좋은 예시이다.

우선, 무거운 작업을 하는 IntroductionInterceptor가 필요할 것이다. 이 경우 간편한 org.springframework.aop.support.DelegatingIntroductionInterceptor 클래스를 확장한다. IntroductionInterceptor를 직접 구현할 수도 있지만, 대부분의 경우 DelegatingIntroductionInterceptor를 사용하는 것이 최상의 선택이다.

DelegatingIntroductionInterceptor는 인트로덕션이 된 인터페이스의 실제 구현체에 인트로덕션을 위임하도록 설계되어서 인터셉션의 사용은 감추면서 생성자 인자로 어떤 객체에도 위임을 설정할 수 있다. 이것이 기본적인 위임(인자가 없는 생성자를 사용하는 경우)이다. 그래서 아래 예제에서 위임은 DelegatingIntroductionInterceptor의 하위클래스인 LockMixin이다. 해당 위임(기본 위임)인 DelegatingIntroductionInterceptor 인스턴스는 위임(IntroductionInterceptor)으로 구현된 모든 인터페이스를 찾고 이 인터페이스에 인트로덕션을 지원할 것이다. LockMixin같은 하위클래스가 노출되면 안되는 인터페이스를 감추도록 suppressInterface(Class intf) 메서드를 호출하게 하는 것이 가능하다. 하지만 IntroductionInterceptor가 얼마나 많은 인터페이스를 지원할 준비가 되었는 지와는 관계없이 사용한 IntroductionAdvisor가 실제로 노출되는 인터페이스를 제어할 것이다. 인트로덕션이 된 인터페이스는 같은 인터페이스의 모든 구현체를 대상한테서 감출 것이다.

LockMixinDelegatingIntroductionInterceptor를 서브클래스화 하고 Lockable를 구현한다. 슈퍼클래스는 인트로덕션으로 지원할 수 있는 Lockable을 자동으로 선택하므로 지정할 필요가 없다. 이 방법으로 다수의 인터페이스를 인트로듀스할 수 있다.

locked인스턴스 변수 의 사용에 유의하십시오 . 이것은 효과적으로 대상 객체에있는 상태에 추가 상태를 추가합니다.

다음 예제에서는 예제 LockMixin클래스 를 보여줍니다 .

public class LockMixin extends DelegatingIntroductionInterceptor implements Lockable {

    private boolean locked;

    public void lock() {
        this.locked = true;
    }

    public void unlock() {
        this.locked = false;
    }

    public boolean locked() {
        return this.locked;
    }

    public Object invoke(MethodInvocation invocation) throws Throwable {
        if (locked() && invocation.getMethod().getName().indexOf("set") == 0) {
            throw new LockedException();
        }
        return super.invoke(invocation);
    }

}

때로는 invoke() 메서드를 오버라이드 할 필요가 없다. DelegatingIntroductionInterceptor 구현체(메서드가 인트로듀스되었다면 delegate (위임) 메서드를 호출하고 인트로유스되지 않았다면 조인포인트로 진행한다.)로 보통은 충분하다. 이 경우에 locked 모드라면 호출할 수 있는 setter 메서드가 없다는 것을 추가로 확인해야 한다.

필요한 인트로덕션 어드바이저는 간단한데 별도의 LockMixin 인스턴스를 보관하고 인트로듀스된 인터페이스(여기서는 Lockable이다)를 지정하는 것이 해야하는 전부이다. 더 복잡한 예시에서는 인트로덕션 인터셉터(prototype으로 정의될 것이다.)를 참조해야 할 수도 있다. 여기서는 LockMixin과 관련된 설정이 없으므로 new로 생성한다.

다음 예제는 우리 LockMixinAdvisor클래스 를 보여줍니다 :

public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

    public LockMixinAdvisor() {
        super(new LockMixin(), Lockable.class);
    }
}

이 어드바이저를 아주 간단히 적용할 수 있는데 필요한 설정이 없다.(하지만 이는 필수적인데 IntroductionAdvisor없이 IntroductionInterceptor는 사용할 수 없다.) 일반적으로 인트로덕션을 사용할 때처럼 어드바이저는 상태를 가져야 하므로 인스터스마다 존재해야 한다. 다른 LockMixinAdvisor 인스턴스가 필요하므로 어드바이즈된 객체마다 LockMixin가 필요하다. 이 어드바이저가 어드바이즈된 객체의 상태를 구성한다.

다른 어드바이저처럼 Advised.addAdvisor()메서드나 XML 설정(권장하는 방법)을 사용해서 이 어드바이저를 프로그래밍으로 적용할 수 있다. 이후에 나오는 모든 프락시 생성방법("auto proxy creators"를 포함해서)은 인트로덕션과 상태를 가진 믹스인을 제대로 다룬다.

Last updated