시작하기 전 ...
프록시 패턴은 목적에 따라 여러가지 패턴의 양상을 띈다. 이번 포스팅에서는 가상 프록시에 대해 알아보도록 하고,
전반적인 프록시 패턴의 개념은 아래 포스팅을 참고하면 된다.
https://dev-rosiepoise.tistory.com/80
Goal
- 가상 프록시에 대해 알아보고 이해한다
- 가상 프록시 샘플 코드를 통해 가상 프록시를 이해한다
프록시 패턴 (Proxy Pattern)
특정 객체로의 접근을 제어하는 대리인(특정 객체를 대변하는 객체)를 제공한다.
가상 프록시 (Virtual Proxy)
가상 프록시를 사용하는 이유
- 실제 사용하려면 비용이 많이 드는 객체를 대신할 경량 객체를 만들어 정말 필요한 경우에만 고비용 객체를 만들 수 있기 때문
- 실제 필요전까지 지연할 수 있기 때문에 불필요하게 메모리를 낭비하지 않을 수 있음
가상 프록시를 활용한 앨컴 커버 뷰어 만들기
가상 프록시는 생성하는데 많은 비용이 드는 객체를 대신 생성한다. 진짜 객체가 필요한 상황이 오기 전까지 객체의 생성을 미루는 기능을 제공한다. 객체 생성 전이나 객체 생성 도중에 객체를 대신하기도 한다. 객체 생성이 끝나면 그냥 RealSubject에 직접 요청을 전달한다.
앨범 가상 프록시 설계
상황 : 앨범 타이틀 메뉴를 만든 다음 아마존 같은 온라인 서비스에서 이미지를 가져오기
문제 : 스윙을 사용하여 아이콘 객체를 통해 가져와 네트워크 이미지를 불러오게 되면, 네트워크의 상태와 인터넷 연결 속도에 따라 앨범 이미지를 가져오는데 시간이 걸림
요구사항 : 이미지를 불러오는 동안, 화면에 뭔가 다른 걸 보여주기. 이미지를 기다리는 동안 어플리케이션 작동에 문제 없어야함
해결방안 : 가상프록시를 이용하여 아이콘 대신 백그라운드에서 이미지를 불러오는 작업을 처리하고, 이미지를 완전히 가져오기 전까지 "앨범커버를 불러오는 중입니다. 잠시만 기다려주세요"와 같은 메세지 불러준다. 이미지 로딩이 끝나면 프록시는 아이콘 객체에게 모든 작업을 넘긴다.
원격 프록시 vs. 가상 프록시
원격 프록시 | 가상 프록시 | |
프록시 용도 | 네트워크로 연결되어 있는 다른 객체를 대변하는 용도 | 생성하는데 많은 비용이 드는 객체(아이콘 데이터를 네트워크로 가져와야 하기에 시간이 많이 듬)를 숨기는 용도 |
ImageProxy 작동 방법
- ImageProxy는 ImageIcon을 생성하고 네트워크 URL로부터 이미지를 불러온다
- 이미지를 가져오는 동안 "앨범커버를 불러오는 중입니다. 잠시만 기다려 주세요"라는 메세지를 화면에 띄운다
- 이미지 로딩이 끝나면 paintIcon(), getWidth(), getHeight()를 비롯한 모든 메소드르 호출을 이미지 아이콘 객체에 넘긴다
- 새로운 이미지 요청이 들어오면 프록시를 새로 만들고 위의 과정을 처음부터 다시 반복한다
앨컴 커버 뷰어 코드
ImageProxy
public class ImageProxy implements Icon {
volatile ImageIcon imageIcon;
final 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;
}
}
synchronized void setImageIcon(ImageIcon imageIcon) {
this.imageIcon = imageIcon;
}
public void paintIcon(final Component c, Graphics g, int x, int y) {
if (imageIcon != null) {
imageIcon.paintIcon(c, g, x, y);
} else {
g.drawString("앨범커버를 불러오는 중입니다. 잠시만 기다려 주세요", x+300, y+190);
if (!retrieving) {
retrieving = true;
retrievalThread = new Thread(new Runnable() {
public void run() {
try {
setImageIcon(new ImageIcon(imageURL, "CD Cover"));
c.repaint();
} catch (Exception e) {
e.printStackTrace();
}
}
});
retrievalThread.start();
}
}
}
}
- public class ImageProxy implements Icon {
- imageProxy는 Icon인터페이스를 구현한다
- volatile ImageIcon imageIcon;
- imageIcon은 이미지 로딩이 끝났을 때 실제 이미지를 화면에 표시하는 진짜 아이콘 객체이다
- public ImageProxy(URL url) { imageURL = url; }
- 이미지의 URL을 생성자에 전달한다. 로딩이 끝나면 이 URL에 있는 이미지를 화면에 표시한다
- return 800; return 600;
- imageIcon 로딩이 끝나기 전까지는 기본 너비와 높이를 리턴한다
- synchronized void setImageIcon(ImageIcon imageIcon) {
- imageIcon은 2개의 서로 다른 스레드에서 사용한다. 따라서 (읽기를 보호하는)변수를 voatile로 선언하면서 (쓰기를 보호하는) 동기화된 세터 메소드를 사용한다.
- public void run() {
try {
setImageIcon(new ImageIcon(imageURL, "CD Cover"));
c.repaint();
} catch (Exception e) {
e.printStackTrace();
}
}- 가장 중요한 부분으로, 이 코드에서는 (imageIcon에 메소드 호출을 위임해서) 아이콘을 화면에 표시해준다.
- 진짜 아이콘 이미지를 로딩하는 부분. IconImage를 사용해서 이미지를 로딩하는 과정은 동기화되어 진행된다. 이미지 로딩이 끝나기 전까지 ImageIcon 생성자에서 아무것도 리턴하지 않는다. 따라서 화면을 갱신할 수 없고 아무 메시지도 출력할 수 없으므로 비동기 방식으로 작업을 처리해야한다.
- if (!retrieving) { retrieving = true;
- 이미지를 가져오고 있는 중이 아니라면, 이미지 로딩 작업을 시작한다. repaint() 메소드는 하나의 스레드에서만 호출하므로 스레들 안정성은 확보되었다
- retrievalThread = new Thread(new Runnable() {
- 사용자 인터페이스가 죽지 않도록 별도의 스레드에서 이미지를 가져온다
- setImageIcon(new ImageIcon(imageURL, "CD Cover"));
- 이 스레드 내에서 Icon 객체의 인스턴스를 생성했다. 이미지가 완전히 로딩되어야 생성자가 객체를 리턴한다
- c.repaint();
- 이미지가 확보되면 repaint() 메소드를 호출하여 화면을 갱신한다.
- public void paintIcon(final Component c, Graphics g, int x, int y) {
- 아이콘을 화면에 표시할 때 호출할 메소드
- imageIcon.paintIcon(c, g, x, y);
- 아이콘이 이미 준비되어 있으면 그 아이콘 객체의 메소드를 호출한다.
- g.drawString("앨범커버를 불러오는 중입니다. 잠시만 기다려 주세요", x+300, y+190);
- 그렇지 않으면 불러오고 있다는 메세지 표시
ImageComponent
public class ImageComponent extends JComponent {
private static final long serialVersionUID = 1L;
private Icon icon;
public ImageComponent(Icon icon) {
this.icon = icon;
}
public void setIcon(Icon icon) {
this.icon = icon;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
int w = icon.getIconWidth();
int h = icon.getIconHeight();
int x = (800 - w)/2;
int y = (600 - h)/2;
icon.paintIcon(this, g, x, y);
}
}
ImageProxyTestDrive
public class ImageProxyTestDrive {
ImageComponent imageComponent;
JFrame frame = new JFrame("CD Cover Viewer");
JMenuBar menuBar;
JMenu menu;
Hashtable<String, String> cds = new Hashtable<String, String>();
public static void main (String[] args) throws Exception {
ImageProxyTestDrive testDrive = new ImageProxyTestDrive();
}
public ImageProxyTestDrive() throws Exception{
cds.put("Buddha Bar","http://images.amazon.com/images/P/B00009XBYK.01.LZZZZZZZ.jpg");
cds.put("Ima","http://images.amazon.com/images/P/B000005IRM.01.LZZZZZZZ.jpg");
cds.put("Karma","http://images.amazon.com/images/P/B000005DCB.01.LZZZZZZZ.gif");
cds.put("MCMXC A.D.","http://images.amazon.com/images/P/B000002URV.01.LZZZZZZZ.jpg");
cds.put("Northern Exposure","http://images.amazon.com/images/P/B000003SFN.01.LZZZZZZZ.jpg");
cds.put("Selected Ambient Works, Vol. 2","http://images.amazon.com/images/P/B000002MNZ.01.LZZZZZZZ.jpg");
URL initialURL = new URL((String)cds.get("Selected Ambient Works, Vol. 2"));
menuBar = new JMenuBar();
menu = new JMenu("Favorite CDs");
menuBar.add(menu);
frame.setJMenuBar(menuBar);
for (Enumeration<String> e = cds.keys(); e.hasMoreElements();) {
String name = (String)e.nextElement();
JMenuItem menuItem = new JMenuItem(name);
menu.add(menuItem);
menuItem.addActionListener(event -> {
imageComponent.setIcon(new ImageProxy(getCDUrl(event.getActionCommand())));
frame.repaint();
});
}
// set up frame and menus
Icon icon = new ImageProxy(initialURL);
imageComponent = new ImageComponent(icon);
frame.getContentPane().add(imageComponent);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800,600);
frame.setVisible(true);
}
URL getCDUrl(String name) {
try {
return new URL((String)cds.get(name));
} catch (MalformedURLException e) {
e.printStackTrace();
return null;
}
}
}
정리
가상 프록시는 생성하는데 많은 비용이 드는 객체를 대신 생성한다. 진짜 객체가 필요한 상황이 오기 전까지 객체의 생성을 미루는 기능을 제공한다. 객체 생성 전이나 객체 생성 도중에 객체를 대신하기도 한다. 객체 생성이 끝나면 그냥 RealSubject에 직접 요청을 전달한다.
참고
헤드퍼스트 디자인 패턴
'JAVA > 디자인 패턴' 카테고리의 다른 글
[Decorator Pattern] 데코레이터 패턴 (0) | 2023.07.04 |
---|---|
[Proxy Pattern] 프록시 패턴 - 보호 프록시 (0) | 2023.07.04 |
[Proxy Pattern] 프록시 패턴 - 원격 프록시 (0) | 2023.07.03 |
[Proxy Pattern] 프록시 패턴이란? (0) | 2023.07.03 |
[Adapter Pattern] 어댑터 패턴이란? (0) | 2023.06.28 |