본문 바로가기

java

[Java] Proxy란?

Proxy와 @Transactional
'구현 코드를 수정하지 않고 부가적인 기능을 어떻게 수행할 것인가?'
'n개의 클래스에 대해 n개의 프록시 클래스를 만들어주어야 할까?'
다음 글 - Dynamic Proxy

 

Proxy란 개념을 공부하게 된 계기는 Spring에서 @Transactional annotation이 어떻게 동작하는가를 알기 위해서였다.

@Transactional annotation이 붙은 코드를 열심히 디버깅하여 따라가보니 다음과 같은 흐름을 볼 수 있었다. 클래스와 메서드에 집중하기 위해 메서드 파라미터나 기타 시그니처 코드들은 생략하였다.

package org.springframework.aop.framework;

class CglibAopProxy implements AopProxy {
    //...
    
    private static class DynamicAdvisedInterceptor implements MethodInterceptor {
        
        @Override
        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            //...
            retVal = new CglibMethodInvocation(...).proceed();
        }
    }


    private static class CglibMethodInvocation extends ReflectiveMethodInvocation {
        
        @Override
        public Object proceed() throws Throwable {
            //...
            return super.proceed();
        }
    }
}
package org.springframework.aop.framework;

public class ReflectiveMethodInvocation implements ProxyMethodInvocation {

    @Override
    public Object proceed() throws Throwable {
        //...
        return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
    }
}
package org.springframework.transaction.interceptor;

public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        //...
        return invokeWithinTransaction(...)
        //...
    }
}

 

public abstract class TransactionAspectSupport {
    
    protected Object invokeWithinTransaction(...) {
        // 1.트랜잭션을 시작한다
        // 2.main logic을 수행한다
        // 3.트랜잭션을 종료한다
    }
}

결론적으로 말하자면 Spring bean으로 관리되는 'Proxy' 객체를 통해서 부가 기능을 구현할 실제 구현 코드에 접근을 시작한다. TransactionInterceptor가 골격 구현 클래스인 TransactionAspectSupport 추상클래스의 invokeWithinTransaction() 메서드를 호출해 실제 트랜잭션 관리를 수행한다.

이를 이해하기 위해서는 Spring AOP를 이해해야 하고, AOP를 이해하기 위해서는 포인트컷, 어드바이저부터 시작해 끝도 없이 많은 개념들을 접해야 한다. 이 글에서는 광범위한 개념들을 하나하나 짚어보는 것이 아니라, 다음의 문제를 해결하기 위해서 사용될 수 있는 Proxy 개념을 살펴보고자 한다.

 

'구현 코드를 수정하지 않고 부가적인 기능을 어떻게 수행할 것인가?'

실제 수행할 target이 되는 구현 코드를 직접 수정하지 않고 부가적인 기능을 수행할 수 있게 하는 Proxy 객체를 이용해 해결한다. Proxy는 일종의 대리인이다. caller가 호출하고자 하는 메서드를 callee가 단독으로 처리하지 않고, 중간 Proxy 객체를 거쳐서 처리되게 한다. 이를 통해 부가적인 기능을 callee의 구현 코드를 수정하지 않고 수행할 수 있다.

송금(transfer) 로직을 담당하는 TransferService 클래스에서 해당 메서드 호출 시 transaction을 시작하고 종료하는 기능을 추가한다고 가정해보자.

(추후에 설명할 JDK dynamic proxy가 interface 기반으로 동작하므로 interface를 구현한 뒤 구현 서비스를 만드는 예제를 선택했다. 구체 클래스의 Proxy를 생성할 수 있는 cglib에 대해서는 우선 다른 블로그의 글을 참고바란다)

public interface TransferService {
    void transfer();
}

public class TransferServiceImpl implements TransferService {

    @Override
    public void transfer() {
        log.info("송금 메인 로직");
    }
}

 

송금 로직 전 후로 트랜잭션을 시작하고 종료하는 기능(로그를 찍는 것으로 대체)을 추가하고자 한다. 어떻게 해야 할까? 다음과 같이 transfer() 메서드 내에 실제 송금 로직 전 후로 트랜잭션을 시작하고 종료하는 코드를 구현할 수 있다.

public class TransferServiceImpl implements TransferService {

    @Override
    public void transfer() {
        log.info("transaction start");
        log.info("송금 메인 로직");
        log.info("transaction end");
    }
}

 

이미 callee의 구현 코드를 건드리고 말았다. 이제 Proxy를 이용해보자. TransferServiceImpl의 송금 메인 로직만 남기고 transaction을 처리하는 로직은 proxy 클래스에 위임한다. Client는 Proxy 클래스를 호출하게 되고, 정상적으로 로그가 출력된다.

public class TransferServiceProxy implements TransferService {

    private final TransferServiceImpl target;

    public TransferServiceProxy(TransferServiceImpl target) {
        this.target = target;
    }

    @Override
    public void transfer() {
        log.info("transaction start");
        target.transfer();
        log.info("transaction end");
    }
}
class TransferServiceProxyTest {

    private final TransferServiceProxy proxy = new TransferServiceProxy(new TransferServiceImpl());

    @Test
    void proxyTest() {
        proxy.transfer();
    }
}

 

'n개의 클래스에 대해 n개의 프록시 클래스를 만들어주어야 할까?'

위와 같이 proxy 클래스를 직접 구현하면 추가 기능을 구현할 n개의 클래스에 대해 n개의 proxy 클래스를 만들어주어야 한다. '트랜잭션을 시작하고 종료한다'는 공통 관심사를 구현하는 코드는 하나인데, 각 클래스마다 생성해주니 중복되는 코드가 매우 많아진다. 이를 해결하기 위해 Dynamic Proxy에 대해서 다음 글에서 알아보자.

 

[다음 글]

[Java] Dynamic Proxy란?

'java' 카테고리의 다른 글

[Java] Reference Type(feat. strong, soft, weak)  (0) 2023.03.27
[Java] Dynamic Proxy란?  (0) 2023.03.21
[Java] JNI(Java Native Interface)란?  (0) 2023.03.06
[Java] Thread Dump  (0) 2023.03.01
[Java] 테스팅 툴 - JMeter  (1) 2023.02.21