가자공부하러!

Proxy 본문

공부/Java

Proxy

오피스엑소더스 2019. 11. 29. 10:29

참고 : https://refactoring.guru/design-patterns/proxy

 

1. Proxy?

1.1. 프록시란?

  - 다른 객체를 대신할 수 있게 해주는 구조적 디자인 패턴

  - 원본객체로의 접근을 제어 : 원본객체로의 요청전 또는 요청 후에 어떤 작업을 할 수 있게 해준다.

  - 프록시는 서비스 객체인 것 처럼 위장해서 지연로딩과 결과캐싱 등을 다룰 수 있다.

1.2. 프록시의 구조

  - ServiceInterface : 프록시는 이 인터페이스를 따라서 자신을 서비스 객체로 위장

  - Service : 유용한 비즈니스 로직을 제공하는 클래스

  - Proxy 

    > Proxy 클래스에는 서비스 객체(Service)를 가리키는 참조 필드가 존재

    > 프록시가 처리를 마치면(지연 초기화, 로깅, 액세스 제어, 캐싱 등) 요청을 서비스 객체로 전달

    > 일반적으로 프록시는 서비스 개체의 전체 수명주기를 관리

  - Client

    > 서비스 객체(Service)가 필요한 모든 코드에 프록시를 전달할 수 있음

      - 조건 : 클라이언트는 동일한 인터페이스를 통해 서비스와 프록시 모두에서 작동해야 한다.

 

프록시의 구조
오른쪽 누워있는 S가 원본객체, 왼쪽에서 작업들에 둘러싸인 S가 프록시인 듯 보인다.

2. 프록시 응용

2.1. 지연 초기화(Lazy Initialization)

  - 무거운 서비스 객체가 있는 경우, 앱이 실행될 때 객체를 생성하는 대신 객체가 실제 필요할 때 초기화를 수행

2.2. 액세스 제어(Protection Proxy)

  - 특정 클라이언트만 서비스 객체를 사용할 수 있게 제어

  - 프록시는 클라이언트의 자격증명이 미리 정해둔 기준과 일치하는 경우에만 요청을 서비스 객체에 전달

2.3. 원격 서비스(Remote Proxy)

  - 서비스 객체가 원격 서버에 위치하는 경우

  - 프록시는 네트워크를 통해 클라이언트 요청을 전달하여 네트워크 작업에 대한 모든 세부 정보를 처리

2.4. 로깅 요청(Logging Proxy)

  - 서비스 객체로의 요청에 대한 이력을 남기고 싶은 경우

  - 프록시는 각각의 요청을 서비스 객체에 전달하기 전에 로그를 남긴다.

2.5. 캐싱 요청 결과(Caching Proxy)

  - 규모가 큰 클라이언트의 요청 결과를 캐싱하고 캐시의 수명주기를 관리해야 하는 경우

  - 항상 동일한 결과를 갖는 반복 요청에 대해 캐싱을 구현할 수 있다.

  - 프록시는 리퀘스트의 매개변수를 캐시의 키로 사용할 수 있다.

2.6. Smart Reference

  - 어떤 사용자도 사용하지 않고 있는 무거운 객체를 없애고 싶을 때

  - 프록시는 클라이언트 목록이 비어있다면, 서비스 오브젝트를 닫고 시스템 자원을 회수할 수 있음

    > 프록시는 서비스 객체나 결과에 대한 참조를 얻은 사용자를 추적할 수 있음

    > 프록시는 클라이언트들이 활성상태인지 조사할 수 있음

  - 클라이언트가 서비스 객체를 수정했는지에 대한 여부를 추적할 수 있음

    > 변경되지 않은 서비스 객체의 경우 다른 클라이언트에서 재사용 가능

 

3. 구현 방법

3.1. 서비스 인터페이스 준비

  - 기존 서비스 인터페이스가없는 경우 프록시와 서비스 개체를 상호 교환 할 수있는 인터페이스를 생성

  - 서비스 클래스에서 인터페이스를 추출하는 것이 불가능한 경우

    > 프록시를 서비스 클래스의 하위 클래스로 설정(상속)

3.2. 프록시 클래스를 작성

  - 서비스에 대한 참조를 저장하기위한 필드 필요

  - 일반적으로 프록시는 서버의 전체 수명주기를 만들고 관리

    > 클라이언트가 생성자를 통해 서비스를 프록시로 전달하는 경우도 드물게 있음

3.3. 목적에 따른 프록시 방법 구현

  - 대부분의 경우, 작업을 수행 한 후 프록시는 작업을 서비스 객체에 위임해야 함

3.4. 생성기법 안내에 대한 고려

  - 클라이언트가 프록시의 서비스를 받는지 실제 서비스를 받는지 여부

  - 프록시 클래스의 간단한 정적 메소드이거나 완전한 팩토리 메소드일 수 있음

3.5. 서비스 객체에 대한 지연 초기화 구현

3.6. 예제

  - 원본 : https://refactoring.guru/design-patterns/proxy/java/example

  - 직접 수행해본 코드 : https://github.com/HyeongJunMin/SpringBootOnmacOS/tree/master/proxydemo

package com.exam.proxydemo.service;

import com.exam.proxydemo.domain.Video;
import java.util.HashMap;

/**서비스 인터페이스 */
public interface ThirdPartyYoutubeLib {
    HashMap<String, Video> popularVideos();

    Video getVideo(String videoId);
}
package com.exam.proxydemo.service;

import com.exam.proxydemo.domain.Video;

import java.util.HashMap;

/**서비스 클래스(원본 객체) */
public class ThirdPartyYoutubeClass implements ThirdPartyYoutubeLib {

    @Override
    public HashMap<String, Video> popularVideos() {
        connectToServer("http://www.youtube.com");
        return getRandomVideos();
    }

    @Override
    public Video getVideo(String videoId) {
        connectToServer("http://www.youtube.com/" + videoId);
        return getSomeVideo(videoId);
    }

    // -----------------------------------------------------------------------
    // Fake methods to simulate network activity. They as slow as a real life.

    private int random(int min, int max) {
        return min + (int) (Math.random() * ((max - min) + 1));
    }

    private void experienceNetworkLatency() {
        int randomLatency = random(5, 10);
        for (int i = 0; i < randomLatency; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }

    private void connectToServer(String server) {
        System.out.print("Connecting to " + server + "... ");
        experienceNetworkLatency();
        System.out.print("Connected!" + "\n");
    }

    private HashMap<String, Video> getRandomVideos() {
        System.out.print("Downloading populars... ");

        experienceNetworkLatency();
        HashMap<String, Video> hmap = new HashMap<String, Video>();
        hmap.put("catzzzzzzzzz", new Video("sadgahasgdas", "Catzzzz.avi"));
        hmap.put("mkafksangasj", new Video("mkafksangasj", "Dog play with ball.mp4"));
        hmap.put("dancesvideoo", new Video("asdfas3ffasd", "Dancing video.mpq"));
        hmap.put("dlsdk5jfslaf", new Video("dlsdk5jfslaf", "Barcelona vs RealM.mov"));
        hmap.put("3sdfgsd1j333", new Video("3sdfgsd1j333", "Programing lesson#1.avi"));

        System.out.print("Done!" + "\n");
        return hmap;
    }

    private Video getSomeVideo(String videoId) {
        System.out.print("Downloading video... ");

        experienceNetworkLatency();
        Video video = new Video(videoId, "Some video title");

        System.out.print("Done!" + "\n");
        return video;
    }

}
package com.exam.proxydemo.proxy;

import com.exam.proxydemo.domain.Video;
import com.exam.proxydemo.service.ThirdPartyYoutubeClass;
import com.exam.proxydemo.service.ThirdPartyYoutubeLib;

import java.util.HashMap;

/**캐싱 프록시 */
public class YoutubeCacheProxy implements ThirdPartyYoutubeLib {
    private ThirdPartyYoutubeLib youtubeService;
    private HashMap<String, Video> cachePopular = new HashMap<String, Video>();
    private HashMap<String, Video> cacheAll = new HashMap<String, Video>();

    /**프록시 객체를 생성할 때 서비스 인터페이스 타입으로 서비스 객체 생성 */
    public YoutubeCacheProxy() {
        this.youtubeService = new ThirdPartyYoutubeClass();
    }

    @Override
    public HashMap<String, Video> popularVideos() {
        if (cachePopular.isEmpty()) {
            cachePopular = youtubeService.popularVideos();
        } else {
            System.out.println("Retrieved list from cache.");
        }
        return cachePopular;
    }

    @Override
    public Video getVideo(String videoId) {
        Video video = cacheAll.get(videoId);
        if (video == null) {
            video = youtubeService.getVideo(videoId);
            cacheAll.put(videoId, video);
        } else {
            System.out.println("Retrieved video '" + videoId + "' from cache.");
        }
        return video;
    }

    public void reset() {
        cachePopular.clear();
        cacheAll.clear();
    }
}

 

수행 결과(왼쪽은 캐싱 전, 오른쪽은 캐싱 후)

Comments