본문 바로가기
개발 기록

2024-07-31 학습일기

by 진꿈청 2024. 7. 31.

스프링

 

프록시 패턴

 

프록시의 주요 기능은 접근 제어이다.

캐시도 접근 제어의 한 종류라고 볼 수 있다.

  • 이미 한번 조회한걸 빠르게 조회하기 위함

이미 개발된 로직을 전혀 수정하지 않고 프록시 객체를 통해 캐시를 적용하려면 어떻게 해야할까

 

Subject라는 인터페이스를 만들고 이걸 실제로 구현한 RealSubject라는 구현체를 만든다.

  • 이때 RealSubject에서 1초 걸린다고 가정하면
  • 세 번 호출시 각각 1초씩 걸리므로 3초가 걸린다.
  • 하지만, 만약 조회하는 내용이 같다면 이건 손해가 될 수 있다.
  • 따라서, 프록시를 통해 캐시 형태로 사용하면 더 빨리 조회할 수 있다.

그렇기 위해 CacheProxy라는 것을 하나 만들고 필드로 RealSubject를 주입받는다.

또한, Cache 값을 저장하기 위한 필드를 하나더 만든다.

 

그 다음에 Client에서 CacheProxy를 호출하게 되면 이제 CacheProxy는 Cache 필드의 값이

null인 경우만 RealSubject를 호출하고 아닌 경우는 그냥 자기가 갖고 있는 값을 반환한다.

 

이처럼 하면 캐시가 적용되므로 뛰어난 성능을 보인다.

 

OCP + DI 라고 할 수 있다.

 

프록시 체인: 프록시가 다른 프록시를 가리키면서 이어지는 것

 

 

데코레이터 패턴

 

프록시로 부가 기능을 처리하는 것을 데코레이터 패턴이라고 할 수 있다.

  • 로그 처리 추가
  • 응답값 변경 추가

데코레이터 패턴을 사용하면 원본 코드에 여러 작업을 추가할 수 있다.

 

즉, 프록시 패턴처럼 데코레이터 구현체를 만들고 필드에 Real 구현체를 갖도록 한 뒤

거기에 이것저것 작업을 추가해나가는 것이다.

 

프록시 체인처럼 데코레이터 체인은 계속해서 데코레이터끼리 이어지면서 꾸며주게 되는 것이다.

 

여기서 중요한 점은 데코레이터 패턴은 혼자서 존재할 수 없다. 항상 꾸며줄 대상이 있어야 한다.

그리고 여러 데코레이터 구현체를 만들면 생성자나 필드가 중복될 수 있는데 이걸 추상 클래스로 처리할 수도 있다.

 

딱 여기까지 알아봤을 때 프록시 패턴과 데코레이터 패턴은 거의 유사하다.

거의 모양이 같고 상황에 따라 아예 똑같을 수도 있다고 한다.

 

따라서, 디자인 패턴에서는 적용할 때의 의도가 가장 중요하다고 한다.

 

프록시 패턴의 의도: 다른 객체에 대한 “접근을 제어”하기 위한 대리자를 제공

데코레이터 패턴의 의도: “객체에 대한 추가 책임(기능)을 동적으로 추가”하고, 기능 확장을 위한 유연한 기능 제공

 

 

정리

 

프록시를 사용하고 해당 프록시가 접근 제어가 목적이라면 프록시 패턴이고,

새로운 기능을 추가하는 것이 목적이라면 데코레이터 패턴이 된다.

 

 

실제 Controller - Service - Repository에 적용(인터페이스 기반)

 

이제까지 프록시 패턴과 데코레이터 패턴을 알아봤으니 이제 원본 로직의 수정이 하나도 없이

로그 추적기를 도입해본다면

우리는

Client → OrderControllerProxy → OrderControllerImpl → OrderServiceProxy → …

형태로 접근하면 된다.

 

 

당연하게도 OrderController 인터페이스 등을 구현해야 할 것이다.

  • OrderControllerProxy
    • 생성자 주입시 Target으로 OrderControllerImpl를 넣어주어야 한다.
  • OrderControllerImpl

그 후, 프록시 클래스에 관해 빈 등록을 실시하면 우리는 OrderController, OrderService등의 인터페이스로 작업이 가능하다.

여기서 중요한점은 클라이언트는 그걸 모른다는 것이다.

 

 

실제 Controller - Service - Repository에 적용(클래스 기반)

 

클래스 기반일 경우 다형성을 이용한다.

즉, 상속을 이용한다.

 

인터페이스의 경우 원본 인터페이스를 Proxy와 실제 구현체를 전부 다 구현하고 Proxy → 실제 구현체 로직 실행으로 진행을 했다면

클래스의 경우 원본 클래스를 Proxy가 상속하고 Proxy의 필드에 원본 클래스를 넣어 주입받고

Proxy → 부모 클래스 로직 호출로 진행이 된다.

 

 

중간 정리

 

지금까지 인터페이스를 기반으로 프록시를 도입했다. 그런데 자바의 다형성은 인터페이스를 구현하든, 아니면 클래스를 상속하든 상위 타입만 맞으면 다형성이 적용된다. 쉽게 이야기해서 인터페이스가 없어도 프록시를 만들수 있다는 뜻이 다. 그래서 이번에는 인터페이스가 아니라 클래스를 기반으로 상속을 받아서 프록시를 만들어보겠다.

 

그러나, 클래스 기반 프록시를 사용하는 경우 상속으로 인해 생성자를 호출하는데에 있어 약간의 아쉬운점이 존재한다.

 

부모 클래스의 Service를 예로 Service 클래스는 Repository 클래스 생성자로 주입받는다.

 

그렇기에 부모 Service를 필드로 갖는 ServiceProxy는 생성자 생성시 상속으로 인해 super(null) 을 입력해주어야 한다.

(기본 생성자가 없으므로)

 

그 후, Configuration으로 주입받아야 한다.

  • 자바의 생성자 특성으로 인해 어쩔 수 없는 작업이 추가됨

 

인터페이스 기반 프록시 vs 클래스 기반 프록시

 

프록시

프록시를 사용한 덕분에 원본 코드를 전혀 변경하지 않고, V1, V2 애플리케이션에 LogTrace 기능을 적용할 수 있었다.

 

인터페이스 기반 프록시 vs 클래스 기반 프록시

  • 인터페이스가 없어도 클래스 기반으로 프록시를 생성할 수 있다.
  • 클래스 기반 프록시는 해당 클래스에만 적용할 수 있다.
  • 인터페이스 기반 프록시는 인터페이스만 같으면 모든 곳 에 적용할 수 있다.
  • 클래스 기반 프록시는 상속을 사용하기 때문에 몇가지 제약이 있다.
    • 부모 클래스의 생성자를 호출해야 한다.(앞서 본 예제)
    • 클래스에 final 키워드가 붙으면 상속이 불가능하다.
    • 메서드에 final 키워드가 붙으면 해당 메서드를 오버라이딩 할 수 없다.

 

 

아래부터는 김영한 강사님의 강의 내용

 

이렇게 보면 인터페이스 기반의 프록시가 더 좋아보인다. 맞다. 인터페이스 기반의 프록시는 상속이라는 제약에서 자유롭다.

 

프로그래밍 관점에서도 인터페이스를 사용하는 것이 역할과 구현을 명확하게 나누기 때문에 더 좋다.

인터페이스 기반 프록시의 단점은 인터페이스가 필요하다는 그 자체이다.

인터페이스가 없으면 인터페이스 기반 프록시를 만들 수 없다.

 

참고: 인터페이스 기반 프록시는 캐스팅 관련해서 단점이 있는데, 이 내용은 강의 뒷부문에서 설명한다.

 

이론적으로는 모든 객체에 인터페이스를 도입해서 역할과 구현을 나누는 것이 좋다.

이렇게 하면 역할과 구현을 나누어서 구현체를 매우 편리하게 변경할 수 있다.

하지만 실제로는 구현을 거의 변경할 일이 없는 클래스도 많다.

 

인터페이스를 도입하는 것은 구현을 변경할 가능성이 있을 때 효과적인데,

구현을 변경할 가능성이 거의 없는 코드에 무작정 인터페이스를 사용하는 것은 번거롭고 그렇게 실용적이지 않다.

 

이런곳에는 실용적인 관점에서 인터페이스를 사용하지 않고 구체 클래스를 바로 사용하는 것이 좋다 생각한다. (물론 인터페이스를 도입하는 다양한 이유가 있다. 여기서 핵심은 인터페이스가 항상 필요하지는 않다는 것이다.)

 

결론

실무에서는 프록시를 적용할 때 V1처럼 인터페이스도 있고, V2처럼 구체 클래스도 있다. 따라서 2가지 상황을 모두 대응할 수 있어야 한다.

 

 

 

너무 많은 프록시 클래스

 

지금까지 프록시를 사용해서 기존 코드를 변경하지 않고, 로그 추적기라는 부가 기능을 적용할 수 있었다. 그런데 문제는 프록시 클래스를 너무 많이 만들어야 한다는 점이다. 잘 보면 프록시 클래스가 하는 일은 LogTrace 를 사용하는 것인데, 그 로직이 모두 똑같다. 대상 클래스만 다를 뿐이다. 만약 적용해야 하는 대상 클래스가 100개라면 프록시 클래스도 100개를 만들어야한다.

프록시 클래스를 하나만 만들어서 모든 곳에 적용하는 방법은 없을까? 바로 다음에 설명할 동적 프록시 기술이 이 문제를 해결해준다.

 

 

Jenkins

 

알고리즘

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

푸는것이 다소 어려웠다. DP 같긴한데 플로이드 워샬 같기도 하고 아이디어가 빨리 떠올랐으면 금방 풀 수 있는 문제같다.

 

 

 

 

참고

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8/dashboard

 

스프링 핵심 원리 - 고급편 강의 | 김영한 - 인프런

김영한 | 스프링의 핵심 원리와 고급 기술들을 깊이있게 학습하고, 스프링을 자신있게 사용할 수 있습니다., 핵심 디자인 패턴, 쓰레드 로컬, 스프링 AOP스프링의 3가지 핵심 고급 개념 이해하기

www.inflearn.com

 

'개발 기록' 카테고리의 다른 글

2024-08-02 학습일기  (0) 2024.08.02
2024-08-01 학습일기  (0) 2024.08.01
2024-07-30 학습일기  (0) 2024.07.30
2024-07-27 ~ 2024-07-29 학습일기  (0) 2024.07.29
2024-07-26 학습일기  (0) 2024.07.26