이전 포스팅 (프록시 패턴과 데코레이터 패턴) 에서 알아본 프록시 패턴을 바탕으로
기존에 로그 찍기 예제를 리팩토링 해보자.
우선은 실습을 위해 준비할 코드가 많아서, 코드를 먼저 준비하고 가자.
실제 프록시 실습은 다음 포스팅부터,,
아래 예제 코드는 깃허브 에 올라가있다.
프록시를 충분히 경험하기 위해, 기존 요구사항에 몇가지를 추가해보자.
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();
}
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";
}
}
public interface OrderServiceInterface {
void orderItem(String itemId);
}
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);
}
}
public interface OrderRepositoryInterface {
void save(String itemId);
}
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();
}
}
}
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();
}
}
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);
}
}
이렇게 하면 드디어 기본 프로젝트 준비가 완료된다.
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";
}
}
public class OrderServiceConcrete {
private final OrderRepositoryConcrete orderRepository;
public OrderServiceConcrete(OrderRepositoryConcrete orderRepository) {
this.orderRepository = orderRepository;
}
public void orderItem(String itemId) {
orderRepository.save(itemId);
}
}
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();
}
}
}
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();
}
}
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);
}
}
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";
}
}
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);
}
}
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 코드에서 scanBasePackage에
위 OrderXXXScan 이 포함된 패키지명을 입력하면 별도 수정 없이 정상 작동한다.
OrderXXXScan은 컴포넌트 스캔을 사용하므로 AppVxConfig 는 필요하지 않다.