프록시는 접근을 제어하고 관리한다.
다양한 변형 프록시가 존재한다.
프록시 패턴의 정의
특정 객체로의 접근을 제어하는 대리인을 제공한다.
- 원격 프록시를 써서 원격 객체로 접근 제어
- 가상 프록시로 생성하기 힘든 자원으로 접근 제어
- 보호 프록시로 접근 권한이 필요한 자원으로 접근 제어
자바는 java.lang.reflect 패키지를 제공한다. 이 패키지 기능을 사용하여 프록시 기능을 구현할 수 있다.
이렇게 만들어진 프록시는 런타임 중 생성되서, 동적 프록시(dynamic proxy)라 한다.
리플랙션 패키지를 사용하면 자바에서 Proxy 클래스를 생성해 주므로 필요한 정보만 전달해주면 된다.
InvocationHandler 가 핵심이다.
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
public class ProtectProxyTest {
static interface Person{
String getName();
String getGender();
String getInterests();
int getGeekRating();
void setName(String name);
void setGender(String gender);
void setInterests(String interests);
void setGeekRating(int rating);
}
static class PersonImpl implements Person{
String name;
String gender;
String interests;
int rating;
int ratingCount = 0;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
@Override
public int getGeekRating() {
if(ratingCount == 0 ) return 0;
return (rating/ratingCount);
}
public void setGender(String gender) {
this.gender = gender;
}
public String getInterests() {
return interests;
}
public void setInterests(String interests) {
this.interests = interests;
}
@Override
public void setGeekRating(int rating) {
this.rating = rating;
ratingCount++;
}
}
static class OwnerInvocationHandler implements InvocationHandler{
Person person;
public OwnerInvocationHandler(Person person) {
this.person = person;
}
@Override
//proxy객체참조, method 객체가 호출한 메서드 정보, args 메서드 인자정보
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if(method.getName().startsWith("get")) {
return method.invoke(person, args);
}else if(method.getName().startsWith("setGeekRating")) {
//나한테 평가는 불가
throw new IllegalAccessException();
}else if(method.getName().startsWith("set")) {
return method.invoke(person, args);
}
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
static class NonOwnerInvocationHandler implements InvocationHandler{
Person person;
public NonOwnerInvocationHandler(Person person) {
this.person = person;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if(method.getName().startsWith("get")) {
return method.invoke(person, args);
}else if(method.getName().startsWith("setGeekRating")) {
return method.invoke(person, args);
}else if(method.getName().startsWith("set")) {
//내것 아니니까 수정 불가
throw new IllegalAccessException();
}
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
static Person getOwnerProxy(Person person) {
return (Person)Proxy.newProxyInstance(//정적 메서드
person.getClass().getClassLoader(),//클래스 로더
person.getClass().getInterfaces(),//프록시에서 구현할 모든 인터페이스
new OwnerInvocationHandler(person));//InvocationHandler 구현한 클래스
}
static Person getNonOwnerProxy(Person person) {
return (Person)Proxy.newProxyInstance(
person.getClass().getClassLoader(),
person.getClass().getInterfaces(),
new NonOwnerInvocationHandler(person));
}
static Person getPersonFromDatabase(String name) {
return (Person)datingDB.get(name);
}
static void initializeDatabase() {
Person joe = new PersonImpl();
joe.setName("김자바");
joe.setInterests("자동차, 컴퓨터, 음악");
joe.setGeekRating(7);
datingDB.put(joe.getName(), joe);
Person kelly = new PersonImpl();
kelly.setName("박자바");
kelly.setInterests("웹쇼핑, 영화, 음악");
kelly.setGeekRating(6);
datingDB.put(kelly.getName(), kelly);
}
static HashMap<String, Person> datingDB = new HashMap<String, Person>();
public static void main(String[] args) {
initializeDatabase();
drive();
}
static void drive() {
Person joe = getPersonFromDatabase("김자바");
Person ownerProxy = getOwnerProxy(joe);//프록시 생성
System.out.println("이름은 " + ownerProxy.getName());
ownerProxy.setInterests("볼링, 바둑");
System.out.println("본인 프록시에 관심 사항을 등록합니다.");
try {
ownerProxy.setGeekRating(10);
} catch (Exception e) {
System.out.println("본인 프록시에 괴짜 지수를 매길 수 없습니다.");
}
System.out.println("괴짜 지수 " + ownerProxy.getGeekRating());
Person nonOwnerProxy = getNonOwnerProxy(joe);// 프록시 생성
System.out.println("이름은 " + nonOwnerProxy.getName());
try {
nonOwnerProxy.setInterests("볼링, 바둑");
} catch (Exception e) {
System.out.println("타인 프록시에는 관심 사항을 등록할 수 없습니다.");
}
nonOwnerProxy.setGeekRating(3);
System.out.println("타인 프록시에 괴짜 지수를 매깁니다.");
System.out.println("괴짜 지수 " + nonOwnerProxy.getGeekRating());
}
}
이름은 김자바
본인 프록시에 관심 사항을 등록합니다.
본인 프록시에 괴짜 지수를 매길 수 없습니다.
괴짜 지수 7
이름은 김자바
타인 프록시에는 관심 사항을 등록할 수 없습니다.
타인 프록시에 괴짜 지수를 매깁니다.
괴짜 지수 1
핵심 정리
프록시 패턴을 사용하면 객체에 대리인을 내세워서 클라이언트 접근을 제어할 수 있다.
원격 프록시는 클라이언트와 원격 객체 사이 데이터 전달을 관리
가상 프록시는 생성비용이 큰 객체 접근을 제어(지연로딩)
보호 프록시는 권한을 확인하여 객체 접근을 제어
이외 도 다양한 프록시 변형이 존재한다. 캐시 서버도 프록시 일종이다.
자바에는 동적 프록시 기능이 내장되어 있다.
'IT책, 강의 > 헤드 퍼스트 디자인 패턴' 카테고리의 다른 글
CHAPTER 10 객체의 상태 바꾸기 상태 패턴 (0) | 2023.06.01 |
---|---|
CHAPTER 09 컬렉션 잘 관리하기 반복자 패턴과 컴포지트 패턴 (0) | 2023.05.30 |
CHAPTER 08 알고리즘 캡슐화하기 템플릿 메소드 패턴 (0) | 2023.05.28 |
CHAPTER 07 적응시키기 어댑터 패턴과 퍼사드 패턴 (0) | 2023.05.26 |
CHAPTER 06 호출 캡슐화하기 커맨드 패턴 (0) | 2023.05.24 |