1. ProxyFactoryBean

자바 JDK에서 제공하는 다이나믹 프록시 외에도 편리하게 프록시를 만들 수 있도록 지원해주는 다양한 기능이 있다. 스프링은 프록시 오브젝트를 생성해주는 기술을 추상화한 팩토리 빈을 제공해준다.

ProxyFactoryBean이 생성하는 프록시에서 사용할 부가기능은 MethodInterceptor인터페이스를 구현해서 만든다.

public class DynamicProxyTest {

    @Test
    public void proxyFactoryBean() {
        ProxyFactoryBean pfBean = new ProxyFactoryBean();
        pfBean.setTarget(new HelloTarget()); // 타깃설정
        pfBean.addAdvice(new UppercaseAdvice()); // 부가기능을 담은 어드바이스 추가

        Hello hello = (Hello) pfBean.getObject(); // proxy 가져오기

        assertThat(hello.sayHello("JinYoung"), is("HELLO JINYOUNG"));
    }


    static interface Hello {

        String sayHello(String name);

        String sayHi(String name);

        String sayThankYou(String name);
    }


    static class UppercaseAdvice implements MethodInterceptor {

        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            return invocation.proceed().toString().toUpperCase(); // MethodInvocation은 메소드 정보와 타깃 오브젝트를 알고 있음.
        }
    }


    static class HelloTarget implements Hello {

        @Override
        public String sayHello(String name) {
            return "Hello" + name;
        }


        @Override
        public String sayHi(String name) {
            return "Hi" + name;
        }


        @Override
        public String sayThankYou(String name) {
            return "ThankYou" + name;
        }
    }
}

1.1 어드바이스 : 타깃이 필요 없는 순수한 부가기능

타깃 오브젝트에 종속되지 않는 순수한 부가기능을 담은 오브젝트

MethodInvocation.proceed() 메소드를 실행하면 타깃 오브젝트의 메소드를 내부적으로 실행해주는 기능이 있다. 일종의 공유 가능한 템플릿처럼 동작하는 것이다. 바로 이점이 ProxyFactoryBean의 장점이다.

ProxyFactoryBean은 작은 단위의 템플릿/콜백 구조를 응용해서 적용(타깃정보 없음)했기 때문에 템플릿 역할을 하는 MethodInvocation을 싱글톤으로 두고 공유할 수 있다.

addAdvice() : ProxyFactoryBean 하나만으로 여러개의 부가기능을 추가할 수 있다. 새로운 부가기능을 추가할 때마다 프록시와 프록시 팩토리 빈도 추가해줘야 한다는 문제를 해결할 수 있다.

어드바이스 : MethodInterceptor 처럼 타깃 오브젝트에 적용하는 부가기능을 담은 오브젝트

1.2 포인트컷 : 부가기능 적용 대상 메소드 선정 방법

예) pattern이라는 메소드 이름 비교용 스트링 값을 DI를 받아 트랜잭션 적용 대상을 판별

기존방식 : 다이내믹 프록시와 부가기능을 분리할 수 있고, 부가기능 적용 대상 메소드를 선정할 수 있다.
문제 : 부가기능을 가진 InvocationHandler가 타깃과 메소드 선정 알고리즘 코드에 의존하고 있다는 점

반면에 스프링의 ProxyFactoryBean은 부가기능메소드 선정 알고리즘을 활용하는 유연한 구조를 제공한다.

.
  • 어드바이스 : 부가기능을 제공하는 오브젝트
  • 포인트컷 : 메소드 선정 알고리즘을 담은 오브젝트

포인트컷까지 적용한 ProxyFactoryBean

@Test
    public void pointcutAdvisor() {
        ProxyFactoryBean pfBean = new ProxyFactoryBean();
        pfBean.setTarget(new HelloTarget());

        // 메소드 이름 비교해서 대상을 선정해주는 포인트컷
        NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
        pointcut.setMappedName("sayH*");

        pfBean.addAdvisor(new DefaultPointcutAdvisor(pointcut, new UppercaseAdvice()));

        Hello proxiedHello = (Hello) pfBean.getObject();

        assertThat(proxiedHello.sayHello("JinYoung"), is("HELLO JINYOUNG"));
    }

Q. 포인트컷과 어드바이스를 Advisor타입으로 묶어야 한다. 어드바이스를 등록하듯이 포인트컷을 그냥 추가하면 될 것을 왜 굳이 별개의 오브젝트로 묶어서 등록해야할까?
A. 그 이유는 포인트컷과 어드바이스를 따로 등록하면 어떤 어드바이스에 대해 포인트컷을 적용할지 애매해지기 때문이다.

  • 어드바이저 = 포인트컷 + 어드바이스

2. ProxyFactoryBean 적용

2.1 TransactionAdvice

public class TransactionAdvice implements MethodInterceptor {
    PlatformTransactionManager transactionManager;

    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public Object invoke(MethodInvocation invocation) throws Throwable {
        TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition());
        
        try {
            Object ret = invocation.proceed();
            this.transactionManager.commit(status);
            return ret;
        } catch (RuntimeException e) {
            this.transactionManager.rollback(status);
            throw e;
        }
    }
}

2.2 스프링 XML 설정파일

트랜잭션 어드바이스 빈 설정

<bean id="transactionAdvice" class="springboot.usr.service.TransactionAdvice">
    <property name="transactionManager" ref="transactionManager" />
</bean>

포인트컷 빈 설정

<bean id="transactionPointcut" class="org.springframework.aop.support.NameMatchMethodPointcut">
    <property name="mappedName" ref="upgrade*" />
</bean>

어드바이저 빈 설정

<bean id="transactionAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
    <property name="advice" ref="transactionAdvice" />
    <property name="pointcut" ref="transactionPointcut" />
</bean>

ProxyFactoryBean 등록 설정

<bean id="userService" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target" ref="userServiceImpl" />
    <property name="interceptorNames">
        <list>
            <value>transactionAdvisor</value> // 한개 이상의 '<value>'태그를 넣을 수 있다.
        </list>    
    </property>
</bean>

2.3 어드바이스와 포인트컷의 재사용

ProxyFactoryBean을 이용해서 많은 수의 서비스 빈에게 트랜잭션 부가기능을 적용했을 때의 구조이다.
트랜잭션 부가기능을 담은 TransactionAdvice는 하나만 만들어서 싱글톤 빈으로 등록해두면, DI 설정을 통해 모든 서비스에 적용이 가능하다.
메소드 선정 방식이 달라지는 경우만 포인트컷의 설정을 따로 등록하고 어드바이저로 조합해서 적용해주면 된다.

ProxyFactoryBean, Advice, Pointcut을 적용한 구조.

댓글남기기