프록시 패턴(Proxy Pattern)은 대상 원본 객체를 대리하여 대신 처리하게 함으로써 로직의 흐름을 제어하는 행동 패턴이다.
프록시(Proxy)의 사전적인 의미는 '대리인'이라는 뜻이다. 즉, 누군가에게 어떤 일을 대신 시키는 것을 의미하는데, 이를 객체 지향 프로그래밍에 접목해보면 클라이언트가 대상 객체를 직접 쓰는게 아니라 중간에 프록시(대리인)을 거쳐서 쓰는 코드 패턴이라고 보면 된다.
따라서 대상 객체(Subject)의 메소드를 직접 실행하는 것이 아닌, 대상 객체에 접근하기 전에 프록시(Proxy) 객체의 메서드를 접근한 후 추가적인 로직을 처리한뒤 접근하게 된다.
이때 프로시는 흐름제어만 할 뿐 결과값을 조작하거나 변경시키면 안된다!
[출처 : https://inpa.tistory.com/]
그냥 객체를 이용하면 되는데 이렇게 번거롭게 중계 대리자를 통해 이용하는 방식을 취하지?
그 이유는 대상 클래스가 민감한 정보를 가지고 있거나 인스턴스화 하기에 무겁거나 추가 기능을 가미하고 싶은데, 원본 객체를 수정할수 없는 상황일 때를 극복하기 위해서 이다.
이로 인해 다음과 같은 효과를 누릴수 있다고 보면 된다.
[출처 : https://inpa.tistory.com/]
interface ISubject {
void action();
}
class RealSubject implements ISubject {
public void action() {
System.out.println("원본 객체 액션 !!");
}
}
class Proxy implements ISubject {
private RealSubject subject; // 대상 객체를 composition
Proxy(RealSubject subject) {
this.subject = subject;
}
public void action() {
subject.action(); // 위임
/* do something */
System.out.println("프록시 객체 액션 !!");
}
}
class Client {
public static void main(String[] args) {
ISubject sub = new Proxy(new RealSubject());
sub.action();
}
}
/*출력 결과
원본 객체 액션 !!
프록시 객체 액션 !!
*/
//Interface와 RealSubject는 생략
class Proxy implements ISubject {
private RealSubject subject; // 대상 객체를 composition
Proxy() {
}
public void action() {
// 프록시 객체는 실제 요청(action(메소드 호출)이 들어 왔을 때 실제 객체를 생성한다.
if(subject == null){
subject = new RealSubject();
}
subject.action(); // 위임
/* do something */
System.out.println("프록시 객체 액션 !!");
}
}
class Client {
public static void main(String[] args) {
ISubject sub = new Proxy();
sub.action();
}
}
//Interface와 RealSubject는 생략
class Proxy implements ISubject {
private RealSubject subject; // 대상 객체를 composition
boolean access; // 접근 권한
Proxy(RealSubject subject, boolean access) {
this.subject = subject;
this.access = access;
}
public void action() {
if(access) {
subject.action(); // 위임
/* do something */
System.out.println("프록시 객체 액션 !!");
}
}
}
class Client {
public static void main(String[] args) {
ISubject sub = new Proxy(new RealSubject(), false);
sub.action();
}
}
//Interface와 RealSubject는 생략
class Proxy implements ISubject {
private RealSubject subject; // 대상 객체를 composition
Proxy(RealSubject subject {
this.subject = subject;
}
public void action() {
System.out.println("로깅..................");
subject.action(); // 위임
/* do something */
System.out.println("프록시 객체 액션 !!");
System.out.println("로깅..................");
}
}
class Client {
public static void main(String[] args) {
ISubject sub = new Proxy(new RealSubject());
sub.action();
}
}
[출처 : https://blogshine.tistory.com/17]
💡 프록시를 스터브라고도 부르며, 프록시로부터 전달된 명령을 이해하고 적합한 메소드를 호출해주는 역할을 하는 보조객체를 스켈레톤이라 한다.
[출처 : http/blog.naver.com/hai0416]
개발자가 직접 디자인 패턴으로서 프록시 패턴을 구현해도 되지만, 자바 JDK에서는 별도로 프록시 객체 구현 기능을 지원한다. 이를 동적 프록시(Dynamic Proxy) 기법이라고 불리운다.
동적 프록시는 개발자가 직접 일일히 프록시 객체를 생성하는 것이 아닌, 애플리케이션 실행 도중 java.lang.reflect.Proxy 패키지에서 제공해주는 API를 이용하여 동적으로 프록시 인스턴스를 만들어 등록하는 방법으로서, 자바의 Reflection APIVisit Website 기법을 응용한 연장선의 개념이다. 그래서 별도의 프록시 클래스 정의없이 런타임으로 프록시 객체를 동적으로 생성해 이용할 수 있다는 장점이 있다.
// 대상 객체와 프록시를 묶는 인터페이스
interface Animal {
void eat();
}
// 프록시를 적용할 타겟 객체
class Tiger implements Animal{
@Override
public void eat() {
System.out.println("호랑이가 음식을 먹습니다.");
}
}
public class Client {
public static void main(String[] arguments) {
// newProxyInstance() 메서드로 동적으로 프록시 객체를 생성할 수 있다.
Animal tigerProxy = (Animal) Proxy.newProxyInstance(
Animal.class.getClassLoader(), // 대상 객체의 인터페이스의 클래스로더
new Class[]{Animal.class}, // 대상 객체의 인터페이스
new InvocationHandler() { // 프록시 핸들러
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object target = new Tiger();
System.out.println("----eat 메서드 호출 전----");
Object result = method.invoke(target, args); // 타겟 메서드 호출
System.out.println("----eat 메서드 호출 후----");
return result;
}
}
);
tigerProxy.eat();
}
}
/*출력결과
----eat 메서드 호출 전----
호랑이가 음식을 먹습니다.
----eat 메서드 호출 후----
*/
스프링 프레임워크에서는 내부적으로 프록시 기술을 정말 많이 사용하고 있다. (AOP, JPA 등)
스프링에서는 Bean을 등록할 때 SingletonVisit Website을 유지하기 위해 Dynamic Proxy 기법을 이용해 프록시 객체를 Bean으로 등록한다. 또한 Bean으로 등록하려는 기본적으로 객체가 Interface를 하나라도 구현하고 있으면 JDK를 이용하고 Interface를 구현하고 있지 않으면 내장된 CGLIB 라이브러리를 이용한다.
@Service
public class GameService {
public void startDame() {
System.out.println("이 자리에 오신 여러분을 진심으로 환영합니다.");
}
}
@Aspect
@Comonent
public class PerfAspect {
@Around("bean(gameService)")
public void timestamp(ProceedingJoinPoint point) throws Throwable {
System.out.println("프록시 실행 1");
point.proceed(); // 대상 객체의 원본 메서드를 실행
System.out.println("프록시 실행 2");
}
}
자바에서도 Proxy 객체를 별도로 지원하듯이, 자바스크립트 진영에서도 독립적인 Proxy 객체가 존재한다.
자바스크립트에서의 Proxy 객체의 역할은 대상 객체을 감싸서(wrapping), 속성 조회, 할당, 열거 및 함수 호출 등 여러 기본 동작을 가로채(trap) 특별한 다른 동작을 가미시키는 대리자 역할을 한다. 대상 객체는 Object, Array 등 자바스크립트의 모든 자료형이 대상이 될 수 있다.
let obj = {
name: '홍길동',
print: function () {
console.log(`My Name is ${this.name}`);
},
};
// print 함수를 프록시로 감싸기
obj.print = new Proxy(obj.print, {
apply(target, thisArg, args) {
console.log('메서드 실행할 때 중간에 가로채어 로직 시행');
console.log('target: ', target); // 대상 함수
console.log('thisArg: ', thisArg); // this의 값
console.log('args: ', args); // 매개변수 목록 (배열)
console.log('이름 바꿔 버리기 ~');
thisArg.name = '임꺽정';
Reflect.apply(target, thisArg, args); // 대상 원본 함수 실행
},
});
obj.print();
/*출력결과
메서드 실행할 때 중간에 가로채어 로직 시행
target : [Function: print]
thisArg: { name: '홍길동', print: [Function: print] }
args: []
이름 바꿔 버리기 ~
My Name is 임꺽정
*/