Archive: 2021

0

Spring 핵심원리 고급편 - Proxy 패턴 컴포넌트 스캔으로 자동 빈 등록

목차 Spring 핵심원리 고급편 - CGLIB Spring 핵심원리 고급편 - Dynamic Proxy 2 Spring 핵심원리 고급편 - Dynamic Proxy 1 Spring 핵심원리 고급편 - 리플렉션 Spring 핵심원리 고급편 - 구체 클래스 기반 프록시 적용 2 Spring 핵심원리 고급편 - 구체 클래스 기반 프록시 Spring 핵심원리 고급편 - 인터페이스 프록시 1 Spring 핵심원리 고급편 - Decorator Pattern 2 Spring 핵심원리 고급편 - Decorator Pattern 1 Spring 핵심원리 고급편 - Proxy 패턴 컴포넌트 스캔으로 자동 빈 등록 Spring 핵심원리 고급편 - Proxy 패턴 인터페이스 없는 없는 구체 클래스 Spring 핵심원리 고급편 - Proxy 패턴 인터페이스와 구체 클래스 Spring 핵심원리 고급편 - Proxy 패턴 Spring 핵심원리 고급편 - Strategy 패턴 Spring 핵심원리 고급편 - Template Method 패턴 Spring 핵심원리 고급편 - Proxy Pattern 컴포넌트 스캔으로 자동 빈 등록@Repositorypublic 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 ex){ ex.printStackTrace(); } }} @Servicepublic class OrderServiceV3 { private final OrderRepositoryV3 orderRepository; public OrderServiceV3(OrderRepositoryV3 orderRepository) { this.orderRepository = orderRepository; } public void orderItem(String itemId) { orderRepository.save(itemId); }} @RestController@Slf4jpublic 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"; }}

0

Spring 핵심원리 고급편 - Proxy 패턴 인터페이스 없는 없는 구체 클래스

목차 Spring 핵심원리 고급편 - CGLIB Spring 핵심원리 고급편 - Dynamic Proxy 2 Spring 핵심원리 고급편 - Dynamic Proxy 1 Spring 핵심원리 고급편 - 리플렉션 Spring 핵심원리 고급편 - 구체 클래스 기반 프록시 적용 2 Spring 핵심원리 고급편 - 구체 클래스 기반 프록시 Spring 핵심원리 고급편 - 인터페이스 프록시 1 Spring 핵심원리 고급편 - Decorator Pattern 2 Spring 핵심원리 고급편 - Decorator Pattern 1 Spring 핵심원리 고급편 - Proxy 패턴 컴포넌트 스캔으로 자동 빈 등록 Spring 핵심원리 고급편 - Proxy 패턴 인터페이스 없는 없는 구체 클래스 Spring 핵심원리 고급편 - Proxy 패턴 인터페이스와 구체 클래스 Spring 핵심원리 고급편 - Proxy 패턴 Spring 핵심원리 고급편 - Strategy 패턴 Spring 핵심원리 고급편 - Template Method 패턴 Spring 핵심원리 고급편 - Proxy Pattern 인터페이스 없는 없는 구체 클래스Repositorypublic 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 ex){ ex.printStackTrace(); } }} Servicepublic class OrderServiceV2 { private final OrderRepositoryV2 orderRepository; public OrderServiceV2(OrderRepositoryV2 orderRepository) { this.orderRepository = orderRepository; } public void orderItem(String itemId) { orderRepository.save(itemId); }} Controller@Slf4j@RequestMapping@ResponseBodypublic 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"; }} Bean 등록

0

Spring 핵심원리 고급편 - Proxy 패턴 인터페이스와 구체 클래스

목차 Spring 핵심원리 고급편 - CGLIB Spring 핵심원리 고급편 - Dynamic Proxy 2 Spring 핵심원리 고급편 - Dynamic Proxy 1 Spring 핵심원리 고급편 - 리플렉션 Spring 핵심원리 고급편 - 구체 클래스 기반 프록시 적용 2 Spring 핵심원리 고급편 - 구체 클래스 기반 프록시 Spring 핵심원리 고급편 - 인터페이스 프록시 1 Spring 핵심원리 고급편 - Decorator Pattern 2 Spring 핵심원리 고급편 - Decorator Pattern 1 Spring 핵심원리 고급편 - Proxy 패턴 컴포넌트 스캔으로 자동 빈 등록 Spring 핵심원리 고급편 - Proxy 패턴 인터페이스 없는 없는 구체 클래스 Spring 핵심원리 고급편 - Proxy 패턴 인터페이스와 구체 클래스 Spring 핵심원리 고급편 - Proxy 패턴 Spring 핵심원리 고급편 - Strategy 패턴 Spring 핵심원리 고급편 - Template Method 패턴 Proxy 패턴 기능을 수행하는 실제 객체 대신 가상의 객체(Proxy) 를 생성해 로직의 흐름을 제어 하는 방법 Proxy 는 사전적인 의미로 대리자, 대변인의 의미를 갖고 있다. 프로그램 로직 실행시 실제 객체 대신 Proxy 객체에 로직을 대신 맡기게 된다.(객체의 접근을 제어한다.) 실제 객체 생성을 해당 객체를 사용하기 전 시점까지 미룰 수 있는 장점이 있다.(lazy-loading 가능) RepositoryRepository 인터페이스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 ex){ ex.printStackTrace(); } }}

0

Spring 핵심원리 고급편 - Proxy 패턴

목차 Spring 핵심원리 고급편 - CGLIB Spring 핵심원리 고급편 - Dynamic Proxy 2 Spring 핵심원리 고급편 - Dynamic Proxy 1 Spring 핵심원리 고급편 - 리플렉션 Spring 핵심원리 고급편 - 구체 클래스 기반 프록시 적용 2 Spring 핵심원리 고급편 - 구체 클래스 기반 프록시 Spring 핵심원리 고급편 - 인터페이스 프록시 1 Spring 핵심원리 고급편 - Decorator Pattern 2 Spring 핵심원리 고급편 - Decorator Pattern 1 Spring 핵심원리 고급편 - Proxy 패턴 컴포넌트 스캔으로 자동 빈 등록 Spring 핵심원리 고급편 - Proxy 패턴 인터페이스 없는 없는 구체 클래스 Spring 핵심원리 고급편 - Proxy 패턴 인터페이스와 구체 클래스 Spring 핵심원리 고급편 - Proxy 패턴 Spring 핵심원리 고급편 - Strategy 패턴 Spring 핵심원리 고급편 - Template Method 패턴 Proxy 패턴 - 실제 객체에 대한 접근 제어를 위한 디자인 패턴 기능을 수행하는 실제 (Real) 객체 대신 가상의 (Proxy) 객체 를 이용해 실제 객체 에 대한 접근을 제어 하는 디자인 패턴 프록시 패턴은 객체 지향 프로그래밍에서 사용되는 디자인 패턴 중 하나로 실제 객체와 같은 인터페이스 를 제공하는 객체를 사용하여 실제 객체에 대한 접근을 제어하는 데 사용됩니다. 즉, 프록시 객체를 사용하여 실제 객체에 대한 액세스를 제어할 수 있습니다. 프록시 객체는 실제 객체에 대한 참조를 유지하고, 클라이언트가 실제 객체에 액세스하려고 할 때 대신 실제 객체에 대한 요청을 처리합니다. 프록시 객체는 실제 객체와 같은 인터페이스를 구현하므로, 클라이언트는 프록시 객체와 실제 객체를 동일한 방식으로 사용할 수 있습니다. 프록시 패턴의 사용 사례로는 다음과 같은 것들이 있습니다. 원격 객체에 대한 액세스 원격 객체에 대한 액세스를 제어하려면, 클라이언트는 원격 객체에 직접 액세스하지 않고, 원격 객체를 대신하여 프록시 객체를 사용합니다. 이때 프록시 객체는 원격 객체에 대한 액세스를 제어하고, 원격 객체에 대한 요청을 처리합니다. 보안 프록시 객체를 사용하여 실제 객체에 대한 액세스를 제어하면, 보안상 이점이 있습니다. 프록시 객체를 사용하면, 클라이언트는 실제 객체에 직접 액세스하지 않고, 프록시 객체를 통해 액세스하므로, 실제 객체에 대한 액세스를 제어하고 보안을 유지할 수 있습니다. 비용 실제 객체에 대한 액세스 비용이 높은 경우, 프록시 객체를 사용하여 액세스 비용을 줄일 수 있습니다. 예를 들어, 원격 객체에 대한 액세스는 네트워크 비용이 들기 때문에, 원격 객체에 직접 액세스하는 것보다 프록시 객체를 사용하여 원격 객체에 대한 액세스 비용을 줄일 수 있습니다. 캐싱 프록시 객체를 사용하여 실제 객체에 대한 액세스를 캐싱할 수 있습니다. 이때 프록시 객체는 실제 객체에 대한 요청을 처리하기 전에 캐시된 결과를 반환합니다. 이를 통해, 실제 객체에 대한 액세스 비용을 줄이고, 성능을 향상시킬 수 있습니다. Proxy 패턴의 구성 요소

0

Spring 핵심원리 고급편 - Template Callback 패턴

목차 Spring 핵심원리 고급편 - CGLIB Spring 핵심원리 고급편 - Dynamic Proxy 2 Spring 핵심원리 고급편 - Dynamic Proxy 1 Spring 핵심원리 고급편 - 리플렉션 Spring 핵심원리 고급편 - 구체 클래스 기반 프록시 적용 2 Spring 핵심원리 고급편 - 구체 클래스 기반 프록시 Spring 핵심원리 고급편 - 인터페이스 프록시 1 Spring 핵심원리 고급편 - Decorator Pattern 2 Spring 핵심원리 고급편 - Decorator Pattern 1 Spring 핵심원리 고급편 - Proxy 패턴 컴포넌트 스캔으로 자동 빈 등록 Spring 핵심원리 고급편 - Proxy 패턴 인터페이스 없는 없는 구체 클래스 Spring 핵심원리 고급편 - Proxy 패턴 인터페이스와 구체 클래스 Spring 핵심원리 고급편 - Proxy 패턴 Spring 핵심원리 고급편 - Strategy 패턴 Spring 핵심원리 고급편 - Template Method 패턴 콜백이란 콜백, 콜에프터 함수란 다른 코드의 인수로서 넘겨주는 실행 가능한 코드콜백을 넘겨받는 코드는 이 콜백을 필요에 따라 즉시 실행할 수 있고, 아니면 나중에 실행할 수 있다. public interface Callback { void call();} @Slf4jpublic class TimeLogTemplate { public void execute(Callback callback){ long startTime = System.currentTimeMillis(); // 비즈니스 로직 실행 callback.call(); // 비즈니스 로직 종료 long endTime = System.currentTimeMillis(); long resultTime = endTime - startTime; log.info("resultTime = {}", resultTime); }} Template Callback Pattern 실행직접적으로 Callback 객체내 call 메소드를 실행하는 것이 아닌 TimeLogTemplate 객체에서 execute 메소드를 실행하게 되면 Callback 객체를 넘겨주고 execute 내에서 Callback 객체의 call 메소드가 실행된다. @Slf4jpublic class TemplateCallbackTest { @Test void callbackV1(){ TimeLogTemplate template = new TimeLogTemplate(); template.execute(new Callback() { @Override public void call() { log.info("비즈니스 로직 1 실행"); } }); // 람다식 적용 template.execute(() -> log.info("비즈니스 로직 2 실행")); }}

0

Spring 핵심원리 고급편 - Strategy 패턴

목차 Spring 핵심원리 고급편 - CGLIB Spring 핵심원리 고급편 - Dynamic Proxy 2 Spring 핵심원리 고급편 - Dynamic Proxy 1 Spring 핵심원리 고급편 - 리플렉션 Spring 핵심원리 고급편 - 구체 클래스 기반 프록시 적용 2 Spring 핵심원리 고급편 - 구체 클래스 기반 프록시 Spring 핵심원리 고급편 - 인터페이스 프록시 1 Spring 핵심원리 고급편 - Decorator Pattern 2 Spring 핵심원리 고급편 - Decorator Pattern 1 Spring 핵심원리 고급편 - Proxy 패턴 컴포넌트 스캔으로 자동 빈 등록 Spring 핵심원리 고급편 - Proxy 패턴 인터페이스 없는 없는 구체 클래스 Spring 핵심원리 고급편 - Proxy 패턴 인터페이스와 구체 클래스 Spring 핵심원리 고급편 - Proxy 패턴 Spring 핵심원리 고급편 - Strategy 패턴 Spring 핵심원리 고급편 - Template Method 패턴 Strategy 패턴 Strategy 패턴 은 변하지 않는 부분을 Context 에 두고 변하는 부분을 Strategy 라는 Interface 를 만들고 해당 인터페이스를 구현해 코드 중복성을 해결한다. Strategy 패턴 코드 중복 문제를 해결하기 위한 디자인 패턴 중 하나로 Context 와 Strategy 으로 구성됩니다. 공통 로직은 Context 클래스로 만들어 관리하고 서로 다른 로직들을 Strategy 인터페이스를 구현하는 방식으로 코드 중복 문제를 해결합니다. Strategy 패턴 에서 Context 클래스는 내부에 Strategy 클래스를 가지고 있습니다. 이 구조는 Context 객체가 실행될 때, 내부에 저장된 Strategy 객체를 활용하여 작업을 수행하게 됩니다. 따라서 Context 객체는 실행 시 Strategy 객체에 의존하게 됩니다. Strategy 클래스들은 서로 다른 로직을 가지고 있지만 동일한 인터페이스로 구현됩니다. 결과적으로 Context 클래스는 동일한 메소드 호출을 통해 다양한 Strategy 객체를 실행할 수 있습니다. Template Method 패턴과의 차이점은 공통적인 부분을 추상 클래스에서 관리하고 상속을 통해 변하는 부분을 구현해 코드 중복성을 해결 했습니다. Startegy Pattern 은 상속이 아니라 위임 으로 문제를 해결한다. Strategy 인터페이스 - 변하는 로직을 관리

0

객체 지향 설계 5원칙 SOLID

컴파일 언어, 인터프리터 언어, 하이브리드 언어 객체 지향 3요소 객체 지향 설계 5원칙 SOLID 객체 지향 설계 5원칙 SOLID1. SRP(Single Responsibility Principle) - 단일 책임 원칙한 클래스는 하나의 책임(역할)만 가져야 한다. 역할의 분리라고 생각하면 편하다!변경이 있을 때 파급 효과가 적으면 단일 책임 원칙을 잘 따르는 것 2. OCP(Open Close Principle) - 개방 폐쇄 원칙 확장 에는 열려 있으나 변경 에는 닫혀 있어야 한다. 기존 코드에 대한 수정은 직접적으로 하지 않고 기능을 확장, 추가 할 수 있도록 해야 한다. 인터페이스와 추상 클래스를 이용해 공통적인 작업에 대해 추상화 를 진행 상속과 구현을 이용해 기존 코드(상위 클래스)에 대한 수정 없이 기능을 확장시킬 수 있다. 이는 곧 다형성 을 이용한 기능 확장이다. OCP 문제점

0

Spring 핵심원리 고급편 - Template Method 패턴

목차 Spring 핵심원리 고급편 - CGLIB Spring 핵심원리 고급편 - Dynamic Proxy 2 Spring 핵심원리 고급편 - Dynamic Proxy 1 Spring 핵심원리 고급편 - 리플렉션 Spring 핵심원리 고급편 - 구체 클래스 기반 프록시 적용 2 Spring 핵심원리 고급편 - 구체 클래스 기반 프록시 Spring 핵심원리 고급편 - 인터페이스 프록시 1 Spring 핵심원리 고급편 - Decorator Pattern 2 Spring 핵심원리 고급편 - Decorator Pattern 1 Spring 핵심원리 고급편 - Proxy 패턴 컴포넌트 스캔으로 자동 빈 등록 Spring 핵심원리 고급편 - Proxy 패턴 인터페이스 없는 없는 구체 클래스 Spring 핵심원리 고급편 - Proxy 패턴 인터페이스와 구체 클래스 Spring 핵심원리 고급편 - Proxy 패턴 Spring 핵심원리 고급편 - Strategy 패턴 Spring 핵심원리 고급편 - Template Method 패턴 Template Method 패턴 상속을 통해 슈퍼클래스의 기능을 확장할 때 사용하는 가장 대표적인 방법. 변하지 않는 기능은 슈퍼클래스 에 만들어두고 자주 변경되며 확장할 기능은 서브클래스 에서 만들도록 한다.(토비의 스프링) 변하는 부분과 변하지 않는 부분 을 분리한다. 템플릿은 기준이 되는 거대한 틀이다. 템플릿 안에 변하지 않는 부분을 넣고, 일부 변하는 부분을 별도로 호출한다. 구조가 동일하고 중간에 핵심기능을 사용하는 코드만 다르다. Template Method 패턴 적용 전@Slf4jpublic class TemplateMethodTest { @Test void templateMethodV0(){ logic1(); logic2(); } private void logic1(){ long startTime = System.currentTimeMillis(); // 비즈니스 로직 실행 log.info("비즈니스 로직1 실행"); // 비즈니스 로직 종료 long endTime = System.currentTimeMillis(); long resultTime = endTime - startTime; log.info("resultTime = {}", resultTime); } private void logic2 (){ long startTime = System.currentTimeMillis(); // 비즈니스 로직 실행 log.info("비즈니스 로직1 실행"); // 비즈니스 로직 종료 long endTime = System.currentTimeMillis(); long resultTime = endTime - startTime; log.info("resultTime = {}", resultTime); }} Template Method 패턴 적용 후

0

Spring 핵심원리 고급편 - ThreadLocal

목차 Post not found: springboot/spring-aop/threadlocal/threadlocal-01 Post not found: springboot/spring-aop/threadlocal/threadlocal-02 Post not found: springboot/spring-aop/threadlocal/threadlocal-03 Post not found: springboot/spring-aop/threadlocal/threadlocal-04 Post not found: springboot/spring-aop/threadlocal/threadlocal-05 Post not found: springboot/spring-aop/threadlocal/threadlocal-06 ThreadLocal@Configurationpublic class LogTraceConfig {// @Bean// public LogTrace logTrace(){// return new FieldLogTrace();// } @Bean public LogTrace logTrace(){ return new ThreadLocalLogTrace(); }} Thread 별로 로그가 정확하게 나눠진 것을 확인할 수 있다. 2021-12-06 02:15:37.485 INFO 21903 --- [nio-8080-exec-1] c.e.a.t.logtrace.ThreadLocalLogTrace : [0b8754c1] OrderController.request()2021-12-06 02:15:37.485 INFO 21903 --- [nio-8080-exec-1] c.e.a.t.logtrace.ThreadLocalLogTrace : [0b8754c1] |-->OrderService.orderItem()2021-12-06 02:15:37.486 INFO 21903 --- [nio-8080-exec-1] c.e.a.t.logtrace.ThreadLocalLogTrace : [0b8754c1] | |-->OrderRepository.save()2021-12-06 02:15:38.488 INFO 21903 --- [nio-8080-exec-1] c.e.a.t.logtrace.ThreadLocalLogTrace : [0b8754c1] | |<--OrderRepository.save() time=1002ms2021-12-06 02:15:38.488 INFO 21903 --- [nio-8080-exec-1] c.e.a.t.logtrace.ThreadLocalLogTrace : [0b8754c1] |<--OrderService.orderItem() time=1003ms2021-12-06 02:15:38.488 INFO 21903 --- [nio-8080-exec-1] c.e.a.t.logtrace.ThreadLocalLogTrace : [0b8754c1] OrderController.request() time=1003ms 2021-12-06 02:15:37.648 INFO 21903 --- [nio-8080-exec-2] c.e.a.t.logtrace.ThreadLocalLogTrace : [9e39132f] OrderController.request()2021-12-06 02:15:37.648 INFO 21903 --- [nio-8080-exec-2] c.e.a.t.logtrace.ThreadLocalLogTrace : [9e39132f] |-->OrderService.orderItem()2021-12-06 02:15:37.648 INFO 21903 --- [nio-8080-exec-2] c.e.a.t.logtrace.ThreadLocalLogTrace : [9e39132f] | |-->OrderRepository.save()2021-12-06 02:15:38.652 INFO 21903 --- [nio-8080-exec-2] c.e.a.t.logtrace.ThreadLocalLogTrace : [9e39132f] | |<--OrderRepository.save() time=1004ms2021-12-06 02:15:38.653 INFO 21903 --- [nio-8080-exec-2] c.e.a.t.logtrace.ThreadLocalLogTrace : [9e39132f] |<--OrderService.orderItem() time=1005ms2021-12-06 02:15:38.653 INFO 21903 --- [nio-8080-exec-2] c.e.a.t.logtrace.ThreadLocalLogTrace : [9e39132f] OrderController.request() time=1005ms

0

Spring 핵심원리 고급편 - ThreadLocal 적용

목차 Post not found: springboot/spring-aop/threadlocal/threadlocal-01 Post not found: springboot/spring-aop/threadlocal/threadlocal-02 Post not found: springboot/spring-aop/threadlocal/threadlocal-03 Post not found: springboot/spring-aop/threadlocal/threadlocal-04 Post not found: springboot/spring-aop/threadlocal/threadlocal-05 Post not found: springboot/spring-aop/threadlocal/threadlocal-06 ThreadLocal 적용@Slf4jpublic class ThreadLocalLogTrace implements LogTrace { private static final String START_PREFIX = "-->"; private static final String COMPLETE_PREFIX = "<--"; private static final String EX_PREFIX = "<X-"; private ThreadLocal<TraceId> traceIdHolder; // traceId 동기화, 동시성 이슈 @Override public TraceStatus begin(String message) { syncTraceId(); TraceId traceId = traceIdHolder.get(); Long startTimeMs = System.currentTimeMillis(); log.info("[{}] {}{}", traceId.getId(), addSpace(START_PREFIX, traceId.getLevel()), message); return new TraceStatus(traceId, startTimeMs, message); } private void syncTraceId(){ TraceId traceId = traceIdHolder.get(); if(traceId == null){ traceIdHolder.set(new TraceId()); }else{ traceIdHolder.set(traceId.createNextId()); } } @Override public void end(TraceStatus status) { complete(status, null); } @Override public void exception(TraceStatus status, Exception e) { complete(status, e); } private void complete(TraceStatus status, Exception e) { Long stopTimeMs = System.currentTimeMillis(); long resultTimeMs = stopTimeMs - status.getStartTimeMs(); TraceId traceId = status.getTraceId(); if (e == null) { log.info("[{}] {}{} time={}ms", traceId.getId(), addSpace(COMPLETE_PREFIX, traceId.getLevel()), status.getMessage(), resultTimeMs); } else { log.info("[{}] {}{} time={}ms ex={}", traceId.getId(), addSpace(EX_PREFIX, traceId.getLevel()), status.getMessage(), resultTimeMs, e.toString()); } releaseTraceId(); } private void releaseTraceId() { TraceId traceId = traceIdHolder.get(); if(traceId.isFirstLevel()){ traceIdHolder.remove(); // destroy }else{ traceIdHolder.set(traceId.createPreviousId()); } } private static String addSpace(String prefix, int level) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < level; i++) { sb.append((i == level - 1) ? "|" + prefix : "| "); } return sb.toString(); }} class ThreadLocalLogTraceTest { ThreadLocalLogTrace trace = new ThreadLocalLogTrace(); @Test void begin_end_level2(){ TraceStatus status1 = trace.begin("hello1"); TraceStatus status2 = trace.begin("hello2"); trace.end(status2); trace.end(status1); } @Test void begin_end_level2_exception(){ TraceStatus status1 = trace.begin("hello1"); TraceStatus status2 = trace.begin("hello2"); trace.exception(status2, new IllegalStateException()); trace.exception(status1, new IllegalStateException()); }} 15:20:49.175 [Test worker] INFO com.example.advancedspring.trace.logtrace.ThreadLocalLogTrace - [fb5762b8] hello115:20:49.179 [Test worker] INFO com.example.advancedspring.trace.logtrace.ThreadLocalLogTrace - [fb5762b8] |-->hello215:20:49.180 [Test worker] INFO com.example.advancedspring.trace.logtrace.ThreadLocalLogTrace - [fb5762b8] |<--hello2 time=3ms15:20:49.180 [Test worker] INFO com.example.advancedspring.trace.logtrace.ThreadLocalLogTrace - [fb5762b8] hello1 time=6ms @Configurationpublic class LogTraceConfig {// @Bean// public LogTrace logTrace(){// return new FieldLogTrace();// } @Bean public LogTrace logTrace(){ return new ThreadLocalLogTrace(); }} Thread 별로 로그가 정확하게 나눠진 것을 확인할 수 있다. 2021-12-06 02:15:37.485 INFO 21903 --- [nio-8080-exec-1] c.e.a.t.logtrace.ThreadLocalLogTrace : [0b8754c1] OrderController.request()2021-12-06 02:15:37.485 INFO 21903 --- [nio-8080-exec-1] c.e.a.t.logtrace.ThreadLocalLogTrace : [0b8754c1] |-->OrderService.orderItem()2021-12-06 02:15:37.486 INFO 21903 --- [nio-8080-exec-1] c.e.a.t.logtrace.ThreadLocalLogTrace : [0b8754c1] | |-->OrderRepository.save()2021-12-06 02:15:38.488 INFO 21903 --- [nio-8080-exec-1] c.e.a.t.logtrace.ThreadLocalLogTrace : [0b8754c1] | |<--OrderRepository.save() time=1002ms2021-12-06 02:15:38.488 INFO 21903 --- [nio-8080-exec-1] c.e.a.t.logtrace.ThreadLocalLogTrace : [0b8754c1] |<--OrderService.orderItem() time=1003ms2021-12-06 02:15:38.488 INFO 21903 --- [nio-8080-exec-1] c.e.a.t.logtrace.ThreadLocalLogTrace : [0b8754c1] OrderController.request() time=1003ms

0

Spring 핵심원리 고급편 - ThreadLocal

목차 Post not found: springboot/spring-aop/threadlocal/threadlocal-01 Post not found: springboot/spring-aop/threadlocal/threadlocal-02 Post not found: springboot/spring-aop/threadlocal/threadlocal-03 Post not found: springboot/spring-aop/threadlocal/threadlocal-04 Post not found: springboot/spring-aop/threadlocal/threadlocal-05 Post not found: springboot/spring-aop/threadlocal/threadlocal-06 ThreadLocal ThreadLocal은 해당 Thread 만 접근할 수 있는 저장소다ThreadLocal을 사용하게 되면 각 Thread마다 별도의 저장소를 제공한다. 사용법 값 저장 : ThreadLocal.set(value) 값 조회 : ThreadLocal.get() 값 삭제 : ThreadLocal.remove() 해당 Thread 가 ThreadLocal 을 모두 사용하고 나면 ThreadLocal.remove() 를 호출해 ThreadLocal 에 저장된 값을 제거해 줘야 한다. @Slf4jpublic class ThreadLocalService { private ThreadLocal<String> nameStore = new ThreadLocal<>(); public String logic(String name) { log.info("저장 name={} -> nameStore = {}", name, nameStore.get()); nameStore.set(name); sleep(1000); log.info("조회 nameStore = {}", nameStore.get()); return nameStore.get(); } private void sleep(int millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { e.printStackTrace(); } }} @Slf4jpublic class ThreadLocalServiceTest { private ThreadLocalService service = new ThreadLocalService(); @Test public void field(){ log.info("main start"); Runnable userA = () -> { service.logic("userA"); }; Runnable userB = () -> { service.logic("userB"); }; Thread threadA = new Thread(userA); threadA.setName("thread - A"); Thread threadB = new Thread(userB); threadB.setName("thread - B"); threadA.start(); sleep(100); threadB.start(); sleep(2000); // 메인 Thread 종료 대기 } private void sleep(int millis){ try { Thread.sleep(millis); }catch (Exception e){ e.printStackTrace(); } }} 15:09:42.290 [Test worker] INFO com.example.advancedspring.trace.threadlocal.ThreadLocalServiceTest - main start15:09:42.293 [thread - A] INFO com.example.advancedspring.trace.threadlocal.code.ThreadLocalService - 저장 name=userA -> nameStore = null15:09:42.397 [thread - B] INFO com.example.advancedspring.trace.threadlocal.code.ThreadLocalService - 저장 name=userB -> nameStore = null15:09:43.301 [thread - A] INFO com.example.advancedspring.trace.threadlocal.code.ThreadLocalService - 조회 nameStore = userA15:09:43.400 [thread - B] INFO com.example.advancedspring.trace.threadlocal.code.ThreadLocalService - 조회 nameStore = userB15:09:44.402 [Test worker] INFO com.example.advancedspring.trace.threadlocal.ThreadLocalServiceTest - main exit

0

Spring 핵심원리 고급편 - 동시성 이슈

목차 Post not found: springboot/spring-aop/threadlocal/threadlocal-01 Post not found: springboot/spring-aop/threadlocal/threadlocal-02 Post not found: springboot/spring-aop/threadlocal/threadlocal-03 Post not found: springboot/spring-aop/threadlocal/threadlocal-04 Post not found: springboot/spring-aop/threadlocal/threadlocal-05 Post not found: springboot/spring-aop/threadlocal/threadlocal-06 동시성 이슈 발생 여러 Thread가 동시에 Application 로직을 호출하게 되면서 Level 상태값이 꼬여서 보이게 된다. Thread 로 구분해서 로그 확인2021-12-06 02:18:26.028 INFO 22044 --- [nio-8080-exec-1] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] OrderController.request()2021-12-06 02:18:26.029 INFO 22044 --- [nio-8080-exec-1] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] |-->OrderService.orderItem()2021-12-06 02:18:26.029 INFO 22044 --- [nio-8080-exec-1] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] | |-->OrderRepository.save()2021-12-06 02:18:27.033 INFO 22044 --- [nio-8080-exec-1] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] | |<--OrderRepository.save() time=1004ms2021-12-06 02:18:27.033 INFO 22044 --- [nio-8080-exec-1] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] |<--OrderService.orderItem() time=1004ms2021-12-06 02:18:27.033 INFO 22044 --- [nio-8080-exec-1] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] OrderController.request() time=1005ms 2021-12-06 02:18:26.114 INFO 22044 --- [nio-8080-exec-2] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] | | |-->OrderController.request()2021-12-06 02:18:26.115 INFO 22044 --- [nio-8080-exec-2] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] | | | |-->OrderService.orderItem()2021-12-06 02:18:26.115 INFO 22044 --- [nio-8080-exec-2] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] | | | | |-->OrderRepository.save()2021-12-06 02:18:27.119 INFO 22044 --- [nio-8080-exec-2] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] | | | | |<--OrderRepository.save() time=1004ms2021-12-06 02:18:27.120 INFO 22044 --- [nio-8080-exec-2] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] | | | |<--OrderService.orderItem() time=1005ms2021-12-06 02:18:27.120 INFO 22044 --- [nio-8080-exec-2] c.e.a.trace.logtrace.FieldLogTrace : [c0f5fc0a] | | |<--OrderController.request() time=1006ms Thread 를 이용한 테스트 코드 작성@Slf4jpublic class FieldService { private String nameStore; public String logic(String name){ log.info("저장 name={} -> nameStore = {}", name, nameStore); nameStore = name; sleep(1000); log.info("조회 nameStore = {}", nameStore); return nameStore; } private void sleep(int millis) { try { Thread.sleep(millis); }catch (InterruptedException e){ e.printStackTrace(); } }}

0

Spring 핵심원리 고급편 - 필드 동기화 적용

목차 Post not found: springboot/spring-aop/threadlocal/threadlocal-01 Post not found: springboot/spring-aop/threadlocal/threadlocal-02 Post not found: springboot/spring-aop/threadlocal/threadlocal-03 Post not found: springboot/spring-aop/threadlocal/threadlocal-04 Post not found: springboot/spring-aop/threadlocal/threadlocal-05 Post not found: springboot/spring-aop/threadlocal/threadlocal-06 필드 동기화 적용Bean 으로 등록@Configuration public class LogTraceConfig { @Bean public LogTrace logTrace() { return new FieldLogTrace(); } } @RestController@RequiredArgsConstructorpublic class OrderControllerV3 { private final OrderServiceV3 orderService; private final LogTrace trace; @GetMapping("/v3/request") public String request(String itemId){ System.out.println(itemId); TraceStatus status = null; try { status = trace.begin("OrderController.request()"); orderService.orderItem(itemId); trace.end(status); return "ok"; }catch (Exception e){ trace.exception(status, e); throw e; // 예외를 반드시 던져 줘야 한다. } }} @Service@RequiredArgsConstructorpublic class OrderServiceV3 { private final OrderRepositoryV3 orderRepository; private final LogTrace trace; public void orderItem (String itemId){ TraceStatus status = null; try { status = trace.begin("OrderService.orderItem()"); orderRepository.save(itemId); trace.end(status); }catch (Exception e){ trace.exception(status, e); throw e; // 예외를 반드시 던져 줘야 한다. } }} @Repository@RequiredArgsConstructorpublic class OrderRepositoryV3 { private final LogTrace trace; public void save(String itemId){ TraceStatus status = null; try { status = trace.begin("OrderRepository.save()"); // 저장 로직 if(itemId.equals("ex")){ throw new IllegalStateException("예외 발생!"); } sleep(1000); trace.end(status); }catch (Exception e){ trace.exception(status, e); throw e; // 예외를 반드시 던져 줘야 한다. } } private void sleep(int millis) { try{ Thread.sleep(millis); }catch (InterruptedException e){ e.printStackTrace(); } }}

0

Spring 핵심원리 고급편 - 필드 동기화

목차 Post not found: springboot/spring-aop/threadlocal/threadlocal-01 Post not found: springboot/spring-aop/threadlocal/threadlocal-02 Post not found: springboot/spring-aop/threadlocal/threadlocal-03 Post not found: springboot/spring-aop/threadlocal/threadlocal-04 Post not found: springboot/spring-aop/threadlocal/threadlocal-05 Post not found: springboot/spring-aop/threadlocal/threadlocal-06 필드 동기화public interface LogTrace { TraceStatus begin(String message); void end(TraceStatus status); void exception(TraceStatus status, Exception e);} 필드 동기화를 위한 로그 분석기 만들기 TraceId 객체가 없을 경우 새로 생성한다. TraceId 객체가 있을 경우 Level 을 증가 시킨다. private void syncTraceId(){ if(traceIdHolder == null){ traceIdHolder = new TraceId(); }else{ traceIdHolder = traceIdHolder.createNextId(); }} TraceId 내 Level 이 0일 경우 해당 객체를 null 로 초기화 한다. TraceId 내 Level 이 0이 아닐 경우 Level을 1 감소시킨다. private void releaseTraceId() { if(traceIdHolder.isFirstLevel()){ traceIdHolder = null; // destroy }else{ traceIdHolder = traceIdHolder.createPreviousId(); }} TraceId 객체는 파라미터로 전달되는 것이 아니라 traceIdHolder 필드에 저장된다.

0

Spring 핵심원리 고급편 - Logging Filter 파라미터 동기화 개발

목차 Post not found: springboot/spring-aop/log-trace/log-trace-01 Post not found: springboot/spring-aop/log-trace/log-trace-02 Post not found: springboot/spring-aop/log-trace/log-trace-03 Spring 핵심원리 고급편 - Logging Filter 파라미터 동기화 개발Logging Filter 파라미터 동기화 개발@Slf4j@Componentpublic class HelloTraceV2 { private static final String START_PREFIX = "-->"; private static final String COMPLETE_PREFIX = "<--"; private static final String EX_PREFIX = "<X-"; public TraceStatus begin(String message) { TraceId traceId = new TraceId(); Long startTimeMs = System.currentTimeMillis(); log.info("[{}] {}{}", traceId.getId(), addSpace(START_PREFIX, traceId.getLevel()), message); return new TraceStatus(traceId, startTimeMs, message); } public TraceStatus beginSync(TraceId beforeTraceId, String message) { TraceId nextId = beforeTraceId.createNextId(); Long startTimeMs = System.currentTimeMillis(); log.info("[{}] {}{}", nextId.getId(), addSpace(START_PREFIX, nextId.getLevel()), message); return new TraceStatus(nextId, startTimeMs, message); } public void end(TraceStatus status) { complete(status, null); } public void exception(TraceStatus status, Exception e) { complete(status, e); } private void complete(TraceStatus status, Exception e) { Long stopTimeMs = System.currentTimeMillis(); long resultTimeMs = stopTimeMs - status.getStartTimeMs(); TraceId traceId = status.getTraceId(); if (e == null) { log.info("[{}] {}{} time={}ms", traceId.getId(), addSpace(COMPLETE_PREFIX, traceId.getLevel()), status.getMessage(), resultTimeMs); } else { log.info("[{}] {}{} time={}ms ex={}", traceId.getId(), addSpace(EX_PREFIX, traceId.getLevel()), status.getMessage(), resultTimeMs, e.toString()); } } private static String addSpace(String prefix, int level) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < level; i++) { sb.append((i == level - 1) ? "|" + prefix : "| "); } return sb.toString(); }} @RestController@RequiredArgsConstructorpublic class OrderControllerV2 { private final OrderServiceV2 orderService; private final HelloTraceV2 trace; @GetMapping("/v2/request") public String request(String itemId){ System.out.println(itemId); TraceStatus status = null; try { status = trace.begin("OrderController.request()"); orderService.orderItem(status.getTraceId(), itemId); trace.end(status); return "ok"; }catch (Exception e){ trace.exception(status, e); throw e; // 예외를 반드시 던져 줘야 한다. } }} @Service@RequiredArgsConstructorpublic class OrderServiceV2 { private final OrderRepositoryV2 orderRepository; private final HelloTraceV2 trace; public void orderItem(TraceId traceId, String itemId){ TraceStatus status = null; try { status = trace.beginSync(traceId, "OrderService.orderItem()"); orderRepository.save(status.getTraceId(),itemId); trace.end(status); }catch (Exception e){ trace.exception(status, e); throw e; // 예외를 반드시 던져 줘야 한다. } }} @Repository@RequiredArgsConstructorpublic class OrderRepositoryV2 { private final HelloTraceV2 trace; public void save(TraceId traceId, String itemId){ TraceStatus status = null; try { status = trace.beginSync(traceId,"OrderRepository.save()"); // 저장 로직 if(itemId.equals("ex")){ throw new IllegalStateException("예외 발생!"); } sleep(1000); trace.end(status); }catch (Exception e){ trace.exception(status, e); throw e; // 예외를 반드시 던져 줘야 한다. } } private void sleep(int millis) { try{ Thread.sleep(millis); }catch (InterruptedException e){ e.printStackTrace(); } }}