스프링 핵심 원리 - 고급편 9

Spring 정리 2023. 5. 19. 18:45

인프런 강의 68일차.

 - 스프링 핵심 원리 - 고급편 (김영한 강사님)

 - 반드시 한번은 정복해야할 쉽지 않은 내용들

 - 크게 3가지 고급 개념을 학습

  1. 스프링 핵심 디자인 패턴

     > 템플릿 메소드 패턴

     > 전략 패턴

     > 템플릿 콜백 패턴

     > 프록시 패턴

     > 데코레이터 패턴

  2. 동시성 문제와 쓰레드 로컬

     > 웹 애플리케이션

     > 멀티쓰레드

     > 동시성 문제

  3. 스프링 AOP

     > 개념, 용어정리

     > 프록시 - JDK 동적 프록시, CGLIB

     > 동작 원리

     > 실전 예제

     > 실무 주의 사항

 - 기타

     > 스프링 컨테이너의 확장 포인트 - 빈 후처리기

     > 스프링 애플리케이션을 개발하는 다양한 실무 팁

 - 타입 컨버터, 파일 업로드, 활용, 쿠키, 세션, 필터, 인터셉터, 예외 처리, 타임리프, 메시지, 국제화, 검증 등등

 

4. 프록시 패턴과 데코레이터 패턴

 4.7. 데코레이터 패턴 - 예제 코드1

  - 데코레이터 패턴에 대해 이해하기

 - 프록시 패턴과 동일한 형태로 구현

package hello.proxy.puerproxy.decorator.code;

public interface Component {
    String operation();
}

 - hello.proxy.puerproxy.decorator.code.Component.java
 - 예제이므로 단순 메소드 하나만 구현

 

package hello.proxy.puerproxy.decorator.code;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class RealComponent implements Component {

    @Override
    public String operation() {
        log.info("RealComponent 실행");
        return "data";
    }
}

 - hello.proxy.puerproxy.decorator.code.RealComponent.java

 

package hello.proxy.puerproxy.decorator;

import hello.proxy.puerproxy.decorator.code.DecoratorPatternClient;
import hello.proxy.puerproxy.decorator.code.RealComponent;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;

@Slf4j
public class DecoratorPatternTest {

    @Test
    void noDecorator() {
        RealComponent realComponent = new RealComponent();
        DecoratorPatternClient client = new DecoratorPatternClient(realComponent);
        client.execute();
    }

    @Test
    void Decorator() {
        RealComponent realComponent = new RealComponent();
        DecoratorPatternClient client = new DecoratorPatternClient(realComponent);
        client.execute();
    }
}

 - hello.proxy.puerproxy.decorator.DecoratorPatternTest.java

 - 테스트 코드는 client -> realComponent 의 의존관계를 설정하고, client.execute() 를 호출한다.

 

//실행 결과
RealComponent - RealComponent 실행
DecoratorPatternClient - result=data

 

 4.8. 데코레이터 패턴 - 예제 코드2

  - 프록시로 부가 기능을 추가하는 것을 데코레이터 패턴이라 한다 (서버가 제공하는 기능에 더해서 부가 기능을 수행)

    > 요청 값이나, 응답 값을 중간에 변형한다.

    > 실행 시간을 측정해서 추가 로그를 남긴다.

 

 - 응답 값을 꾸며주는 데코레이터 프록시를 만들어보자.

 

package hello.proxy.puerproxy.decorator.code;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class MessageDecorator implements Component {

    private Component component;

    public MessageDecorator(Component component) {
        this.component = component;
    }

    @Override
    public String operation() {
        log.info("MessageDecorator 실행");

        String result = component.operation();
        String decoResult = "*****" + result + "*****";
        log.info("MessageDecorator 꾸미기 적용 전 ={} ,적용 후 ={}", result, decoResult);
        return decoResult;
    }
}

 - hello.proxy.puerproxy.decorator.code.MessageDecorator.java

 

public class DecoratorPatternTest {
	...
    @Test
    void decorator1() {
        RealComponent realComponent = new RealComponent();
        MessageDecorator messageDecorator = new MessageDecorator(realComponent);
        DecoratorPatternClient client = new DecoratorPatternClient(messageDecorator);
        client.execute();
    }
}

 - hello.proxy.puerproxy.decorator.DecoratorPatternTest.java

 

//실행 결과
MessageDecorator - MessageDecorator 실행
RealComponent - RealComponent 실행
MessageDecorator - MessageDecorator 꾸미기 적용 전=data, 적용 후=*****data*****
DecoratorPatternClient - result=*****data*****

 -  MessageDecorator 가 RealComponent 의 응답 메시지를 꾸며서 반환한 것을 확인할 수 있다.

 

 4.9. 데코레이터 패턴 - 예제 코드3

  - 실행 시간을 측정하는 데코레이터 구현

 

package hello.proxy.puerproxy.decorator.code;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class TimeDecorator implements Component {

    private Component component;

    public TimeDecorator(Component component) {
        this.component = component;
    }

    @Override
    public String operation() {
        log.info("TimeDecorator 실행");
        long startTime = System.currentTimeMillis();
        String result = component.operation();
        long endTime = System.currentTimeMillis();
        long resultTime = endTime - startTime;
        log.info("TimeDecorator 종료 resultTime={}ms", resultTime);
        return null;
    }
}

 - hello.proxy.puerproxy.decorator.code.TimeDecorator.java

 

public class DecoratorPatternTest {
	...
    @Test
    void decorator2() {
        RealComponent realComponent = new RealComponent();
        MessageDecorator messageDecorator = new MessageDecorator(realComponent);
        TimeDecorator timeDecorator = new TimeDecorator(messageDecorator);
        DecoratorPatternClient client = new DecoratorPatternClient(timeDecorator);
        client.execute();
    }
}

 - hello.proxy.puerproxy.decorator.DecoratorPatternTest.java

 - client -> timeDecorator -> messageDecorator -> realComponent 의 객체 의존관계를 세팅하고 실햄

 

//실행 결과
TimeDecorator 실행
MessageDecorator 실행
RealComponent 실행
MessageDecorator 꾸미기 적용 전=data, 적용 후=*****data*****
TimeDecorator 종료 resultTime=7ms
result=*****data*****

 - 실행 결과를 보면 TimeDecorator 가 MessageDecorator 를 실행하고 실행 시간을 측정해서 출력한 것을 확인할 수 있다

 

 4.10. 프록시 패턴과 데코레이터 패턴 정리

GOF 데코레이터 패턴

 - Decorator 기능에 일부 중복이 있다. 꾸며주는 역할을 하는 Decorator 들은 스스로 존재할 수 없이 항상 꾸며줄 대상이 있어야 한다.

 - 따라서 내부에 호출 대상인 component 를 가지고 있어야 한다. 그리고 component 를 항상 호출해야 한다.

 - 이 부분이 중복이다. 이런 중복을 제거하기 위해 component 를 속성으로 가지고 있는 Decorator 라는 추상 클래스를 만드는 방법도 고민할 수 있다.

 - 이렇게 하면 추가로 클래스 다이어그램에서 어떤 것이 실제 컴포넌트 인지, 데코레이터인지 명확하게 구분할 수 있다.

 - 여기까지 고민한 것이 바로 GOF에서 설명하는 데코레이터 패턴의 기본 예제이다.

 

* 프록시 패턴 vs 데코레이터 패턴

 - Decorator 라는 추상 클래스를 만들어야 데코레이터 패턴일까? 프록시 패턴과 데코레이터 패턴은 그 모양이 거의 비슷한 것 같은데?

 - 패턴의 구분은 intent에 있다.

 

*의도(intent)

 - 프록시 패턴과 데코레이터 패턴은 그 모양이 거의 같고, 상황에 따라 정말 똑같을 때도 있다.

 - 디자인 패턴에서 중요한 것은 해당 패턴의 겉모양이 아니라 그 패턴을 만든 의도가 더 중요하다.

 - 따라서 의도에 따라 패턴을 구분한다

 

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

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

 > 프록시를 사용하고 해당 프록시가 접근 제어가 목적이라면 프록시 패턴이고, 새로운 기능을 추가하는 것이 목적이라면 데코레이터 패턴이 된다.