일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 회사폐업
- 사회초년생
- IT기초
- 생애첫계약
- 모여봐요동물의숲
- 프로그래머스
- 자취준비
- 실업인정인터넷신청
- 정보처리기사개정
- leetcode
- 막대기자르기
- 청년내일채움공제
- 후니의쉽게쓴시스코라우팅
- 네트워크
- C++
- 코딩테스트
- 순열
- 실업급여
- HeadFirstDesignPatterns
- 튜터링
- 동적계획법
- 정보
- array
- 부분합알고리즘
- 후니의쉽게쓴시스코네트워킹
- 자료구조
- 캡쳐링
- 취업사실신고
- 전화영어
- 알고리즘
- Today
- Total
따봉도치야 고마워
Head First Design Patterns : (11)프록시 패턴 본문
프록시 패턴
-
어떤 객체에 대한 접근을 제어하기 위한 용도로 대리인이나 대변인에 해당하는 객체를 제공하는 패턴
-
원격 객체 / 생성하기 힘든 객체 / 보안이 필요한 객체에 대한 대변자 객체 생성
-
프록시 패턴의 수많은 변종 - "접근을 제어하는 방법"에서 차이가 있음
-
원격 프록시를 사용해 원격 객체에 대한 접근 제어
-
가상 프록시를 사용해 생성하기 힘든 자원에 대한 접근 제어
-
보호 프록시를 사용해 접근 권한이 필요한 자원에 대한 접근 제어
-
프록시
-
원격 객체에 대한 로컬 대변자
-
원격 객체 : 다른 JVM에 있는 객체 ex) 뽑기기계
-
로컬 대변자 : 로컬 대변자의 메소드를 호출하면 원격 객체의 메소드를 호출하는 것처럼 행동
=
1. 클라이언트는 원격 서비스에 있는 메소드를 호출한다고 생각하고 작업 처리
2. 실제로는 클라이언트 보조 객체(프록시)가 서버에 연락을 취하고, 메소드에 대한 정보를 전달 및 서버 응답을 기다림.
3. 서버 쪽에선 서비스 보조 객체가 있어 요청을 받고 해석해 실제 서비스 객체에 있는 메소드 호출, 리턴 값 전송
자바 RMI
-
클라이언트 서비스 보조 객체를 만들어줌(stub) + 서비스 보조객체(skeleton)
-
직접 네트워킹 및 입출력 관련 코드를 작성하지 않아도 됨
-
클라이언트에서 원격 객체에 접근할 수 있는 룩업(lookup)서비스 제공
-
클라이언트 입장에선 로컬 메소드 호출과 똑같은 방식으로 사용하면 되지만, 실제론 네트워킹 및 입출력 기능이 활용되고 있으므로 예외처리 중요
원격 서비스 만들기
-
원격 인터페이스 생성
- 클라이언트에서 원격으로 호출할 수 있는 메소드를 정의
- 해당 인터페이스를 서비스의 클래스 형식으로 사용 -> 추후 스터브와 서비스가 모두 구현
-
서비스 구현 클래스 생성
-
실제 작업을 하는 클래스
-
1번의 인터페이스를 구현 ex) GumballMachine
-
-
rmic를 이용해 스터브와 스켈레톤 만들기
-
rmic 툴을 사용해(JDK에 포함) 두 개의 클래스 생성 - stub, skeleton
-
ex) rmic GumballMachine
-
-
RMI 레지스트리 실행
-
rmiregistry 명령어 실행
-
클라이언트는 해당 레지스트리로부터 프록시를 받아갈 수 있음
-
-
원격 서비스 시작
-
서비스를 구현한 클래스의 인스턴스를 만들고 RMI 레지스트리에 등록
-
등록 후엔 클라이언트에서 사용 가능
-
EX1) 원격 인터페이스 생성
- java.rmi.Remote 를 상속하는(확장) 원격 인터페이스 정의
- 해당 인터페이스에서 원격 호출을 지원할 것임을 알려주는 역할
- 모든 메소드를 RemoteException을 던지는 메소드로 선언
- 네트워킹, 입출력 작업 중 생길 수 있는 예외 처리를 위함
- 인자와 리턴값은 반드시 원시형식(primitive) 또는 Serializable 형식으로 선언
- 메소드의 인자들은 네트워크를 통해 전달되어 직렬화를 통해 포장될 것
- 즉, 원시 형식이 아닌 직접 만든 형식을 사용한다면 클래스를 만들 때 Serialize 인터페이스 구현
2. 서비스 구현 클래스 생성
- 원격 인터페이스를 구현, UnicastRemoteObject 상속
- 원격 서비스 객체 역할을 하려면 필요한 기능을 추가해야함
- 가장 간단한 방법이 java.rmi.server 패키지에 있는 UnicastRemoteObejct 상속
- RemoteException을 선언하는 인자없는 생성자 만들기
- 수퍼 클래스인 UnicastRemoteObject 생성자에서 RemoteException를 던지기 때문에 똑같은 생성자 필요
- 서비스를 RMI 레지스트리에 등록
- java.rmi.Naming 에 있는 rebind() 메소드를 통해 등록
- RMI 레지스트리를 통해 서비스를 검색할 때 여기서 등록한 이름으로 사용
try{
MyRemote service = new MyRemoteImpl();
Naming.rebind("RemoteHello", service);
}catch(Exception e){}
3. rmic를 돌려 스터브와 스켈레톤 생성
-
원래 클래스 이름에 _Skel, _Stub가 붙어 생성됨
4. rmiregistry 실행 - 터미널을 새로 띄워 클래스에 접근할 수 있는 디렉토리에서 실행 (classes)
5. 서비스 가동
6. 클라이언트에서 스터브 객체 찾기 (lookup)
-
lookup 메소드를 통해 레지스트리에서 스터브 객체 리턴 받음
-
스터브에 대해 메소드 호출
MyRemote service = (MyRemote)Naming.lookup("rmi://127.0.0.1/RemoteHello");
//항상 서비스 형식을 원격 인터페이스 형식으로 (MyRemote)
//서비스가 돌아가고 있는 IP주소/호스트 주소 + 등록시 지정한 이름
+
클라이언트에서 스터브 클래스를 가져오는 법
1. 간단한 시스템은 그냥 복사해도됨
2. 동적 다운로딩(dynamic class downloading)
: 직렬화된 객체에 클래스 파일 위치 URL이 내장됨.
역직렬화 과정에서 로컬 시스템에서 해당 클래스 파일을 찾지 못하면 URL로 GET 요청을 보내 가져옴
EX2) 뽑기 기계 원격 조정
- 해당 예제는 스테이트 패턴과 이어지는 코드입니다 (bb-dochi.tistory.com/83)
1. GumballMachine용 원격 인터페이스 정의
import java.rmi.*;
//원격 인터페이스
public interface GumballMachineRemote extends Remote{
public int getCount() throws RemoteException;
public String getLocation() throws RemoteException;
public State getState() throws RemoteException;
}
2. 인터페이스의 모든 형식이 직렬화 되는지 확인
-
State 형식은 직렬화가 안되므로 수정이 필요함
-
Serializable 상속해 해결
-
-
또한 State 클래스에 GumballMachine에 대한 레퍼런스가 들어있는데, 이를 포함해 직렬화하는 것은 바람직하지 않음
-
각 State에 있는 GumballMachine 인스턴스 변수에 transient 키워드 추가
-
이렇게 하면 해당 필드는 직렬화되지 않음 + 하지만 직렬화해 받은 뒤 호출하게 되면 문제가 발생할 수 있음
-
//Serializable 인터페이스를 확장하면 직렬화 가능
import java.io.*;
public interface State extends Serializable{
.
.
}
public class NoQuarterState implements State{
//transient 키워드로 해당 필드는 직렬화 되지 않도록 함
transient GumballMachine gumballMachine;
.
.
}
3. 구상 클래스에서 인터페이스를 구현
-
GumballMachine이 서비스 역할과 네트워크 요청을 처리할 수 있도록 수정
import java.rmi.*;
import java.rmi.server.*;
public class GumballMachine extends UnicastRemoteObject implements GumballMachineRemote{
.
.
public GumballMachine(String location, int numberGumballs) throws RemoteException{
//수퍼클래스에서 RemoteException 예외를 던질 수 있기 때문에
//해당 생성자에서도 던질 수 있도록 수정
}
.
.
4. RMI 레지스트리 등록
5. 클라이언트 코드 수정
-
GumballMachine -> GumballMachineRemote
-
뽑기 기계의 인스턴스를 만들 땐 항상 try/catch문으로 감싸야함
가상프록시
-
생성하는데 많은 비용이 드는 객체를 대변, 실제로 진짜 객체가 필요하기 전까지 객체 생성을 미루는 기능 제공
-
객체 생성 전, 또는 생성 도중에 객체를 대신하기도 함
-
객체 생성이 완료 되면 그냥 RealSubject에 요청을 직접 전달
+ 원격프록시 : 다른 JVM에 있는 객체의 대변인, 프록시 메소드 호출 시 네트워크를 통해 원격 객체의 메소드 호출
EX) CD 커버 뷰어
: CD 커버를 보여주는 뷰어를 만들 때, 네트워크 상태와 연결 속도에 따라 이미지를 가져오는데 시간이 걸릴 수 있으므로 기다리는 동안 화면에 다른 것을 보여주고, 전체 어플리케이션이 멈추지 않도록 하는 가상 프록시를 만들어보자
- ImageProxy에선 우선 ImageIcon을 생성하고, 네트워크 URL로부터 이미지 불러옴
- 이미지 로딩 도중 간단한 메시지 화면에 표시
- 로딩이 완료되면 모든 메소드 호출을 ImageIcon 객체에 넘김
- 사용자가 새로운 이미지를 요청하면 프록시를 새로 만들고 위 과정을 진행
class ImageProxy implements Icon {
ImageIcon imageIcon;
URL imageURL;
Thread retrievalThread;
boolean retrieving = false;
public ImageProxy(URL url) { imageURL = url; }
//이미지 로딩 전까지 기본 너비, 높이를 리
public int getIconWidth() {
if (imageIcon != null) {
return imageIcon.getIconWidth();
} else {
return 800;
}
}
public int getIconHeight() {
if (imageIcon != null) {
return imageIcon.getIconHeight();
} else {
return 600;
}
}
//이미지 아이콘 객체가 없다면 이미지를 가져오고
//간단한 메시지를 출력해줌
public void paintIcon(final Component c, Graphics g, int x, int y) {
if (imageIcon != null) {
imageIcon.paintIcon(c, g, x, y);
} else {
g.drawString("Loading CD cover, please wait...", x+300, y+190);
if (!retrieving) {
retrieving = true;
retrievalThread = new Thread(new Runnable() {
public void run() {
try {
imageIcon = new ImageIcon(imageURL, "CD Cover");
//이미지가 완전히 로딩되어야 생성자에서 객체 리턴해줌
c.repaint();
} catch (Exception e) {
e.printStackTrace();
}
}
});
retrievalThread.start();
}
}
}
}
//ImageProxyTestDrive.java
Icon icon = new ImageProxy(initialURL);
Q. 원격 프록시와 가상 프록시는 완전히 다른 것 같은데?
- 클라이언트에서 실제 객체의 메소드를 호출하면, 그 호출을 중간에 가로채 접근 제어한다는 점에서 같음 -> 프록시 패턴
Q. ImageProxy 는 데코레이터로 볼 수 있지 않나?
- 비슷해 보일 수 있지만 용도의 차이
- 데코레이터는 클래스에 새로운 행동을 추가하기 위한 용도지만, 프록시는 접근 제어 용도
- ImageProxy가 클라이언트와 ImageIcon을 분리시키면서 이미지 로딩 전에도 다른 작업들이 가능하게끔 해줌
Q. 어떤 식으로 클라이언트에서 진짜 객체가 아닌 프록시를 사용하도록 만드는지
- 객체의 인스턴스를 생성해서 리턴하는 팩토리를 사용
- 팩토리 메소드 내에서 진행되기 때문에 실제 객체를 프록시로 감싼 다음 리턴 할 수 있음
Q. ImageProxy는 이미지를 받아올 때마다 ImageIcon 객체를 새로 생성하던데 전에 가져왔던 이미지에 대해 캐싱은?
- 가상 프록시의 변종인 캐싱 프록시를 사용해 캐시에 저장해뒀다 상황에 따라 리턴할 수 있음
Q. 프록시와 어댑터 패턴의 차이
- 둘다 클라와 다른 객체 사이에서 전달해주는 역할을 하지만, 어댑터는 다른 객체의 인터페이스를 바꿔주고, 프록시는 똑같은 인터페이스 사용
- 보호 프록시는 '상황에 따라 객체에 있는 특정 메소드에 대한 접근을 제어, 프록시에서 클라에게 인터페이스의 일부만 제공'할 수 있다는 점이 좀 더 유사함
자바 API를 이용한 보호프록시
- java.lang.reflect 패키지에 프록시 기능이 내장되어있음
- 이 패키지를 통해 즉석에서 한 개 이상의 인터페이스를 구현하고 메소드 호출을 지정한 클래스로 전달하는 프록시를 생성 가능
- 실제 프록시 클래스는 실행중에 생성되므로 이런 기술을 '동적 프록시(dynamic proxy)' 라고 함
- Proxy 클래스는 자바가 만들어주기 때문에 필요한 코드는 InvocationHandler에 넣어주면 됨
- InvocationHandler는 프록시에 대한 모든 호출에 응답하는 역할
EX) 결혼 정보 업체
: 결혼정보 업체에서 고객들의 정보와 선호도를 포함한 클래스를 생성함. getter/setter에 대한 보호가 없기 때문에 타인의 정보를 수정하거나, 자신의 선호도를 직접 수정하는 등의 문제가 발생함. 보호프록시를 사용해보자!
1. InvocationHandler 생성 - 프록시의 행동을 구현
- 2 가지 (본인을 위한, 타인을 위한) 호출 핸들러를 만들어야함
+ 호출 핸들러 ?
- 프록시의 메소드가 호출되면 프록시에선 그 호출을 핸들러에 넘김.
- 핸들러엔 invoke() 메소드 밖에 없고 어떤 메소드를 호출하든 invoke()만 실행
- 주어진 요청을 어떻게 처리할지 결정한 다음 상황에 따라 RealSubject에 요청 전달
1) 프록시의 setHotOrNotRating() 메소드 호출
proxy.setHotOrNotRating(9);
2) 프록시에선 InvocationHandler의 invoke() 호출
invoke(Object proxy, Method method, Object[] args)
//리플렉션 API getName()을 통해 어떤 메소드를 호출했는지 알 수 있음
3) 핸들러에선 주어진 요청을 어떻게 처리할지 결정 후 RealSubject에 요청 전달
return method.invoke(person, args);
//프록시에 대해 호출되었던 진짜 메소드 호출
//인자 값으로 진짜 객체와, 처음에 받은 인자 전달
//본인 호출에 대한 핸들러
public class OwnerInvocationHandler implements InvocationHandler {
//InvocationHandler 인터페이스 구현
PersonBean person;
public OwnerInvocationHandler(PersonBean person) {
this.person = person;
}
public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException {
try {
//게터 메소드의 경우 주 객체의 메소드를 호출
if (method.getName().startsWith("get")) {
return method.invoke(person, args);
//setHotOrNot() 메소드인 경우 거부
} else if (method.getName().equals("setHotOrNotRating")) {
throw new IllegalAccessException();
} else if (method.getName().startsWith("set")) {
return method.invoke(person, args);
}
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
2. 동적 프록시를 생성하는 코드 작성
//PersonBean 객체를 인자로 받고, 프록시를 리턴함
PersonBean getOwnerProxy(PersonBean person){
return (PersonBean) Proxy.newProxyInstance(
person.getClass().getClassLoader(),
person.getClass().getInterfaces(),
new OwnerInvocationHandler(person));
//newProxyInstance()로 프록시를 생성
//프록시에서 구현해야하는 클래스로더, 인터페이스를 인자로 전달
//호출핸들러도 전달, 핸들러의 인자로 person을 전달
}
3. 클라이언트 테스트
PersonBean joe = getPersonFromDatabase("Joe Javabean");
PersonBean ownerProxy = getOwnerProxy(joe);
//ownerProxy 객체론 자신의 선호도 점수 수정이 불가능
Q. 어떤 클래스가 Proxy 클래스인지 알 수 있는지?
- Proxy 클래스에는 isProxyClass() 라는 정적 메소드가 있음. (이를 제외하면 실제 클래스와 동일)
Q. newProxyInstance()를 호출해 인자로 전달할 수 있는 인터페이스 형식에 제한이 있는지
- 일단 항상 인터페이스의 배열을 인자로 전달해야 함. 클래스는 x
- 또한 public으로 지정되지 않은 인터페이스의 경우 같은 패키지 내 인터페이스만 가능.
- 같은 이름을 가진 인터페이스를 여러개 전달하는 것도 불가능 + 등등
Q. 왜 스켈레톤을 사용해야 하는지?
- 자바 1.2부터는 RMI 런타임에서 클라이언트 호출을 리플렉션을 이용해 직접 원격 서비스로 넘길 수 있게 되었음
- 여기선 스터브와 원격서비스 간 통신 메커니즘 설명을 위해 사용
Q. 자바5부턴 스터브도 필요가 없다는데?
- 자바5부터 RMI와 동적 프록시가 결합해 스터브마저도 동적으로 생성됨
- java.lang.reflect.Proxy 인스턴스로 자동으로 생성 -> rmic 사용 안해도됨
기타 변종 프록시
방화벽 프록시
- 일련의 네트워크 자원에 대한 접근을 제어
- 주 객체를 나쁜 클라이언트로부터 보호
- ex. 기업용 방화벽 시스템
스마트 레퍼런스 프록시(Smart Reference Proxy)
- 주 객체가 참조될 때마다 추가 행동을 제공
- ex. 객체 참조에 대한 선/후 작업
캐싱 프록시(Caching Proxy)
- 비용이 많이 드는 작업의 결과를 임시로 저장함
- 여러 클라에서 결과를 공유하게 해줌으로 계산 시간 또는 네트워크 지연을 줄여주는 효과
- ex. 웹 서버 프록시 or 컨텐츠 관리 및 퍼블리싱 시스템
동기화 프록시(Synchronization Proxy)
- 여러 스레드에서 주 객체에 접근하는 경우에 안전하게 작업을 처리할 수 있게 해줌
- ex. 분산 환경에서 일련의 객체에 대한 동기화 된 접근을 제어해주는 자바 스페이스
복잡도 숨김 프록시(Complexity Hiding Proxy)
- 복잡한 클래스들의 집합에 대한 접근을 제어하고 복잡도를 숨겨줌
- 퍼사드 프록시(Facade Proxy)라고 부르기도 함.
- 이 프록시와 퍼사드 패턴의 차이는 프록시는 접근을 제어하지만, 퍼사드는 대체 인터페이스만 제공한다는 점
지연 복사 프록시(Copy-On-Write Proxy)
- 클라이언트에서 필요로 할 때까지 객체가 복사되는 것을 지연시킴으로 객체의 복사를 제어
- 변형된 가상 프록시
- ex. 자바5의 CopyOnWriteArrayList
'프로그래밍 > 공부' 카테고리의 다른 글
Head First Design Patterns : (12)컴파운드 패턴 (0) | 2020.10.07 |
---|---|
Head First Design Patterns : (10)스테이트 패턴 (0) | 2020.09.30 |
Head First Design Patterns : (9)이터레이터와 컴포지트 패턴 (0) | 2020.09.24 |
Head First Design Patterns : (8)템플릿 메소드 패턴 (0) | 2020.09.23 |
Head First Design Patterns : (7)어댑터 패턴과 퍼사드 패턴 (0) | 2020.09.21 |