본문 바로가기
자바

디자인 패턴 3. 프록시 패턴 (Proxy Pattern), AOP

by 호놀롤루 2022. 3. 12.

1. 개요

Proxy 대리인 이라는 듯으로써, 뭔가를 대신하여 처리하는 

Proxy Class 통해서 대신 전달하는 형태로 설계되며, 실제 Client Proxy로부터 결과를 받는다.

Cache 기능으로도 활용이 가능하다.

SOLID 개방폐쇄 원칙(OCP) 의존역전 법칙(DIP) 따른다.

 

AOP란 Aspect Oriented Programming의 약자로 관점 지향 프로그래밍이라 불린다.

어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누고, 그 관점을 기준으로 각각 모듈화하는 기법이다.

 

AOP는 프록시 패턴 기반의 모듈에 부가기능(접근 제어, 실행시간 측정, 요청 정보 저장, 흩어진 기능 모으기 등)을

추가하기 위해서 사용한다.

 

2. 코드

proxy pattern을 이용해서 캐시의 구동 방식, aop를 확인한다.

 

proxy 폴더

package com.company.design.proxy;

public interface IBrowser {
    Html show();
}

 

 

package com.company.design.proxy;

public class Html {

    private String url;

    public Html(String url) {
        this.url = url;
    }
}

구조만 알아보는 거니 구현은 안했지만 url을 가지는 클래스다.

 

package com.company.design.proxy;

public class Browser implements IBrowser {

    private String url;

    public Browser(String url) {
        this.url = url;
    }

    @Override
    public Html show() {
        System.out.println("browser loading html from : "+url);
        return new Html(url);
    }
}

브라우저는 생성자에서 url을 받고, 자신의 url을 가지게 된다. 그리고 show()메소드로 url과 그 출처를

확인하고, Html을 반환한다.

 

package com.company.design.proxy;

public class BrowserProxy implements IBrowser {

    private String url;
    private Html html;

    public BrowserProxy(String url) {
        this.url = url;
    }

    @Override
    public Html show() {

        if (html == null) {
            this.html = new Html(url);
            System.out.println("BrowserProxy loading html from : "+url);
        }
        System.out.println("BrowserProxy use cache html : "+url); // 캐싱된 html 사용
        return html;
    }
}

프록시의 경우, 생성자에서 url을 받고, 자신의 url을 가지는 부분은 브라우저와 같다. 하지만 자체 Html을 가지고

있고, html이 한번도 사용되지 않았다면, html에 url을 넣은 인스턴스를 집어넣는다.

그리고 처음에는 html을 생성하는 시간이 있지만, 여러번 불릴 경우 처음을 제외하곤 자체로 가지고 있는 html

즉 cache와 같은 기능을 하는 멤버 변수를 가지게 된다.

 

그래서 2번째 부턴 html을 생성하지 않고, 가지고 있는 html에 접근하니 속도가 빨라진다.

 

package com.company.design.proxy;

public class Main {
    public static void main(String[] args) {
//        Browser browser = new Browser("www.naver.com");
//        browser.show();
//        browser.show();
//        browser.show();

        IBrowser browser = new BrowserProxy("www.naver.com");
        browser.show();
        browser.show();
        browser.show();
        browser.show();
        browser.show();
        // 처음에만 로딩, 나머지는 캐시로 처리
    }
}

메인에서 Proxy의 show를 불러보면 처음만 인스턴스를 생성하는 시간이 소요되고 그 이후론 더 빨라진다.

 

 

aop 폴더

package com.company.design.aop;

import com.company.design.proxy.Html;
import com.company.design.proxy.IBrowser;

public class AopBrowser implements IBrowser {

    private String url;
    private Html html;
    // AOP는 관점지향이니 그걸 볼 Runnable이 필요, function interface임
    private Runnable before;
    private Runnable after;

    public AopBrowser(String url, Runnable before, Runnable after) {
        this.url = url;
        this.before = before;
        this.after = after;
    }

    @Override
    public Html show(){
        before.run();

        if (html == null) {
            this.html = new Html(url);
            System.out.println("AopBrowser html loading from : "+url);

            try {
                Thread.sleep(1500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        after.run();

        System.out.println("AopBrowser html cache : "+url);
        return html;
    }

}

Runnable은 Thread를 구현할 때 쓰는, 구현할 메소드가 run()밖에 없는 함수형 인터페이스다.

자바 8에서 도입된 lambda를 이용하여 더 깔끔하게 만들었다.

 

우선 생성자에서 url, before, after 다 채워주고, 마찬가지로 html이 null이면 Html인스턴스를 생성하는데

그냥 생성하면 너무 빨라서 구별이 안될 수도 있으니 Thread.sleep으로 1.5초 대기하게 만들었다.

 

if문이 돌기 전에 before.run(), if문이 끝나고 after.run()을 했다.

 

그리고 처음에는 html loading과 cache가 둘 다 출력될 것이고, 그 다음부턴 cache만 출력될 것이다.

 

 

package com.company.design.aop;

import com.company.design.proxy.IBrowser;

import java.util.concurrent.atomic.AtomicLong;

public class Main {
    public static void main(String[] args) {

        AtomicLong start = new AtomicLong();
        AtomicLong end = new AtomicLong();
        IBrowser aopBrowser = new AopBrowser("www.naver.com",
                // lambda
                ()->{
                    System.out.println("before");
                    start.set(System.currentTimeMillis());
                },
                ()->{
                    long now = System.currentTimeMillis();
                    end.set(now - start.get());
                }
            );

        aopBrowser.show();
        System.out.println("loading time : "+end.get());

        aopBrowser.show();
        System.out.println("loading time : "+end.get());

    }
}

AtomicLong은 Long 자료형을 갖고 있는 Wrapper 클래스다.

Thread-safe로 구현되어 있는 멀티 Thread는 동기화(Synchronized)없이 사용할 수 있다.

synchronized보다 적은 비용으로 동시성을 보장할 수 있다.

 

메인이 시작되면 start와 end를 만들고, aop를 만드는데 첫번째 파라미터는 url을 넣지만 Runnable인

before와 after는 람다식으로 구현해서 집어넣는다.

 

before는 파라미터 없이 일단 before를 출력하고, start.set으로 현재 시간을 집어넣는다.

 

after는 now에 현재 시간을 집어넣고, now에 현재 시간을 집어넣고, 만약 처음으로 실행한다면

1.5초 기다리고 html을 넣는 시간이 있으니 now는 start보다 뒤의 시간일 것이다.

end에다 그 값을 집어넣는다.

 

before와 after에 들어갈 함수가 정해졌으니 실행하는 것만 남았다.

1. 처음으로 show()메소드를 실행하면 before.run()이 돈다.

우선 sout으로 "before"를 출력하고 start에 시간을 기록한다.

 

아직 html이 null이니 html에 url이 들어간 인스턴스를 넣어주고, loading from을 출력해준다.

그리고 1.5초 기다린 후, after.run()이 돈다.

 

2. now에 현재 시간을 넣고, end에 현재시간 - (1.5 + 인스턴스 생성시간) 을 집어넣는다.

 

3. cache를 출력하고 html을 return하고 show()가 끝난다.

 

그리고 한번 더 show()를 하면 시작시간은 기록하지만 html이 null이 아니니 if문 자체가 안돈다.

 

그리고 after를 실행해봐도 시간차이가 너무 짧아서 0이다.

 

이후 cache를 출력하고 html을 return한다.

그리고 속도차이를 출력하는데 결과를 보면

 

before
AopBrowser html loading from : www.naver.com
AopBrowser html cache : www.naver.com
loading time : 1531
before
AopBrowser html cache : www.naver.com
loading time : 0

 

aop가 제대로 작동하고 있는 것을 확인할 수 있다.

댓글