Skip to Content
Suffering builds character

3. 프록시(Proxy) 패턴

프록시 패턴은 디자인 패턴 중의 하나로, 구조와 관련된 패턴 중 하나

Proxy라는 이름이 대리자라는 의미를 가진다고 할 때,

프록시 패턴에서의 Proxy는 클라이언트가 타겟 객체에 접근하는 방식을 제어하는 역할을 수행할 수 있음

1-1. 프록시 패턴이 적용된 예시 1 - Lazy Loading

JPA Hibernate에서 제공하는 엔티티 지연 로딩

지연 로딩이란 개념은 다양한 맥락에서 사용되는데, JPA에서 EntityManager를 통해 엔티티를 조회할 경우에는 기본적으로 @ManyToOne 등과 같이 일련의 연관 관계로 맵핑된 엔티티들까지 모두 조회되는 옵션으로 동작함

Student.java
@Entity class Student { // ... @ManyToOne(fetch = FetchType.EAGER) // 기본값 private Major major; }
Main.java
Student student = manager.find(Student.class, 1); // major 조회 Major major = student.getMajor(); // ㅇㅇ학과

위처럼 연관 관계가 Student와 Major와 같이 두 테이블만으로 연결된 관계가 아닌 2개 이상의 수많은 테이블들이 연결되어 있을 경우 더 많은 조인이 발생하여 엔티티 조회에 소요되는 시간이 더 길어지게됨

하지만 Student 엔티티를 조회하는 시점에 따라 Major에 대한 정보가 필요한 곳이 존재할 수도 있고, 필요하지 않은 곳이 존재할 수도 있기 때문에,

매번 조회할 때마다 연관된 모든 엔티티들을 불러오지 않고, 실제로 해당 엔티티가 사용되는 시점에 불러오도록 로딩 과정을 지연시킬 수 있음(Lazy Loading)

Student.java
@Entity class Student { // ... @ManyToOne(fetch = FetchType.LAZY) // 지연 로딩 방식으로 변경 private Major major; }

이처럼 Proxy 패턴은 타겟 객체가 코드가 동작하는 맥락에 따라 당장 필요하지 않은 경우를 대비하여 실제로 필요한 시점까지 해당 객체를 미리 생성하지 않도록 함으로서 성능적인 측면에서 이점을 얻을 수 있음

1-2. 프록시 패턴이 적용된 예시 2 - UnmodifiableCollection

java.util.Collections의 내장 클래스인 UnmodifiableCollection

프록시 패턴을 활용할 수 있는 또 다른 사례는 특별한 상황에서 타겟에 대한 접근 권한을 제어하기 위해 활용할 수도 있음

java.util 패키지 내 Collections 클래스에서는 수정할 수 없는 형태(불변)의 내장 클래스인 UnmodifiableCollection 타입을 제공함

이 클래스는 메서드 사용의 접근 권한을 제어하는 역할을 담당하는 Proxy 객체로 동작하며,

이 클래스를 이용할 경우, 단순한 조회 등은 메서드 호출이 가능하지만 값을 변경하는 메서드 호출 시 UnsupportedOperationException 예외가 발생하게 됨

정리하면 UnmodifiableCollection은 값에 대한 추가, 변경, 제거 등의 처리는 예외를 던짐으로서 값을 변경할 수 없도록 강제, 제한하고 조회만 가능하도록 접근 권한을 제어하는 역할을 수행하면서 Collections 클래스 내에서 Proxy 역할을 담당하고 있음

Collections.java
public class Collections { // ... public static <T> Collection<T> unmodifiableCollection(Collection<? extends T> c) { return new UnmodifiableCollection<>(c); } }

Collections.java 내 내장 클래스인 UnmodifiableCollection

UnmodifiableCollection.java
static class UnmodifiableCollection<E> implements Collection<E>, Serializable { private static final long serialVersionUID = 1820017752578914078L; final Collection<? extends E> c; UnmodifiableCollection(Collection<? extends E> c) { if (c==null) throw new NullPointerException(); this.c = c; } // 컬렉션 내 단순한 값 조회 등의 로직은 정상적으로 동작 public int size() {return c.size();} public boolean isEmpty() {return c.isEmpty();} public boolean contains(Object o) {return c.contains(o);} public Object[] toArray() {return c.toArray();} public <T> T[] toArray(T[] a) {return c.toArray(a);} public <T> T[] toArray(IntFunction<T[]> f) {return c.toArray(f);} public String toString() {return c.toString();} public Iterator<E> iterator() { return new Iterator<E>() { private final Iterator<? extends E> i = c.iterator(); public boolean hasNext() {return i.hasNext();} public E next() {return i.next();} public void remove() { throw new UnsupportedOperationException(); } @Override public void forEachRemaining(Consumer<? super E> action) { // Use backing collection version i.forEachRemaining(action); } }; } // 그 외에 컬렉션 내 요소의를 변경하는 메서드 호출 시 예외를 던지도록 처리 public boolean add(E e) { throw new UnsupportedOperationException(); } public boolean remove(Object o) { throw new UnsupportedOperationException(); }
Last updated on