Queue<E> 인터페이스

클래스로 구현된 스택과는 달리 자바에서 큐 메모리 구조는 별도의 인터페이스 형태로 제공됩니다.

큐를 상속받는 하위 인터페이스는 다음과 같습니다.

 

 1) Deque<E>

 2) BlockingDeque<E>

 3) BlockingQueue<E>

 4) TransferQueue<E>

 

큐 메모리 구조는 선형 메모리 공간에 데이터를 저장하면서 선입선출의 시맨틸을 따르는 자료구조입니다.

즉, 먼저 저장된 데이터가 먼저 인출됩니다.

 

 

Queue 메소드

 

Queue를 활용한 BFS 구현

import java.io.*;
import java.util.*;

/* 인접 리스트를 이용한 방향성 있는 그래프 클래스 */
class Graph {
  private int V; // 노드의 개수
  private LinkedList<Integer> adj[]; // 인접 리스트

  /** 생성자 */
  Graph(int v) {
    V = v;
    adj = new LinkedList[v];
    for (int i=0; i<v; ++i) // 인접 리스트 초기화
      adj[i] = new LinkedList();
  }

  /** 노드를 연결 v->w */
  void addEdge(int v, int w) { adj[v].add(w); }

  /** s를 시작 노드으로 한 BFS로 탐색하면서 탐색한 노드들을 출력 */
  void BFS(int s) {
    // 노드의 방문 여부 판단 (초깃값: false)
    boolean visited[] = new boolean[V];
    // BFS 구현을 위한 큐(Queue) 생성
    LinkedList<Integer> queue = new LinkedList<Integer>();

    // 현재 노드를 방문한 것으로 표시하고 큐에 삽입(enqueue)
    visited[s] = true;
    queue.add(s);

    // 큐(Queue)가 빌 때까지 반복
    while (queue.size() != 0) {
      // 방문한 노드를 큐에서 추출(dequeue)하고 값을 출력
      s = queue.poll();
      System.out.print(s + " ");

      // 방문한 노드와 인접한 모든 노드를 가져온다.
      Iterator<Integer> i = adj[s].listIterator();
      while (i.hasNext()) {
        int n = i.next();
        // 방문하지 않은 노드면 방문한 것으로 표시하고 큐에 삽입(enqueue)
        if (!visited[n]) {
          visited[n] = true;
          queue.add(n);
        }
      }
    }
  }
}

 

'JAVA' 카테고리의 다른 글

stream 활용해 list 최소, 최대값 구하기  (0) 2022.11.20
2차원 배열 정렬, 문자열 배열 정렬  (1) 2022.10.03
정렬과 lambda  (0) 2022.09.29
PriorityQueue ( 우선순위큐 )  (0) 2022.09.27
putIfAbsent  (0) 2022.08.11

 

     - 목표
     베스트 앨범에 들어갈 노래의 고유 번호를 순서대로 출력

     - 노래를 수록하는 기준
     1) 속한 노래가 많이 재생된 장르를 먼저 수록
     2) 장르 내에서 많이 재생된 노래를 먼저 수록
     3) 장르 내에서 재생 횟수가 같은 노래중에서 고유 번호가 낮은 노래를 먼저 수록

     - 배열 설명
     1) genres : 노래의 장르를 나타냄
     2) plays : 노래별 재생횟수를 나타냄

     - 해결과정
     1) play 횟수를 중첩해서 더해준다.
     2) 키 값만을 추출하여 리스트를 만들고, 리스트를 play횟수를 기준으로 정렬한다.
        ( 그 이유는 hashMap 은 순서가 없기 때문에 정렬할 수 없기 때문이다. )
     3) key값을 정렬한 리스트에서 제일 많은 횟수를 재생한 장르부터 장르별 제일 많은 횟수가 플레이된 인덱스,
        두번째로 많은 횟수가 플레이된 인덱스를 찾아 정답 배열에 순서대로 넣어준다.
     4) 이때, 두번째로 많은 횟수가 플레이된 인덱스는 존재하지 않을 수 있기 때문에 이를 처리
     5) 정답 리스트를 배열로 변환하여 반환
     
      * 단, 장르별 두 곡까지 수록곡에 담을 수 있음

 

 

[ 소스코드 ]

import java.util.*;

public class BestElbum {

    public static void main(String[] args) {
        String[] genres = {"classic", "pop", "classic", "classic", "pop"};
        int[] plays = {500, 600, 150, 800, 2500};
        String resultArr = Arrays.toString(solution(genres, plays));
        System.out.println(resultArr);
    }



    public static int[] solution(String[] genres, int[] plays) {
        ArrayList<String> genre = new ArrayList<>();
        Map<String, Integer> hashMap = new HashMap<>();
        ArrayList<Integer> list = new ArrayList<>();

        // 키 값이 이미 있으면 0을 반환하고 중첩되는 부분이 있으면 더해준다.
        for (int i = 0; i < genres.length; i++) {
            hashMap.put(genres[i], hashMap.getOrDefault(genres[i], 0) + plays[i]);
        }

        for (String key : hashMap.keySet()) {
            genre.add(key);
        }

        Collections.sort(genre, (o1, o2) -> hashMap.get(o2) - hashMap.get(o1));
        // key 값을 더해준 hashMap 에 대한 값을 내림차순으로 정렬한다.

        for (int i = 0; i < genre.size(); i++) {
            String g = genre.get(i); // 내림차순 정렬된 장르 리스트를 조회

            int max = 0; // 장르의 음악에서 재생 횟수가 가장 큰 인덱스를 찾는다.
            int firstIdx = -1;
            for (int j = 0; j < genres.length; j++) { // 기존의 장르 배열만큼 반복
                if(g.equals(genres[j]) && max < plays[j]){
                    // 장르 명이 동일하면 그 장르의 인덱스를 뽑아준다.
                    max = plays[j];
                    firstIdx = j;
                }
            }

            max = 0;
            int secondIdx = -1;
            for (int j = 0; j < genres.length; j++) {
                if (g.equals(genres[j]) && max < plays[j] && j != firstIdx) {
                    max = plays[j];
                    secondIdx = j;
                }
            }

            list.add(firstIdx);
            if (secondIdx >= 0) { // 장르에 대한 곡이 하나밖에 없는 경우는 -1로 남음
                list.add(secondIdx);
            }
        }

        int[] result = new int[list.size()];
        for (int i = 0; i < list.size(); i++) {
            result[i] = list.get(i);
        }
        return result;
    }
}

'프로그래머스' 카테고리의 다른 글

여행 경로  (1) 2022.10.03
가장 먼 노드  (0) 2022.09.30
단어 변환  (0) 2022.09.28
네트워크 ( DFS )  (0) 2022.09.28
이중우선순위큐  (0) 2022.09.27

lambda의 스타일

 

1. (parameter) -> expression

2. (parameter) -> {statement;}

 

이를 활용한 정수 리스트를 정렬하는 방식 : 

numbers.sort((Integer num1, Integer num2) -> {return num1-num2;});

 

우선, 람다는 함수형 인터페이스만 쓰일 수 있습니다.

함수형 인터페이스란 오직 하나의 추상메서드만 가지는 인터페이스를 의미합니다.

그래서 유일한 추상메서드를 보고 타입을 추론할 수 있기 때문에 타입을 명시하지 않아도 상관없습니다.

 

numbers.sort((num1,num2)->num1-num2);
or
numbers.sort((num1,num2)->Integer.compare(num1,num2));

 

그래서 이를 응용하여 sort() 함수에 적용시켜준다면 아래와 같이 작성할 수 있습니다.

 

ArrayList<String> g = new ArrayList<>();
g.add("a");
g.add("c");
g.add("b");
Collections.sort(g,(p1,p2)->g.get(p2)-g.get(p1));

'JAVA' 카테고리의 다른 글

2차원 배열 정렬, 문자열 배열 정렬  (1) 2022.10.03
Queue와 BFS  (1) 2022.09.30
PriorityQueue ( 우선순위큐 )  (0) 2022.09.27
putIfAbsent  (0) 2022.08.11
Java의 이중 중괄호 초기화  (0) 2022.08.11

 

[ 문제 해설 ]

     - 문제
     begin 에서 target 으로 변환하는 가장 짧은 변환 과정을 찾아라

     - 조건
     1) 한 번에 한 개의 알파벳만 바꿀 수 있음
     2) words 에 있는 단어로만 변환할 수 있음

     - 알고리즘
     dfs

     - 해결 과정
     1) 한 글자 빼고 나머지가 같은 단어를 words 에서 찾는다.
     2) 찾은 단어를 visited = true 로 설정
     3) cnt 를 증가시키며 dfs 함수를 재귀 호출
     4) 모든 경우의 수를 보기 위해 check = false 로 재설정
     5) begin 과 target이 같은 경우 cnt를 answer에 대입하고 종료

 

 

 

[ 소스코드 ]

 

public class wordChange {

    static boolean[] check;
    static int answer = 0;

    public static void main(String[] args) {
        String begin = "hit";
        String target = "cog";
        String[] words = {"hot", "dot", "dog", "lot", "log", "cog"};

        answer = solution(begin, target, words);
        System.out.println(answer);

    }

    public static int solution(String begin, String target, String[] words) {
        check = new boolean[words.length];
        dfs(begin, target, words, 0);
        return answer;
    }

    public static void dfs(String begin, String target,String[] word, int cnt) {

        if (begin.equals(target)) { // 타겟과 시작 단어가 같으면 바로 리턴
            answer = cnt; // 두 글자 빼고 같은 경우 0 -> 1 -> 2로 증가함
            return;
        }

        for (int i = 0; i < word.length; i++) {
            int k = 0; // 같은 스펠링이 몇개인지 세기 위한 변수
            if(check[i]){ // 한번 지나간 단어는 패스
                continue;
            }
            for (int j = 0; j < begin.length(); j++) {
                if(begin.charAt(j) == word[i].charAt(j)){
                    // begin 과 word 의 각 단어 내부를 확인
                    /*
                    ex. hit, dog, ["hot", "dot", "dog", "lot", "log", "cog"] 일 때,
                    begin : hit
                    word[0] : hot
                    k : 2
                     */
                    k++;
                }
            }

            /*
            ex. k = 2 이므로,
            check[0] = true;
            dfs ( hot, cog, word, 1 );
            check[0] = false;

             */
            if (k == begin.length() - 1) { // 한글자 빼고 두 같은 경우만 보면 됨
                check[i] = true; // 지나간 자리는 true
                dfs(word[i], target, word, cnt + 1);
                check[i] = false;
            }
        }
    }


}

'프로그래머스' 카테고리의 다른 글

가장 먼 노드  (0) 2022.09.30
베스트 앨범  (0) 2022.09.29
네트워크 ( DFS )  (0) 2022.09.28
이중우선순위큐  (0) 2022.09.27
정수 삼각형  (1) 2022.09.26

 

 

[ 해결 과정 ]

 

1) n개수만큼 불린 배열을 만들고 false 로 초기화

2) n만큼 for문을 돌리다가, check[i] 값이 false 인게 있으면 깊이 우선 탐색을 하는 dfs 메서드를 호출하고,

     네트워크 연결 개수를 ++ 해준다.

   * dfs 인자 : computer[][] , i , check[]

 

3) 전달받은 피라미터인 check[i] 값을 true로 바꿔준다.

4) computer[]의 길이만큼 반복문을 돈다.

5) 만약 아래 조건이 모두 만족하면, 재귀호출을 한다.

 - 자기 자신이 아니다. ( i != j )

 - check 배열 i 위치의 값이 false 이며, (check[i] == false )

 - computer 배열의 값이 1인 것 ( computer[i][j] == 0 )

6) 2번으로 돌아간다.

7) answer을 리턴한다.

 

 

[ 작성 코드 ]

 

public class network {

    /**
    - 컴퓨터 개수 n
    - 연결에 대한 정보가 담긴 2차원 배열 (computer)
     - 출력 : 네트워크 개수
    **/
    
    public static void main(String[] args) {
        int n = 3;
        int[][] computer1 = {{1, 1, 0}, {1, 1, 0}, {0, 0, 1}};
        int[][] computer2 = {{1, 1, 0}, {1, 1, 1}, {0, 1, 1}};

        solution(n, computer1);
        solution(n, computer2);
    }

    public static int solution(int n, int[][] computers) {
        int answer = 0;

        boolean[] check = new boolean[computers.length];
        for (boolean i : check) {
            i = false;
        }

        for (int i = 0; i < computers.length; i++) {
            if (check[i] == false) {
                dfs(computers, i, check);
                answer++;
            }
        }
        return answer;
    }

    public static boolean[] dfs(int[][] computers, int n, boolean[] check) {
        // n : 방문 노드, computer : 네트워크 배열, check : 방문했는지 확인하는 배열
        check[n] = true;
        for (int i = 0; i < computers.length; i++) {
            if(n != i && computers[n][i] == 1 && check[i] == false){
                // n != i : 방문 할 노드의 인덱스와 방문한 노드가 같지 않음
                // computer[n][i] : 네트워크 배열에서 이제 방문할 배열 원소가 1인 경우
                // check[i] == false : 아직 방문 하지 않은 경우
                check = dfs(computers, i, check);
            }
        }
        return check;
    }
}

'프로그래머스' 카테고리의 다른 글

베스트 앨범  (0) 2022.09.29
단어 변환  (0) 2022.09.28
이중우선순위큐  (0) 2022.09.27
정수 삼각형  (1) 2022.09.26
마라톤 문제  (1) 2022.09.25

 

[ 해결 과정 ]

 

1. [ I : 뒤의 숫자를 넣어준다. ] 

2. [ D - 큐에 값을 삭제한다 ] => D 1 : 최대값 삭제, D -1 : 최솟값 삭제

 

( 두번째 입출력 과정 예제 )

// -45 653 
// -45 -642 45 97
// -45 -642 45
// -45 45
// -45 45 333
// 최솟값 : -45, 최대값 : 333

 

 

[ 풀이 방향 ]

1. string 배열을 split 해줌

2. 우선순위 큐 2개를 생성해 오름차순 내림차순으로 초기화시켜 사용

 

[ 우선순위 큐 개념 ]

https://tjdwns4537.tistory.com/126

 

PriorityQueue ( 우선순위큐 )

우선순위 큐 일반적인 큐 구조를 가지면서, 데이터가 들어온 순서대로 나가는것이 아닌 우선순위를 먼저 결정하고 우선순위가 높은 데이터가 먼저 나가는 자료구조입니다. 우선순위 큐의 조건

tjdwns4537.tistory.com

 

 

[ 소스코드 ]

 

import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;

public class heapQueue {
    public static void main(String[] args) {
        String[] b = {"I -45", "I 653", "D 1", "I -642", "I 45", "I 97", "D 1", "D -1", "I 333"};

        String resultArr = Arrays.toString(solution(b));
        System.out.println(resultArr);
    }

    public static int[] solution(String[] operations) {
        int[] answer = {0,0};

        PriorityQueue<Integer> priorityQueueMax = new PriorityQueue<>(Comparator.reverseOrder());
        PriorityQueue<Integer> priorityQueueMin = new PriorityQueue<>();

        for (String operation : operations) {
            String[] splitOrder = operation.split(" ");

            if (splitOrder[0].equals("I")) {
                // 일단 두 큐에 뒤에 숫자를 넣어준다.
                priorityQueueMax.add(Integer.parseInt(splitOrder[1]));
                priorityQueueMin.add(Integer.parseInt(splitOrder[1]));
            }

            if (splitOrder[0].equals("D")) {
                if (!priorityQueueMax.isEmpty()) {
                    // 우선순위 큐가 비지 않을경우만 동작
                    if (splitOrder[1].equals("1")) {
                        // 1이면 최대값을 삭제한다.
                        int max = priorityQueueMax.peek();
                        priorityQueueMax.remove(max);
                        priorityQueueMin.remove(max);
                        // 삭제는 max,min 큐 둘다 해준다.
                    }
                    else {
                        // -1 이면 최솟값을 삭제한다.
                        int min = priorityQueueMin.peek();
                        priorityQueueMax.remove(min);
                        priorityQueueMin.remove(min);
                    }
                }
            }
        }

        if (!priorityQueueMax.isEmpty()) {
            answer[0] = priorityQueueMax.peek();
            answer[1] = priorityQueueMin.peek();
        }

        return answer;
    }
}

'프로그래머스' 카테고리의 다른 글

단어 변환  (0) 2022.09.28
네트워크 ( DFS )  (0) 2022.09.28
정수 삼각형  (1) 2022.09.26
마라톤 문제  (1) 2022.09.25
신규 아이디 추천 ( replaceAll, 정규표현식 )  (0) 2022.08.18

우선순위 큐

일반적인 큐 구조를 가지면서, 데이터가 들어온 순서대로 나가는것이 아닌 우선순위를 먼저 결정하고

우선순위가 높은 데이터가 먼저 나가는 자료구조입니다.

 

 

 

우선순위 큐의 조건

필수적으로 Comparable Interface를 구현해야 한다.

 

 - Comparable Interface를 구현하면 compareTo method 를 오버라이딩하게 되고,

    객체에서 처리할 우선순위 조건을 리턴해주면 PriorityQueue가 알아서 우선순위 높은 객체를 추출하는 구조입니다.

 

Heap 을 이용해 구현하는 것이 일반적입니다.

 

 - 데이터를 삽입할 때  우선순위를 기준으로 최대 힙 또는 최소 힙을 구성합니다.

    데이터를 꺼낼 때 루트 노드를 얻어낸 뒤 루트 노드를 삭제할 때는 빈 루트 노드 위치에 맨 마지막 노드를 삽입 후 아래로 내려가면서

    적절한 위치를 찾아 옮기는 방식으로 진행됩니다.  ( * 최대힙과 최소힙 * )

 

 

우선순위 큐 특징

1. 높은 우선순위의 요소를 먼저 꺼내 처리하는 구조입니다.

2. 내부 요소는 힙으로 구성되어 이진트리 구조를 가집니다.

3. 우선순위를 중요시해야하는 상황에 주로 쓰입니다.

4. 시간복잡도는 O(NlogN) 입니다.

 

 

 

우선순위 큐 선언

Priority Queue 를 사용하면 java.util.PriorityQueue 를 import 합니다.

import java.util.PriorityQueue;
import java.util.Collections;

//낮은 숫자가 우선 순위인 int 형 우선순위 큐 선언
PriorityQueue<Integer> priorityQueueLowest = new PriorityQueue<>();

//높은 숫자가 우선 순위인 int 형 우선순위 큐 선언
PriorityQueue<Integer> priorityQueueHighest = new PriorityQueue<>(Collections.reverseOrder());

 

 

 

우선순위 큐 동작 방법

// add(value) 메서드의 경우 만약 삽입에 성공하면 true를 반환, 
// 큐에 여유 공간이 없어 삽입에 실패하면 IllegalStateException을 발생
priorityQueueLowest.add(1);
priorityQueueLowest.add(10);
priorityQueueLowest.offer(100);

priorityQueueHighest.add(1);
priorityQueueHighest.add(10);
priorityQueueHighest.offer(100);


// 첫번째 값을 반환하고 제거 비어있다면 null
priorityQueueLowest.poll();

// 첫번째 값 제거 비어있다면 예외 발생
priorityQueueLowest.remove(); 

// 첫번째 값을 반환만 하고 제거 하지는 않는다.
// 큐가 비어있다면 null을 반환
priorityQueueLowest.peek();

// 첫번째 값을 반환만 하고 제거 하지는 않는다.
// 큐가 비어있다면 예외 발생
priorityQueueLowest.element();

// 초기화
priorityQueueLowest.clear();

'JAVA' 카테고리의 다른 글

Queue와 BFS  (1) 2022.09.30
정렬과 lambda  (0) 2022.09.29
putIfAbsent  (0) 2022.08.11
Java의 이중 중괄호 초기화  (0) 2022.08.11
HashSet  (0) 2022.08.11

문제 : 정수 삼각형

 

- 출력: 꼭대기에서 바닥까지 이어지는 경로 중, 거쳐간 숫자의 합이 가장 큰 경우를 찾는다.
- 조건: 아래 칸으로 이동할 때에는 대각선 방향으로 한 칸 오른쪽 or 왼쪽으로만 이동 가능

- 해결 방법:
[ DFS or DP ] 이 중 DP를 활용해 중복해서 거쳐가는 곳의 값을 따로 저장해 사용합니다.

예를 들어서 설명해보면 위 그림에서 
 1) [ 8,1,0 ] 에서 1은 7+3 / 7+8 중 큰 값을 골라주면 됩니다.
 2) [ 2,7,4,4 ] 에서 
 		- 7은 (7+3+8) / (7+8+1) 중에 큰 값을 고르게 됩니다.
 		- 4는 (7+8+0) / (7+8+1) 중에 큰 값을 고르게 됩니다.
        
이렇게 계산은 맨 왼쪽값, 중간값, 맨 오른쪽 값을 나눠서 계산하게 됩니다.
그러면 저장될 배열인 dp[][] 에는 dp[i-1][j-1], dp[i-1][j] 중 큰 값 + 현재 선택된 값이 들어가게 됩니다. 


- 점화식 : dp[i][j] = MAX(dp[i-1][j-1], dp[i-1][j]) + triangle[i][j]

 

 

[ 작성 코드 ]

public static int solution(int[][] triangle) {
        int answer = 0;

        int[][] dp = new int[triangle.length][triangle.length];
        dp[0][0] = triangle[0][0]; // 맨 위에 값을 넣어줌

        for (int i = 1; i < triangle.length; i++) {
            /* 맨 왼쪽 */
            dp[i][0] = dp[i - 1][0] + triangle[i][0];
            // 7+3, 7+8, 7+2 와 같이 맨 왼쪽 부분을 더하게 된다.
            // dp[1][0] (10) = dp[0][0] (7) + triangle[1][0] (3) -- 1
            // dp[2][0] (18) = dp[1][0] (10) + triangle[2][0] (8) -- 2

            /* 중간값 */
            for (int j = 1; j <= i; j++) {
                dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - 1]) + triangle[i][j];
                // dp[1][1] = dp[0][1], dp[0][0] 중에 max + triangle[1][1] (8) -- 1
                // dp[2][1] = dp[1][1], dp[1][0] 중에 max + triangle[2][1] (1)
                // dp[2][2] = dp[1][2], dp[1][1] 중에 max + triangle[2][2] (0) -- 2
            }

            /* 맨 오른쪽 */
//            dp[i][i] = dp[i - 1][i - 1] + triangle[i][i];
            // dp[1][1] (15) = dp[0][0] (7) + triangle[1][1] (8) -- 1
            // dp[2][2] (15) = dp[1][1] (중간값의 max 값) + triangle[2][2] (1) -- 2

            for (int w = 0; w < dp.length; w++) {
                for (int k = 0; k < dp.length; k++) {
                    System.out.print(dp[w][k] + " ");
                }
                System.out.println();
            }

            System.out.println("------------------------------");
        }

        for (int i = 0; i < triangle.length; i++) {
            answer = Math.max(answer, dp[triangle.length - 1][i]);
        }

        return answer;
}

* 위 DP 문제에는 중간 값 구할 때 맨 오른쪽 값도 같이 구해지기 때문에 맨 오른쪽 구하는 경우를 주석 처리 하였습니다.

'프로그래머스' 카테고리의 다른 글

네트워크 ( DFS )  (0) 2022.09.28
이중우선순위큐  (0) 2022.09.27
마라톤 문제  (1) 2022.09.25
신규 아이디 추천 ( replaceAll, 정규표현식 )  (0) 2022.08.18
신고 결과 받기  (0) 2022.08.11

Dynamic Programming

큰 문제를 작은 문제로 나누어 푸는 문제

 

 

Divide and Conquer ( 분할정복 ) 과 차이점

작은 문제가 중복되어 일어나는지 안일어나는지입니다.

 - 분할 정복 : 큰 문제를 해결하기 어려워 작은 문제로 나누어 푸는 방법 ( 작은 문제에서 반복은 없음 )

 - 동적 프로그래밍 : 작은 문제들이 반복되는 것 ( 답은 바뀌지 않음 )

 

 

Dynamic Programming 방법

모든 작은 문제들은 한번만 풀어야 합니다.

정답을 구한 작은 문제를 어딘가에 메모해놓고, 다시 그보다 큰 문제를 풀어나갈 때 똑같은 작은 문제가 나타나면

메모한 작은 문제의 결과값을 이용합니다.

 

 

Dynamic Programming 조건

1) 작은 문제가 반복일 일어날 경우

2) 같은 문제는 구할 때마다 정답이 같음

 

 

Memoization ( 메모이제이션 )

작은 문제들이 반복되고, 이 작은 문제들의 결과값은 항상 같다는 점을 이용해 한번 계산한 작은 문제를 저장해놓고 다시 사용하는 것을 의미합니다. 피보나치로 예를 들 수 있습니다.

 

 

[ 피보나치 수열 예제 ]


피보나치는 1, 1, 2, 3, 5, 8 .. 의 수를 이루게 됩니다. 재귀로 풀게 될 경우 했던 작업을 또 해야하므로 일정 수 이상의 순열을 구하기가 어렵습니다.

 - 점화식 : [이전 수열] +  [두 단계 전 수열의 합]


 - DP 조건이 해당되는지 확인
     1) 작은 문제들이 반복된다.
         : F(5) = F(4) + F(3) , F(4) = F(3) + F(2) 로 F(3) 이라는 수열이 F(4), F(5) 에서 반복됩니다.
      2) 같은 문제를 구할때 마다 정답이 같다.
         : 첫번째, 두번째 수열은 각각 1로 고정되어 있습니다. 즉, 3번째 수열은 항상 결과가 2입니다. 그리고 4번째 수열은 3번째와 2번째 

           수열을 이용해 구하므로 언제나 정답이 같습니다.

 

public class fibonacci { 
    static long[] memo;
    public static int fibonacci_rec(int n) { // 재귀
        if (n <= 1) {
            return n;
        } else {
            return fibonacci_rec(n-1) + fibonacci_rec(n-2);
        }
    }

    public static long fibonacci_memoization(int n) { // 메모이제이션
        if (n <= 1) {
            return n;
        }
        else if(memo[n] != 0){
            return memo[n];
        }
        else {
            return memo[n] = fibonacci_memoization(n - 1) + fibonacci_memoization(n - 2);
        }
    }
    public static void main(String[] args) {
        memo = new long[10];
        System.out.println(fibonacci_rec(10));
        System.out.println(fibonacci_memoization(10));

    }
}

 

 

[ 재귀와의 차이점 ]

기존 재귀함수와는 다르게 이미 계산된 값을 Memo 라는 배열에 저장하여 사용한다는 차이점이 있습니다.

 

 

 

Bottom-up , Top-down

 

 1. Bottom-up

작은 문제부터 구해가는 방법입니다.

    public static int bottomUp(int n){
        int[] arr = new int[n];
        arr[0] = 0;
        arr[1] = 1;
        if(n==0) return arr[0];
        else if(n==1) return arr[1];
        else{
            for(int i=2; i<=n; i++){
                arr[i] = arr[i - 1] + arr[i - 2];
            }
            return arr[n];
        }
    }

 

 2. Top-down

 재귀함수로 구현하는 경우 대부분이 top-down이라고 생각하면 됩니다. 큰 문제를 풀 때 작은 문제가 아직 풀리지 않았다면 그제서야 작은 문제를 해결하게 됩니다.

 

 

* Top-down / Bottom-up 의 장점

 - Top-down : 소스의 가독성이 증대

 - Bottom-up : 풀기는 쉽지만 소스의 가독성이 안좋음

'알고리즘' 카테고리의 다른 글

시뮬레이션과 완전탐색  (0) 2022.10.10
DFS 코드 이해하기 ( java )  (1) 2022.10.06
후위 표기식  (0) 2022.09.25
decryptCaesarCipher  (0) 2022.08.09
인접 행렬 길찾기  (0) 2022.08.04

- 조건 :

 

 

- 작성 코드 :

import java.util.ArrayList;
import java.util.Scanner;
import java.util.Stack;

public class postfix {

    public static Stack<Character> stack = new Stack();

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String input = sc.nextLine();
        System.out.println(postfix(input));
    }

    public static int priority(char ch) {
        if(ch=='+' || ch=='-'){
            return 0;
        }
        else if(ch=='*' || ch=='/'){
            return 1;
        }
        else return -1;
    }

    public static String postfix(String input) {
        String str = "";

        for (int i = 0; i < input.length(); i++) {
            char ch = input.charAt(i);
            if(('0'<=ch && ch<='9') || ch == '.'){
                str += ch;
            }
            else if (ch == '(') {
                stack.push(ch);
            }
            else if (ch == ')'){
                while(!stack.empty()){
                    char stackChar = stack.pop();
                    if(stackChar == '('){
                        break;
                    }
                    else {
                        str += stackChar;
                    }
                }
            }

            else if(ch =='*' || ch =='+' || ch =='-' || ch == '/'){
                if(stack.empty()){
                    System.out.println(ch);
                    stack.push(ch);
                } else{
                    int op1 = priority(ch); // 입력 연산자 우선순위
                    int op2 = priority(stack.peek()); // 스택 연산자 우선순위

                    if(op1 <= op2){
                        str += stack.pop();
                    } else {
                        stack.push(ch);
                    }
                }
            }
        }

        while(!stack.empty()){
            str += stack.pop();
        }

        return str;
    }
}

 

 

- 출력화면 :

'알고리즘' 카테고리의 다른 글

DFS 코드 이해하기 ( java )  (1) 2022.10.06
Dynamic Programming  (0) 2022.09.26
decryptCaesarCipher  (0) 2022.08.09
인접 행렬 길찾기  (0) 2022.08.04
인접 행렬 생성하기  (0) 2022.08.04

문제 설명

수많은 마라톤 선수들이 마라톤에 참여하였습니다. 단 한 명의 선수를 제외하고는 모든 선수가 마라톤을 완주하였습니다.

마라톤에 참여한 선수들의 이름이 담긴 배열 participant와 완주한 선수들의 이름이 담긴 배열 completion이 주어질 때, 완주하지 못한 선수의 이름을 return 하도록 solution 함수를 작성해주세요.

제한사항
  • 마라톤 경기에 참여한 선수의 수는 1명 이상 100,000명 이하입니다.
  • completion의 길이는 participant의 길이보다 1 작습니다.
  • 참가자의 이름은 1개 이상 20개 이하의 알파벳 소문자로 이루어져 있습니다.
  • 참가자 중에는 동명이인이 있을 수 있습니다.
import static java.util.Arrays.sort;

class Solution {

	/**
     - 문제
        마라톤 선수 이름이 담긴 배열 participant
        완주한 선수 이름이 담긴 배열 completion
        완주하지 못한 선수의 이름을 return 하는 함수 작성

     - 제한 사항
        1) 참여자수 : 1~100,000
        2) completion == participant + 1
        3) 참가자 이름
            - 알파벳 소문자 20글자 이하
            - 동명이인 있을 수 있음
     */
    public String solution(String[] participant, String[] completion) {
        String result = "";

        sort(participant);
        sort(completion);

        for (int i = 0; i < participant.length-1; i++) {
            if(!participant[i].equals(completion[i])){
                return participant[i];
            }
        }

        result = participant[participant.length-1];

        return result;
    }
}

[ 해결 방법 ]

1. 입력받은 배열을 정렬

2. 정렬하고 나면 두 가지 경우만 다루면 됩니다.

  - 참가자의 명단에 일치하지 않는 사람

  - 모두 비교하다가 참가자 명단의 끝에 나오는 사람

 

 

 

 

'프로그래머스' 카테고리의 다른 글

이중우선순위큐  (0) 2022.09.27
정수 삼각형  (1) 2022.09.26
신규 아이디 추천 ( replaceAll, 정규표현식 )  (0) 2022.08.18
신고 결과 받기  (0) 2022.08.11
숫자 문자열과 영단어  (0) 2022.08.09

Firbase 의 Storage 사용 중 발생한 오류입니다.

 

문제가 발생한 이유는 Storage 에 권한이 없기 때문인데,

 

Storage - Rules 에 다음 코드를 입력해주면 됩니다.

 

service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write: if request.auth != null;
    }
  }
}

 

< 해결완료 >

발생한 오류는 다음과 같습니다.

 

1. layout 요소를 id 로 가져온후 BoardModel 에 get 함수로 텍스트를 넣어준다.

TextView tv_uid = (TextView) convertView.findViewById(R.id.tv_time);
tv_uid.setText(BoardModel.getUid());

 

2. BoardModel 의 멤버와 메서드들이 모두 static 으로 선언되어 있습니다.

public static String uid = "";

 

[ 해결 방법 ]

1. static 을 제거 후 모두 public 으로 선언해줍니다.

2. BoardModel.getUid() 와 같이 클래스에 직접 접근하지말고, 객체로 생성 후 접근해줍니다.

BoardModel boardModel;
TextView tv_uid = (TextView) convertView.findViewById(R.id.tv_time);
tv_uid.setText(boardModel.getUid());

 

-> 해결 완료

생성자가 정의되지 않아서 생기는 문제였습니다.

 

아래와 같이 인수와 피라미터가 없는 생성자를 추가해주면 해결됩니다.

 

public BoardModel() { // 새로추가
        
}

public BoardModel(String title, String content, String uid, String time) {
        this.title = title;
        this.content = content;
        this.uid = uid;
        this.time = time;
}

 

 

 

 

관련 링크 : https://stackoverflow.com/questions/47706601/users-does-not-define-no-argument-constructor

첫번째 오류

 

 

[ 환경 ] 

- 스프링 부트

- 자바11

- JPA + MySQL

- Gradle

- Application.properties 설정

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/~~?useSSL=false&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=??
spring.datasource.password=??
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# mysql 사용
spring.jpa.database=mysql
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto=none
spring.jpa.generate-ddl=false

# 로깅 레벨
logging.level.org.hibernate=info

# 하이버네이트가 실행한 모든 SQL문을 콘솔로 출력
spring.jpa.properties.hibernate.show_sql=true
# SQL문을 가독성 있게 표현
spring.jpa.properties.hibernate.format_sql=true
# 디버깅 정보 출력
spring.jpa.properties.hibernate.use_sql_comments=true

 

 

두번째 오류

Access to DialectResolutionInfo cannot be null when 'hibernate.dialect' not set

 

 

[ 해결방법 ]

두 오류를 종합해봤을 때, application.properties 의 MySQL 설정을 읽지 못하고 있는 것으로 보입니다.

그 이유는 제가 META-INF 에 PersistenceUnit 을 설정하기위해 persistence.xml 을 만들어 놓고

내부 태그에 아무 내용을 적지 않았기 때문입니다.

 

=> persistence.xml 에 다음과 같은 코드를 작성 후 해결완료

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             version="2.1">
    <persistence-unit name="Recycler">

        <class>kbbank.recycler.domain.MEMBER</class>

        <properties>
            <property name="javax.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
            <property name="javax.persistence.jdbc.user" value=""/>
            <property name="javax.persistence.jdbc.password" value=""/>
            <property name="javax.persistence.jdbc.url" value="jdbc:mysql://127.0.0.1:3306/Recycler"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect"/>

            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.use_sql_comments" value="true"/>
            <property name="hibernate.jdbc.batch_size" value="10"/>
            <property name="hibernate.hbm2ddl.auto" value="update" />
            <!--property name="hibernate.id.new_generator_mappings" value="true"/-->
        </properties>
    </persistence-unit>

</persistence>

* JPA 와 스프링 부트로 H2Database 를 사용해봤다는 가정하에 작성하였습니다.

 

1. 의존성 추가

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-data-jpa</artifactId>
	</dependency>

	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
	</dependency>
</dependencies>

 

2. application.properties 설정 추가

server.address=localhost
server.port=8080

spring.datasource.url=jdbc:mysql://localhost:3306/TEST_DB?useSSL=false&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=test_user
spring.datasource.password=admin
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# mysql 사용
spring.jpa.database=mysql
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect

# 로깅 레벨
logging.level.org.hibernate=info

# 하이버네이트가 실행한 모든 SQL문을 콘솔로 출력
spring.jpa.properties.hibernate.show_sql=true
# SQL문을 가독성 있게 표현
spring.jpa.properties.hibernate.format_sql=true
# 디버깅 정보 출력
spring.jpa.properties.hibernate.use_sql_comments=true

 

 

3. Entity 생성

 

4. Repository 생성

 

5. Controller 생성

 

6. 결과 확인

테스트 코드에서 위 사진과 같은 에러가 발생하고 있습니다.

 

  1. 멤버 리포지토리에 멤버 객체를 저장 ( java memory 사용 )
  2. findById 로 저장한 객체를 꺼내와서 비교

간단하게 위 두 과정을 거치는데 null 값을찾고 있다는 오류가 발생해 디버깅을 해보니

 

 

저는 분명 하나의 객체를 저장했는데 sequence 는 2를 가리키고 있습니다.

 

 

[ 해결 ]

간단하게 테스트하기 위해 save 할 때 , Id 값을 직접 지정해주도록 했습니다.

그런데 member 객체에 Id 값을 설정안해주고, member.getId() 메서드로 Id를 호출하니 발생하는 오류 였습니다.

 

해결방법 : 테스트 코드의 member 객체에 Id 값을 설정해줘야함

Fragment 란?

Activity 개념과 View 개념이 합쳐진 것이라고 할 수 있습니다.

 

  • View : Activity 위에 놓이는 layout 이나 Button 같은 그림들
  • Activity : View 를 올릴 수 있는 도화지
  • Fragment : Activity 라는 도화지 위에 얹는 도화지 느낌

 

Fragment 를 왜 사용할까요?

Activity 를 변경하지 않고, Fragment 를 변경하는 것만으로도 Activity 위에 보이는 view 를 손쉽게 변경 가능합니다.

 

 

Fragment 주된 특징

  • Activity 를 분할하여 화면의 한 부분을 정의
  • 액티비티와 같이 레이아웃, 동작처리 ,생명주기를 가지는 독립적인 모듈
  • 다른 액티비티에서도 사용할 수 있어 재사용성이 뛰어남
  • 액티비티 내에서 실행 중 추가/제거가 가능합니다.
  • 프래그먼트는 필수적으로 하나의 액티비티에 종속되어야 합니다.

 

 

Fragment 생명주기

생명주기를 지키지 않는다면 메모리 누출 또는 비정상 종료가 발생할 수 있습니다.

  • onCreate()

프래그먼트를 생성할 때 호출됩니다.

프래그먼트가 일시정지 혹은 중단 후 재개되었을 때 유지하고 있어야하는 것을 여기서 초기화해야합니다.

프래그먼트를 생성하면서 넘겨준 값들이 있다면, 여기서 변수에 넣어주면 됩니다. 하지만 여기서는 UI 초기화는 못합니다.

 

  • onCreateView()

프래그먼트가 자신의 인터페이스를 처음 그리기 위해 호출합니다.

View 를 반환해야 합니다. 그래서 버튼이나 텍스트 뷰 등을 초기화 할 수 있습니다.

이 메서드는 프래그먼트의 레이아웃 루트이기 때문에 UI 를 제공하지 않는 경우 null 을 반환합니다.

 

  • onPause()

사용자가 프래그먼트를 떠나면 첫번째로 이 메서드를 호출합니다.

예를 들어, 홈 버튼을 눌러 화면에서 벗어나게 되면 이 메서드가 호출됩니다.

그래서 프래그먼트의 부모 액티비티가 아닌, 다른 액티비티가 foreground 로 나오게 되면, onPause 를 콜하고 backstack 으로 들어감

 

 

  • onStart()

액티비티가 시작됨 상태에 들어가면 이 메서드를 호출합니다.

사용자에게 프래그먼트가 보이게 되고, 이 메서드에서 UI 를 관리하는 코드를 초기화합니다.

이 메서드가 완료되면 Resumed 상태로 들어가 onResume() 을 호출합니다.

 

 

  • onResume()

이 상태에 들어갔을 때 사용자와 상호작용 합니다.

어떤 이벤트가 발생하여 포커스가 떠날 때까지 이 상태에 머무릅니다.

프로그램이 일시 정지되어 onPause() 를 호출하고 다시 재게되면 onResume() 메서드를 호출합니다.

즉, 유저에게 프래그먼트가 보여지고 상호작용 직전에 호출되는 함수입니다.

 

  • onStop()

다른 액티비티가 완전히 화면을 가리게 되면 호출합니다.

여기에 화면에 보이지 않을 때 실행할 필요가 없는 기능을 모두 정지할 수 있습니다.

유저가 다시 해당 액티비티를 호출하면 다시 복원할 수 있는 상태입니다.

One or more issues found when checking AAR metadata values: [ 에러 ]

안드로이드 SDK 버전이 30이 넘어가면서 발생하고 있는 여러 오류 중 하나입니다.

 

[ build.gradle ] ->

[ dependency ] 내부를 부면 appcompat:appcompat:1.5.0 ~~ 으로 되있을 겁니다.

 

implementation 'androidx.appcompat:appcompat:1.3.0' 으로 수정해줍니다.

 

 

DTO ( Data Transfer Object )

[ 클라이언트에서 서버쪽으로 전송하는 요청 데이터 ]

[서버에서 클라이언트 쪽으로 전송하는 응답 데이터 ] 의 형식으로 클라이언트와 서버 간에 데이터 전송이 이뤄집니다.

 

 

DTO 를 왜 사용하는가?

 

1. 코드의 간결성

 

ResponseEntity 를 통해 데이터를 저장하는 곳에는 @RequestParam 애노테이션이 사용됩니다.

그런데 프로젝트의 규모가 커질 수록 RequestParam 의 개수는 늘어날 수 밖에 없습니다.

만약, 클라이언트와 요청 데이터를 하나의 객체로 모두 전달 받을 수 있다면 코드 자체가 아주 간결해질 것입니다.

DTO 클래스가 바로 요청 데이터를 하나의 객체로 전달 받는 역할을 해줍니다.

 

 

2. 데이터 유효성 검증의 단순화

 

이메일이 유효한 입력 값인지 확인하려면 email.matches("^[~~$") 와 같이 복잡한 정규 표현식으로 코드를 작성하게 됩니다.

하지만 HTTP 요청을 전달 받는 핸들러 메서드는 요청을 받는 것이 주목적이기 때문에 최대한 간결하게 작성되는 것이 좋습니다.

 

그래서 DTO 클래스에 이러한 유효성 검증을 작성하면 핸들러 메서드에는 아주 간결하게 표현될 수 있습니다.

 

 

DTO 클래스를 적용하기 위한 코드 리팩토링 절차

  • 회원 정보를 전달 받을 DTO 클래스를 생성합니다.

컨트롤러에서 현재 회원 정보로 전달 받는 각 데이터 항복을 DTO 클래스의 멤버 변수로 추가해줍니다.

 

  • 클라이언트 쪽에서 전달하는 요청 데이터를 @RequestParam 애너테이션으로 전달 받는 핸들러 메서드를 찾습니다.

RequestBody 가 필요한 핸들러는 POST, PATCH, PUT 과 같이 리소스 수정/추가 발생할 때 입니다.

GET 은 조회하는 용도이기 때문에 Body 는 필요 없습니다.

 

  • @RequestParam 코드를 DTO 클래스의 객체로 수정합니다.

Map 객체로 JSON 을 받아오는 객체 였다면 Reponse Body 를 DTO 클래스 객체로 변경해줍니다.

 

 

 

 

DTO 클래스에 유효성 검증 적용하기

org.springframework.boot:spring-boot-starter-validation 이라는 디펜던시 항목을 추가해줘야 합니다.

 

[ DTO 내부 멤버에서 검증 애노테이션 ]

@NotBlank : 정보가 비어있지 않은지를 검증

@Email : 이메일 주소인지 검증

@Pattern : 정규표현식에 매치되는 유효한 번호인지 검증

 

[ 유효성 검증 시작 ]

@RestController
@RequestMapping("/v1/members")
public class MemberController {
    @PostMapping
    public ResponseEntity postMember(@Valid @RequestBody MemberPostDto memberDto) {
        return new ResponseEntity<>(memberDto, HttpStatus.CREATED);
    }
		...
		...
}

@Valid 애노테이션을 추가해줍니다.

 

[ @PathVariable 이 추가된 유효성 검증하기 ]

@RestController
@RequestMapping("/v1/members")
@Validated   // (1)
public class MemberController {
		...
		...

    @PatchMapping("/{member-id}")
    public ResponseEntity patchMember(@PathVariable("member-id") @Min(1) long memberId,
                                    @Valid @RequestBody MemberPatchDto memberPatchDto) {
        memberPatchDto.setMemberId(memberId);

        // No need Business logic

        return new ResponseEntity<>(memberPatchDto, HttpStatus.OK);
    }
}

- @Min(1) : memberId 가 1 이상의 숫자일 경우에만 검증에 통과하도록 해줍니다.

- @Validated : @PathVariable 이 추가된 변수에 유효성 검증이 정상적으로 수행되려면 해당 애노테이션이 필수입니다.

'HTTP' 카테고리의 다른 글

JSON 형식으로 받은 데이터를 객체로 변환하기  (0) 2022.11.26
HTTP 요청 데이터 작성하기  (0) 2022.11.26
RestAPI 활용하기  (0) 2022.08.22
Message States Server  (0) 2022.08.19
HTTP 메서드와 속성  (0) 2022.08.18

[ 기본 개념 ]

@RestController

Spring MVC 에서 특정 클래스에 @RestController 를 추가하면 해당 클래스가 REST API 리소스를 처리하기 위한 API 엔드포인트로 동작함을 정의합니다.

그리고 해당 애노테이션이 추가된 클래스는 애플리케이션 로딩 시, Spring Bean 으로 등록해줍니다.

 

 

 

@RequestMapping

클라이언트의 요청과 그 요청을 처리하는 핸들러 메서드를 매핑해주는 역할을 합니다.

예를 들어, RequsetMapping("v1/member") 을 하게 된다면 괄호 안에 있는 코드는 Controller 클래스 레벨에 추가하여 클래스 전체에 사용되는 공통 URL 설정을 하게 됩니다. 이러한 메서드를 핸들러 메서드라고 부릅니다.

 

 

 

@RequestMapping(value, produces)

[produces]
해당 속성은 응답 데이터를 어떤 미디어 타입으로 클라이언트에게 전송할 지를 설정합니다.
JSON 형식의 데이터를 응답 데이터로 전송하겠다는 의미로는
MediaType.APPLICATION_JSON_VALUE 로 값을 설정할 수 있습니다.
이 설정을 하지않는다면 JSON 형식이 아닌, 문자열 자체를 전송하게 됩니다.

 

 

 

@PostMapping

클라이언트 요청 데이터를 서버에 생성할 때 사용하는 애너테이션입니다.

주로 회원 정보를 등록해주는 역할을 해줍니다.



 

@RequsetParam

핸들러 메서드의 피라미터 종류 중 하나입니다.

주로 클라이언트쪽에서 전송하는 요청 데이터를 [ 쿼리 피라미터, 폼 데이터, x-www-form-urlencoded 형식 ] 으로 전송하면

이를 서버쪽에서 전달 받을 때 사용하는 애너테이션입니다.

 

 

* 쿼리 피라미터

요청 URL 에서 "?" 를 기준으로 붙는 key/value 쌍의 데이터를 말합니다.

 

- 예시
http://localhost:8080/coffees/1?page=1&size=10

 

 

* 리턴 값

postMapping 애노테이션 메서드의 리턴 타입이 String 인 경우에는,

클라이언트 쪽에서 JSON 형식의 데이터를 전송 받아야 하기 때문에, 응답 문자열을 JSON 형식에 맞게 작성해줘야합니다.

 

일반적으로 POST Method 를 처리하는 핸들러 메서드는 데이터를 생성한 후에 클라이언트 쪽에서 생성한 데이터를 리턴해주는 것이 관례입니다.

 

 

 

@GetMapping

클라이언트가 서버에 리소스를 조회할 때 사용하는 애너테이션입니다.

주로 회원 정보를 클라이언트쪽에서 제공할 때 사용하는 메서드입니다.

 

- 예시
/v1/members/{member-id} 의 경우,
member-id 는 회원 식별자를 의미하며 클라이언트가 요청을 보낼 때 URI 로 어떤 값을 지정하느냐에 따라서 동적으로 바뀌는 값입니다.

 

 

@PathVariable

핸들러 피라미터 종류 중 하나입니다.

괄호 안에 입력한 문자열 값은 중괄호 안의 문자열과 동일해야합니다.

다를 경우 MssingPathVariableException 이 발생합니다.

[예시]
@GetMapping("/{member-id}")
public String getMember(@PathVariable("member-id") long memberId){ ~~ }

 

 

 

 

JSON 형식의 응답 문자열을 수작업으로 작성하는 경우를 개선하는 방법

[ 기존 코드와 차이점 ]

1. produce 설정 제거

 

 

2. map 객체로 생성

Map 객체를 리턴해주게 되면 내부적으로 이 데이터는 JSON 형식의 응답 데이터로 변환해야 되는 거구나를 인지하게 됩니다.

그래서 자동으로 JSON 형식으로 변환하게 됩니다.

 

 

3) return value 를 ResponseEntity 객체로 변경합니다.

사실 리턴값을 Map 객체로 리턴해도 JSON 응답 데이터를 받을 수 있습니다.

하지만 ResponseEntity 를 사용하게 될 경우 응답 데이터를 래핑함으로써 응답 상태를 명시적으로 함께 전달 할 수 있다는 장점이 있습니다.

 

- 예시
return new ResponseEntity<>(map, Httpstatus.CREATE);
or
return new ResponseEntity<>(HttpStatus.OK);

* HttpStatus.CREATE 는 클라이언트의 POST 요청을 처리해서 요청 데이터가 정상적으로 생성되었음을 의미하는 HTTP 응답 상태입니다. ( 응답 상태 문서 : https://developer.mozilla.org/ko/docs/Web/HTTP/Status )

 

 

 

 

HTTP 헤더

HTTP 메시지의 구성 요소 중 하나로써 클라이언트 요청이나 서버의 응답에 포함되어 부가적인 정보를 HTTP 메시지에 포함할 수 있도록 해줍니다.

 

[ 사용 목적 ]

 

  • 클라이언트와 서버 관점에서의 대표적인 HTTP 헤더 예시

클라이언트와 서버 관점에서 내부적으로 가장 많이 사용되는 헤더 정보로는 "Content-Type" 이 있습니다.

클라이언트와 서버가 HTTP 메시지 바디의 데이터 형식이 무엇인지를 알려주는 역할을 합니다.

 

그래서 클라이언트와 서버는 Content-Type 이 명시된 데이터 형식에 맞는 데이터를 주고 받는 것입니다.

 

 

  • 개발자들이 직접 실무에서 사용하는 대표적인 HTTP 헤더 예시

사실 개발자들이 직접 HTTP 헤더를 건드릴 일은 없습니다.

하지만 코드 레벨에서 컨트롤해야 하는 경우가 있는데 이런 경우의 대표적인 예시 두가지를 보겠습니다.

 

1. Authorization

이는 클라이언트가 적절한 자격 증명을 가지고 있는지를 확인하기 위한 정보입니다.

일반적으로 REST API 기반 애플리케이션의 경우 클라이언트와 서버간의 로그인 인증에 통과한 클라이언트들은

"Authorization" 헤더 정보를 기준으로 인증에 통과한 클라이언트가 맞는지 확인하는 절차를 가집니다.

 

 

2. User-Agent

여러 유형의 클라이언트가 하나의 서버 애플리케이션에 요청을 전송하는 경우가 많습니다.

어떤 사용자는 데스크탑 웹 브라우저에서 서버에 요청을 보내고, 또 다른 사람은 스마트폰에서 요청을 보냅니다.

이런 경우 들어오는 요청을 구분해서 응답 데이터를 다르게 보내줘야 되는 경우가 있습니다.

예를 들어, 데스크탑 브라우저에서는 더 큰 화면인만큼 더 많은 정보를 보여줍니다.

이 경우, "User-Agent" 정보를 통해서 구분할 수 있습니다.

 

 

 

HTTP Request 헤더 정보 얻기

  • @RequestHeader("user-agent") : 특정 헤더 정보만 읽는 예제입니다.
  • HttpServletRequest 객체 : Request 헤더 정보에 다양한 방법으로 접근이 가능합니다.
  • HttpEntity 객체 : Request 헤더와 바디 정보를 래핑하고 있으며, 조금 더 쉽게 헤더와 바디에 접근할 수 있습니다.

* HttpEntity 객체

Entry 를 통해 각각의 헤더 정보에 접근할 수 있는데, 특이한 것은 자주 사용될만한 헤더 정보들은 get() 으로 가져올 수 있습니다.

 

 

 

HTTP Response 헤더 정보 추가

  • ResponseEntity 와 HttpHeaders 를 이용해 헤더 정보 추가하기
@RestController
@RequestMapping(path = "/v1/members")
public class MemberController{
    @PostMapping
    public ResponseEntity postMember(@RequestParam("email") String email,
                                     @RequestParam("name") String name,
                                     @RequestParam("phone") String phone) {
        // (1) 위치 정보를 헤더에 추가
        HttpHeaders headers = new HttpHeaders();
        headers.set("Client-Geo-Location", "Korea,Seoul");

        return new ResponseEntity<>(new Member(email, name, phone), headers,
                HttpStatus.CREATED);
    }
}

 

 

 

 

RestClient

REST API 서버에 HTTP 요청을 보낼 수 있는 클라이언트 툴 또는 라이브러리를 의미합니다.

Postman 은 UI 가 갖춰진 RestClient 라고 볼 수 있습니다.

 

- Client : 서버쪽의 리소스를 이용하는 쪽

 

* 그래서 [ 어떤 서버가 HTTP 통신을 통해서 다른 서버의 리소스를 이용한다면 그 때만큼은 클라이언트의 역할을 합니다. ]

위의 그림에서는 웹 브라우저가 클라이언트가 됩니다.

 

 

 

RestTemplate

Java 에서 사용할 수 있는 다양한 HTTP Client 라이브러리가 있습니다. ( HttpURLConnection, OkHttp 3, Netty 등 )

Spring 에서는 이 HTTP Client 라이브러리 중 하나를 이용해 다른 백엔스 서버에 HTTP 요청을 보낼 수 있는 REST Client API 를 제공하는데, 이를 RestTemplate 라고 합니다.

 

이 템플릿을 이용하면 Rest EndPoint 지정, 헤더 설정, 피라미터 및 바디 설정을 한 줄의 코드로 쉽게 할 수 있습니다.

 

[ URI 생성 ]

import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;

public class RestClientExample01 {
    public static void main(String[] args) {
        // (1) 객체 생성
        RestTemplate restTemplate =
                new RestTemplate(new HttpComponentsClientHttpRequestFactory());

        // (2) URI 생성
        UriComponents uriComponents =
                UriComponentsBuilder
                        .newInstance()
                        .scheme("http")
                        .host("worldtimeapi.org")
//                        .port(80)
                        .path("/api/timezone/{continents}/{city}")
                        .encode()
                        .build();
        URI uri = uriComponents.expand("Asia", "Seoul").toUri();
    }
}
[ 설명 ]
newInstance(): UriComponentsBuilder 객체를 생성합니다.
scheme(): URI의 scheme을 설정합니다.
host(): 호스트 정보를 입력합니다.
port(): 디폴트 값은 80이므로 80 포트를 사용하는 호스트라면 생략 가능합니다.
path(): URI의 경로(path)를 입력합니다.
     URI의 path에서 {continents}, {city} 의 두 개의 템플릿 변수를 사용하고 있습니다.
    두 개의 템플릿 변수는 uriComponents.expand("Asia", "Seoul").toUri(); 에서 expand() 메서드 파라미터의 문자열로 채      워집니다. 즉, 빌드 타임에 {continents}는 ‘Asia’, {city}는 ‘Seoul’로 변환됩니다.
encode(): URI에 사용된 템플릿 변수들을 인코딩 해줍니다.여기서 인코딩의 의미는 non-ASCII 문자와 URI에 적절하지 않은 문자를 Percent Encoding 한다는 의미입니다.
build(): UriComponents 객체를 생성합니다.
expand(): 파라미터로 입력한 값을 URI 템플릿 변수의 값으로 대체합니다.
toUri(): URI 객체를 생성합니다.

 

[ 요청 전송 ]

public class RestClientExample01 {
    public static void main(String[] args) {
        // (1) 객체 생성
        RestTemplate restTemplate =
                new RestTemplate(new HttpComponentsClientHttpRequestFactory());

        // (2) URI 생성
        UriComponents uriComponents =
                UriComponentsBuilder
                        .newInstance()
                        .scheme("http")
                        .host("worldtimeapi.org")
//                        .port(80)
                        .path("/api/timezone/{continents}/{city}")
                        .encode()
                        .build();
        URI uri = uriComponents.expand("Asia", "Seoul").toUri();

        // (3) Request 전송
        String result = restTemplate.getForObject(uri, String.class);

        System.out.println(result);
    }
}

- getForObject : HTTP get 요청을 통해 서버의 리소스를 조회합니다.

    이는 커스텀 클래스 타입으로 원하는 정보만 응답으로 전달 받을 수도 있습니다.

 

 

 

 

 

 

이 외에도 정보를 전달받는 여러 방법이 있습니다.

'HTTP' 카테고리의 다른 글

HTTP 요청 데이터 작성하기  (0) 2022.11.26
DTO  (0) 2022.08.22
Message States Server  (0) 2022.08.19
HTTP 메서드와 속성  (0) 2022.08.18
HTTP API 설계와 메서드  (0) 2022.08.14

Endpoint

  • root-endpoint( root-URL )

: API 로 요청을 서버와 통신할 때, 서버가 요청을 수락하는 시작점을 의미합니다.

 

  • path ( url-path )

: path 는 API 를 통해 서버와 통신할 때, 서버와 통신할 수 있는 key 역할을 합니다.

서버에 정의된 문자열에 따라 path 가 달라집니다. 예를 들어, https://api.github.com/user 에서는

'user' 가 path입니다.

 

Github API 의 root-endpoint
: https://api.github.com

트위터 API 의 root-endpoint
: https://api.twitter.com

일반적으로 root-endpoint 는 도메인 주소와 루트(/) 를 가리킵니다.

마찬가지로 Message States Server 의 URL 을 기준으로 파악할 수 있는 root-endpoint 는
Message States Server 의 가장 마지막 Location 인 호스트의 루트(/) 입니다.

 

 

메시지 조회

Request

GET /{githubID}/messages

* 추가적인 피라미터 : /{GithubID}/message?roomname=로비

 

Response

 

( 예시 )

[
  {
     "username": "김코딩", "text": "안녕하세요", "roomname": "로비",
   },
   // ...여러 개의 메시지
]

 

 

메세지 추가

Request

POST /{githubID}/messages

 

Response

 

(예시)

{
   "id": 5
}

 

 

메세지 초기화

Request

POST /{githubID}/clear

 

Reponse

 

(예시)

{
   "message": "message initialized!"
}

'HTTP' 카테고리의 다른 글

DTO  (0) 2022.08.22
RestAPI 활용하기  (0) 2022.08.22
HTTP 메서드와 속성  (0) 2022.08.18
HTTP API 설계와 메서드  (0) 2022.08.14
HTTP - 짧게 정리  (0) 2022.08.13

문제 설명

카카오에 입사한 신입 개발자 네오는 "카카오계정개발팀"에 배치되어, 카카오 서비스에 가입하는 유저들의 아이디를 생성하는 업무를 담당하게 되었습니다. "네오"에게 주어진 첫 업무는 새로 가입하는 유저들이 카카오 아이디 규칙에 맞지 않는 아이디를 입력했을 때, 입력된 아이디와 유사하면서 규칙에 맞는 아이디를 추천해주는 프로그램을 개발하는 것입니다.
다음은 카카오 아이디의 규칙입니다.

  • 아이디의 길이는 3자 이상 15자 이하여야 합니다.
  • 아이디는 알파벳 소문자, 숫자, 빼기(-), 밑줄(_), 마침표(.) 문자만 사용할 수 있습니다.
  • 단, 마침표(.)는 처음과 끝에 사용할 수 없으며 또한 연속으로 사용할 수 없습니다.

"네오"는 다음과 같이 7단계의 순차적인 처리 과정을 통해 신규 유저가 입력한 아이디가 카카오 아이디 규칙에 맞는 지 검사하고 규칙에 맞지 않은 경우 규칙에 맞는 새로운 아이디를 추천해 주려고 합니다.
신규 유저가 입력한 아이디가 new_id 라고 한다면,

1단계 new_id의 모든 대문자를 대응되는 소문자로 치환합니다.
2단계 new_id에서 알파벳 소문자, 숫자, 빼기(-), 밑줄(_), 마침표(.)를 제외한 모든 문자를 제거합니다.
3단계 new_id에서 마침표(.)가 2번 이상 연속된 부분을 하나의 마침표(.)로 치환합니다.
4단계 new_id에서 마침표(.)가 처음이나 끝에 위치한다면 제거합니다.
5단계 new_id가 빈 문자열이라면, new_id에 "a"를 대입합니다.
6단계 new_id의 길이가 16자 이상이면, new_id의 첫 15개의 문자를 제외한 나머지 문자들을 모두 제거합니다.
     만약 제거 후 마침표(.)가 new_id의 끝에 위치한다면 끝에 위치한 마침표(.) 문자를 제거합니다.
7단계 new_id의 길이가 2자 이하라면, new_id의 마지막 문자를 new_id의 길이가 3이 될 때까지 반복해서 끝에 붙입니다.

예를 들어, new_id 값이 "...!@BaT#*..y.abcdefghijklm" 라면, 위 7단계를 거치고 나면 new_id는 아래와 같이 변경됩니다.

1단계 대문자 'B'와 'T'가 소문자 'b'와 't'로 바뀌었습니다.
"...!@BaT#*..y.abcdefghijklm"  "...!@bat#*..y.abcdefghijklm"

2단계 '!', '@', '#', '*' 문자가 제거되었습니다.
"...!@bat#*..y.abcdefghijklm"  "...bat..y.abcdefghijklm"

3단계 '...'와 '..' 가 '.'로 바뀌었습니다.
"...bat..y.abcdefghijklm"  ".bat.y.abcdefghijklm"

4단계 아이디의 처음에 위치한 '.'가 제거되었습니다.
".bat.y.abcdefghijklm"  "bat.y.abcdefghijklm"

5단계 아이디가 빈 문자열이 아니므로 변화가 없습니다.
"bat.y.abcdefghijklm"  "bat.y.abcdefghijklm"

6단계 아이디의 길이가 16자 이상이므로, 처음 15자를 제외한 나머지 문자들이 제거되었습니다.
"bat.y.abcdefghijklm"  "bat.y.abcdefghi"

7단계 아이디의 길이가 2자 이하가 아니므로 변화가 없습니다.
"bat.y.abcdefghi"  "bat.y.abcdefghi"

따라서 신규 유저가 입력한 new_id가 "...!@BaT#*..y.abcdefghijklm"일 때, 네오의 프로그램이 추천하는 새로운 아이디는 "bat.y.abcdefghi" 입니다.


[문제]

신규 유저가 입력한 아이디를 나타내는 new_id가 매개변수로 주어질 때, "네오"가 설계한 7단계의 처리 과정을 거친 후의 추천 아이디를 return 하도록 solution 함수를 완성해 주세요.

[제한사항]

new_id는 길이 1 이상 1,000 이하인 문자열입니다.
new_id는 알파벳 대문자, 알파벳 소문자, 숫자, 특수문자로 구성되어 있습니다.
new_id에 나타날 수 있는 특수문자는 -_.~!@#$%^&*()=+[{]}:?,<>/ 로 한정됩니다.


[입출력 예]nonew_idresult
예1 "...!@BaT#*..y.abcdefghijklm" "bat.y.abcdefghi"
예2 "z-+.^." "z--"
예3 "=.=" "aaa"
예4 "123_.def" "123_.def"
예5 "abcdefghijklmn.p" "abcdefghijklmn"
입출력 예에 대한 설명

입출력 예 #1
문제의 예시와 같습니다.

입출력 예 #2
7단계를 거치는 동안 new_id가 변화하는 과정은 아래와 같습니다.

1단계 변화 없습니다.
2단계 "z-+.^."  "z-.."
3단계 "z-.."  "z-."
4단계 "z-."  "z-"
5단계 변화 없습니다.
6단계 변화 없습니다.
7단계 "z-"  "z--"

입출력 예 #3
7단계를 거치는 동안 new_id가 변화하는 과정은 아래와 같습니다.

1단계 변화 없습니다.
2단계 "=.="  "."
3단계 변화 없습니다.
4단계 "."  "" (new_id가 빈 문자열이 되었습니다.)
5단계 ""  "a"
6단계 변화 없습니다.
7단계 "a"  "aaa"

입출력 예 #4
1단계에서 7단계까지 거치는 동안 new_id("123_.def")는 변하지 않습니다. 즉, new_id가 처음부터 카카오의 아이디 규칙에 맞습니다.

입출력 예 #5
1단계 변화 없습니다.
2단계 변화 없습니다.
3단계 변화 없습니다.
4단계 변화 없습니다.
5단계 변화 없습니다.
6단계 "abcdefghijklmn.p"  "abcdefghijklmn."  "abcdefghijklmn"
7단계 변화 없습니다.

 

[ 내가 작성한 코드 ]

import java.util.Locale;

public class 신규아이디추천 {
    /*
    - 해야할 일
    1) 규칙에 맞지 않는 아이디를 입력했을 때, 입력된 아이디와 유사하면서 규칙에 맞는 아이디를 추천

    - 아이디 규칙
    1) 길이: 3 ~ 15
    2) 사용가능 문자 : 소문자, 빼기, 밑줄, 마침표
    3) 단, 마침표는 시작과 끝에 사용 불가능, 연속으로 사용 불가능

    - 해야할 일의 처리 과정
    1단계 new_id의 모든 대문자를 대응되는 소문자로 치환합니다.
    2단계 new_id에서 알파벳 소문자, 숫자, 빼기(-), 밑줄(_), 마침표(.)를 제외한 모든 문자를 제거합니다.
    3단계 new_id에서 마침표(.)가 2번 이상 연속된 부분을 하나의 마침표(.)로 치환합니다.
    4단계 new_id에서 마침표(.)가 처음이나 끝에 위치한다면 제거합니다.
    5단계 new_id가 빈 문자열이라면, new_id에 "a"를 대입합니다.
    6단계 new_id의 길이가 16자 이상이면, new_id의 첫 15개의 문자를 제외한 나머지 문자들을 모두 제거합니다.
         만약 제거 후 마침표(.)가 new_id의 끝에 위치한다면 끝에 위치한 마침표(.) 문자를 제거합니다.
    7단계 new_id의 길이가 2자 이하라면, new_id의 마지막 문자를 new_id의 길이가 3이 될 때까지 반복해서 끝에 붙입니다.
  능  */
    public static void main(String[] args) {
        String newId = 	"00.@cdefgTWhijklm...!_I',"	;
        String solution = solution(newId);

        System.out.println("res: " + solution);
    }

    public static String solution(String new_id) {
        String answer = "";
        String one = "";
        String two = "";
        String three = "";
        String four = "";
        String five = "";
        String six = "";
        String seven = "";

        // 1
        one = new_id.toLowerCase(Locale.ROOT);

        char[] ch = one.toCharArray();

        // 2
        for (int i = 0; i < ch.length; i++) {
            if(('a'<= ch[i] && ch[i] <= 'z') || ('0' <= ch[i] && ch[i] <= '9') ||
                    (ch[i]=='-') || (ch[i] =='_') || (ch[i] == '.')){
                two += ch[i];
            } else {
                continue;
            }
        }

        // 3
        three = two;
        while(three.contains("..")){
            three = three.replace("..", ".");
        }

        //4
        four = three;
        if (four.length() > 0 && four.charAt(0) == '.') {
            four = four.substring(1, four.length());
        }
        if (four.length() > 0 && four.charAt(four.length() - 1) == '.') {
            four = four.substring(0, four.length() - 1);
        }

        //5
        five = four;
        if (five.isEmpty()) {
            five = "a";
        }

        //6
        six = five;
        if(six.length() > 15){
            six = six.substring(0, 15);
            if(six.substring(six.length()-1,six.length()).equals(".")){
                six = six.substring(0,six.length()-1);
            }
        }

        //7
        seven = six;
        while(seven.length()<=2){
            ch = seven.toCharArray();
            seven += ch[ch.length-1];
        }

        answer = seven;
        return answer;
    }
}

저는 위와 같이 1단계부터 7단계까지 각 단계별로 풀이를 하였습니다.

 

하지만 다른 사람의 풀이를 보니 replaceAll 과 정규표현식의 중요성을 알 수 있었습니다.

 

 

 

replaceAll()

형식

String replaceAll(String regex, String replacement)

대상 문자열을 원하는 문자값으로 변환하는 함수입니다.

- 첫번째 매개변수는 변화하고자 하는 대상이 될 문자열

- 두번째 매개변수는 변환할 문자 값

 

 

그렇다면 replace 와 차이점이 무엇일까요??

 

이는 CharSequence 와 String 의 차이점입니다. CharSequence 에 정규 표현식이 사용 가능한것입니다.

 

정규 표현식 (정규식)

특정한 규칙을 가진 문자열의 집합을 표현하는데 사용되는 언어입니다.

주로 텍스트 편집기나 스크립트 언어에서 문자열의 검색과 치환을 위해 지원되고 있습니다.

 

 

정규표현식 문법은 다음과 같습니다.

^ 문자열의 시작
$ 문자열의 끝
. 임의의 한 문자
* 문자가 0번 이상 발생
+ 문자가 1번 이상 발생
? 문자가 0번 혹은 1번 발생
[ ] 문자의 집합 범위를 나타냄
[0-9] : 숫자 (0 ~ 9)
[a-z] : 알파벳 (a ~ z)
앞에 ^ 가 나타나면 not 임
{ } 횟수 또는 범위를 의미
( ) 소괄호 안의 하나의 문자
\w 알파벳이나 숫자
\d [0-9] 와 동일
\D 숫자를 제외한 모든 문자

 

자주 사용하는 정규 표현식

^[0-9]*$ 숫자
^[a-zA-Z]*$ 영문자
^[가-힣]*$ 한글
\\w+@\\w+\\.\\w+(\\.\\w+)? 이메일 주소
^\d{2,3}-\d{3,4}-\d{4}$ 전화번호
^01(?:0|1|[6-9])-
(?:]\d{3}|\d{4}-\d{4}$
핸드폰 번호
\d{6} \- [1-4]\d{6} 주민등록 번호

 

위를 응용한 예시를 살펴보겠습니다.

 

알파벳의 중복은 허용하고, 띄어쓰기 ( \s ) 는 한칸만 허용

@Pattern(regexp = "^[a-zA-Z]*\\s?[a-zA-Z]*$")

- [ ] * : 해당 알파벳이 0회 이상 반복 ( 있어도 되고, 없어도 됨 )

- \\s? : 공백이 1회 또는 0회만 있어야함

- [ ] * : 공백 뒤에 알파벳 0회 이상 반복을 한번 더 선언해줌으로써 공백 뒤에 알파벳 오는 경우를 대비

 

** 정규식 확인 페이지

https://www.regexplanet.com/advanced/java/index.html

 

'프로그래머스' 카테고리의 다른 글

정수 삼각형  (1) 2022.09.26
마라톤 문제  (1) 2022.09.25
신고 결과 받기  (0) 2022.08.11
숫자 문자열과 영단어  (0) 2022.08.09
폰켓몬  (0) 2021.07.31

안전

  • 호출해도 리소스가 변하지 않음
  • 만약, 로그가 쌓여서 장애가 발생한다면?
  • 그런 부분은 고려하지 않고, 오로지 리소스가 변경되고 삭제됬는지만 고려합니다.

 

 

멱등

  • 한번 호출하든, 백번 호출하든 결과가 똑같습니다.
  • 멱등 메서드는 무엇이 있을까?
PUT : 결과를 대체하는 메서드이므로, 몇번 하든 결과가 같다 ( 멱등 )
GET : 한번 조회하든, 두번 조회하든 결과가 조회된다. ( 멱등 )
DELETE : 결과를 삭제한다. 같은 요청을 여러번해도 삭제된 결과는 같다 ( 멱등 )
POST : 두 번 호출하면 결과가 중복될 수 있다. ( 멱등이 아니다 !! )

 

 

멱등 활용하기

  • 자동 복구 메커니즘
  • 서버가 TIMEOUT 등으로 정상 응답을 주지 못할 때, 클라이언트가 같은 요청을 다시 해도 되는가의 판단 근거가 됩니다.

 

 

멱등은 중간에 리소스를 변경하는 것도 고려를 하는가?

GET -> PUT -> GET 으로 데이터가 바뀐것을 조회할 수 있습니다.

멱등은 외부 요인으로 중간에 리소스가 변경되는 것은 고려하지 않습니다.

 

 

 

캐시 가능

  • 응답 결과 리소스를 캐시해서 사용해도 되는가?
  • GET, HEAD, POST, PATCH 캐시 가능
  • 실제로는 GET, HEAD 정도만 캐시로 사용되며, POST,PATCH 는 본문 내용까지 캐시 키로 고려해야하는데, 구현이 쉽지 않음

 

 

클라이언트에서 서버로 데이터 전송하는 방법

  • 쿼리 파라미터를 통한 데이터 전송

URI 끝에 쿼리 파라미터를 넣어서 전송하는 방법입니다.

이것은 주로 GET 으로 정렬 필터 ( 검색어 ) 에 사용을 많이 하는 편입니다.

 

  • 메시지 바디를 통한 데이터 전송

HTTP 메세지 바디를 통해서 전송하는데, POST/PUT/PATCH 를 이용해서

회원가입, 상품주문, 리소스 등록, 리소스 변경할 때 주로 사용합니다.

 

 

클라이언트에서 서버로 데이터를 전송하는 상황 4가지

  • 정적 데이터 조회

- 이미지, 정적텍스트 문서

- 조희는 GET 사용

- 정적 데이터는 일반적으로 쿼리 피라미터 없이 리소스 경로로 단순하게 조회 가능

 

 

  • 동적 데이터 조회

- 주로 검색, 게시판 목록에서 정렬필터 ( 검색어 )

- 조회 조건을 줄여주는 필터, 조회 결과를 정렬하는 정렬 조건에 주로 사용

- 조회는 GET 사용

- GET 은 쿼리 피라미터 사용해서 데이터 전달

 

        클라이언트                                                                                서버

 

  

 

  • HTML Form 을 통한 데이터 전송

- 회원가입, 상품 주문, 데이터 변경

 

클라이언트                                                                               서버

- HTML Form submit 시 POST 전송

- 회원 가입, 상품 주문, 데이터 변경에 주로 사용됨

- Content-Type : application/x-www-form-urlencoded 사용

   -> form 내용을 메세지 바디에 넣어줘서 전송 ( 쿼리 피라미터 형식 )

   -> 전송 데이터를 url encoding 처리

 

클라이언트                                                                                 서버

- GET 은 메세지 바디를 쓰지않고, 쿼리 파라미터에 넣어서 서버에 전달합니다.

- 그러나 위의 예제에서는 저장하는 save 메서드에 GET ( 조회 ) 메서드를 사용하므로 쓰면 안되는 문장입니다.

 

- 위와 같이 멤버를 조회하는 문장에는 사용할 수 있습니다.

 

- 파일 업로드 같은 바이너리 데이터 전송시 사용

- 이름이 multipart 인 이유는 다른 종류의 여러 파일과 폼의 내용 함께 전송 가능하기 때문입니다.

 

*** HTML Form 전송은 GET,POST 만 지원

 

 

 

  • HTTP API 를 통한 데이터 전송

ex. 안드로이드 앱 애플리케이션에서 클라이언트에서 서버로 데이터를 바로 전송해줘야 할 때,

       이를 HTTP API 로 전송한다고 부릅니다. ( 그냥 다 만들어서 넘기는 것입니다. )

 

- 서버에서 서버로 백엔드 시스템 통신

- 앱 클라이언트 ( 아이폰, 안드로이드 )

- 웹 클라이언트

     -> HTML 에서 Form 전송 대신 자바 스크립트를 통한 통신에 사용 ( AJAX )

     -> React, Vue.js 같은 웹 클라이언트와 API 통신

- POST, PUT, PATCH : 메세지 바디를 통해 데이터 전송

- GET : 조회,쿼리 피라미터로 데이터 전달

- Content-Type : application/json 을 주로 사용 ( TEXT, XML, JSON 등등 )

 

'HTTP' 카테고리의 다른 글

RestAPI 활용하기  (0) 2022.08.22
Message States Server  (0) 2022.08.19
HTTP API 설계와 메서드  (0) 2022.08.14
HTTP - 짧게 정리  (0) 2022.08.13
HTTP  (0) 2022.08.03

Async function

AsyncFunction 객체를 반환하는 하나의 비동기 함수를 정의합니다.

 

 

  • Syntax
async function name([param[, param[, ... param]]]) {
        statements
}

 

  • Description

async 함수에는 await 식이 포함될 수 있습니다. ( await 키워드는 async 함수에만 유효함 )

await 식은

1) async 함수의 실행을 일시 중지하고

2) 전달 된 Promise 의 해결을 기다린 다음

3) async 함수의 실행을 다시 시작하고

4) 완료 후 값을 반환합니다.

 

Note

- async/await 함수의 목적은 사용하는 여러 Promise 의 동작을 동기스럽게 사용할 수 있게 하고,
어떤 동작을 여러 Promise 의 그룹에서 간단하게 동작하게 하는 것입니다.

- async 함수는 항상 Promise 를 반환합니다. 만약 반환값이 명시적으로 Promise 가 아니라면
  암묵적으로 Promise 로 감싸집니다.
  아래의 코드를 예시로 볼 수 있습니다.
async function foo() {
        return 1
}

function foo() {
        return Promise.resolve(1)
}


 

 

비동기 함수

이벤트 루프를 통해 비동기적으로 작동하는 함수로, 암시적으로 Promise 를 사용하여 결과를 반환합니다.

 

 

 

Promise

Promise 객체는 비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과값을 나타냅니다.

 

기본적으로 promise 는 함수에 콜백을 전달하는 대신에, 콜백을 첨부하는 방식입니다.

 

예를 들어, createAudioFileAsync() 라는 함수가 있다고 생각해보겠습니다.

 

해당 함수는 [ 음성 설정에 대한 정보 ] 를 받고, [ 두 가지 콜백 함수 ] 를 받습니다.

 

1) 음성 파일이 성공적으로 생성됬을 때, 실행되는 콜백

2) 에러가 발생했을 때 실행되는 콜백

 

function successCallback(result) {
  console.log("Audio file ready at URL: " + result);
}

function failureCallback(error) {
  console.log("Error generating audio file: " + error);
}

createAudioFileAsync(audioSettings, successCallback, failureCallback);

 

만약 createAudioFileAsync () 함수가 Promise 를 반환하도록 수정한다면, 다음과 같이 사용할 수 있습니다.

 

createAudioFileAsync(audioSettings).then(successCallback, failureCallback);

// or

const promise = createAudioFileAsync(audioSettings);
promise.then(successCallback, failureCallback);

 

이러한 방식을 비동기 함수 호출이라고 부릅니다.

 

 

'HTML-CSS-JS' 카테고리의 다른 글

JavaScript 요약 강좌2  (0) 2022.08.16
JavaScript 요약강좌1  (0) 2022.08.16
Form 태그의 이해  (0) 2022.07.02

강좌2.

String 변수를 콘솔에서 사용하는 방법

 

  • Template Literal 문법
let name = 'sungjun'
console.log('제 이름은 ${name} 입니다.' )

 

 

 

Array 의 형태 : Object

['하나','둘','셋']
[{'one':1},{'two':2}]
[[10,20],[100,200]]
[[[10,20],[30,40]],[[100,200],[300,400]]] // 3차원 배열 형태

 

  • 다양한 메서드 ( 실습 필요 )

1) length

2) foreach

3) map

4) filter

5) push / pop ... 등등

 

 

 

Object ( 객체 )

key 와 value 형태를 지닙니다.

 

  • 다양한 메소드

1) ObjectKeys

2) Object.values

3) Object.entries ... 등등

 

  • Map,Set : object 

 

 

Function 

함수 A ( 피라미터 ) 의 형태로 선언하고, 함수 A ( 인자 ) 의 형태로 호출합니다.

 

 

 

호이스팅

console.log(add(10,20)) // 호이스팅 되서 출력됨
console.log(add2(10,20)) // 호이스팅이 안되서 에러 발생함


function add (x,y){
	return x+y;
}

let add2 = function(x,y){
	return x+y;
}

 

 

콜백 함수

: 함수를 인자로 받아서, 내부에서 실행을 시켜주는 함수입니다.

즉, 나중에 불러주는 함수입니다.

function add(x,y){
	return x+y;
}

function mul(x,y){
	return x*y;
}

function cal(a,b){
	return a(10,10) + b(10,10); // a라는 함수는 선언된적 없음
}

cal(mul,mul); // cal 이 호출되면서 a라는 함수에 각 함수들이 할당됨

 

 

화살표 함수

함수의 네이밍이 없고, 인자와 함수 본문만 보여진다.

  • 형태 :
(a,b) => {
	let z = 10
    let res = z + a + b
    return res
 };

 

 

만약, 화살표 함수를 콜백함수로 사용한다면?

  1. 네이밍을 안해도됨
  2. 다른 곳에서 사용할 수 없음
  3. 콜백 지옥에 빠질 수 있음
function cal(a,b){
	return a(10,10) + b(10,10);
}

cal((a,b) => a+b, (a+b) => a*b);
// cal 이 호출됨과 동시에 화살표 함수로 함수가 선언되어 실행됨

 

 

동기와 비동기

  • js는 일을 처리할 수 있는 스레드가 1개 싱글 쓰레드라고 합니다.
  • 모든 일을 여러명이 처리할 수 없다면 항상 기다려야하는 문제가 발생하고, 무한대기에 빠질 수 있습니다.

* 동기와 비동기를 이해하려면 자바시크립트 실행 동작원리를 이해해야함 ( 숙제 )

 

 

 

DOM

  • 메소드
  1. getElementById() : 해당하는 Id 를 가진 요소에 접근하기
  2. querySelector() : css 선택자로 단일 요소에 접근하기
  3. document.createElement (target) : 타겟 요소를 생성합니다.
  4. element.appendChild ( target ) : 타겟 요소를 요소의 자식으로 위치합니다.
  5. ... 등등

 

 

'HTML-CSS-JS' 카테고리의 다른 글

async function  (0) 2022.08.17
JavaScript 요약강좌1  (0) 2022.08.16
Form 태그의 이해  (0) 2022.07.02

강좌 1.

주의사항

 

  • JavaScript 와 CSS 는 분리해주는게 유지보수면에서 좋다.
  • JavaScript 출력 방법 4가지를 익혀놓을 것
  • HTML 문법 자동 완성해주는 코드 : html 에서 ! 입력

 

JavaScript 출력방법

  • Chrome 에서 콘솔창을 이용
  • about:blank 접속
  • cmd + option + I 입력 ( 맥북 )
  • 요소로 들어가서 원하는 부분 소스코드 수정 ( 바로 반영됨 )
  • 콘솔에 javascript 부분 입력
  1. document.getElementById('one').innerHTML='hi world'  : 문서 내에 요소를 선택하여 출력
  2. document.write('js hello') : 문서에 직접 입력
  3. alert('hello') : 사용자 인터랙션 ( alert 이외에 confirm 등이 있음 )
  4. console.log('hello') : 콘솔에 찍는 방법

 

코드 구조

  1. statement 는 세미콜론으로 구분 ( 붙치지 않는곳도 존재 )
  2. statement 는 값, 연산자, 키워드, 명령어, 표현식 등으로 구성됨

 

엄격 모드

  • use strict 라는 지시자를 통해 엄격모드를 활성화할 수 있습니다.
  • 활성화된 경우에만 반영이 됩니다.
  • class 문법의 경우 엄격모드가 기본입니다.
  • 함수별로 엄격모드를 다르게 적용할 수 있으나 이는 혼란을 야기합니다.

 

 

변수 선언 방법

  • let : 값을 중간에 변경해야하는 경우 사용합니다.
  • var : 앞에 선언하고, 뒤에 또 선언해도 에러를 발생시키지 않아서 사용하는 것을 권장하지 않음
  • const : 상수로 선언할 시 문제가 될 것 같은 경우에만 let을 사용하고 웬만하면 const 를 항상 사용

ex.

const xx = 10;
xx= 30; // Error !

let xx = 10;
xx = 30; // Success

* 특이사항

- let,const 는 블록 단위의 스코프를 가집니다. ( 블럭이라하면 중괄호 ( { } ) 를 의미 )

 

 

 

nullish 병합 연산자

let result1;
let result2 = result1 ?? 100; // 100

let result3 = 10;
let result4 = result3 ?? 100; // 10

result1 이 null 이거나 undefined 인 경우, 뒤에 값을 넣어줌

 

 

 

프로퍼티 접근 연산자

  • 마침표 프로퍼티 접근 연산자
  • 대괄호 프로퍼티 접근 연산자
let x = {'one':1, 'two':2}
x.one // 1
x['one'] // 1

 

 

 

관계 연산자

키만 가지고 판단

10 in [10,20,30] // false
1 in [10,20,30] // true
1 in 'hello' // error
'name' in {'name':'hojun','age':10} // true
'length' in [10,20,30] // true
  • 10 이 false 가 나오는 이유

[10,20,30] 은 기본적으로 인덱스 값이 키값으로 들어가있습니다.

[ 0:10, 1:20, 2:30 ] 이런식입니다.

 

그래서 10은 키값으로 없으니 false 이고, 1은 1이라는 인덱스가 존재하는 true 가 반환됩니다.

 

  • length 가 true 가 나오는 이유

[10,20,30] 을 console.dir 로 찍어보면 Array(3) 안에 length : 3 이 존재하는 것을 볼 수 있습니다.

 

 

 

변수의 형(타입)

typeof 로 확인 가능

  • 원시타입 ( primitive type ) : 유일한 값을 가진 것 ex. boolean, string, number, null ....
  • 참조타입 ( reference types ) : 값을 변경할 수 있는 것 ex. object, array, map, set, function ...
  • Number : 숫자

 

 

 

 

'HTML-CSS-JS' 카테고리의 다른 글

async function  (0) 2022.08.17
JavaScript 요약 강좌2  (0) 2022.08.16
Form 태그의 이해  (0) 2022.07.02

주제

회원 정보 관리 API 생성

  • 회원 목록 조회
  • 회원 조회
  • 회원 등록
  • 회원 수정
  • 회원 삭제

 

 

 API URI 설계

  • 회원 목록 조회 / read-member-list
  • 회원 조회 / read-member-by-id
  • 회원 등록 / create-member
  • 회원 수정 / update-member
  • 회원 삭제 / delete-member

이렇게 설계하는 것은 좋은 URI 설계인가?

 

URI 설계를 할 때 가장 중요한 것은 리로스 식별 입니다.

 

 

API URI 고민

  • 리소스의 의미는 뭘까?

- 회원 등록/수정/조회 하는게 리소스가 아니다.

- 회원이라는 개념 자체가 바로 리소스이다.

 

  • 리소스를 어떻게 식별하는게 좋을까?

- 회원을 등록, 수정, 조회하는 것을 모두 배제

- 회원이라는 리소스만 식별하면 됨 => 회원 리소스를 URI 에 매핑

 

 

리소스와 행위를 분리

가장 중요한 것은 리소스를 식별하는 것

 

  • URI 는 리소스만 식별
  • 리소스와 해당 리소스를 대상으로 하는 행위를 분리 ( 리소스 : 회원 / 행위 : 조회,등록,삭제,수정 )
  • 리소스는 명사, 행위는 동사 ( 미네랄을 캐라 )
  • 행위 ( 메서드 ) 는 어떻게 구분? ( HTTP 메서드 - GET,POST )

HTTP 메서드

클라이언트가 서버에 무언가를 요청을 할 때 기대하는 행동입니다.

 

[ 주요 메서드 ]

  1. GET : 리소스 조회
  2. POST : 요청 데이터 처리, 주로 등록에 사용
  3. PUT : 리소스를 대체, 해당 리소스가 없으면 생성
  4. PATCH : 리소스 부분 변경
  5. DELETE : 리소스 삭제

[ 기타 메서드 ]

  1. HEAD : GET 과 동일하지만 메시지 부분을 제외하고, 상태 줄과 헤더만 반환
  2. OPTIONS : 대상 리소스에 대한 통신 가능 옵션(메서드) 를 설명 ( 주로 CORS 에서 사용 )
  3. CONNECT : 대상 자원으로 식별되는 서버에 대한 터널을 설정
  4. TRACE : 대상 리소스에 대한 경로를 따라 메시지 루프백 테스트를 수행

 

GET

  • 리소스 조회
  • 서버에 전달하고 싶은 데이터는 query 를 통해서 전달
  • 메시지 바디를 사용해서 데이터 전달 가능하지만, 지원하지 않는 곳이 많아서 권장하지 않음

 

[ 과정 ]

1. 클라이언트가 GET 메서드로 리소스 요청

2. 서버는 데이터를 JSON 형태로 응답데이터를 전송해줌

 

 

 

POST

  • 요청 데이터 처리
  • 메시지 바디를 통해 서버로 요청 데이터 전달
  • 서버는 요청 데이터를 처리 -> 메시지 바디를 통해 들어온 데이터를 처리하는 모든 기능 수행
  • 주로 전달된 데이터로 신규 리소스 등록, 프로세스 처리에 사용 

[ 부가 설명 ]

서버한테 위와 같이 데이터를 줄 것이라고 알려주고,

데이터를 처리해달라고 클라이언트에서 서버로 요청을 하는 것

 

즉, 핵심은 [ 메시지 바디를 통해 서버로 요청 데이터를 전달 ] 하는 것입니다.

그러면 서버는 이 요청데이터를 받아서 처리하는데, 보통 메시지 바디를 통해 들어온 데이터를 처리하는 모든 기능을

다 수행합니다.

 

* 신규 등록시, 리소스 등록3 에서 Location 에 자원의 신규 생성된 URI 경로를 적어줍니다.

 

POST

요청 데이터를 어떻게 처리한다는 뜻일까?

 

  • 스펙 : POST 메서드는 대상 리소스가 리소스의 고유 한 의미 체계에 따라 요청에 포함 된 표현을 처리하도록 요청합니다.
  • 예시
- HTML 양식에 입력 된 필드와 같은 데이터 블록을 데이터 처리 프로세스에 제공
 ex.  HTML 폼에 입력한 정보로 회원가입,주문 등에서 사용

- 게시판, 뉴스, 그룹, 메일링, 리스트, 블로그 또는 유사한 기사 그룹에 메시지 게시
 ex. 게시판 글쓰기, 댓글 달기

- 서버가 아직 식별하지 않은 새 리소스 생성
 ex. 신규 주문 생성

- 기존 자원에 데이터 추가
 ex. 한 문서 끝에 내용 추가하기
  • 정리 : 이 리소스 URI 에 POST 요청이 오면 요청 데이터를 어떻게 처리할지 리소스마다 따로 정해야함 ( 정해진게 없음 )

 

POST 정리

  • 새 리소스 생성 ( 등록 ) - 서버가 아직 식별하지 않은 새 리소스 생성
  • 요청 데이터 처리
- 단순히 데이터를 생성/변경하는 것을 넘어서 프로세스를 처리하는 경우
ex. 주문의 결제완료 -> 배달시작 -> 배달 완료처럼 단순히 값 변경을 넘어 프로세스의 상태가 변경되는 경우
   ( 배달 시작을 누르게되면 그 후에 굉장히 큰 프로세스가 실행됨 )

- POST 의 결과로 새로운 리소스가 생성되지 않을 수 있음
ex. POST /orders/{ordered}/start-delivery ( 컨트롤 URI )
  • 다른 메서드로 처리하기 애매한 경우 ( JSON 으로 조회 데이터 넘겨야하는데, GET 사용 어려운 경우 )

 

PUT

  • 리소스 수정이 아니라 리소스를 완전히 갈아치움
  • 리소스가 있으면 대체 ( 쉽게 말해 덮어 쓰기 )
  • 리소스가 없으면 생성
  • 클라이언트가 리소스 위치를 알고 URI 지정 ( POST 와의 차이점 )
  • 즉, 클라이언트가 리소스 위치를 알고 있어야함.

 

위의 예제에서 만약 member/100 리소스가 없으면, 신규로 생성이 됩니다.

그런데 만약 있으면 [ 기존것은 없어지고 새로 들어감 ]

 

[ age 만 50으로 수정 ] 을 하고 싶어도 username 이 없어지고 age 50이 들어와버립니다.

그래서 이러한 수정을 위해서 PATCH 를 사용합니다.

 

 

PATCH

- 리소스 부분 변경

- PATCH 를 못쓰는 버젼이 있어서 그런 경우 POST 를 사용해야함

 

 

DELETE

리소스 제거

 

 

 

 

'HTTP' 카테고리의 다른 글

RestAPI 활용하기  (0) 2022.08.22
Message States Server  (0) 2022.08.19
HTTP 메서드와 속성  (0) 2022.08.18
HTTP - 짧게 정리  (0) 2022.08.13
HTTP  (0) 2022.08.03

HTTP

모든 데이터를 전송 가능

 

  • HTML,TXT
  • IMAGE, 음성, 영상, 파일
  • JSON, XML
  • 서버간에 데이터를 주고 받을 때도 대부분 HTTP 사용

 

 

HTTP 메시지 구조

* 공백은 무조건 필요함

 

 

HTTP 요청 메시지

 

 

HTTP 응답 메시지

 

 

시작 라인

요청 메시지

 

  • start-line = request-line / status-line

start-line 을 request-line 과 status-line 이 있는데

요청 메시지는 request-line 이라고 부릅니다.

 

  • request-line = method SP(공백) request-target SP HTTP-version CRLF (엔터) 

method : GET/POST 등

request-target : 요청하는 대상

 

 

 

HTTP method

  • 종류 : GET,POST,PUT,DELETE...
  • 서버가 수행해야 할 동작 지정

- GET : 리소스를 달라고 요청하고, 리소스 조회

- POST : 리소스를 줄테니까 작업을 요청, 요청 내역 정리

 

 

 

Request-target

  • absolute-path [?query] ( 절대경로 [?쿼리] )
  • 절대경로= "/" 로 시작하는 경로
  • 참고 : * , http://...?x=y 와 같이 다른 유형의 경로 지정 방법도 있음
[ 예제 ]
GET / search?q=hello&hl=ko HTTP/1.1
Host : www.google.com 

 

 

응답 메시지

  • start-line = request-line / status-line

status-line 에 해당합니다.

  • status-line = HTTP-version SP(공백) status-code SP reason-phrase CRLF

 

  • HTTP version
  • HTTP status code : 요청 성공, 실패를 나타냄
200 - 성공
400 - 클라이언트 요청 오류
500 - 서버 내부 오류

[ 예제 ]

 

 

HTTP 헤더

  • header-field = field-name ":" OWS field-value OWS ( OWS : 띄어쓰기를 써도 되고,안해된다. 즉 허용해준다는 뜻 )
  • field-name : 대소문자 구분 없음

* 단,  [Host SP :] 의 형태는 안됩니다. 즉, 공백을 주의깊게 보고 사용해야합니다.

 

 

HTTP 헤더의 용도

  • HTTP 전송에 필요한 모든 부가정보 ( 메시지 바디 내용, 메시지 바디 크기, 압축, 인증, 캐시 관리 정보,,, 등등 )
  • 표준 헤더가 너무 많음
  • 필요시 임의의 헤더 추가 가능

 

 

HTTP 메시지 바디의 용도

  • 실제 전송할 데이터
  • HTML 문서, 이미지, 영상, JSON 등 byte로 표현할 수 있는 모든 데이터 전송 가능

 

 

HTTP 정리

  • HTTP 메시지에 모든 것을 전송
  • HTTP/1.1 을 기준으로 학습
  • 클라이언트 서버 구조
  • 무상태 프로토콜 ( 스테이스리스 )
  • HTTP 메시지
  • 단순함, 확장 가능

'HTTP' 카테고리의 다른 글

RestAPI 활용하기  (0) 2022.08.22
Message States Server  (0) 2022.08.19
HTTP 메서드와 속성  (0) 2022.08.18
HTTP API 설계와 메서드  (0) 2022.08.14
HTTP  (0) 2022.08.03

Bean definition

빈이 존재할 수 있는 범위를 의미합니다.

 

 

  • Spring Framework 는 6개의 범위를 지원하며, 그 중 4개는 ApplicationContext 를 사용하는 경우에만 사용할 수 있습니다.
  • bean 은 여려가지 범위 중에서 하나에 배치될 수 있도록 정의할 수 있습니다.
  • 사용자 정의 범위를 생성할 수 있습니다.

 

싱글톤 스코프

클래스 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴

 

  • 스프링 컨테이너의 시작과 함께 생성되어서 스프링 컨테이너가 종료될때까지 유지됩니다.
  • 싱글톤 빈의 하나의 공유 인스턴스만 관리하게 됩니다.
  • [해당 bean Definition 과 일치하는 ID] or [ID 를 가진 빈] 에 대한 모든 요청은 스프링 컨테이너에서 해당 특정 빈 인스턴스를 반환
  • 스프링 컨테이너 종료시 소멸 메서드도 자동으로 실행됨

 

싱글톤 특징

  • 해당 빈의 인스턴스를 오직 하나만 생성해서 사용
  • 인스턴스는 싱글톤 빈의 캐시에 저장됨
  • 정해진 빈에 대한 모든 요청과 참조는 캐시된 개체를 반환 ( 즉, 여러번 호출해도 모두 같은 인스턴스 참조 주소값 가짐 )

 

싱글톤 패턴의 문제점

1) 구현하는 코드 자체가 많아짐

2) 의존관계상 클라이언트 구체 클래스에 의존

3) 지정해서 가져오기 때문에 테스트가 어려움

4) private 생성자를 사용해 자식 클래스를 만들기 때문에 유연성이 떨어짐

5) 싱글톤 빈은 기본적으로 애플리케이션 구동 시 생성되므로 빈이 많을수록 구동 시간이 증가됨

 

 

싱글톤 패턴 문제와 싱글톤 컨테이너

위의 싱글톤 패턴의 문제점은 싱글톤 컨테이너를 사용함으로써 해결이 가능합니다.

 

  • 스프링 컨테이너는 싱글톤 컨테이너 역할을 합니다.
  • 싱글톤 객체로 생성하고 관리하는 기능을 싱글톤 레지스트리라고 합니다.
  • 스프링 컨테이너의 위 기능 덕분에 싱글턴 패턴의 모든 단점을 해결하며 객체를 싱글톤으로 유지할 수 있습니다.

 

'스터디' 카테고리의 다른 글

멀티 쓰레드  (0) 2022.11.24
서블릿  (1) 2022.11.22
스프링 컨테이너  (0) 2022.08.11
UML 작성법  (0) 2022.08.10
AOP - 관점 지향 프로그래밍  (0) 2022.08.09

+ Recent posts