내가 공부한것을 올리며, 중요한 단원은 저 자신도 곱씹어 볼겸 상세히 기록하고 얕은부분들은 가겹게 포스팅 하겠습니다.
이번 시간에는 우선 예제 프로젝트부터 만들어보자!
디자인 패턴에 대한 글을 다음 게시물 부터 시작할 예정이다.
1. 예제 프로젝트 만들기 v1
이번 글 에서는 다음 글에서 설명할 데코레이터, 프록시 패턴을 위한 준비 예제 작업 시간이다.
설명보다는 단순 코드만 많은 점 양해 부탁 드립니다.
예제는 크게 3가지 상황으로 만든다.
v1 - 인터페이스와 구현 클래스 - 스프링 빈으로 수동 등록
v2 - 인터페이스 없는 구체 클래스 - 스프링 빈으로 수동 등록
v3 - 컴포넌트 스캔으로 스프링 빈 자동 등록
V1 - 인터페이스와 구현 클래스 - 스프링 빈으로 수동 등록
우선 코드는 다음과 같다.
▶ OrderRepositoryV1
public interface OrderRepositoryV1 {
void save(String itemId);
}
===================================================================
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();
}
}
}
▶ OrderServiceV1
public interface OrderServiceV1 {
void orderItem(String itemId);
}
===================================================================
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);
}
}
▶ OrderContrllerV1
Spring은 @RequestMapping만 추가해도 컨트롤러로 인식해준다!
@RequestMapping
@ResponseBody
public interface OrderControllerV1 {
@GetMapping("/v1/request")
String request(@RequestParam("itemId") String itemId);
@GetMapping("/v1/no-log")
String noLog();
}
===================================================================
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 null;
}
}
Controller는 조금 설명이 필요하다.
@RequestMapping
스프링MVC는 타입에 @Controller 또는 @RequestMapping 애노테이션이 있어야 스프링 컨트롤러로 인식한다. 그리고 스프링 컨트롤러로 인식해야, HTTP URL이 매핑되고 동작한다. 이 애노테이션은 인터페이스에 사용해도 된다.
@ResponseBody
HTTP 메시지 컨버터를 사용해서 응답한다. 이 애노테이션은 인터페이스에 사용해도 된다.
@RequestParam("itemId") String itemId
인터페이스에는 @RequestParam("itemId") 의 값을 생략하면 itemId 단어를 컴파일 이후 자바 버전에 따라 인식하지 못할 수 있다.
인터페이스에서는 꼭 넣어주자. 클래스에는 생략해도 대부분 잘 지원된다.
코드를 보면 request() , noLog() 두 가지 메서드가 있다. request() 는 LogTrace 를 적용할 대상이고, noLog() 는 단순히 LogTrace 를 적용하지 않을 대상이다.
이제 Bean을 등록하기 위한 Config 파일을 만들어보자!
@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();
}
}
Bean을 등록시키는 간단한 코드이다.
이후 @Import(AppV1Config.class)를 사용하여 config 빈을 등록시켜 줍니다.
@Import(AppV1Config.class)
@SpringBootApplication(scanBasePackages = "hello.proxy.app") //주의
public class ProxyApplication {
public static void main(String[] args) {
SpringApplication.run(ProxyApplication.class, args);
}
}
원래였다면 ProxyApplication 이하의 모든 페키지는 자동 스캔 되기 때문에 따로 @Import로 AppV1Config를 Import할 필요가 없다.
하지만 위에서는 이를 사용하고 있다.
다음 페키지 구조를 살펴보자.
ProxyApplication 에서 @SpringBootApplication(scanBasePackages = "hello.proxy.app") 로 설정 되어있기 때문에 빨간박스 안의 v1을 전부 등록한다.
하지만 이때 AppV1Config는 자동으로 등록되지 않는다!
scanBasePackages=hello.proxy.app 로 설정했기 때문이다!
원래였다면 @Configuration 은 내부에 @Component 애노테이션을 포함하고 있어서 컴포넌트 스캔의 대상이 된다.
base scan 범위가 달라져서 자동 스캔되지 않는 것 이다.
따라서 @Import 를 사용해서 AppV1Config 파일을 추가로 등록시켜주는 것 이다.
이후 config파일을 v2, v3로 점진적으로 발전시키면서 원하는 config만 등록하려는 목적이다.
지금은 AppV1Config.class 를 @Import 를 사용해서 설정하지만 이후에 다른 것을 설정한다는 이야기이다.
2. 예제 프로젝트 만들기 v2
v2 - 인터페이스 없는 구체 클래스 - 스프링 빈으로 수동 등록
이번에는 인터페이스가 없는 Controller, Service, Repository를 직접 등록해보자!
▶ OrderRepositoryV2
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();
}
}
}
▶ OrderServiceV2
public class OrderServiceV2 {
private final OrderRepositoryV2 orderRepository;
public OrderServiceV2(OrderRepositoryV2 orderRepository) {
this.orderRepository = orderRepository;
}
public void orderItem(String itemId) {
orderRepository.save(itemId);
}
}
▶ OrderControllerV2
@Slf4j
@RequestMapping
@ResponseBody
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 "no log";
}
}
@RequestMapping : Spring은 @Controller 또는 @RequestMapping 애노테이션이 있어야 스프링 컨트롤러로 인식한다.
그리고 스프링 컨트롤러로 인식해야, HTTP URL이 매핑되고 동작한다.
그런데 여기서는 @Controller 를 사용하지 않고, @RequestMapping 애노테이션을 사용했다.
그 이유는 @Controller 를 사용하면 자동 컴포넌트 스캔의 대상이 되기 때문이다.
우리는 수동 빈 등록을 하는 것이 목표다. 따라서 컴포넌트 스캔과 관계 없는 @RequestMapping 를 타입에 사용했다.
▶ AppV2Config
@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();
}
}
▶ ProxyApplication
@Import({AppV1Config.class, AppV2Config.class}) // 추가된 config
@SpringBootApplication(scanBasePackages = "hello.proxy.app")
public class ProxyApplication {
public static void main(String[] args) {
SpringApplication.run(ProxyApplication.class, args);
}
}
@Import를 통해서 AppV2Config가 추가되었다.
실행시 이전 v1과 동일하게 정상작동 한다.
3. 예제 프로젝트 만들기 v3
v3 - 컴포넌트 스캔으로 스프링 빈 자동 등록
이번 시간에는 컴포넌트 스캔을 통한 빈 등록 방식을 처리해보자!
▶ OrderRepositoryV3
@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();
}
}
}
▶ OrderServiceV3
@Service
public class OrderServiceV3 {
private final OrderRepositoryV3 orderRepository;
public OrderServiceV3(OrderRepositoryV3 orderRepository) {
this.orderRepository = orderRepository;
}
public void orderItem(String itemId) {
orderRepository.save(itemId);
}
}
▶ OrderControllerV3
@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 "no log";
}
}
다음 게시물 부터 이를 활용하여 학습해 보자!
우리의 기존 로그 추적기에 다음 요구사항이 추가되었다.
원본 코드를 전혀 수정하지 않고, 로그 추적기를 적용해라.
특정 메서드는 로그를 출력하지 않는 기능 -> 보안상 일부는 로그를 출력하면 안된다.
- 다음과 같은 다양한 케이스에 적용할 수 있어야 한다.
- v1 - 인터페이스가 있는 구현 클래스에 적용
- v2 - 인터페이스가 없는 구체 클래스에 적용
- v3 - 컴포넌트 스캔 대상에 기능 적용
가장 어려문 문제는 원본 코드를 전혀 수정하지 않고, 로그 추적기를 도입하는 것이다.
이 문제를 해결하려면 프록시(Proxy)의 개념을 먼저 이해해야 한다.
'BackEnd > Spring' 카테고리의 다른 글
[Spring] 데코레이터 패턴 (0) | 2022.08.10 |
---|---|
[Spring] 프록시 패턴 (0) | 2022.08.10 |
[Spring] 템플릿 콜백 패턴 (0) | 2022.08.07 |
[Spring] 전략 패턴 (0) | 2022.08.07 |
[Spring] 템플릿 메서드 패턴 (0) | 2022.08.06 |
댓글