목차

    자바 스트림의 종류

    스트림에 대한 내용은 크게 세 가지로 나눌 수 있습니다.

    1. 생성하기 : 스트림 인스턴스 생성.
    2. 가공하기 : 필터링(filtering) 및 맵핑(mapping) 등 원하는 결과를 만들어가는 중간 작업(intermediate operations).
    3. 결과 만들기 : 최종적으로 결과를 만들어내는 작업(terminal operations).
    • 생성하기
      • 배열 / 컬렉션 / 빈 스트림
      • Stream.builder() / Stream.generate() / Stream.iterate()
      • 기본 타입형 / String / 파일 스트림
      • 병렬 스트림 / 스트림 연결하기
    • 가공하기
      • Filtering
      • Mapping
      • Sorting
      • Iterating
    • 결과 만들기
      • Calculating
      • Reduction
      • Collecting
      • Matching
      • Iterating

    자바 Streams

    자바 8에서 추가한 스트림(Streams)은 람다를 활용할 수 있는 기술 중 하나입니다. 자바 8 이전에는 배열 또는 컬렉션 인스턴스를 다루는 방법은 for 또는 foreach 문을 돌면서 요소 하나씩을 꺼내서 다루는 방법이었습니다. 간단한 경우라면 상관없지만 로직이 복잡해질수록 코드의 양이 많아져 여러 로직이 섞이게 되고, 메소드를 나눌 경우 루프를 여러 번 도는 경우가 발생합니다.

    스트림은 '데이터의 흐름’입니다. 배열 또는 컬렉션 인스턴스에 함수 여러 개를 조합해서 원하는 결과를 필터링하고 가공된 결과를 얻을 수 있습니다. 또한 람다를 이용해서 코드의 양을 줄이고 간결하게 표현할 수 있습니다. 즉, 배열과 컬렉션을 함수형으로 처리할 수 있습니다.

    또 하나의 장점은 간단하게 병렬처리(multi-threading)가 가능하다는 점입니다. 하나의 작업을 둘 이상의 작업으로 잘게 나눠서 동시에 진행하는 것을 병렬 처리(parallel processing)라고 합니다. 즉 쓰레드를 이용해 많은 요소들을 빠르게 처리할 수 있습니다.

     

    자바 스트림 사용에서 주의할 점

    지금까지 스트림을 생성하는 방법과 필요에 맞게 가공하는 중간 연산(Intermediate Operations) 그리고 원하는 결과를 구하는 단말 연산(Terminal Operations)에 대해서 알아보았고 스트림을 활용하는 여러 가지 예제도 살펴보았습니다.

    이전보다 더 간결한 코드를 얻게 되었고 비교적 최신 기술을 적용했기 때문에 스트림 API를 활용하는 것이 더 좋아 보일 수 있습니다. 하지만 경우에 따라서 다를 수 있고 오히려 성능이 더 좋지 않을 수 있습니다.

    이번 포스팅에서는 스트림 API를 사용할 때 주의할 부분과 적용할 때 고민해보면 좋을 포인트를 정리해봅시다.

     

    스트림 재사용

    흔하게 접할 수 있는 실수 하나는 스트림을 재사용하는 것입니다. 스트림을 컬렉션처럼 사용했다가 적지않게 겪은 것 같습니다.

    // 문자열 스트림 생성
    Stream<String> langNames = Stream.of("Java", "C++", "Python", "Ruby");
    
    // 스트림 내 모든 요소 출력
    langNames.forEach(System.out::println);
    
    // "Java" 만 제외한 스트림을 다시 생성... Exception이 발생한다.
    Stream<String> filtered = langNames.filter(lang -> !lang.equals("Java"));
    filtered.forEach(System.out::println);
    

     

    위 코드를 실행하면 IllegalStateException이 발생합니다.

    java.lang.IllegalStateException: stream has already been operated upon or closed

    스트림은 오직 한 번만 소비될 수 있기 때문에 사용한 이후에 다시 사용하는 경우 에러를 발생시킬 수 있습니다.

     

    스트림은 무조건 좋다?

    기존의 배열이나 컬렉션을 반복하는 for-loop 문장을 스트림의 foreach로 변경하는 경우도 많습니다. 하지만 성능을 비교해보면 무조건적으로 스트림이라고 성능적으로 빠른 것은 아닙니다.

    10만개의 랜덤값이 들어있는 배열에서 가장 큰 값을 찾는 코드가 있습니다. 첫 번째는 기본 for-loop를 이용하여 찾고 두 번째 방법으로 Stream을 이용해서, 마지막으로 병렬 수행이 가능한 Parallel Stream으로 수행했을 때의 수행 속도 차이를 비교해봅시다.

    // new Random().nextInt로 초기화된 배열로 가정
    int[] intArray;
    
    // Case 1: for-loop
    int maxValue1 = Integer.MIN_VALUE;
    for (int i = 0; i < intArray.length; i++) {
        if (intArray[i] > maxValue) {
            maxValue1 = intArray[i];
        }
    }
    
    // Case 2: Stream
    int maxValue2 = Arrays.stream(intArray)
        .reduce(Integer.MIN_VALUE, Math::max);
    
    // Case 3: Parallel Stream
    int maxValue3 = Arrays.stream(intArray).parallel()
        .reduce(Integer.MIN_VALUE, Math::max);    
    
    측정 시간은 각 Case에 수행 전과 후에 System.nanoTime()로 측정했으며, TimeUnit 클래스를 사용하여 밀리초 단위로 변환했습니다.
    // nanoseconds to milliseconds
    TimeUnit.MILLISECONDS.convert((endTime - startTime), TimeUnit.NANOSECONDS)
    
     

    출력 결과는 어떻게 나올까요? 기본 for-loop를 사용했을 때 가장 빠릅니다.

    for-loop: 8ms
    Stream: 123ms
    Parallel Stream: 15ms
    
     

    관련 참고자료를 인용하면, 단순 for-loop의 경우 오버헤드가 없는 단순한 인덱스 기반 메모리 접근이기 때문에 Stream을 사용했을 때보다 더 빠르다고 합니다. (“It is an index-based memory access with no overhead whatsoever.”)

    또, 컴파일러의 관점에서 오랫동안 유지해온 for-loop의 경우 최적화를 할 수 있으나 반대로 비교적 최근에 도입된 스트림의 경우 for-loop와 같은 정교한 최적화를 수행하지 않습니다.

    가독성

    소제목은 ‘가독성(readability)’ 이지만 스트림을 사용하면서 주의해야 한다기보다는 조금 고민해볼만한 부분이라고 할 수 있습니다. 특히 다른 사람들과 같이 개발을 진행할 때 말이지요. 코드 리뷰를 하다보면 “코드가 라인 수가 줄어들었고 가독성이 좋아졌다.” 라고 하지만 이는 어떻게 보면 팀원 모두가 스트림 API 사용에 익숙해야 하는 조건이 필요합니다.

    개인적으로도 Java 8 버전 이상의 코드 스타일이 익숙하지 않아서 리뷰에 어려움이 있었던 적이…

    문자열이 들어있는 배열에서 특정 문자열을 찾고 출력하는 코드는 아래와 같이 두 가지 방법으로 작성할 수 있습니다.

    // array
    String[] languages = {"Java", "C", "Python", "Ruby", "C++", "Kotlin"};
            
    // for-loop
    String result = "";
    for (String language : languages) {
        if (language.equals("Java")) {
            result = language;
            break;
        }
    }
    if(result != null && result != "") {
        System.out.println(result);
    }
    
    // Stream
    Arrays.stream(languages)
            .filter(lang -> lang.equals("Java"))
            .findFirst().ifPresent(System.out::println);
    
     

    자바8의 스트림을 이용하여 여러 if 조건문과 결과를 담는 부수적인 변수 할당도 사라졌습니다. 짧고 간결한 코드가 되는 것에는 전적으로 공감할 수 있습니다. 메서드의 네이밍도 명확하기 때문에 이해하기도 쉽습니다. 물론 이부분은 앞서 말한 것처럼 스트림 API에 익숙해야하며 개인차이가 있을 수도 있습니다.

    무한 스트림

    기존에 생성된 배열이나 컬렉션을 통해서 스트림을 생성하는 경우에는 이슈가 없을 수 있으나, 특정 조건에 따라서 스트림을 생성하는 경우에는 무한 스트림(Infinite Streams)이 생성될 수 있습니다. 심지어 요소 개수에 제한을 걸었음에도 불구하고요.

    Stream.iterate(0, i -> (i + 1) % 2)
            .distinct()
            .limit(10)
            .forEach(System.out::println);
    System.out.println("코드 실행 종료");
    
     

    위 코드의 각 라인을 설명하면 아래와 같습니다.

    • iterate: 2로 나머지 연산을 했으므로 0과 1을 반복적으로 생성합니다.
    • distinct: 각각 단일 0과 1을 유지합니다.
    • limit: 10개의 스트림 크기 제한이 생깁니다.
    • forEach: 생성된 스트림 요소를 모두 출력합니다.

    위 코드에서 distinct 연산자는 스트림을 생성하는 iterate 메서드에서 0과 1만 생성된다는 것을 알지 못합니다. 따라서 요소의 개수를 10개로 제한하는 limit 연산에 도달할 수 없습니다.

    그러므로 이어지는 forEach를 이용한 요소 출력도 진행되지 않으며, 모든 코드가 종료된 것을 출력하는 문장도 수행되지 않습니다. 코드는 종료되지 않고 계속 리소스를 차지하게 됩니다.

     

    변수 접근

    스트림을 이용하면서 람다(lambda) 또는 메서드 참조(method references)를 사용하는 경우에는 지역 변수(local variables)에 접근할 수 없습니다.

    int sumForLambda = 0;
    for (int i = 0; i < 5; i++) {
        // it works!
        sumForLambda += i;
    }
    
    int sumForloop = 0;
    IntStream.range(0, 5).forEach(i -> {
        // compile error
        sumForloop += i;
    });
     

    그리고 스트림의 파이프라인에서 연결된 각 단계의 값들에 접근할 수 없습니다. peek 메서드로 연산 사이의 결과를 확인하고 싶지만 불가능합니다.

    Arrays.stream(array)
            .filter(first -> first % 2 == 0)
            .filter(second -> second > 3)
            .peek(value -> {
                // compile error, can't access second filter's variable
                int printValue = value + second;
                System.out.println(printValue);
            })
            .sum();
     

    참고로 peek 메서드의 경우 스트림의 결과를 구하는 단말 연산(Terminal Operations)이 실행되지 않으면 메서드 자체가 실행되지 않습니다. 위의 예제에서는 sum 메서드가 단말 연산으로 실행되었습니다.

    스트림의 동작 순서

    스트림을 사용할 때는 동작 방식을 이해할 필요가 있습니다. 아래와 같이 3개의 요소를 가진 배열로 스트림 연산을 수행하는 코드가 있습니다. 그리고 연결된 각 메서드마다 출력문으로 메서드가 실행되었는지 확인합니다.

    Arrays.stream(new String[]{"c", "python", "java"})
            .filter(word -> {
                System.out.println("Call filter method: " + word);
                return word.length() > 3;
            })
            .map(word -> {
                System.out.println("Call map method: " + word);
                return word.substring(0, 3);
            }).findFirst();
    
     

    결과는 어떻게 나올까요? 3개의 요소에 대해서 메서드를 수행하므로 각각 3번의 호출이 이뤄질 것 같지만, 출력 결과는 그렇지 않습니다.

    Call filter method: c
    Call filter method: python
    Call map method: python
    
     

    위 결과를 통해서 스트림의 동작 방식을 유추할 수 있는데요. 스트림 내의 모든 요소가 중간 연산인 filter 메서드를 수행하는 것이 아니라 요소 하나씩 모든 파이프라인을 수행합니다.

    따라서 조금 더 자세히 살펴보면 아래와 같이 단계적으로 수행됩니다.

    • 배열의 첫 번째 요소 “c”
      • filter 메서드가 실행되지만 length가 3보다 크지 않으므로 다음으로 진행되지는 않음
      • “Call filter method: c” 출력
    • 배열의 두 번째 요소 “python”
      • filter 메서드가 실행되며 length가 3보다 크므로 다음으로 진행됨
      • “Call filter method: python” 출력
      • map 메서드에서 substring 메서드를 수행합니다.
      • “Call map method: python” 출력
      • 마지막 연산인 findFirst가 수행됩니다.
    • 조건에 맞는 하나의 결과를 찾았기 때문에 다음 요소인 “java”에 대해 연산을 수행하지 않습니다.
      • 최종 결과는 “pyth” 입니다.

    성능 개선

    이러한 특성을 잘 이용하면 스트림의 성능을 조금 더 개선할 수 있습니다. 아래와 같이 배열의 모든 문자열 요소를 대문자로 변환하는 코드가 있습니다. 그리고 수행된 결과에서 2개를 생략하여 리스트로 만듭니다.

    Arrays.stream(new String[]{"c", "python", "java"})
            .map(word -> {
                System.out.println("Call map method: " + word);
                return word.toUpperCase();
            })
            .skip(2)
            .collect(Collectors.toList());
    
    // Call map method: c
    // Call map method: python
    // Call map method: java
     

    map 메서드가 총 3번 호출되는 것을 알 수 있습니다. 여기서 메서드의 실행 순서를 변경하면 어떻게 될까요?

    Arrays.stream(new String[]{"c", "python", "java"})
            .skip(2)
            .map(word -> {
                System.out.println("Call map method: " + word);
                return word.toUpperCase();
            })
            .collect(Collectors.toList());
    
    // Call map method: java
    
     

    물론 filter 메서드와 같은 특정 조건을 추가해서 사용할 때는 skip 메서드의 위치에 따라서 결과가 달라질 수 있으므로 주의해야 합니다.

    List<String> list = Arrays.stream(new String[]{"abc", "abcd", "abcde", "abcdef"})
            .filter(word -> word.length() > 3)
            .map(word -> word.toUpperCase())
            .skip(2)
            .collect(Collectors.toList());
            
    // ABCDEF 
    list.forEach(System.out::println);
    
    List<String> list2 = Arrays.stream(new String[]{"abc", "abcd", "abcde", "abcdef"})
            .skip(2)
            .filter(word -> word.length() > 3)
            .map(word -> word.toUpperCase())
            .collect(Collectors.toList());
            
    // ABCDE
    // ABCDEF 
    list2.forEach(System.out::println);

    참고

    https://futurecreator.github.io/2018/08/26/java-8-streams/

     

    Java 스트림 Stream (1) 총정리

    이번 포스트에서는 Java 8의 스트림(Stream)을 살펴봅니다. 총 두 개의 포스트로, 기본적인 내용을 총정리하는 이번 포스트와 좀 더 고급 내용을 다루는 다음 포스트로 나뉘어져 있습니다. Java 스트

    futurecreator.github.io

     

    목차

    Memcached란?

    • 무료로 사용할 수 있는 오픈소스이며 분산 메모리 캐싱 시스템이다. 
    • 데이터 베이스의 부하를 줄여 동적 웹 어플리케이션의 속도개선을 위해 사용되기도 함
    • DB나 API호출 또는 렌더링 등으로부터 받아오는 결과 데이터를 작은 단위의 key - value 형태로 메모리에 저장하는 방식
    • Memcached는 필요량보다 많은 메모리를 가졌을 때, 시스템으로부터 메모리를 사용하고 필요로하는 메모리가 부족한 경우 이를 더 쉽게 가져다 사용할 수 있도록 만들어 줌
    • Memcached 사용여부에 따른 메모리 운영방식

    Memcached를 사용하지 않을 경우

    각 노드는 완벽하게 독립적임

    이 경우 고전적으로 사용되던 방식으로 총 캐시 크기가 웹팜(여러 대를 사용해서 웹사이트를 구축한 형태)의 실제 용량의 일부분으로만 사용이 가능하다는 점에서 낭비가 심하다. 각각의 서버에 할당된 캐시 크기만큼 사용할 수 있으므로 웹팜의 캐시 사이즈는 128MB이지만 각 서버에서 사용할 수 있는 사이즈는 65MB이다.

    Memcached를 사용할 경우

    Memcached를 사용할 경우 Memcached로 묶인 모든 서버는 동일한 가상 메모리 풀을 공유한다. 이것은 특정한 항목이 주어졌을 때, 전체 웹 클러스터에서 항상 동일한 위치에 저장되고 검색되어짐을 뜻한다. 또한 응용프로그램에 대한 수요가 증가하여 서버증설에 대해 필요성을 느낄 때, 정기적으로 접근되어져야 하는 데이터의 관점에서도 수요가 증가한다고 볼 수 있다.

    Memcached를 적용하면 분산 메모리 캐시를 적용하게 되는 것이므로 캐싱을 통해 DB나 API 호출에 대한 횟수를 줄일 수 있고 이로 인해 응용프로그램의 수요나 DB 데이터 접근에 대한 부하를 줄여 성능을 향상할 수 있다.

    Redis란

    redis는 오픈소스로서 데이터베이스(NOSQL DBMS)로 분류가 되기도 하고 Memcached와 같이 인메모리 솔루션으로 분류되기도 한다.

    성능은 memcached에 버금가면서 다양한 데이터 구조체를 지원함으로써 Message Queue, Shared memory, Remote Dictionary 용도로도 사용될 수 있으며, 이런 이유로 인스타그램, 네이버 재팬의 LINE 메신져 서비스, StackOverflow,Blizzard,digg 등 여러 소셜 서비스에 널리 사용되고 있다. 

     

    NoSQL관점에서 봤을 때 redis는 가장 단순한 키-밸류 타입을 사용하고 있다. 데이터 모델을 복잡할수록 성능이 떨어지므로 redis는 단순한 구조를 통해 높은 성능을 보장한다고 할 수 있다.

    NoSQL에는 다양한 제품이 있지만 이 중, redis가 주목받는 이유는 다음과 같다.

    • 데이터 저장소로 가장 입/출력이 빠른 메모리를 채택
    • 단순한 구조의 데이터 모델인 키- 밸류 방식을 통해 빠른 속도를 보장
    • 캐시 및 데이터스토어에 유리
    • 다양한 API지원

    redis, memcached, 구아바 라이브러리등 인메모리 캐시방식을  적용한 제품 중 redis는 global cache 방식을 채택하였다. global cache 방식은 네트워크 트래픽이 발생하기 때문에 java heap영역에서 조회되는 local cache의 성능이 더 낫지만, WAS 인스턴스가 증가할 경우엔 캐시에 저장되는 데이터 크기가 커질수록 redis 방식이 더 유리하다.

    redis는 급격한 사용자가 집중되는 상황이나 대규모의 확장이 예정되어 있는 환경에 적합하다. global cache 방식이 적용되어 was 인스턴스 확장에는 유리하지만 cache 및 redis 세션 등 관리 포인트가 늘어난다는 단점이 존재한다.

     

      Redis vs Memcached

      Redis (Remote Dictionary Storage, 레디스)와 Memcached(맴캐시드)는 유명한 오픈소스인, 인메모리 데이터 저장소이다. 둘 모두 고성능이고 쉽다. 하지만 엔진으로 사용할 때 차이를 반드시 고려야한다.  맴캐쉬드는 명료하고 단순함을 위하여 개발한 반면, 레디스는 다야한 용도에 효과적으로 사용할 수 있도록 많은 특징을 가지고 개발되었다.

        Redis Memcached
      공통점 1. 빠른 응답 시간(1ms이내)
      2. 개발의 용이성
      3. 데이터 파티셔닝 : 데이터를 노드에 분산 하여 저장가능, 수요가 증가 할 때 더 많은 데이터 효과적으로 처리하기 위하여 스케일아웃이 가능하다.
      4. 다양한 프로그래밍 언어 지원
      차이점 1. 더욱 다양한 데이터 구조 : List, Set, 정렬된 Set, Hash, Bit배열 등 다양한 자료구조 제공
      2. SnapShots : 레디스는 트겆ㅇ시점에 데이터를 저장하여 파일 보관이 가능하여 장애상황시 복구에 용이
      3. 복제 : Master -Salve 구조로, 여러개의 복제본을 만들 수 있다. 따라서 데이터베이스 읽기를 확장가능
      4. 트랜잭션 : 트랜잭션이란 데이터베이스 상태를 변경시키는 작업 단위이며, 원자성, 일관성, 독립성, 지속성의 특징을 가지고 있다. Redis는 이러한 특징을 지원한다.


      멀티스레드를 지원하기 때문에, 멀티프로세스코어를 사용할 수 있다. 따라서 스케일업을 통해 더욱 많은 작업처리를 할 수 있다.
      주의점 위의 특징만 보면, 모든 상황에서 Redis를 선택해야할 것 같습니다.
      하지만, Redis는 싱글 쓰레드이기 때문에, 1번에 1개의 명령어만 실행할 수 있습니다. Keys(저장된 모든키를 보여주는 명령어)나 flushall(모든 데이터 삭제)등의 명령어를 사용할 때, 맴캐쉬드의 경우 1ms정도 소요되지만, 레디스의 경우 100만건의 데이터 기준 1초로 엄청난 속도 차이가 있습니다.
      또한, RDB 작업(특정 간격마다 모든 데이터를 디스크에 저장)이 매우 오래걸립니다. AWS, 60기가 메모리 기준으로 10분이나 소요됩니다. Redis 장애에 원인의 대부분이 해당 기능 때문에 발생하기 때문에 사용할 때 주의해야합니다.

      단, 공통점의 스케일 아웃은  Redis에 해당된다. 즉, Memcached의 확장성은 Scale up(vertial)을 통해서 얻을 수 있는 반면, Redis는 Scale out(horizontal) 왜냐하면 레디스는 복제가 가능하며 Memcached 복제가 불가능하다. 

      Data Eviction 전략

      캐시는 유한한 리소스를 지니므로, 결국 메모리에 자리잡은 자원을 언젠가 삭제해야한다. Memcache 같은 경우 LRU(Least Recenly Used) 알고리즘만을 채택하고 있다. 반면에 Redis의 경우 보다 다양하고 미세한 방법을 제공한다.

      Redis 알고리즘 전략

      참고

      https://brownbears.tistory.com/43

       

      Memcached, Redis

      Memcached Memcached란? 무료로 사용할 수 있는 오픈소스이며 분산 메모리 캐싱 시스템 데이터 베이스의 부하를 줄여 동적 웹 어플리케이션의 속도개선을 위해 사용되기도 함 DB나 API호출 또는 렌더링

      brownbears.tistory.com

       

      'Back-end > cache' 카테고리의 다른 글

      Simple Spring Memcached(SSM) ... 작성중  (0) 2022.04.05
      [Server] Cache(캐시)  (0) 2021.12.10

      목차

        Simple Spring Memcached(SSM)

        스프링에서 Memcache를 사용하려면 SSM 라이브러리를 자주 이용한다. SSM 어노테이션으로 메서드에 선언하면 쉽게 관련 데이터가 캐시에 관리된다. 스프링에서도 3.1버전부터는 캐시 서비스 추상화 기능이 지원되어 비즈니스 로직 변경 없이 쉽게 다양한 캐시 구현체(ex. Ehcache, Redis)로 교체가 가능하게 되었다.

        Simple Spring Memcached(SSM) 설정 및 사용법

        SSM 사용에 필요한 dependency와 스프링 빈 설정 파일을 추가해야한다.

        Maven dependency 추가

        • spymemcached
          • 단순 비동기, 단일 쓰레드 memcached 자바 라이브러리
        • xmemcached (주로 사용됨)
          • 고성능 멀티 쓰레드 memcached 자바 라이브러리

        스프링 설정 파일

        더보기

        추가 설명

        ConsistentHashing 방식은 여러 서버가 변경되더라도 각 서버에 할당된 데이터를 재분배하지 않고 다운된 서버의 데이터만 다른 서버로 재분배하는 방식입니다. K(keys) = 10,000, N(memcache 서버 수 : 슬롯) = 5대 => 각 서버마다 2000 keys만큼을 hashing 할 수 있다. 서버 장애 (1 서버 다운 A)가 발생한 A 서버의 key만 다른 서버로 재분배하는 방식이다. 
        더 자세한 설명은 Consistent Hashing를 참고해주세요.

        스프링 빈 설정에 Memcached 관련 설정이 포함된다. Memcached의 서버 정보와 캐시 설정은 ConsistentHashing 방식으로 지정되어 있다.

        아래의 코드는 재직중인 회사에서 추가한 스프링 설정 파일 내용이다.

        <property name="cacheClientFactory">
            <bean class="com.google.code.ssm.providers.xmemcached.MemcacheClientFactoryImpl" />
        </property>

        SSM Cache 대표 어노테이션

        • Read
          • @ReadThroughAssignCache
          • @ReadThroughSingleCache
          • @ReadThroughMultiCache
        • Update
          • @UpdateAssignCache
          • @UpdateSingleCache
          • @UpdateMultiCache
        • Invalidate
          • @InvalidateAssignCache
          • @InvalidateSingleCache
          • @InvalidateMultiCache
        • Counter
          • @ReadCounterFromCache
          • @IncrementCounterInCache
          • @DecrementCounterInCache
        Cache Action
         Description
        ReadThrough
        Cache에 저장된 key가 없는 경우 Cache에 저장한다
        Update
        Cache에 저장된 key의 값을 업데이트한다
        Invalidate
        Cache에 저장된 key를 삭제한다
         
        Cache Type
        Description
        AssignCache
        캐시 키는 assignedKey 속성으로 지정한 값이고 메서드 인자가 없는 경우에 사용된다
        ex. List<Person> getAllUsers()와 같은 메서드에 사용된다
        SingleCache
        캐시 키는 SSM 어노테이션으로 선언된 메서드 인자로 생성되며 인자가 하나인 경우에 사용된다. 
        - 인자가 List 타입인 경우에는 캐시 키로 생성해서 사용할 수 없다
        ex. Person getUser(int int)
        MultiCache
        캐시 키는 SSM 어노테이션으로 선언된 메서드 여러 인자로 생성된다. 인자중에 한개가 List 타입 형이여야하며 반환결과도 List 타입이여야 한다. 반환된 결과 List의 각 요소는 지정된 캐시 키로 저장된다. 
        ex. List<Person> getUserFromData(List workInfo)
         
        SingleCache와 MultiCache인 경우 반드시 메서드 인자 중에 @ParameterValueKeyProvider 어노테이션을 지정해야 합니다. 기본 캐시 어노테이션 외에도 여러 어노테이션과 같이 사용하는 속성들이 존재하며 예제를 통해서 더 자세히 알아보도록 하겠습니다. 
        • 기타 어노테이션 및 속성
          • @CacheName(“QuoteApp”) :  관련 캐시를 하나로 묶을 수 있는 개념이고 클래스외에도 메서드에도 선언할 수 있다
          • @CacheKeyMethod : 캐시의 key 값으로 이용할 메서드를 선언한다
          • 캐시 key는 @CacheKeyMethod로 선언된 메서드가 사용된다
          • @CacheKeyMethod가 지정되지 않은 경우에는 Object.toString() 메서드가 사용된다
          • @ParameterValueKeyProvider : 메서드 인자에 적용되며 @CacheKeyMethod로 선언된 메서드나 toString()을 이용해서 key 값을 구한다
          • @ParameterDataUpdateContent : 메서드 인자에 어노테이션이 적용되며 새로 저장할 값을 지정한다
          • @ReturnDataUpdateContent : 반환 값을 캐시에 저장한다
        • 속성
          • namespace : 동일한 키 값의 이름이 있을 경우를 방지하기 위해서 사용된다
          • expiration : key값이 만료되는 시간 (초 단위)이다
          • assignedKey : 캐시 저장시 사용되는 키 값이다 

        Read Cache 예시

        @ReadThroughAssignCache

        @ReadThroughAssignCache 어노테이션은 인자가 없는 메서드에 적용할 수 있습니다. 캐시 영역에서 네임스페이스 'area' 이름으로 key:value(all:List<Product>)가 저장돕니다.....

        @ReadThroughAssignCache(namespace = "area", assignedKey="all")
        public List<Product> findAllProducts() {
            slowly(); // 고의로 지연시킴
            List<Product> productList = new ArrayList<>();
            return storage.values().stream().collect(Collectors.toList());
        }

        실무에서 사용

        @ReadThroughSingleCache(namespace = "intraCs", expiration = 60 * 60)
        @CacheConnectingKey("PartnerQnaCategoryCacheService.getPartnerQnaCategoryTreeList")
        public List<PartnerQnaCategoryTree> getPartnerQnaCategoryTreeList(@ParameterValueKeyProvider String serviceCode){
        ...
        }

        Unit Test

        @Test
        public void testReadThroughAssignCache() {
            this.executeWithMemcachedFlush(productService, () -> {
                productService.addProduct(new Product("microsoft", 100));
                productService.addProduct(new Product("sony", 100));
                productService.findAllProducts(); //#1 - caching
        
                productService.findAllProducts(); //#2 - cache에서 가져옴
        
            });
        }

        @ReadThroughSingleCache

        @ReadThroughSingleCache 어노테이션은 인자가 하나인 경우에만 사용한다. 인자에 @ParameterValueKeyProvider 어노테이션을 선언하면 @CacheKeyMethod로 지정된 메서드는 캐시 키를 생성하는데 사용되고 없는 경우에는 toString() 메서드가 사용됩니다.

        @ReadThroughSingleCache(namespace = "area")
        public Product findProduct(@ParameterValueKeyProvider String name) {
            slowly(); // 고의로 지연시킴
        
            return storage.get(name);
        }
        
        @CacheKeyMethod
        public String getName() {
            return name;
        }

        실무

        @ReadThroughSingleCache(namespace = "intraCs", expiration = 60 * 60)
        @CacheConnectingKey("PartnerQnaCategoryCacheService.getPartnerQnaCategoryRootPathMap")
        public Map<Long, List<PartnerQnaCategory>> getPartnerQnaCategoryRootPathMap(@ParameterValueKeyProvider String serviceCode){
        ...
        }

        Unit Test

        @Test
        public void testReadThroughSingleCache() {
            this.executeWithMemcachedFlush(productService, () -> {
                productService.addProduct(new Product("microsoft", 100));
                productService.addProduct(new Product("sony", 100));
                productService.findProduct("microsoft”); //#1 - caching
        
                productService.findProduct(("microsoft”); //#2 - cache에서 가져옴
        
            });
        }}

        @ReadThroughMultiCache

        MultiCache는 메서드 인자 중에 List 타입인 인자가 있어야 합니다. 그리고 캐시되는 key:value 인자의 요소와 반환 결과 요소가 각각 key:value로 캐시에 저장된다.

        @ReadThroughMultiCache(namespace = "area")
        public List<Integer> getIncrementValue(@ParameterValueKeyProvider List<Integer> nums, int incrementValue) {
            slowly(); // 고의로 지연시킴
            return nums.stream().map(x -> x + incrementValue).collect(Collectors.toList());
        }

        실무

        @ReadThroughMultiCache(namespace = "intraCs", expiration = 60 * 60)
        public List<OpenMarketDealInfo> getOpenMarketDealInfo(@ParameterValueKeyProvider List<String> mainDealSrlList) {
        ...
        }

        Unit Test

        @Test
        public void testReadThroughMultiCache() {
            this.executeWithMemcachedFlush(productService, () -> {
                List<Integer> nums = new ArrayList<Integer>(Arrays.asList(2, 3, 4, 5));
                productService.getIncrementValue(nums, 4); //#1 - caching 함
        
                productService.getIncrementValue(nums, 1); //#2 - caching된 값을 가져옴
        
            });
        }

        Update Cache

        Update로 시작하는 어노테이션은 캐시에 저장된 값을 강제적으로 덮어쓰는 어노테이션이다. Update에 대한 여러 어노테이션에 대해서 알아보자.

        @UpdateAssignCache

        AssignCache 어노테이션은 assignedKey로 지정된 값을 키로 사용한다.

        @ReturnDataUpdateContent
        @UpdateAssignCache(namespace = "area", assignedKey = "all")
        public List<Product> resetPriceForAllProducts() {
            slowly(); // 고의로 지연시킴
            return storage.values().stream()
                    .map(product -> {
                        product.setPrice(0);
                        return product;
                    }).collect(Collectors.toList());
        }

         

         

         

        참고

        https://advenoh.tistory.com/27#recentComments

         

        'Back-end > cache' 카테고리의 다른 글

        Redis vs Memcached  (0) 2022.04.06
        [Server] Cache(캐시)  (0) 2021.12.10

        목차

          젠킨스 파이프 라인이란?

          젠킨스 파이프 라인이란 연속적인 작업들을 젠킨스에서 파이프라인(작업)으로 묶어서 관리할 수 있게 만드는 플러그인이다.

          CI/CD

          CI(Continue Integration) 즉, 지속적인 통합이라는 의미이다. 지속적인 통합이란, 어플리케이션의 소스 변경 사항이 정기적으로 빌드 및 테스트되어 공유 레파지토리에 통합하는 것을 의미한다.

          지속적인 통합이란, 어플리케이션의 소스 변경 사항이 정기적으로 빌드 및 테스트되어 공유 레파지토리에 통합하는 것을 의미한다,

          CD(Continueous Delivery) 혹은 Continuous Deployment 두 용어 모두의 축약어이다.

          전자인, Continuous Delivery는 공유 레파지토리로 자동으로 Release하는 것을 의미하며, 지속적인 서비스 제공을 뜻한다.

          후자인, Continuous Deployment는 Production 레벨까지 자동으로 deploy 하는 것을 의미하며, 고객에게 배포하는 것을 뜻한다.

           

          정리하면, 

          CI는 애플리케이션 소스를 빌드 테스트, 병합하는 것을 의미하고, 

          CD는 개발자의 변경사항이 레파지토리르 넘어, 고객에게 배포하는 것까지를 의미한다.

           

          Pipeline 정의

          • 1) Pipeline script : 위의 사진과 같이 Jenkins내에서 Script를 작성하여 Pipeline을 구성한다. Jenkins내에서 작성을 하기 때문에 간편하다는 이점이 있다.
          • 2) Pipeline script from SCM : SCM은 Git이나 SVN과 같이 형상관리툴을 의미하며, 소스쪽에 Jenkins 파일을 생성하여 별도로 Script를 작성하여 구성하는 방식이다. 보안상의 이점이 있기때문에 이 방식을 많이 사용한다.

          pipeline source

          node {
            stage 'Setting'
            def javaHome = tool name: 'jdk8', type: 'hudson.model.JDK'
            env.JAVA_HOME = "${javaHome}"
            env.PATH = "${env.PATH}:${env.JAVA_HOME}/bin"
          
            // github에서 소스 얻어오기
            stage 'Checkout'
            git branch: 'master', credentialsId: 'Your Credentials ID', url: 'Your GitHub URL'
          
            // Maven으로 빌드 실행하기
            stage 'Build'
            def mvnHome = tool 'M3'
              sh "'${mvnHome}/bin/mvn' -Dmaven.test.skip=true clean install"
            // 패키지 저장
            step([$class: 'ArtifactArchiver', artifacts: '**/target/*.jar', fingerprint: true])
            
            // 서버실행 
            //stage 'Start'
              //sh "java -jar C:/ProgramData/Jenkins/.jenkins/workspace/test-pipeline/target/hr-0.0.1-SNAPSHOT.jar"
          }

          Script 통해 Setting, Checkout, Build, Start라는 단계별 Stage 정의하였으며,  Stage들이 모여 CI/CD 진행시키는 하나의 pipeline 구축한 것이다.

           

          파이프 라인 활용

          • 같은 Job인데 파라미터 혹은 스케줄만 다르게 하고 싶을 때? 파이프라인!
          • 여러 Job을 순서대로 실행하지만 가끔은 개별로도 실행이 필요할 때? 파이프라인!
          • 스프링 배치에서 여러 작업을 순차적으로 실행할 때 Step으로 나누기보다는 파이프 라인을 우선 고려!
            • 처음엔 그럴 경우가 없다고 생각하더라도 혹시 이후에 요구사항이 바뀔 수 있으니까 고려해보는 것이 좋음.

          'Back-end > 젠킨스CICD' 카테고리의 다른 글

          [Maven] Artifact / maven-war-plugin / SNAPSHOT  (0) 2021.11.15
          메이븐(Maven) - 개념  (0) 2021.01.29

          목차

            Spring 버전 별 특징

            • spring 3.0
            • spring 4.0
            • spring 5.0

            Spring 3.0

            spring 3.0 부터 java 5 가 지원된다. 기존에 유지하던 하위호환성에 Generic이나 가변인자(varargs) 등과 같은 개선사항이 추가된다.

            • 전체 프레임 워크를 하나의 spring.jar 파일로 제공하던 부분을 여러개의 jar 파일로 나누어 제공한다.
            • SPEL(Spring Expression Language)가 도입되었다.
            • Rest API에 대한 지원이 추가 되었다.
            • xml 지원
            • Java annotation을 이용해서 DI의 지원
            • Java 5의 기능(제네릭, 가변 매개변수 등)을 사용하여 개정되었다.
              • 이로 인해서 BeanFactory 등 핵심 API가 업데이트 되었다.
            • @Async 주석을 통해 비동기 메서드 호출을 지원하기 시작했다.
            • 하나의 Spring.jar로 제공하던 것을 여러 Spring 모듈의 jar 파일로 나누어 제공하기 시작했다.
              (ex : spring-core, spring-web 등)
            • SPEL(Spring Expression Language) 가 도입되어 XML및 Annotation 기반 Bean정의에서 사용할 수 있게 되었다.
              • 이로 인해서 외부 프로퍼티 파일이나 환경변수에서 값을 가져오기 쉬워졌다.
            • Java 클래스로부터 @Configuration, @Bean 등의 Annotation을 사용해서 직접 메타 데이터를 설정하고, DI 지원을 받을 수 있다.
            • OXM(Object Xml Mapping)을 사용하여 Bean을 XML형태로 관리할 수 있게 되었다.
            • Rest API 에 대한 지원이 추가되었다.
              • 서버로서는 기존 MVC Framework 레벨에서 Annotation 기반 확장이 추가되었다.
              • 클라이언트로서는 RestTemplate 을 추가해 지원한다.
            • H2등의 Embeded Database를 지원한다.
            • 2016년 12월 31일부로 개발 및 지원이 종료되었다.

            Spring 4.0

            spring 3.0 이 java 5+ 버전들에 대해 지원을 한다면 spring 4.0 버전은 java 8의 특징을 적용할 수 있게 지원한다.

            • POM 설정을 도와준다.
            • Hibernate 3.6이상, EhCache 2.1 이상, Groovy 1.8이상, Joda-Time 2.0 이상 새로운 Dependency 지원
            • java6, java7, java8 의 고유 기능 지원 
              • 람다식, Optional, Callback, Interface 등의 기능을 스프링 레벨에서 사용가능
            • Java EE6, 7 에 대한 고려, JPA 2.0과 Servelet 3.0에 대한 지원이 포함
            • Groovy를 이용한 Bean 설정이 가능하다.
            • Core 컨테이너들의 기능 지원이 확대되었다. 먼저 Repository 들이 더 쉽게 Inject 될 수 있으며, 각종 Metadata Annotation 들을 이용한 Custom Annotation 작성이 가능하다. 
            • Web 개발 하기 위한 도구들이 생김. @RestController
            • Web Socket이나 STOMP 등의 프로토콜을 같이 지원한다.
            • 테스트 환경이 개선되었다.
            • Core Container 들의 기능 지원이 확대되었다.
              • 예를 들어, Spring Data Repository 를 사용하고 있다면 간단한 구현으로 주입할 수 있다. (@Autowired Repository<Customer> customerRepository)
              • meta-annotation 지원과 함께 custom-annotation 을 만들 수 있다.
              • Bean 관리가 더 용이해졌다.
                • @Order 어노테이션을 통해 배열과 리스트 형태의 Bean을 정렬 할 수 있다.
                • @Lazy 어노테이션을 통해 Lazy Injection이 가능하다.

            Spring 5.X 

            spring 5.X은 JDK 8+, 9등에 대해서 지원하며 Java8을 표준으로 사용한다.

            • 전체 프레임워크가 Java8을 기반 소스코드로 삼으며, 제네릭과 람다 등을 통해 가독성을 향상 되었다.
            • JDK 9와도 완벽 호환된다.
            • Jackson2.9, Protobuf 3, Reactor 3.1과의 호환 추가
            • Spring WebFlux 추가, 비동기와 넌 블로킹 이벤트 루프 모델 사용가능
            • Kotlin 지원
            • Junit 5 지원
            • 5.0.x 버전은 2020년 10월까지 지원되며, 5.1.x 버전과 5.2.x 버전은 각각 20년 10월, 21말까지 확발히 개발될 것이다. 

            Java 버전 별 특징 

            java 8 - 17 특징

            Java 8

            Java 8은 대규모 릴리스였다. 

            • Lambda
            • stream
            • interface default method
            • Optinal
            • new Data and Time API(LocalDateTime)

            Lambda

            Java 8 이전 익명 클래스의 사용을 람다를 이용하여 더욱 간결하고 직관적으로 구현

            before

            Runnable runnable = new Runnable(){
               @Override
               public void run(){
                 System.out.println(*"Hello world !"*);
               }
             };

            after

            Runnable runnable = () -> System.out.println(*"Hello world two!"*);

            Stream

            Java 8은 스트림 API를 통해 컬렉션을 처리하면서 발생하는 모호함과 반복적인 코드 문제와 멀티코어 활용 어려움이라는 두 가지 모두 해결

            List<String> list = Arrays.asList(*"franz"*, *"ferdinand"*, *"fiel"*, *"vom"*, *"pferd"*);
            list.stream()
                .filter(name -> name.startsWith(*"f"*))
                .map(String::toUpperCase)
                .sorted()
                .forEach(System.out::println);

            Java 9 

            Java 9는 다음과 같은 몇 가지 추가 사항이 포함된 상당히 큰 릴리즈

            모듈 시스템 등장 및 try-with-resources 문 또는 다이아몬드 연산자(<>) 확장, HTTP클라이언트와 같은 몇 가지 다른 개선 사항 존재

            컬렉션

            컬렉션에는 list, set, map을 쉽게 구성할 수 있는 몇 가지 추가 기능

            List<String> list = List.of(*"one"*, *"two"*, *"three"*);
            Set<String> set = Set.of(*"one"*, *"two"*, *"three"*);
            Map<String, String> map = Map.of(*"foo"*, *"one"*, *"bar"*, *"two"*);

            스트림

            takeWhile, dropWhile, iterate 메서드의 형태로 몇 가지 추가 기능

            Stream<String> stream = Stream.iterate(*""*, s -> s + *"s"*)
              .takeWhile(s -> s.length() < 10);

            Java 10

            가비지 컬렉션 등과 같은 Java 10에 몇 가지 변경 사항이 존재

            개발자로서 보게 될 유일한 실제 변경 사항은 로컬 변수 유형 추론이라고도 하는 "var" 키워드의 도입

            • var 키워드
            • 병렬 처리 가비지 컬렉션 도입으로 인한 성능 향상
            • JVM 힙 영역을 시스템 메모리가 아닌 다른 종류의 메모리에도 할당 가능

            지역 변수 유형 추론: var-keyword

            // Pre-Java 10
            String myName = "Marco";
            
            // With Java 10
            var myName = "Marco"

            Java 11

            Java 11은 개발자의 관점에서 볼 때 약간 작은 릴리스

            • Oracle JDK와 OpenJDK 통합
            • Oracle JDK가 구독형 유료 모델로 전환
            • 서드파티 JDK 로의 이전 필요
            • lambda 지역변수 사용법 변경
            • 기타 추가

            Strings & Files

            Strings and Files에는 몇 가지 새로운 메서드 추가

            *"Marco"*.isBlank();
            *"Mar\nco"*.lines();
            *"Marco  "*.strip();
            
            Path path = Files.writeString(Files.createTempFile(*"helloworld"*, *".txt"*), *"Hi, my name is!"*);
            String s = Files.readString(path);

            Run Source Files

            Java 10부터 Java 소스 파일 을 먼저 컴파일 하지 않고도 실행할 수 있다. 스크립팅을 향한 한 걸음

            ubuntu@DESKTOP-168M0IF:~$ java MyScript.java

            Java 12

            Java 12에는 몇 가지 새로운 기능과 정리가 포함 되어 있지만

            • 언급할 가치가 있는 것은 유니코드 11 지원과 새로운 스위치 표현식의 preview 뿐

            Java 13

            여기 에서 전체 기능 목록을 찾을 수 있지만 기본적으로 유니코드 12.1 지원과 두 가지 새롭거나 개선된 preview 기능(향후 변경될 수 있음)이 제공

            스위치 표현식(preview)

            이제 스위치 표현식이 값을 반환 가능하며 fall-through/break 문제 없이 표현식에 람다 스타일 구문을 사용 가능

            before

            switch(status) {
              case SUBSCRIBER:
                *// code block*break;
              case FREE_TRIAL:
                *// code block*break;
              default:
                *// code block*}

            after(람다 스타일)

            boolean result = switch (status) {
                case SUBSCRIBER -> true;
                case FREE_TRIAL -> false;
                default -> throw new IllegalArgumentException(*"something is murky!"*);
            };

            Multiline Strings (Preview)

            \n 같은 표현 안써도 인식함

             

            String htmlBeforeJava13 = *"<html>\n"* +
                          *"    <body>\n"* +
                          *"        <p>Hello, world</p>\n"* +
                          *"    </body>\n"* +
                          *"</html>\n"*;
            String htmlWithJava13 = *"""
                          <html>
                              <body>
                                  <p>Hello, world</p>
                              </body>
                          </html>
                          """*;

            Java 14

            • 스위치 표현시 표준화
            • instanceof 패턴 매칭 (preview)
            • record (data object) 선언 기능 추가 (preview)

            스위치 표현(Standard)

            버전 12 및 13에서 preview 였던 스위치 표현식 이 이제 표준화 되었다.

            int numLetters = switch (day) {
                case MONDAY, FRIDAY, SUNDAY -> 6;
                case TUESDAY                -> 7;
                default      -> {
                  String s = day.toString();
                  int result = s.length();
                  yield result;
                }
            };

            record(preview)

            Java로 많은 상용구를 작성하는 고통을 완화하는 데 도움이 되는 레코드 클래스가 있다.

            데이터, (잠재적으로) getter/setters, equals/hashcode, toString만 포함하는 이 Java 14 이전 클래스

            final class Point {
                public final int x;
                public final int y;
            
                public Point(int x, int y) {
                    this.x = x;
                    this.y = y;
                }
            }
                *// state-based implementations of equals, hashCode, toString// nothing else*

            레코드를 사용

            record Point(int x, int y) { }

            유용한 NullPointerExceptions

            마지막으로 NullPointerExceptions는 정확히 어떤 변수가 null 인지 설명한다 .

            author.age = 35;
            ---
            Exception in thread *"main"* java.lang.NullPointerException:
                 Cannot assign field *"age"* because *"author"* is null

            Pattern Matching For InstanceOf (Preview)

            이전에는 다음과 같이 instanceof 내부에서 객체를 캐스팅 필요

            if (obj instanceof String) {
                String s = (String) obj;
                *// use s*}

            이제 이 작업을 수행하여 캐스트를 효과적으로 삭제 가능

            if (obj instanceof String s) {
                System.out.println(s.contains(*"hello"*));
            }

            Java 15

            • Text-Blocks / Multiline Strings
            • Records & Pattern Matching(2차 preview, 상단 Java 14 참조)
            • 스케일링 가능한 낮은 지연의 가비지 컬렉터 추가(ZGC)
            • 레코드 (2차 preview, 상단 Java 14 참조)
            • Sealed Classes - Preview
            • Nashorn JavaScript Engine 제거

            Text-Blocks / Multiline Strings

            Java 13의 실험 기능으로 도입된 여러 줄 문자열은 이제 프로덕션 준비 완료

            String text = *"""
                            Lorem ipsum dolor sit amet, consectetur adipiscing \
                            elit, sed do eiusmod tempor incididunt ut labore \
                            et dolore magna aliqua.\
                            """*;

            Sealed Classes - Preview

            • 상속 가능한 클래스를 지정할 수 있는 봉인 클래스가 제공된다.
            • 상속 가능한 대상은 상위 클래스 또는 인터페이스 패키지 내에 속해 있어야 한다.
            public abstract sealed class Shape
                permits Circle, Rectangle, Square {...}

            즉, 클래스가 public인 동안 하위 클래스로 허용되는 유일한 Shape 클래스들은 Circle, Rectangle 및 Square 이다.

             

            Java 16

            • Pattern Matching for instanceof
            • Unix-Domain Socket Channels
            • Foreign Linker API - Preview
            • Records & Pattern Matching

            Unix-Domain Socket Channels

            이제 Unix 도메인 소켓에 연결할 수 있다(macOS 및 Windows(10+)에서도 지원됨).

             socket.connect(UnixDomainSocketAddress.of(
                    *"/var/run/postgresql/.s.PGSQL.5432"*));

            Foreign Linker API - Preview

            JNI(Java Native Interface)에 대한 계획된 교체로, 기본 라이브러리에 바인딩할 수 있다(C 생각).

             

            Java 17

            Java 17은 Java 11 이후의 새로운 Java LTS(장기 지원) 릴리스

            • Pattern Matching for switch (Preview)
            • Sealed Classes (Finalized)
            • Foreign Function & Memory API (Incubator)
            • Deprecating the Security Manager

             

            Pattern Matching for switch (Preview)

            이제 객체를 전달하여 기능을 전환하고 특정 유형을 확인할 수 있다.

            public String test(Object obj) {
            
                return switch(obj) {
            
                case Integer i -> *"An integer"*;
            
                case String s -> *"A string"*;
            
                case Cat c -> *"A Cat"*;
            
                default -> *"I don't know what it is"*;
            
                };
            
            }

            Sealed Classes (Finalized)

            Java 15에서 preview 제공되었던 기능 완료

            Foreign Function & Memory API (Incubator)

            Java Native Interface(JNI)를 대체한다. 기본 함수를 호출하고 JVM 외부의 메모리에 액세스할 수 있다. 지금은 C가 C++, 포트란과 같은 추가 언어 지원 계획을 가지고 있다고 생각

            Deprecating the Security Manager

            자바 1.0 이후로 보안 관리자가 존재해 왔었지만 현재는 더 이상 사용되지 않으며 향후 버전에서는 제거될 예정

             

            Spring Boot

            Spring Boot 1.1 (Release 2014.05)

            • Java 1.6 이상
            • Spring Framework 4.0.5
            • Tomcat 7.0.54, Hibernate 4.3.1

            Spring Boot 1.5 (Release 2017.01)

            • Java 8 이상
            • Spring Framework 4.3
            • Tomcat 8.5, Hibernate 5.0
            • ConfigurationProperties 에 JSR303 지원

            Spring Boot 2.0.0 (Release 2018.03)

            • Java 8 , Java 9 tested
            • Spring Framework 5.0
            • Tomcat 8.5, Hibernate 5.2

            Spring Boot 2.1.0(Release 2018.10)

            • Java 11 Support (Java 8 ~ 11)
            • Spring Framework 5.1
            • Tomcat 9, Hibernate 5.3

             

             

            참고

            https://velog.io/@ljo_0920/java-%EB%B2%84%EC%A0%84%EB%B3%84-%EC%B0%A8%EC%9D%B4-%ED%8A%B9%EC%A7%95

             

            'Java' 카테고리의 다른 글

            [Java] Java 8 에서의 변경사항  (0) 2022.05.05
            추상클래스 vs 인터페이스  (0) 2022.04.07
            GC (Garbage Collector)  (0) 2022.04.06
            객체의 래퍼클래스(WrapperClass)  (0) 2021.09.16
            IntelliJ IDEA 단축키  (0) 2021.01.28

            목차 돌아가기

            목차

              퍼사드(Facade)란?

              • 퍼사드 : 건물의 입구쪽 전경이라는 뜻으로 외벽에선 안이 어떻게 생겼는지 알 수없다는 의미

              퍼사드(Facade) 패턴

              복잡한 서브 시스템 의존성을 최소화하는 방법

              클라이언트가 사용해야 하는 복잡한 서브 시스템 의존성을 간단한 인터페이스로 추상화 할 수 있다.

              레거시 코드

              package com.patten.degine_patten.facade._01_before;
              
              import javax.mail.Message;
              import javax.mail.MessagingException;
              import javax.mail.Session;
              import javax.mail.Transport;
              import javax.mail.internet.InternetAddress;
              import javax.mail.internet.MimeMessage;
              import java.util.Properties;
              
              /**
               * 실제로 동작하지 않는 코드
               * 가장 간단한 이메일을 보내는 소스 코드
               * 특정한 자바가 제공하는 라이브러리에 의존성이 많은 코드 : SOLID 등 자바가 지향하는 것과 다른 느낌
               */
              public class Client {
              
                  public static void main(String[] args) {
                      String to = "keesun@whiteship.me";
                      String from = "whiteship@whiteship.me";
                      String host = "127.0.0.1";
              
                      Properties properties = System.getProperties();
                      properties.setProperty("mail.smtp.host", host);
              
                      Session session = Session.getDefaultInstance(properties);
              
                      try {
                          MimeMessage message = new MimeMessage(session);
                          message.setFrom(new InternetAddress(from));
                          message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
                          message.setSubject("Test Mail from Java Program");
                          message.setText("message");
              
                          Transport.send(message);
                      } catch (MessagingException e) {
                          e.printStackTrace();
                      }
                  }
              }

              해당 코드에서 java로 이메일을 보내는 코드를 작성한 것이다. 하지만 해당 코드는 너무 javax.mail 이라는 인터페이스에 의존적이다. 

              퍼사드 패턴 적용 추상화 고민하기

              • 메일을 보내는(sender) 클래스
              • 메일 설정
              • 메일 메시지

              이메일을 보내는(sender)

              센더가 의존을 담당한다. (이게 뭐하는 거지..? 조삼모사 아니냐?) 아니다. 의미가 있다. send하는 기능이 여러 곳에서 한다? 약간 util 성으로 의미가 생기게된다.

              package com.patten.degine_patten.facade._02_after;
              
              import javax.mail.Message;
              import javax.mail.MessagingException;
              import javax.mail.Session;
              import javax.mail.Transport;
              import javax.mail.internet.InternetAddress;
              import javax.mail.internet.MimeMessage;
              import java.util.Properties;
              
              public class EmailSender {
              
                  private EmailSettings emailSettings;
              
                  public EmailSender(EmailSettings emailSettings) {
                      this.emailSettings = emailSettings;
                  }
              
                  /**
                   * 이메일 보내는 메소드
                   * @param emailMessage
                   */
                  public void sendEmail(EmailMessage emailMessage) {
                      Properties properties = System.getProperties();
                      //properties.setProperty("mail.smtp.host", host);
                      properties.setProperty("mail.smtp.host", emailSettings.getHost()); //host정보는 setting에
              
                      Session session = Session.getDefaultInstance(properties);
              
                      try {
                          MimeMessage message = new MimeMessage(session);
                          //message.setFrom(new InternetAddress(from));
                          message.setFrom(new InternetAddress(emailMessage.getFrom())); //from 정보는 메시지에
                          //message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
                          message.addRecipient(Message.RecipientType.TO, new InternetAddress(emailMessage.getTo())); //to 정보는 메시지에
                          message.addRecipient(Message.RecipientType.CC, new InternetAddress(emailMessage.getCc()));
                          message.setSubject(emailMessage.getSubject());
                          //message.setText("message");
                          message.setText(emailMessage.getText());
              
                          Transport.send(message);
                      } catch (MessagingException e) {
                          e.printStackTrace();
                      }
                  }
              
              
              }

              이메일 메시지

              가장 많은 부분을 담당하는데 이메일에 필요한 내용과 관련된 정보를 담당한다.

              public class EmailMessage {
              
                  private String from;
                  private String to;
                  private String cc;
                  private String bcc;
                  private String subject;
                  private String text;
              
                  public String getFrom() {
                      return from;
                  }
              
                  public void setFrom(String from) {
                      this.from = from;
                  }
              
                  public String getTo() {
                      return to;
                  }
              
                  public void setTo(String to) {
                      this.to = to;
                  }
              
                  public String getSubject() {
                      return subject;
                  }
              
                  public void setSubject(String subject) {
                      this.subject = subject;
                  }
              
                  public String getText() {
                      return text;
                  }
              
                  public void setText(String text) {
                      this.text = text;
                  }
              
                  public String getCc() {
                      return cc;
                  }
              
                  public void setCc(String cc) {
                      this.cc = cc;
                  }
              
                  public String getBcc() {
                      return bcc;
                  }
              
                  public void setBcc(String bcc) {
                      this.bcc = bcc;
                  }
              }

              이메일 설정

              host같은 기본 이메일 설정을 담당한다.

              public class EmailSettings {
              
                  private String host;
              
                  public String getHost() {
                      return host;
                  }
              
                  public void setHost(String host) {
                      this.host = host;
                  }
              }

              이메일 메인

              기존과 변화를 살펴보면 메인에서 import 했던 부분들이 사라졌다. 바로 import를 안하지만, 퍼사드에 의존하게 된다.

              public class Client {
              
                  public static void main(String[] args) {
                      EmailSettings emailSettings = new EmailSettings();
                      emailSettings.setHost("127.0.0.1");
              
                      EmailSender emailSender = new EmailSender(emailSettings);
              
                      EmailMessage emailMessage = new EmailMessage();
                      emailMessage.setFrom("keesun");
                      emailMessage.setTo("whiteship");
                      emailMessage.setCc("일남");
                      emailMessage.setSubject("오징어게임");
                      emailMessage.setText("밖은 더 지옥이더라고..");
              
                      emailSender.sendEmail(emailMessage);
                  }
              }

              퍼사드(Facade) 패턴의 장점과 단점

              복잡한 서브 시스템 의존성을 최소화하는 방법

              장점

              서브 시스템에 대한 의존성을 한곳으로 모을 수 있다. 결국 코드 읽는게 더 편해지는 것이다. 즉 추상화 정도가 강해진다.

              또한 진입 장벽이 낮다.

              단점

              퍼사드 클래스가 서브 시스템에 대한 모든 의존성을 가지게 된다.

               

              퍼사드 패턴 예제 - Spring

              패턴이 적용 되었다는 것은 보는 각도에 따라 다르게 보일 수 있다. 해당 예제는 브릿지패턴의 예제랑 같지만 보는 각도에 따라 퍼사드라고 볼 수 있다.

              package com.patten.degine_patten.facade._03_java;
              
              import org.springframework.jdbc.support.JdbcTransactionManager;
              import org.springframework.mail.MailSender;
              import org.springframework.mail.javamail.JavaMailSenderImpl;
              import org.springframework.transaction.PlatformTransactionManager;
              
              public class FacadeInSpring {
              
                  public static void main(String[] args) {
                      MailSender mailSender = new JavaMailSenderImpl();
              
                      PlatformTransactionManager platformTransactionManager = new JdbcTransactionManager();
                  }
              }

               

              'Java > *****디자인패턴' 카테고리의 다른 글

              [싱글톤 패턴] Spring에서 싱글톤을 사용하는 이유  (0) 2022.05.05
              빌더 패턴  (0) 2022.04.01
              싱글톤 (Singleton) 패턴  (0) 2022.04.01
              디자인 패턴  (0) 2022.03.31
              디자인 패턴 싱글톤 패턴 개념 정리  (0) 2022.03.18

              목차

                빌더 패턴 이란?

                동일한 프로세스를 거쳐 다양한 구성의 인스턴스를 만드는 방법

                빌더 패턴을 사용하면 객체의 생성을 깔금하고 유연하게 할 수 있다.

                Effective Java의 두번째 규칙의 제목이 바로 생성자의 인자가 많을 때는 빌더(Builder) 패턴을 이용하라

                빌더 패턴 아키텍처

                가장 중요한 부분은 Director 부분이며 클라이언트는 Director를 거친다.

                빌더 패턴 아닌 경우

                /**
                * 짧은 여행 : 짧은 여행이기 때문에 set해줄게 별로 없음
                */
                TourPlan shortTrip = new TourPlan();
                shortTrip.setTitle("오레곤 롱비치 여행");
                shortTrip.setStartDate(LocalDate.of(2021, 7, 15));
                
                /**
                * 긴 여행
                */
                TourPlan tourPlan = new TourPlan();
                tourPlan.setTitle("칸쿤 여행");
                tourPlan.setNights(2); //2박
                tourPlan.setDays(3);   //3일
                tourPlan.setStartDate(LocalDate.of(2020, 12, 9));
                tourPlan.setWhereToStay("리조트");
                tourPlan.addPlan(0, "체크인 이후 짐풀기");
                tourPlan.addPlan(0, "저녁 식사");
                tourPlan.addPlan(1, "조식 부페에서 식사");
                tourPlan.addPlan(1, "해변가 산책");
                tourPlan.addPlan(1, "점심은 수영장 근처 음식점에서 먹기");
                tourPlan.addPlan(1, "리조트 수영장에서 놀기");
                tourPlan.addPlan(1, "저녁은 BBQ 식당에서 스테이크");
                tourPlan.addPlan(2, "조식 부페에서 식사");
                tourPlan.addPlan(2, "체크아웃");

                문제점

                • set 해줘야 하는 값이 꼬일 수있다.
                  • 2박 3일을 입력하기 위해선 setNights(2), setDays(3) 처럼 입력해야하는데 둘 중 하나를 빼 먹을 수 있다.
                • 생성자 초기 선언 할 때 너무 장황하다.
                • 그러다 보면 생성자를 조정할 수 있는데, 사용자 입장에서 어떤 생성자를 써야할지 모호해질 수 있다. 
                더보기

                초기 선언이 너무 장황한 예시

                 

                빌더 패턴 구현

                Interface - TourPlanBulder

                public interface TourPlanBuilder {
                
                    TourPlanBuilder nightsAndDays(int nights, int days);
                
                    TourPlanBuilder title(String title);
                
                    TourPlanBuilder startDate(LocalDate localDate);
                
                    TourPlanBuilder whereToStay(String whereToStay);
                
                    TourPlanBuilder addPlan(int day, String plan);
                
                    /**
                     * 데이터 검증을 할 수 있음.
                     * nights, days 중 하나만 들어갔다던지, 근교여행인데 굳이 필요없는 값이 들어간건 아닌지
                     * @return
                     */
                    TourPlan getPlan(); //데이터 검증
                }

                Class - DefalutTourBuilder

                /**
                 * 구현체 생성
                 */
                public class DefaultTourBuilder implements TourPlanBuilder {
                
                    private String title;
                
                    private int nights;
                
                    private int days;
                
                    private LocalDate startDate;
                
                    private String whereToStay;
                
                    private List<DetailPlan> plans;
                
                    /**
                     * this를 리턴한다는 것은 DefaultTourBulder가 리턴이 된다는 뜻
                     * @param nights
                     * @param days
                     * @return
                     */
                    @Override
                    public TourPlanBuilder nightsAndDays(int nights, int days) {
                        this.nights = nights;
                        this.days = days;
                        return this;
                    }
                
                    @Override
                    public TourPlanBuilder title(String title) {
                        this.title = title;
                        return this;
                    }
                
                    @Override
                    public TourPlanBuilder startDate(LocalDate startDate) {
                        this.startDate = startDate;
                        return this;
                    }
                
                    @Override
                    public TourPlanBuilder whereToStay(String whereToStay) {
                        this.whereToStay = whereToStay;
                        return this;
                    }
                
                    @Override
                    public TourPlanBuilder addPlan(int day, String plan) {
                        if (this.plans == null) {
                            this.plans = new ArrayList<>();
                        }
                
                        this.plans.add(new DetailPlan(day, plan));
                        return this;
                    }
                
                
                    /**
                     * 마지막에 인스턴스를 생성한다.
                     * @return
                     */
                    @Override
                    public TourPlan getPlan() {
                        return new TourPlan(title, nights, days, startDate, whereToStay, plans);
                    }
                }

                TourDirector

                public class TourDirector {
                
                    private TourPlanBuilder tourPlanBuilder;
                
                    public TourDirector(TourPlanBuilder tourPlanBuilder) {
                        this.tourPlanBuilder = tourPlanBuilder;
                    }
                
                    public TourPlan cancunTrip() {
                        return tourPlanBuilder.title("칸쿤 여행")
                                .nightsAndDays(2, 3)
                                .startDate(LocalDate.of(2020, 12, 9))
                                .whereToStay("리조트")
                                .addPlan(0, "체크인하고 짐 풀기")
                                .addPlan(0, "저녁 식사")
                                .getPlan();
                    }
                
                    public TourPlan longBeachTrip() {
                        return tourPlanBuilder.title("롱비치")
                                .startDate(LocalDate.of(2021, 7, 15))
                                .getPlan();
                    }
                }

                Main

                 public static void main(String[] args) {
                        TourDirector director = new TourDirector(new DefaultTourBuilder());
                        TourPlan tourPlan = director.cancunTrip();
                        TourPlan tourPlan1 = director.longBeachTrip();
                    }

                빌더 패턴 장점 & 단점

                장점

                • 빌더 패턴으로 생성자의 복잡함을 단순화 할 수 있다.
                •  

                단점

                자바와 스프링에서 찾아보는 패턴

                StringBuffer (Sync 사용)

                StringBuffer와 StringBuilder의 차이는 Sync(동기화)여부의 차이가 있다.

                 public static void main(String[] args) {
                        StringBuilder stringBuilder = new StringBuilder();
                        String result = stringBuilder.append("whiteship").append("keesun").toString();
                        System.out.println(result);
                }

                 

                StringBuilder (Sync 미사용)

                Stream.Builder

                UriComponents howToStudyJava = UriComponentsBuilder.newInstance()
                            .scheme("http")
                            .host("www.whiteship.me")
                            .path("java playlist ep1")
                            .build().encode();
                System.out.println(howToStudyJava);

                lombok

                @Builder
                public class LombokExample {
                
                    private String title;
                
                    private int nights;
                
                    private int days;
                
                    public static void main(String[] args) {
                        LombokExample trip = LombokExample.builder()
                                .title("여행")
                                .nights(2)
                                .days(3)
                                .build();
                    }
                
                }

                Spring

                   //URI를 만들어주는 Builder
                   public static void main(String[] args) {
                       UriComponents howToStudyJava = UriComponentsBuilder.newInstance()
                                .scheme("http")
                                .host("www.whiteship.me")
                                .path("java playlist ep1")
                                .build().encode();
                        System.out.println(howToStudyJava);
                    }

                 

                 

                 

                 

                 

                 

                 

                 

                 

                 

                 

                 

                 

                 

                 

                출처

                인프런

                김영한-코딩으로 학습하는 GoF 디자인 패턴

                목차

                  싱글톤 패턴이란?

                  인스턴스를 오직 한개만 제공하는 클래스

                  • 시스템 런타임, 환경 세팅에 대한 정보 등, 인스턴스가 여러개 일 때 문제가 생길 수 있는 경우가 있다. 인스턴스를 오직 한개만 만들어 제공하는 클래스가 필요하다.

                  이른 초기화

                  synchronzied

                  double checked locking (복잡)

                  static inner 클래스 (가장권장)

                  자바와 스프링에서 찾아보는 패턴

                  자바

                  runtime 메서드는 자바에서 제공하는 라이브러리이며 자바에서 지금 동작하는 환경의 정보를 담고있다. 하기와 같이 Runtime은 new로 생성이 불가능하다. 즉, 싱글톤 패턴으로 구현되어있는 것을 확인할 수 있다. 

                  Spring

                  bean이라는 스코프에서 관리하고 있기 때문에 hello == hello2 는 true를 반환한다.

                  엄밀히 따지면 싱글톤  패턴이 쓰인 것은 아니지만 싱글톤 소코프를 사용한 케이스이다.

                   

                   

                   

                   

                   

                  싱글톤 패턴은 어떻게 쓰이나?

                  • 스프링에서 빈의 스코프 중에 하나로 싱글톤 스코프
                  • 자바 Java.lang.Runtime
                  • 다른 디자인 패턴(빌더, 퍼사드, 추상 팩토리 등) 구현체의 일부로 쓰이기도 한다.

                   

                  'Java > *****디자인패턴' 카테고리의 다른 글

                  [싱글톤 패턴] Spring에서 싱글톤을 사용하는 이유  (0) 2022.05.05
                  퍼사드(Facade) 패턴  (0) 2022.04.03
                  빌더 패턴  (0) 2022.04.01
                  디자인 패턴  (0) 2022.03.31
                  디자인 패턴 싱글톤 패턴 개념 정리  (0) 2022.03.18

                  + Recent posts