본문 바로가기
Spring/spring AOP

스프링 코어2 - 프록시 학습을 위한 프로젝트 만들기

by 킹차니 2022. 1. 6.

프록시를 학습하기 위한 스프링 프로젝트의 의존성은 아래와 같다.

plugins {
   id 'org.springframework.boot' version '2.5.5'
   id 'io.spring.dependency-management' version '1.0.11.RELEASE'
   id 'java'
}

group = 'hello'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
   compileOnly {
      extendsFrom annotationProcessor
   }
}

repositories {
   mavenCentral()
}

dependencies {
   implementation 'org.springframework.boot:spring-boot-starter-web'
   compileOnly 'org.projectlombok:lombok'
   annotationProcessor 'org.projectlombok:lombok'
   testImplementation 'org.springframework.boot:spring-boot-starter-test'
   //테스트에서 lombok 사용
   testCompileOnly 'org.projectlombok:lombok'
   testAnnotationProcessor 'org.projectlombok:lombok'
}

test {
   useJUnitPlatform()
}

 

예제를 3가지 상황으로 만들어 볼 것이다.

V1 - 인터페이스와 구현 클래스 - 스프링 빈으로 수동 등록

V2 - 인터페이스 없는 구체 클래스 - 스프링 빈으로 수동 등록

V3 - 컴포넌트 스캔으로 스프링 빈 자동 등록

 

 


 

V1 - 인터페이스와 구현 클래스 - 스프링 빈으로 수동 등록

 

주문 서비스를 위한 Controller, Service, Repository를 만들었다. 특히 Controller도 인터페이스를 만들었다는 것이 특이점이다. 그 외에는 모두 이전과 같다.

 

Controller Interface

@RequestMapping//스프링은 @Controller 또는 @RequestMapping이 있어야 스프링 컨트롤러로 인식.
@ResponseBody
public interface OrderControllerV1 {

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

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

 

Controller 

public class OrderControllerV1Impl implements OrderControllerV1{

    private final OrderServiceV1 orderService;

    public OrderControllerV1Impl(OrderServiceV1 orderService) {
        this.orderService = orderService;
    }

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

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

 

Service Interface

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

Service

public class OrderServiceV1Impl implements OrderServiceV1{

    private final OrderRepositoryV1 orderRepository;

    public OrderServiceV1Impl(OrderRepositoryV1 orderRepository) {
        this.orderRepository = orderRepository;
    }

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

 

Repository Interface

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

Repository

public class OrderRepositoryV1Impl implements OrderRepositoryV1{

    @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();
        }
    }
}

 

그리고 현재 @Controller, @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") //주의
public class ProxyApplication {
   public static void main(String[] args) {
      SpringApplication.run(ProxyApplication.class, args);
   }
}
@Import(Appv1Config.class) : 클래스를 스프링 빈으로 등록한다. 여기서는 AppV1Config.class를 스프링 빈으로 등록한다. 일반적으로 @Configuration 같은 설정 파일을 등록할 때 사용한다. (물론 스프링 빈을 등록할 때도 사용된다.)
@SpringBootApplication(scanBeansPackages = "hello.proxy.app") : @ComponentScan의 기능과 같다. 컴포넌트 스캔을 시작할 위치를 지정한다. 이 값을 설정하면 해당 패키지와 그 하위 패키지를 컴포넌트 스캔한다. 이 값을 설정하지 않으면 현재 ProxyApplication이 있는 패키지와 그 하위 패키지를 스캔한다. 

위와 같이 @Import와 @SpringBootApplication(scanBeansPackages="")를 설정한 이유는 각 V1, V2, V3버전 마다 다른 Config 클래스를 빈으로 떼었다가 붙였다가를 하기 위함이다.

 

 


 

 

V2 - 인터페이스 없는 구체 클래스 - 스프링 빈으로 수동 등록

 

 

 

Controller

@Slf4j
@ResponseBody
@RequestMapping
public class OrderControllerV2{

    private final OrderServiceV2 orderService;

    public OrderControllerV2(OrderServiceV2 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";
    }
}

 

Service

public class OrderServiceV2{

    private final OrderRepositoryV2 orderRepository;

    public OrderServiceV2(OrderRepositoryV2 orderRepository) {
        this.orderRepository = orderRepository;
    }

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

 

Repository

public class OrderRepositoryV2{

    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();
        }
    }
}

 

메인 함수

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

 

 

 


 

 

V3 - 컴포넌트 스캔으로 스프링 빈 자동 등록

 

Controller

@Slf4j
@RestController
public class OrderControllerV3 {
    private final OrderServiceV3 orderService;

    public OrderControllerV3(OrderServiceV3 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";
    }
}

 

Service

@Service
public class OrderServiceV3 {
    private final OrderRepositoryV3 orderRepository;

    public OrderServiceV3(OrderRepositoryV3 orderRepository) {
        this.orderRepository = orderRepository;
    }

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

 

Repository

@Repository
public class OrderRepositoryV3 {

    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();
        }
    }
}

 위의 클래스들은 @Controller, @Service, @Repository를 사용하여 빈으로 등록해주고 있으므로 따로 Config클래스가 필요하지 않다.

 

 

 

다음부터 각 V1, V2, V3에 프록시를 도입해보자.

 

 

 

 

 

김영한님의 인프런 강의와 PDF를 바탕으로 정리하였습니다.