[김영한 스프링 review] 스프링 핵심 원리 - 고급편 (3) - 프로젝트 준비

조갱·2024년 5월 6일
0

스프링 강의

목록 보기
15/23

이전 포스팅 (프록시 패턴과 데코레이터 패턴) 에서 알아본 프록시 패턴을 바탕으로
기존에 로그 찍기 예제를 리팩토링 해보자.
우선은 실습을 위해 준비할 코드가 많아서, 코드를 먼저 준비하고 가자.
실제 프록시 실습은 다음 포스팅부터,,

아래 예제 코드는 깃허브 에 올라가있다.

요구사항 추가

프록시를 충분히 경험하기 위해, 기존 요구사항에 몇가지를 추가해보자.

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

v1 - 인터페이스가 있는 구현 클래스에 적용

OrderControllerInterface

import org.springframework.web.bind.annotation.*;

// Spring 3.0 미만에서만 정상 동작
@RequestMapping //스프링은 @Controller 또는 @RequestMapping 이 있어야 스프링 컨트롤러로 인식
@ResponseBody // ResponseBody 어노테이션은 인터페이스에 사용해도 된다.
public interface OrderControllerInterface {
    @GetMapping("/v1/request")
    String request(@RequestParam("itemId") String itemId);

    @GetMapping("/v1/no-log")
    String noLog();
}

OrderControllerImpl

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class OrderControllerImpl implements OrderControllerInterface {
    private final OrderServiceInterface orderService;

    public OrderControllerImpl(OrderServiceInterface orderService) {
        this.orderService = orderService;
    }

    @Override
    public String request(String itemId) {
        orderService.orderItem(itemId);
        return "ok";
    }

    @Override
    public String noLog() {
        return "ok";
    }
}

OrderServiceInterface

public interface OrderServiceInterface {
    void orderItem(String itemId);
}

OrderServiceImpl

public class OrderServiceImpl implements OrderServiceInterface {
    private final OrderRepositoryInterface orderRepository;

    public OrderServiceImpl(OrderRepositoryInterface orderRepository) {
        this.orderRepository = orderRepository;
    }

    @Override
    public void orderItem(String itemId) {
        orderRepository.save(itemId);
    }
}

OrderRepositoryInterface

public interface OrderRepositoryInterface {
    void save(String itemId);
}

OrderRepositoryImpl

public class OrderRepositoryImpl implements OrderRepositoryInterface {
    @Override
    public void save(String itemId) {
        //저장 로직
        if (itemId.equals("ex")) {
            throw new IllegalStateException("예외 발생!");
        }
        sleep(1000);
    }

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

AppV1Config

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppV1Config {
    @Bean
    public OrderControllerInterface orderControllerInterface() {
        return new OrderControllerImpl(orderServiceInterface());
    }

    @Bean
    public OrderServiceInterface orderServiceInterface() {
        return new OrderServiceImpl(orderRepositoryInterface());
    }

    @Bean
    public OrderRepositoryInterface orderRepositoryInterface() {
        return new OrderRepositoryImpl();
    }
}

Application

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;

@Import(AppV1Config.class) // 수동으로 설정파일 주입
// 아래 scanBasePackages는 설정 시 주의해야 한다.
// 이 예제에서는 설정 파일을 수동으로 주입하기 때문에, @Configuration 이 컴포넌트 스캔되면 안된다.
// 따라서 위 예제인 AppV1Config 는 hello.proxy.configuration 패키지에 두고
// 나머지 로직은 hello.proxy.app 과 같이 패키지를 분리하고, scanBasePackages 에는 hello.proxy.app 만 스캔하는 등의 설정이 필요하다.
@SpringBootApplication(scanBasePackages = "{서비스로직 패키지명}") // 주의
public class ProxyApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProxyApplication.class, args);
    }
}

이렇게 하면 드디어 기본 프로젝트 준비가 완료된다.

v2 - 인터페이스가 없는 구체 클래스에 적용

OrderControllerConcrete

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Slf4j
@RequestMapping
@ResponseBody
public class OrderControllerConcrete {
    private final OrderServiceConcrete orderService;

    public OrderControllerConcrete(OrderServiceConcrete orderService) {
        this.orderService = orderService;
    }

    @GetMapping("/v2/request")
    public String request(String itemId) {
        orderService.orderItem(itemId);
        return "ok";
    }

    @GetMapping("/v2/no-log")
    public String noLog() {
        return "ok";
    }
}

OrderServiceConcrete

public class OrderServiceConcrete {
    private final OrderRepositoryConcrete orderRepository;

    public OrderServiceConcrete(OrderRepositoryConcrete orderRepository) {
        this.orderRepository = orderRepository;
    }

    public void orderItem(String itemId) {
        orderRepository.save(itemId);
    }
}

OrderRepositoryConcrete

public class OrderRepositoryConcrete {
    public void save(String itemId) {
        //저장 로직
        if (itemId.equals("ex")) {
            throw new IllegalStateException("예외 발생!");
        }
        sleep(1000);
    }

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

AppV2Config

import hello.proxy.app.v2.OrderControllerV2;
import hello.proxy.app.v2.OrderRepositoryV2;
import hello.proxy.app.v2.OrderServiceV2;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppV2Config {

    @Bean
    public OrderControllerConcrete orderControllerConcrete() {
        return new OrderControllerConcrete(orderServiceConcrete());
    }

    @Bean
    public OrderServiceConcrete orderServiceConcrete() {
        return new OrderServiceConcrete(orderRepositoryConcrete());
    }

    @Bean
    public OrderRepositoryConcrete orderRepositoryConcrete() {
        return new OrderRepositoryConcrete();
    }
}

Application

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;

@Import({AppV1Config.class, AppV2Config.class})
@SpringBootApplication(scanBasePackages = "{서비스로직 패키지명}")
public class ProxyApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProxyApplication.class, args);
    }
}

컴포넌트 스캔 대상에 기능 적용

OrderControllerScan


import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
public class OrderControllerScan {

    private final OrderServiceScan orderService;

    public OrderControllerScan(OrderServiceScan orderService) {
        this.orderService = orderService;
    }

    @GetMapping("/v3/request")
    public String request(String itemId) {
        orderService.orderItem(itemId);
        return "ok";
    }

    @GetMapping("/v3/no-log")
    public String noLog() {
        return "ok";
    }
}

OrderServiceScan

import org.springframework.stereotype.Service;

@Service
public class OrderServiceScan {

    private final OrderRepositoryScan orderRepository;

    public OrderServiceScan(OrderRepositoryScan orderRepository) {
        this.orderRepository = orderRepository;
    }

    public void orderItem(String itemId) {
        orderRepository.save(itemId);
    }
}

OrderRepositoryScan

import org.springframework.stereotype.Repository;

@Repository
public class OrderRepositoryScan {

    public void save(String itemId) {
        //저장 로직
        if (itemId.equals("ex")) {
            throw new IllegalStateException("예외 발생!");
        }
        sleep(1000);
    }

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

Application

이전에 설정한 Application 코드에서 scanBasePackage에
위 OrderXXXScan 이 포함된 패키지명을 입력하면 별도 수정 없이 정상 작동한다.

OrderXXXScan은 컴포넌트 스캔을 사용하므로 AppVxConfig 는 필요하지 않다.

profile
A fast learner.

0개의 댓글