이전에 InvocationHandler 를 사용한 프록시 패턴과 팩토리 빈을 포스트 한 적이 있습니다.
저 위의 내용을 모르신다면 읽으셔야 이 포스트 내용이 이해가 가능합니다.
Spring에서는 이 두 개를 단점을 보완하고 장점을 살려 두 개를 동시에 지원해 줄 ProxyFactoryBean이라는 클래스가 있습니다.
먼저 InvocationHandler를 대체할 MethodInterceptor를 설명할까요.
InvocationHandler는 MethodInterceptor와 비슷하지만 한가지 다른 점이 있습니다.
InvocationHandler는 아래와 같이
invoke 메소드가 타겟 함수에 대한 정보를 가지고 있습니다.
추상화를 기반으로 코드 재활용도를 어떻게 높일까 매번 고민하는 객체 지향 언어에서 이러한 의존 코드는 매우 성가신 장애물이죠.
때문에 ProxyFactoryBean에서는 MethodInterceptor를 사용합니다.
MethodInterceptor는 InvocationHandler와 비슷하지만 InvocationHandler와 다르게 타겟에 대한 정보를 가지지 않습니다.
구현할 당시에는 MethodInterceptor는 타겟에 대한 정보를 받지 않지만
프록시가 MethodInterceptor를 주입받을 때 메소드 정보와 함께 타겟 오브젝트가 담긴 MethodInvocation 오브젝트를 전달해줍니다.
이 오브젝트는 타겟을 호출하는 기능을 지닌 콜백 오브젝트입니다.
때문에 MethodInterceptor는 타겟에 대한 정보를 가지고 있지 않지만, 외부에서 전달해주는 정보를 이용하여 작업을 하게됩니다.
객체 지향에 적합한 구조를 가지고 있는 것이죠.
예제 코드를 한번 봐 볼까요.
구성은 이러합니다.
Hello와 HelloTarget 은 Proxy 패턴 포스팅에서 사용했던 것 그대로 전혀 바뀌지 않았습니다.
저번 보다 더 간소화 되었죠.
public class ClientTest {
public static void main(String[] args){
ProxyFactoryBean pfBean = new ProxyFactoryBean();
pfBean.setTarget(new HelloTarget());
pfBean.addAdvice(new Advice());
Hello proxiedHello = (Hello) pfBean.getObject();
System.out.println(proxiedHello.sayHello("HHHHH"));
System.out.println(proxiedHello.sayHi("HH"));
System.out.println(proxiedHello.others("||"));
System.out.println(proxiedHello.others2("&&"));
}
// you might wanna identify each method in here. Like the code below. but it is not ideal
static class Advice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
if(invocation.getMethod().getName().equals("sayHi")){
String plus2 = (String)invocation.proceed();
return plus2+" 2";
}
if(invocation.getMethod().getName().startsWith("sayHell")){
String plus5 = (String)invocation.proceed();
return plus5+" 5";
}
String others = (String)invocation.proceed();
return others + " other methods";
}
}
두개는 이전 포스트와 똑같아서 접어놨습니다.
코드를 볼까요. 우선 위쪽은 테스트 코드고
밑쪽은 MethodInterceptor구현 코드입니다.
먼저 ProxyFactoryBean은 Spring에서 지원하는 프록시를 생성해서 빈 오브젝트로 등록하게 해주는 팩토리 빈입니다.
팩토리 빈이 뭔지 모르시다면, 팩토리 빈의 이전 포스트를 보셨다면 이해하기 쉬우실 겁니다.
이 ProxyFactoryBean은 순하게 프록시를 생성하고 리턴해주는 작업만 합니다.
사용법도 타겟 오브젝트를 등록, 핸들러 역할을 하는 Advice를 등록하는 정도에서 해결되는 간편한 팩토리 빈입니다.
그럼 Advice 클래스를 한번 볼까요. 먼저
String plus2 = (String)invocation.proceed();
코드를 보시면 Target 메소드가 포함되어있지 않은 것을 확인할 수 있습니다.
위에서 설명한 대로 타겟 객체를 가지고 있지 않습니다.
결과는 어떨까요?
프록시 패턴에서 테스트했던 예제 코드 그대로 잘 나옵니다.
원하는 대로 잘 나오는군요.
하지만 이대로 괜찮을까요?
위에서도 설명했지만 타겟객체를 이 클래스에서 제외하려는 이유는 객체지향의 조건에 맞추기 위함이었습니다.
의존하는 구체적인 정보를 외부에 맡기고 외부가 전해주는 정보만으로 자신의 로직에 충실하기 위해서죠.
근데 이 클래스에서 정작 메소드를 구별하는 방법은 특정 계층에 의존적입니다.
invocation.getMethod().getName().equals("sayHi")
sayHi 라는 메소드명을 알고 있다는 점이 Target을 외부에 맡긴 이점을 다 망쳐놓고 있습니다.
이러한 문제를 해결하기 위해 Spring에서는 NameMatchMethodPointcut 이라는 클래스를 제공합니다.
이 객체에 적용할 메소드의 이름을 정의해서 ProxyFactoryBean의 오브젝트에 등록할 수 있습니다.
예제코드를 한번 볼까요
@Test
public void pointcut() {
ProxyFactoryBean pfBean = new ProxyFactoryBean();
pfBean.setTarget(new HelloTarget());
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.setMappedName("sayH*");
pfBean.addAdvisor(new DefaultPointcutAdvisor(pointcut, new Advice()));
Hello proxiedHello = (Hello) pfBean.getObject();
System.out.println(proxiedHello.sayHello("HHHHH"));
System.out.println(proxiedHello.sayHi("HH"));
System.out.println(proxiedHello.others("||"));
System.out.println(proxiedHello.others2("&&"));
}
마찬가지로 프록시 팩토리 빈을 생성하고 타겟을 설정해줍니다.
NameMatchMethodPointcut 객체를 만들어 그 안에 메소드 정보를 저장합니다.
그리고 그 객체를 ProxyFactoryBean 객체에 다시 저장하죠.
그리고 받은 오브젝트를 한번 테스트 해보겠습니다.
결과는 어떻게 나와야 할까요?
setMappedName으로 인해 sayH 로 시작하는 메소드만이 허용 되었습니다.
이 말인즉 MethodInterceptor 인터페이스를 구현한 클래스(Advice)에서 아무리 지지고 볶아도
결국 출력이 허용되는 메소드는 sayH 로 시작하는 메소드라는 것이죠.
결과는
예상했던 대로 나왔습니다.
정리하자면 위의 Advice static 클래스는 잘못된 코드입니다. 저런 방식으로 사용하시면 안됩니다.
프록시 오브젝트를 사용할 클래스가 단 하나라면 괜찮은 방법이 될 수는 있지만
객체 지향 언어로 프로그래밍을 하신다면 단 하나를 사용하더라도, 몸에 익숙해지게끔 객체지향으로 하는 게 좋을 거 같다는 것이 개인적 의견입니다.
메소드의 구분의 목표는 pointcut에게 맡기고
Advice static 클래스는 부가기능의 목표만 맡는 것이 가장 이상적인 방법입니다.
'프로그래밍 > Web-Spring' 카테고리의 다른 글
Mybatis 다중 매개변수 중 객체(오브젝트) 변수 넘기기 (0) | 2018.02.28 |
---|---|
SpringFramework(스프링) - 팩토리 빈(FactoryBean) (0) | 2018.02.19 |
JDBC 트랜잭션과 트랜잭션 추상화 (Transaction, Abstraction of Transaction) (0) | 2018.02.10 |