6주차 정리

2025. 4. 29. 23:49·Book/디자인 패턴의 아름다움

프록시 패턴

종류

  • 인터페이스 기반의 프록시 패턴
  • 상속 기반의 프록시 패턴
  • 리플렉션 기반의 프록시 패턴 → 회사에서 썼던 사용 사례 다시 찾아보기

실제로 Spring AOP의 기본 구현 원칙은 동적 프록시를 기반으로 한다. 사용자는 프록시 클래스를 생성할 클래스를 구성하고 원본 클래스의 주요 비즈니스 코드가 실행되기 전과 후에 수행할 추가 기능을 정의한다. Spring은 이러한 클래스에 대해 동적 프록시 클래스를 생성해주고 원본 클래스의 객체를 JVM의 동적 프록시 클래스 객체로 대체한다. 코드에서 원본 클래스를 실행해야 하는 메서드는 프록시 클래스를 실행하는 메서드로 대체된다. -p.318

https://docs.spring.io/spring-framework/docs/4.3.14.RELEASE/spring-framework-reference/html/aop.html#aop-proxying

페이지를 보면 프록시 메커니즘에 대한 내용이 나오는데, 인터페이스 여부에 따라 JDK로 동적 프록시가 생성될지, 아니면 CGLIB으로 정적 프록시? 가 생성될지에 대한 내용이 나온다. CGLIB의 경우는 권장하지 않는 이유는

  • final 메서드는 오버라이드가 불가능하기 때문에, 프록시 로직(Advice)을 적용할 수 없다.
  • 과거에 CGLIB 프록시를 생성할 때, 원본 객체의 생성자를 두 번 호출하는 문제가 있었으나, 지금은 1회만 하게 최적화됐다. 하지만 일부 특수 JVM의 경우에는 여전히 문제가 생길 수도 있다.

그리고, AOP를 하는 경우 본인 자체를 참조하는 경우에는 프록시 로직을 적용할 수 없다.

public class SimplePojo implements Pojo {

    public void foo() {
        // this next method invocation is a direct call on the 'this' reference
        this.bar();
    }

    public void bar() {
        // some logic...
    }
}

public class Main {

    public static void main(String[] args) {

        ProxyFactory factory = new ProxyFactory(new SimplePojo());
        factory.addInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());

        Pojo pojo = (Pojo) factory.getProxy();

        // this is a method call on the proxy!
        pojo.foo();
    }
}
// 이렇게 호출

이 경우는 AOP의 프록시 로직이 적용 불가능..

public class SimplePojo implements Pojo {

    public void foo() {
        // this works, but... gah!
        ((Pojo) AopContext.currentProxy()).bar();
    }

    public void bar() {
        // some logic...
    }
}

public class Main {

    public static void main(String[] args) {

        ProxyFactory factory = new ProxyFactory(new SimplePojo());
        factory.adddInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());
        factory.setExposeProxy(true);
        // 설정이 하나 더 필요;;

        Pojo pojo = (Pojo) factory.getProxy();

        // this is a method call on the proxy!
        pojo.foo();
    }
}

근데 이렇게 하면 프록시 로직을 AOP에 결합할 수는 있음! (그러나 스프링 팀에서는 비추)

프록시 패턴의 활용 방법

  1. 주요 비즈니스와 관련 없는 요구 사항의 개발에 활용될 수 있다.
  2. RPC에서 프록시 패턴을 적용할 수 있다.
  3. 캐시에 프록시 패턴을 적용할 수 있다.

캐시에 프록시 패턴을 적용하는 방법에 대한 궁금증

  • 예시에 보면, URL에 cached=true라는 게 나오는데 엔드포인트에 대한 캐싱을 말하는것인지? 아니면 aop 얘기가 나오니 aop를 적용한 메서드 자체에 대한 캐싱인지?

메서드(인터페이스) 단위로 캐시하는 것을 말함!!

  • 인메모리에 대한 캐싱인지? 아니면 redis나 rdbms에도 캐싱할 수 있는지?

인메모리 / redis 둘다 가능 그러나 rdbms에는 캐싱을 잘 하지 않는다. (가능은 함)

@Around("@annotation(CachedMethod)")
public Object cacheAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
    if (isCachedRequest()) { // HTTP 요청에 cached=true 있는지 검사
        String cacheKey = generateCacheKey(joinPoint);
        if (cache.exists(cacheKey)) {
            return cache.get(cacheKey);
        }
    }

    Object result = joinPoint.proceed(); // 실제 메서드 실행
    cache.put(cacheKey, result); // 캐시 저장
    return result;
}

GPT가 말해준 rdbms에는 캐싱을 할 수도 있는 경우

  • 장기 보존용 캐시 (Persistent Cache)
  • (예: 캐시 결과를 서버 재시작 후에도 복구하고 싶을 때)
  • 비동기적 캐시 갱신 (Async Cache Update)
  • (ex: 업데이트가 자주 일어나지 않는 값들)

이런 경우, "영속성(persistence)"을 위해 RDBMS에 저장 할 수도 있다.

캐시를 RDBMS에 두는 건 보통 "캐시"가 아니라 "오래 저장할 결과"를 보관하는 용도로 설계

데커레이터 패턴

데커레이터 패턴은 단순히 상속을 합성으로 대체하는 데 사용되는 패턴이라고 생각할 수도 있지만, 그렇지 않다.

  1. 데커레이터 클래스가 원본 클래스와 동일한 상위 클래스를 상속하기 때문에, 원본 클래스 내에 여러 개의 데커레이터 클래스를 중첩할 수 있다는 점이다.
  2. 다른 차이점은 데커레이터 클래스의 기능이 원본 클래스의 기능을 향상시키는 것인데, 이는 데커레이터 패턴의 활용에서도 중요한 기능이다. 프록시 패턴과 데커레이터 패턴은 비록 매우 유사하지만, 프록시 패턴에서 프록시 클래스가 원본 클래스와 관련이 없는 기능을 추가하는 반면, 데커레이터 패턴에서는 데커레이터 클래스가 원본 클래스와 관련이 깊은 기능을 추가한다.

데커레이터랑 프록시랑 그럼 뭐가 다른가?

데커레이터는 원본 클래스의 기능을 향상 시키는 것이 주 이고, 프록시는 원본 클래스와 무관한 기능을 추가하는 것이 주 관점이다.

어댑터 패턴

클래스 어댑터와 객체 어댑터

어댑터 패턴은 그 이름에서도 알 수 있듯이 조정에 따른 적응(adaption)에 사용되며, 호환되지 않는 인터페이스를 호환 가능한 인터페이스로 변환하여, 두 클래스를 함께 작동할 수 있게 한다.

클래스 어댑터와 객체 어댑터 두 가지의 형태가 있다. 클래스 어댑터는 상속 관계를 사용한 방식이고, 객체 어댑터는 합성 관계를 사용한 방식이다.

어댑터 패턴의 응용

일반적으로 어댑터 패턴은 설계 결함을 교정하는 보상 패턴으로 볼 수 있다. 이 패턴을 적용해야 한다는 것은 이미 어쩔 수 없는 상황에 도달했다는 뜻이며, 설계 초기 단계에서 인터페이스 비호환성 문제를 피할 수 있다면 이 패턴은 사용될 필요가 없다.

어댑터 패턴이 사용되는 것은 인터페이스의 비호환성에서 비롯된 것이다. 5가지의 상황으로 요약하자면,

  1. 결함이 있는 인터페이스 설계가 캡슐화된 경우
  2. 여러 클래스의 인터페이스 설계를 통합할 경우
  3. 사용 중인 외부 시스템을 교체해야 할 경우
  4. 이전 버전 인터페이스와 호환성이 필요한 경우
  5. 다양한 형식의 데이터에 적응해야 할 경우

브리지 패턴

정의

추상화와 구현을 디커플링해야만 두 가지가 서로 독립적으로 변화할 수 있다.

정의를 조금 구체화 한다면? 클래스에는 독립적으로 변하는 두 개 또는 그 이상의 차원이 존재하고, 합성 메서드를 통해 이 클래스를 두 개 또는 그 이상의 차원에서 확장할 수 있다.

브리지 패턴을 적용하면 M*N의 상속 관계를 M+N의 합성 관계로 단순화할 수 있다.

논의?

  • 실제로 AOP의 프록시를 캐시 할만한 기준이 있는지?? (트래픽,, 기준이라고 해야될까요? 써본 적이 없어서 감이 안 잡혀서,,!!)

'Book > 디자인 패턴의 아름다움' 카테고리의 다른 글

8주차 정리  (0) 2025.05.03
7주차 정리  (0) 2025.04.29
5주차 정리  (0) 2025.04.29
4주차 정리  (0) 2025.04.29
3주차 정리  (0) 2025.04.29
'Book/디자인 패턴의 아름다움' 카테고리의 다른 글
  • 8주차 정리
  • 7주차 정리
  • 5주차 정리
  • 4주차 정리
jun96
jun96
프로그래밍 공부
  • jun96
    jun의 공부노트
    jun96
  • 전체
    오늘
    어제
    • 분류 전체보기 (66)
      • Spring (6)
        • 개념 (3)
        • 에러 (1)
      • Java (1)
      • Book (20)
        • 모던 자바 인 액션 (12)
        • 디자인 패턴의 아름다움 (7)
      • Algorithm (1)
      • 코딩테스트 (35)
      • 일상 (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 이력서
  • 공지사항

  • 인기 글

  • 태그

    자바
    datetime
    Algorithm
    모던자바인액션
    백준
    아직 미완성
    전자정부프레임워크
    알고리즘
    Java
    junit5
    DeepDive
    python설치
    프로그래머스
    디자인패턴의아름다움
    도커컨테이너빌드업
    최프
    aws에 배포하기
    스프링
    aws배포
    wikidocs
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
jun96
6주차 정리
상단으로

티스토리툴바