[Design Pattern] Proxy 패턴

Loopy·2023년 9월 9일
0
post-thumbnail

원격 프록시

원격 프록시는 실제 객체처럼 행동하지만, 실제로는 네트워크로 진짜 객체와 데이터를 주고 받게 된다. 즉 원격 프록시는 각 사용자의 JVM 내부의 힙 영역에 존재하는 로컬 객체라고도 할 수 있다.

  1. 클라이언트가 로컬에서 원격 프록시를 호출한다.(대변자 역할)
  2. 프록시가 실제 원격 힙에 존재하는 객체를 호출하며, 네트워크 통신과 저수준 작업 또한 해당 과정에서 처리된다.
  3. 원격 힙 객체의 메서드가 호출된다.

클라이언트는 프록시 객체임을 몰라야 하고, 실제 서비스를 제공한다고 생각한다.

원격 호출 과정

Duck d = new Duck();  // 같은 힙 공간에 존재해야만 함

다른 컴퓨터의 힙에 들어있는 객체 레퍼런스를 가져오기 위해서는, 자바의 원격 메서드 호출( RMI:Remote Method Invocation ) 기능을 알아야 한다.

RMI 과정

  1. 클라이언트 힙에 존재하는 보조 객체가 메소드 호출에 관한 정보(이름, 인자)등을 전달한다.
  2. 실제 객체가 존재하는 서버의 힙에서 서비스 보조 객체가 소켓 연결을 통해 보조 객체의 요청을 받고, 호출 정보를 해석해서 실제 객체의 메서드를 호출한다.
  3. 실제 객체로부터 리턴값을 받고, 소켓의 출력 스트림으로 클라이언트 보조 객체에게 전달한다.

네트워킹 및 입출력 관련 코드가 프록시 내부에 존재하니, 클라이언트는 단순히 JVM에 있는 메서드를 호출하듯이 로컬에서도 원격 메서드를 호출할 수 있게 되는 것이다.

인터페이스

실제 객체와 프록시 객체가 공통으로 사용할 원격 인터페이스를 만들고 구현한다.

import java.rmi.*;

public interface MyRemote extends Remote { 
	public String sayHello() throws RemoteException;
}
  • Remote : 해당 인터페이스에서 원격 호출을 지원한다는 것을 알려주는 마커용 인터페이스
  • 네트워크 장애에 대비한 예외 처리가 필요하다.
  • 원격 메서드의 인자와 리턴값은 원시 형식 혹은 네트워크로 전달될 때 직렬화가 가능한 Serializable 형식이여야 한다.

스텁 객체(프록시)

import java.rmi.*;

public class MyRemoteImpl extends UnicastRemoteObject implemtents MyRemote {
	private static final long serialVersionUID = 1L;
    
    public MyRemoteImpl() throws RemoteException {}
    
	public String sayHello() {
    	return "hi";
    }
    
    public static void main(String[] args) {
    	try {
        	MyRemote service = new MyRemoteImpl();
            Naming.rebind("RemoteHello", service);  // Rmi 레지스트리에 결합
        }
    }
}
  • 원격 객체 기능을 사용하기 위해 상속받은 UnicastRemoteObjectSerializable 을 구현하고 있으며, 예외를 던지기 때문에 모두 잡아서 처리해줘야 한다.

  • rebind() : 서버의 RMI 레지스트리에 등록한다. 이후에 클라이언트 쪽에서 요청이 오면 해당 레지스트리에 등록된 프록시 객체를 반환한다.

클라이언트

콘솔에서 프로그램 시작 전에, rmiregistry 를 먼저 실행시켜야 한다.

% rmiregistry
try {
    MyRemote service = (MyRemote) Naming.lookup("rmi://127.0.0.1/RemoteHello");
    String s = service.sayHello();  // 프록시 객체 호출
} catch (Exception e) {}
  • lookup() : 스텁 객체(프록시 객체)를 요청하는 메서드, 인자로 서비스가 돌아가고 있는 시스템의 호스트 이름 혹은 IP 주소를 전달한다.

프록시 패턴

특정 객체로의 접근을 제어하는 대리인을 제공한다. 프록시 객체와 실제 객체 모두 같은 인터페이스를 구현하여, 클라이언트에서 어느 객체를 호출하고 있는지 모르도록 할 수 있다.

프록시 패턴의 변형으로는 다음의 세가지가 존재하며, 모두 형태는 같지만 그 쓰임이 다르다.

  1. 원격 프록시 패턴 : 다른 JVM에 들어있는 객체의 대리인에 해당하는 로컬 객체이다.
  2. 가상 프록시 패턴 : 생성하는데 많은 비용이 드는 객체를 대신한다.

가상 프록시

앞단에서 프록시 객체를 통해 생성 비용이 비싼 객체의 생성을 실제 요청이 오기 전까지 뒤로 미루는 기능을 제공한다.

혹은, 생성 비용이 오래 걸리는데 클라이언트에게 높은 응답 속도를 제공해줘야 할 때 프록시 객체에서 실제 객체의 작업이 완료되기 전까지 다른 반환값을 보여줄 수도 있다.

JPA에서도 프록시가 있다고?

JPA 역시 가상 프록시 방식으로 동작한다. 이를 통해, 연관되어 있는 실제 객체의 메서드가 호출되기 전까지(필드를 불러오는 메서드) ID만 담은 프록시 객체가 동작한다.

메서드가 호출되면, 프록시 객체가 DB 호출을 통해 실제 객체에 값을 담고 이후로는 실제 객체로 메서드 호출을 위임하게 되는 것이다. 마찬가지로 이를 통해 불필요한 쿼리가 나가는 성능 저하를 막고, 비용을 아낄 수 있다.

동적 프록시 패턴

컴파일 시점이 아닌 런타임 시점에 동적으로 프록시 클래스를 만들어주는 패턴으로, 자바에서 공식적으로 지원하는 기능이다.

public class CustomInvocationHandler implements InvocationHandler {
	Person person;
    
	public Object invoke(Object Proxy, Method method, Object[] args) {
    	method.invoke(args);
    }
}
profile
개인용으로 공부하는 공간입니다. 잘못된 부분은 피드백 부탁드립니다!

0개의 댓글