본문 바로가기
개발 이론/Spring

프록시??? 스프링 AOP?? 트랜젝션?? (1)

by dal_been 2024. 2. 22.
728x90

이전 개인프로젝트때 동시성 해결하기 위해 aop와 Redisson을 활용하여 해결한 적이 있다.

그때 이때 aop 프록시와 동시성메서드 트랜젝션 프록시?라고 해야하나 아무튼 두개의 트랜 젝션의 레벨이 동일하여 내가 원하는 대로 호출이 안되는 상황이 있었다. 그래서 동시성 메서드 트랜젝션을 제거하였더니 잘 호출되었다.

이때는 동일한 레벨트랜젝션이라 무엇이 먼저 호출될지 모른다로 이해하고 끝났다... 근데 계속 프로젝트 하다보니...

프록시가 뭔데,,? 그게 트랜젝션이란 뭔상관이고, aop랑은 무슨 상관인데..? 라는 물음표들이 머리속을 꽉채웠다...

그래서 이번 기회에 차분히 정리해보려고 한다.

 


프록시 

 

공부하기전 나에게 프록시 은 단순히 가짜객체의 느낌이었다.근데 공부해보니 그 방향보다는 

프록시 이란 "대상 원복 객체를 대리하여 대신 처리하게 함으로써 로직의 흐름을 제어한다" 이다.

 

한마디로 대상 객체한테 직접 접근?? 하는게 아니라 중간에 프록시를 거쳐서 접근하다.

그런데 왜 이런 프록시가 생겼을까?? 굳이 대리인을 통해서 접근해야하는 이유가 뭘까?

간단하게 말하자면 원본 객체를 수정할 수 없는 상황을 극복하기 위해서다.

 

- 보안 : 프록시는 클라이언트가 권한이 있는 지 확인하고 긍정적이면 요청을 대상에게 전달

- 캐싱 : 프록시가 내부 캐시를 유지하여 데이터가 캐시되지 않았을때만 대상에게 작업 실행하도록 전달

- 데이터 유효성 검사

- 지연 초기화

- 로깅 : 프록시는 메서드 호출과 상대 매개변수를 인터셉트하고 이를 기록한다

 

등과 같은 효과를 누릴 수 있다.

 

여기서 프록시 패턴과 데코레이터 패턴이라는게 존재한다. 이 둘다 프록시를 사용하는게 맞지만 의도가 다르다.

프록시 패턴은 접근 제어가 목적이라면 데코레이터 패턴은 새로운 기능 추가가 목적이다.

 

여기서 프록시 패턴과 데코레이턴 패턴은 의도가 다를뿐 구현된 방법은 비슷하기 때문에 프록시 패턴에 대해서 알아볼 것이다.

 

프록시 패턴

 

프록시는 다른 객체에 접근을 제어하는 개체이다. 여기서 다른 객체를 대상 subject라고 부른다.

프록시와 대상은 동일한 인터페이스를 가지고 있으며 이를 통해 다른 인터페이스와 완전히 호환되도록 바꿀 수 있다.

 

프록시 패턴을 사용하는 이유는 앞서얘기했듯이 기존의 특정 객체를 수정할 수 없는 상황일때이다.

그러다보니 자연스레 개방폐쇄원칙(기존대상객체의 코드 변경없이 새로운기능 추가가능),

단일책임원칙(대상객체는 자신의 기능에만 집중하고, 그외 부가적인 것은 프록시 객체에 위임) 과 같은 장점이 있다.

 

다만 프록시 패턴을 가미하고 싶다면 각각 클래스에 해당되는 프록시 클래스를 만들어서 적용해야하기때문에 코드량이 많아진다.

뿐만 아니라 모든 클래스에 로깅과같은 기능을 가미하고 싶다면 각각 클래스에 프록시 클래스를 또 만들어서 적용해야하는 중복성이 생긴다. 물론 자바에서는 리플렉션에서 제공하는 동적 프록시라는 기법을 이용해서 해결할 수 있다(추후 블로그에서 작성예정)

 

 

예시

 

프록시에 대해서 간단한 예제를 만들어보자

캐시 기능을 구현하다고 가정해보자

public interface Subject {
	String operation();
}
public class RealSubject implements Subject{

	@Override
    public String operation(){
    	System.out.println("실제객체");
        return "hello";

    }
}
public class CacheProxy implements Subject {

	private Subject target;
    private String cacheValue;
    
    public CacheProxy(Subject target) {
    	this.target= target;
    }
    
    @Override
    public String operation(){
    	System.out.println("proxy");
        if(cacheValue == null){
        	cacheValue = target.operation();
        }
        return cacheValue;
    }
}

 

이렇게 prozxy가 RealSubject가 implement한 인터페이스를 구현하였다.

 

@Test
void cacheProxyTest() {
    Subject realSubject = new RealSubject();
    Subject cacheProxy = new CacheProxy(realSubject);
    ProxyPatternClient client = new ProxyPatternClient(cacheProxy);
    client.execute();
    client.execute();
    client.execute();
          
}

 

여기서 ProxyPatterClient는 proxy의 메서드를 execute를 통해 실행하다고 이해하면된다.즉 client이다.

 

어찌됬든 결과는

프록시
실제객체
프록시
프록시

 

첫번째 execute를 실행했을떄는 cache값이 없기때문에 실제 객체의 System.out도 실행되지만 이후에는 캐시가 되었기때문에 프록시만 호출된다.

이결과를 통해 말하고자하는 것은 캐시 프록시를 통해 빠르게 반환가능하다는 점이다.

 

 

실무에서의 Proxy패턴

 

DynamicProxy(다음 블로그에 올릴 예정),

스프링 AOP는 동시성 해결할때 사용해봤다고 앞서 시작할때 이야기 했다.

JPA 지연로딩

이외에도 javascript에서도 사용된다고 한다.

 

 


프록시에 대해 조금 알아보았다.  사실 지금까지 이야기한거는 프록시의 개념일뿐 실제로 어떻게 적용되고 사용되는지는 자세하게 안알봤다. 다음블로그에 DynamicProxy와 AOP, JPA 지연로딩 , 트랜젝션 에 대해서 더 알아볼 것이다.