본문 바로가기

디자인 패턴

프록시 패턴(Proxy Pattern)

프록시 패턴

다른 객체를 대변하는 객체를 만들어서 주 객체에 대한 접근을 제어할 수 있다.


Subject

  • Proxy를 구현하기 위해 Interface로 설계되어 있으며 RealSubject와 Proxy는 이를 구현한다.

RealSubject

  • 실제 서비스를 하는 주 객체이다.

Proxy

  • Client가 Subject에게 접근하면 Proxy에게 접근이 된다.
  • Proxy는 접근을 제어할 수도 있고 추가 설정을 한 후 RealSubject에게 위임하여 RealSubject의 실제 서비스를 실행시킬 수 있다.

예제

Subject

public interface Subject {
    void print();
}
  • Subject는 주 객체와 프록시가 사용할 기능을 정의한다.

RealSubject​

public class RealSubject implements Subject {
    @Override
    public void print() {
        System.out.println("안녕하세요!");
    }
}
  • RealSubject는 실제 서비스들을 하는 주 객체가 된다.
  • 실제 필요한 로직을 작성한다.

Proxy

public class Proxy implements Subject {

    Subject realSubject;

    @Override
    public void print() {
        realSubject = new RealSubject();
        System.out.println("프록시가 제어 합니다.");
        realSubject.print();
    }
}
  • Proxy에서는 필드에 realSubject를 가지고 있다.
  • print() 메서드가 호출되면 프록시는 상황에 맞게 제어 혹은 기능을 추가할 수 있다.
  • 그 후 원하는 지점에 realSubject의 print()를 호출하여 실제 서비스를 수행한다.
public class Main {
    public static void main(String[] args) {
        Subject proxy = new Proxy();
        proxy.print();
    }
}

  • 클라이언트에서는 프록시로 접근하므로 접근이 제한된 것을 알 수 있다.

보호 프록시

  • 프록시를 조금 더 활용하면 보호 프록시를 만들 수 있다.
  • 자바에서 제공해주는 프록시를 이용하면 더욱 간단하게 만들 수 있다.

  • 위의 다이어그램에서 InvocationHandler가 추가된 것을 알 수 있다.
  • InvocationHandler는 자바에서 제공해주는 API를 사용하면 간단하게 구현이 가능하다.

예제

  • Person이라는 객체가 있고 그 안에는 name이 들어가 있다.
  • User와 NoUser가 존재한다고 할 때 User에게는 getName, setName의 권한을 준다.
  • NoUser에게는 getName의 권한을 준다.

Subject

public interface Person {

    String getName();

    void setNmae(String name);
}
  • Person은 Subject이며 하위 클래스에서 구현할 기능인 getName, setName을 정의하였다.


RealSubject

public class PersonImpl implements Person {
    String name;

    public PersonImpl(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void setNmae(String name) {
        this.name = name;
    }
}
  • Person을 구현한 RealSubject인 PersonImpl이다.

InvocationHandler

public class UserHandler implements InvocationHandler {

    Person person;

    public UserHandler(Person person) {
        this.person = person;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        if (method.getName().startsWith("get")) {
            return method.invoke(person, args);
        } else if (method.getName().startsWith("set")) {
            return method.invoke(person, args);
        }

        return null;
    }
}
  • InvocationHandler를 구현한 UserHandler이다.
  • Person을 필드에 멤버 변수로 가지고 있다.
  • User는 getName, setName이 모두 허용되므로 그에 따른 요청이 들어올 때 person에게 invoke 해준다.

 

public class NoUserHandler implements InvocationHandler {
    Person person;

    public NoUserHandler(Person person) {
        this.person = person;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        if (method.getName().startsWith("get")) {
            return method.invoke(person, args);
        } else if (method.getName().startsWith("set")) {
            System.out.println("접근권한이 없습니다.");
        }

        return null;
    }
}
  • InvocationHandler를 구현한 NoUserHandler이다.
  • UserHandler와 동일하게 Person을 가지고 있다.
  • NoUser는 getName만 가능하므로 setNmae이 호출되면 권한이 없음을 알려준다.

프록시는 자바에서 제공하는 프록시를 사용할 것이므로 따로 설계하지 않는다.

Test

public class Main {


    public static void main(String[] args) {
        Person person = new PersonImpl("홍길동");

        // 1
        Person userProxy = getUserProxy(person);
        System.out.println("====== UserProxy ======");
        System.out.println(userProxy.getName());
        userProxy.setNmae("김삿갓");
        System.out.println(userProxy.getName());

        // 2
        Person noUserProxy = getNoUserProxy(person);
        System.out.println("====== NoUserProxy ======");
        System.out.println(noUserProxy.getName());
        noUserProxy.setNmae("홍길동");
        System.out.println(noUserProxy.getName());
    }

    private static Person getUserProxy(Person person) {
        return (Person) Proxy.newProxyInstance(
                person.getClass().getClassLoader(),
                person.getClass().getInterfaces(),
                new UserHandler(person));
    }

    private static Person getNoUserProxy(Person person) {
        return (Person) Proxy.newProxyInstance(
                person.getClass().getClassLoader(),
                person.getClass().getInterfaces(),
                new NoUserHandler(person));
    }
}
  • 우선 PersonImpl을 통해 홍길동이라는 Person을 생성한다.
  • User, NoUser Proxy를 자바에서 제공하는 Proxy를 통해 생성하였다.
  • Proxy.newProxyInstance의 두 번째 전달 인자로 해당 클래스의 인터페이스를 넘긴다.
  • 그 이유는 자바에서는 인터페이스를 구현한 객체만 프록시를 만들 수 있다.
  • 그리고 세 번째 전달 인자를 보면 위에서 생성한 Handler를 넘기는 것을 알 수 있다.
  • 이를 통해 프록시는 행동을 handler에게 넘기고 handler는 권한이 있다면 realSubject에게 invoke 한다.
  • 1에서는 userProxy를 통해 person 이름 get 하여 출력하고 다른 이름으로 set 하였다.
  • 2에서는 noUserProxy를 통해 person 이름 get 하여 출력하고 다른 이름으로 set 하였다.

  • 결과를 확인해보면 정상적으로 프록시가 접근을 제어하는 것을 알 수 있다.

참고 도서