[Spring AOP] 프록시 패턴과 데코레이터 패턴

2025. 7. 6. 16:46·Spring/AOP

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

예제 프로젝트 생성

  • v1 - 인터페이스와 구현 클래스 - 스프링 빈으로 수동 등록
  • v2 - 인터페이스 없는 구체 클래스 - 스프링 빈으로 수동 등록
  • v3 - 컴포넌트 스캔으로 스프링 빈 자동 등록

실무에서 세 가지 경우를 모두 만날 수 있으므로, 세 가지 빈 등록 방식에 따른 프록시 패턴, 데코레이터 패턴의 적용에 대해 알아본다.
예제의 기능은 이전 챕터에서의 상품 주문과 동일하다.

예제 프로젝트 만들기 v1

OrderControllerV1

@RestController
public interface OrderControllerV1 {

    @GetMapping("/v1/request")
    String request(@RequestParam("itemId") String itemId);

    @GetMapping("/v1/no-log")
    String noLog();
}
  • 컨트롤러를 인터페이스로 선언하여 등록하는 경우는 드물긴 하지만, 이 방식까지도 알아본다. 인터페이스로 선언하는 경우 @XXXMapping 애노테이션이 존재해야 한다.
  • 코드를 보면 request(), noLog() 두 가지 메서드가 있다. request()는 LogTrace 를 적용할 대상이고, noLog()는 단순히 LogTrace를 적용하지 않을 대상이다.

Service와 Repository 인터페이스에는 @Service, @Repository 를 사용하지 않는다. (수동으로 빈 등록할꺼임!)

수동 빈 등록

@Configuration
public class AppV1Config {

    @Bean
    public OrderControllerV1 orderControllerV1() {
        return new OrderControllerV1Impl(orderServiceV1());
    }

    @Bean
    public OrderServiceV1 orderServiceV1() {
        return new OrderServiceV1Impl(orderRepositoryV1());
    }

    @Bean
    public OrderRepositoryV1 orderRepositoryV1() {
        return new OrderRepositoryV1Impl();
    }
}

스프링부트 빈 설정 클래스 등록

@Import(AppV1Config.class)
@SpringBootApplication(scanBasePackages = "hello.proxy.app.v3") //주의
public class ProxyApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProxyApplication.class, args);
    }
}
  • @Import(AppV1Config.class) : 클래스를 스프링 빈으로 등록한다. 일반적으로 @Configuration 같은 설정 파일을 등록할 때 사용하지만, 스프링 빈을 등록할 때도 사용할 수 있다.
  • @SpringBootApplication(scanBasePackages = "hello.proxy.app"): @ComponentScan 의 기능과 같다. 컴포넌트 스캔을 시작할 위치를 지정한다. 이 값을 설정하면 해당 패키지와 그 하위 패키지를 컴포넌트 스캔한다. 이 값을 사용하지 않으면 ProxyApplication 이 있는 패키지와 그 하위 패키지를 스캔한다.
    • 컴포넌트 스캔에 의해 hello.proxy.config 위치의 설정 파일들이 스프링 빈으로 자동 등록 되지 않도록 컴포넌스 스캔의 시작 위치를 scanBasePackages=hello.proxy.app 로 설정해야 한다.
    • V1의 Controller 인터페이스에 @RestController를 등록하지 않으면, 컨트롤러가 동작하지 않아 @RestController를 어쩔 수 없이 추가하였다. (Spring Boot 3.0부터) 만약, 스캔 패키지를 hello.proxy.app로 지정하면 V1Controller가 수동 빈 등록 + 컴포넌트 스캔 중복으로 충돌이 발생한다
    • v3에서는 어차피 컴포넌트 스캔을 사용하므로 문제가 발생하지 않는다. 따라서 hello.proxy.app.v3로 지정한다.

예제 프로젝트 만들기 v2

구체 클래스로 각 계층을 만들고, 수동 등록하는 예제이다. (특별한 부분은 없으므로 각 계층의 코드 생략)

@Configuration
public class AppV2Config {

    @Bean
    public OrderControllerV2 orderControllerV2() {
        return new OrderControllerV2(orderServiceV2());
    }

    @Bean
    public OrderServiceV2 orderServiceV2() {
        return new OrderServiceV2(orderRepositoryV2());
    }

    @Bean
    public OrderRepositoryV2 orderRepositoryV2() {
        return new OrderRepositoryV2();
    }
}
@Import({AppV1Config.class, AppV2Config.class})
@SpringBootApplication(scanBasePackages = "hello.proxy.app")
public class ProxyApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProxyApplication.class, args);
    }
}

예제 프로젝트 만들기 v3

컴포넌트 스캔으로 스프링 빈을 자동 등록해보자. @Repository, @Service, @RestController를 사용하여 등록하면 된다.
ProxyApplication 에서 @SpringBootApplication(scanBasePackages = "hello.proxy.app.v3")에 의해 자동 컴포넌트 스캔 대상이 된다.

요구사항 추가

이전 챕터까지 로그 추적기를 개발했지만, 여전히 로그를 남기고 싶은 클래스가 수백개라면 수백개의 클래스를 모두 고쳐야 한다. 로그를 남길 때 기존 원본 코드를 변경해야 한다는 사실 그 자체가 개발자에게는 가장 큰 문제로 남는다.

요구사항 추가

  • 원본 코드를 전혀 수정하지 않고, 로그 추적기를 적용해라.
  • 특정 메서드는 로그를 출력하지 않는 기능
    • 보안상 일부는 로그를 출력하면 안된다.
    • 우리 예제의 no-log() 메서드
  • 다음과 같은 다양한 케이스에 적용할 수 있어야 한다.
    • v1 - 인터페이스가 있는 구현 클래스에 적용
    • v2 - 인터페이스가 없는 구체 클래스에 적용
    • v3 - 컴포넌트 스캔 대상에 기능 적용

프록시, 프록시 패턴, 데코레이터 패턴

클라이언트와 서버

  • 클라이언트: 고객, 요청하는 주체
  • 서버: 제공해주는 주체. 클라이언트의 요청 처리

직접 호출과 간접 호출

클라이언트와 서버 개념에서 일반적으로 클라이언트가 서버를 직접 호출하고, 처리 결과를 직접 받는다. 이것을 직접 호출이라 한다.

그런데 클라이언트가 요청한 결과를 서버에 직접 요청하는 것이 아니라 어떤 대리자를 통해서 대신 간접적으로 서버에 요청할 수 있다. 이 대리자를 프록시(Proxy)라고 한다.

간접 호출을 하면 대리자는 중간에 한 가지 일 뿐만 아니라, 여러가지 일을 할 수 있다.

웹 개발을 공부하다보면 프록시라는 개념은 여러 군데에서 접할 수 있다. HTTP 프록시 서버부터 시작해서, 프로그래밍 수준에서 프록시 객체, … 규모가 다양하다. 하지만 이들 모두 근본적인 역할은 같다. 우리가 서버에 어떤 요청을 보내서 웹 페이지를 받아올 때, 프록시 서버라는 게 있다는 걸 전혀 모른체 요청을 보낸다. 하지만, 실제 요청은 수 많은 프록시 서버를 거친다.

대체 가능
객체에서도 마찬가지로, 객체에서 프록시가 되려면 클라이언트는 서버에게 요청을 한 것인지, 프록시에게 요청을 한 것인지 조차 몰라야 한다.
쉽게 이야기해서 서버와 프록시는 같은 인터페이스를 사용해야 한다. 그리고 클라이언트가 사용하는 서버 객체를 프록시 객체로 변경해도 클라이언트 코드를 변경하지 않고 동작할 수 있어야 한다.

클래스 의존 관계

클라이언트는 인터페이스에 의존하게 하여, 서버와 프록시가 같은 인터페이스를 사용한다면, DI를 사용하여 서버를 프록시가 대체할 수 있다.

프록시의 주요 기능
프록시를 통해서 할 수 있는 일은 크게 2가지로 구분할 수 있다.

  • 접근 제어
    • 권한에 따른 접근 차단
    • 캐싱
    • 지연 로딩
  • 부가 기능 추가
    • 원래 서버가 제공하는 기능에 더해서 부가 기능을 수행한다.
    • 예) 요청 값이나, 응답 값을 중간에 변형한다.
    • 예) 실행 시간을 측정해서 추가 로그를 남긴다.

GOF 디자인 패턴
둘다 프록시를 사용하는 방법이지만 GOF 디자인 패턴에서는 이 둘을 의도(intent)에 따라서 프록시 패턴과 데코레이터 패턴으로 구분한다.

  • 프록시 패턴: 접근 제어가 목적
  • 데코레이터 패턴: 새로운 기능 추가가 목적
    • 프록시라는 기술을 쓰면 프록시 패턴인게 아님. 그냥 패턴 이름을 프록시 패턴으로 붙인 것. 데코레이터 패턴도 프록시 기술 쓴다..!

프록시 패턴 - 예제 코드1

Subject 인터페이스

public interface Subject {
    String operation();
}

예제에서 Subject 인터페이스는 단순히 operation() 메서드 하나만 가지고 있다.

RealSubject

@Slf4j
public class RealSubject implements Subject {

    @Override
    public String operation() {
        log.info("실제 객체 호출");
        sleep(1000);
        return "data";
    }

    private void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

ProxyPatternClient

public class ProxyPatternClient {
    private Subject subject;

    public ProxyPatternClient(Subject subject) {
        this.subject = subject;
    }

    public void execute() {
        subject.operation();
    }
}

Test

public class ProxyPatternTest {
    @Test
    void noProxyTest() {
        RealSubject realSubject = new RealSubject();
        ProxyPatternClient client = new ProxyPatternClient(realSubject);
        client.execute();
        client.execute();
        client.execute();
    }
}

약 3초의 수행 시간을 거쳐 완료된다. 그런데, 매 호출마다 반환되는 데이터가 같다. 이 데이터가 한번 조회하면 변하지 않는 데이터라면 어딘가에 보관해두고 이미 조회한 데이터를 사용하는 것이 성능상 좋다. (캐시) 프록시 객체로 캐시를 적용해보자.

프록시 패턴 - 예제 코드2

@Slf4j
public class CacheProxy implements Subject {

    private Subject target;
    private String cacheValue;

    public CacheProxy(Subject target) {
        this.target = target;
    }

    // 값을 가지고 있으면 바로 반환
    @Override
    public String operation() {
        log.info("프록시 호출");
        if (cacheValue == null) {
            cacheValue = target.operation();
        }
        return cacheValue; 
    }
}
  • target: 클라이언트가 프록시를 호출하면 프록시가 최종적으로 실제 객체를 호출해야 한다. 따라서 내부에 실제 객체의 참조를 가지고 있어야 한다. 이렇게 프록시가 호출하는 대상을 target 이라 한다.

런타임 객체 의존관계만 바꿔서 테스트해보자

@Test
void cacheProxyTest() {
    Subject realSubject = new RealSubject();
    Subject cacheProxy = new CacheProxy(realSubject);
    ProxyPatternClient client = new ProxyPatternClient(cacheProxy);
    client.execute();
    client.execute();
    client.execute();
}

client의 최초 호출시에만 realSubject.operation()이 호출되고, 이후에는 캐시 값에 의해 즉시 반환된다. (수행시간 약 1초)

  • 프록시 패턴의 핵심은 RealSubject 코드와 클라이언트 코드를 전혀 변경하지 않고, 프록시를 도입해서 접근 제어를 했다는 점이다.
  • 또한 클라이언트는 프록시 객체가 주입되었는지, 실제 객체가 주입되었는지 알지 못한다.

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

데코레이터 패턴은 새로운 기능 추가가 목적이었다. 실제 객체의 반환값에 특정 문자열을 더해주는 프록시를 만들어보자.

public interface Component {
    String operation();
}
@Slf4j
public class RealComponent implements Component {
    @Override
    public String operation() {
        log.info("RealComponent 실행");
        return "data";
    }
}
  • 단순히 로그를 남기고 "data" 문자를 반환한다.
@Slf4j
public class DecoratorPatternClient {
    private Component component;

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

    public void execute() {
        String result = component.operation();
        log.info("result={}", result);
    }
}

테스트

@Slf4j
public class DecoratorPatternTest {

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

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

위 코드에, 프록시를 활용하여 부가 기능을 더해보자. 이렇게 프록시로 부가 기능을 추가하는 것을 데코레이터 패턴이라 한다.

@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;
    }
}
@Test
void decorator1() {
    Component realComponent = new RealComponent();
    Component messageDecorator = new MessageDecorator(realComponent);
    DecoratorPatternClient client = new DecoratorPatternClient(messageDecorator);
    client.execute();
}

client messageDecorator realComponent 의 객체 의존 관계를 만들고 client.execute()를 호출한다.

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

메시지를 꾸며주는 기존 기능에, 실행 시간을 측정하는 기능을 추가해보자.
프록시 체인 구조로 구성한다.

@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 result;
    }
}

테스트

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

client timeDecorator messageDecorator realComponent 의 객체 의존관계를 설정하고, 실행한다.

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

여기서 생각해보면 Decorator 기능에 일부 중복이 있다. 꾸며주는 역할을 하는 Decorator 들은 스스로 존재할 수 없다. 항상 꾸며줄 대상이 있어야 한다. 따라서 내부에 호출 대상인 component 를 가지고 있어야 한다. 그리고 component 를 항상 호출해야 한다. 이 부분이 중복이다. 이런 중복을 제거하기 위해 component 를 속성으로 가지고 있는 Decorator 라는 추상 클래스를 만드는 방법도 고민할 수 있다. 이렇게 구분하면 실제 컴포넌트와 데코레이터를 명확히 구분할 수 있다. (GOF에서 설명하는 예제)

물론, 추상 클래스를 만들고 안만들고에 따라서 디자인 패턴이 구분되는 건 아니다.

  • 프록시 패턴의 의도: 다른 개체에 대한 접근을 제어하기 위해 대리자를 제공
  • 데코레이터 패턴의 의도: 객체에 추가 책임(기능)을 동적으로 추가하고, 기능 확장을 위한 유연한 대안 제공

의도에 집중!!!

인터페이스 기반 프록시 - 적용

로그 추적기 예시에 프록시를 적용해보자!

@RequiredArgsConstructor
public class OrderControllerInterfaceProxy implements OrderControllerV1 {

    private final OrderControllerV1 target;
    private final LogTrace logTrace;

    @Override
    public String request(String itemId) {
        TraceStatus status = null;
        try {
            status = logTrace.begin("OrderController.request()");
            //target 호출
            String result = target.request(itemId);
            logTrace.end(status);
            return result;
        } catch (Exception e) {
            logTrace.exception(status, e);
            throw e;
        }
    }

    @Override
    public String noLog() {
        return target.noLog();
    }
}

이런식으로 나머지 계층도 구현한다.

수동 빈 등록 - 프록시 객체

@Configuration
public class InterfaceProxyConfig {

    @Bean
    public OrderControllerV1 orderController(LogTrace logTrace) {
        OrderControllerV1Impl controllerImpl = new OrderControllerV1Impl(orderService(logTrace));
        return new OrderControllerInterfaceProxy(controllerImpl, logTrace);
    }

    @Bean
    public OrderServiceV1 orderService(LogTrace logTrace) {
        OrderServiceV1Impl serviceImpl = new OrderServiceV1Impl(orderRepository(logTrace));
        return new OrderServiceInterfaceProxy(serviceImpl, logTrace);
    }

    @Bean
    public OrderRepositoryV1 orderRepository(LogTrace logTrace) {
        OrderRepositoryV1Impl repositoryImpl = new OrderRepositoryV1Impl();
        return new OrderRepositoryInterfaceProxy(repositoryImpl, logTrace);
    }
}
  • 프록시를 생성하고 프록시를 실제 스프링 빈 대신 등록한다. 실제 객체는 스프링 빈으로 등록하지 않는다.
    • 실제 객체는 프록시 내부에 실제 객체 참조를 가지고 동작한다.

  • 이제 실제 객체는 스프링 컨테이너와는 상관이 없다. 실제 객체는 프록시 객체를 통해서 참조될 뿐이다.
  • 프록시 객체는 스프링 컨테이너가 관리하고 자바 힙 메모리에도 올라간다. 반면에 실제 객체는 자바 힙 메모리에는 올라가지만 스프링 컨테이너가 관리하지는 않는다.

//@Import({AppV1Config.class, AppV2Config.class})
@Import(InterfaceProxyConfig.class)
@SpringBootApplication(scanBasePackages = "hello.proxy.app")
public class ProxyApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProxyApplication.class, args);
    }

    @Bean
    public LogTrace logTrace() {
        return new ThreadLocalLogTrace();
    }
}

구체 클래스 기반 프록시 - 예제

구체 클래스 기반의 수동 빈 등록 방식을 사용하는 경우에, 프록시 객체를 어떻게 만들까?
자바의 다형성은 인터페이스 뿐만 아니라 모든 클래스에서 적용된다. (final 선언 제외) 인터페이스가 없어도 상속하여 프록시를 만들 수 있다.

@Slf4j
public class TimeProxy extends ConcreteLogic {

    private ConcreteLogic realLogic;

    public TimeProxy(ConcreteLogic realLogic) {
        this.realLogic = realLogic;
    }

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

테스트

@Test
void addProxy() {
    ConcreteLogic concreteLogic = new ConcreteLogic();
    TimeProxy timeProxy = new TimeProxy(concreteLogic);
    ConcreteClient client = new ConcreteClient(timeProxy);
    client.execute();
}

ConcreteClient 는 ConcreteLogic 을 의존하는데, 다형성에 의해 ConcreteLogic에 concreteLogic 도 들어갈 수 있고, timeProxy 도 들어갈 수 있다.

구체 클래스 기반 프록시 - 적용

상품 주문 예제에 적용해보자.

public class OrderServiceConcreteProxy extends OrderServiceV2 {

    private final OrderServiceV2 target;
    private final LogTrace logTrace;

    public OrderServiceConcreteProxy(OrderServiceV2 target, LogTrace logTrace) {
        super(null);
        this.target = target;
        this.logTrace = logTrace;
    }

    @Override
    public void orderItem(String itemId) {
        TraceStatus status = null;
        try {
            status = logTrace.begin("OrderService.orderItem()");
            //target 호출
            target.orderItem(itemId);
            logTrace.end(status);
        } catch (Exception e) {
            logTrace.exception(status, e);
            throw e;
        }
    }
}

클래스 기반 프록시의 단점

  • super(null): OrderServiceV2 : 자바 기본 문법에 의해 자식 클래스를 생성할 때는 항상 super()로 부모 클래스의 생성자를 호출해야 한다. 이 부분을 생략하면 기본 생성자가 호출된다. 그런데 부모 클래스인 OrderServiceV2 는 기본 생성자가 없고, 생성자에서 파라미터 1개를 필수로 받는다. 따라서 파라미터를 넣어서 super(..)를 호출해야 한다.
  • 프록시는 부모 객체의 기능을 사용하지 않기 때문에 super(null)을 입력해도 된다.
  • 인터페이스 기반 프록시는 이런 고민을 하지 않아도 된다.

이런식으로 Repository, Controller를 만든 뒤 새로운 Config 클래스에서 빈을 등록하고 스프링부트에 적용해주자.

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

원본 코드를 수정하지 않았다는 점에서 두 방식 모두 요구사항을 충족시켰다.

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

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

인터페이스 기반 프록시가 좋아보이지만, 모든 클래스가 인터페이스를 도입해야 할 이유는 없다.(구현 변경이 거의 발생하지 않는 경우 구체 클래스를 바로 사용하는게 더 실용적일 수 있음)
결국, 두 가지 상황에 대응할 수 있어야 한다.

남아있는 문제점
요구사항은 만족시켰지만, 프록시 클래스를 너무 많이 만들어야 한다는 문제가 생겼다. 잘 보면 프록시 클래스가 하는 일은 LogTrace를 사용하는 것인데, 그 로직이 모두 똑같다. 대상 클래스만 다를 뿐이다. 만약 적용해야 하는 대상 클래스가 100개라면 프록시 클래스도 100개를 만들어야한다. 프록시 클래스를 하나만 만들어 사용할 수는 없을까? 다음 챕터의 동적 프록시 기술

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

'Spring > AOP' 카테고리의 다른 글

[Spring AOP] 빈 후처리기  (0) 2025.07.06
[Spring AOP] 스프링이 지원하는 프록시  (0) 2025.07.06
[Spring AOP] 동적 프록시 기술  (0) 2025.07.06
[Spring AOP] 템플릿 메서드 패턴과 콜백 패턴  (0) 2025.07.06
[Spring AOP] 쓰레드 로컬 - Thread Local  (0) 2025.07.06
'Spring/AOP' 카테고리의 다른 글
  • [Spring AOP] 스프링이 지원하는 프록시
  • [Spring AOP] 동적 프록시 기술
  • [Spring AOP] 템플릿 메서드 패턴과 콜백 패턴
  • [Spring AOP] 쓰레드 로컬 - Thread Local
lumana
lumana
배움을 나누는 공간 https://github.com/bebeis
  • lumana
    Brute force Study
    lumana
  • 전체
    오늘
    어제
    • 분류 전체보기 (462)
      • 개발 일지 (0)
        • Performance (0)
        • TroubleShooting (0)
        • Refactoring (0)
        • Code Style, Convetion (0)
        • Architecture (0)
      • Software Engineering (36)
        • Test (8)
        • 이론 (18)
        • Clean Code (10)
      • Java (72)
        • Basic (5)
        • Core (21)
        • Collection (7)
        • 멀티스레드&동시성 (13)
        • IO, Network (8)
        • Reflection, Annotation (3)
        • Modern Java(8~) (13)
        • JVM (2)
      • Spring (53)
        • Framework (12)
        • MVC (23)
        • Transaction (3)
        • AOP (11)
        • Boot (0)
        • AI (0)
      • DB Access (16)
        • Jdbc (1)
        • JdbcTemplate (0)
        • JPA (14)
        • Spring Data JPA (0)
        • QueryDSL (0)
      • Computer Science (130)
        • Data Structure (27)
        • OS (14)
        • Database (10)
        • Network (21)
        • 컴퓨터구조 (6)
        • 시스템 프로그래밍 (23)
        • Algorithm (29)
      • HTTP (8)
      • Infra (1)
        • Docker (1)
      • 프로그래밍언어론 (15)
      • Programming Language(Sub) (76)
        • Kotlin (0)
        • Python (25)
        • C++ (51)
        • JavaScript (0)
      • FE (11)
        • HTML (1)
        • CSS (9)
        • React (0)
        • Application (1)
      • Unix_Linux (0)
        • Common (0)
      • PS (13)
        • BOJ (7)
        • Tip (3)
        • 프로그래머스 (0)
        • CodeForce (0)
      • Book Review (4)
      • Math (3)
        • Linear Algebra (3)
      • AI (7)
        • DL (0)
        • ML (0)
        • DA (0)
        • Concepts (7)
      • 프리코스 (4)
      • Project Review (6)
      • LegacyPosts (11)
      • 모니터 (0)
      • Diary (0)
  • 블로그 메뉴

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

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
lumana
[Spring AOP] 프록시 패턴과 데코레이터 패턴
상단으로

티스토리툴바