코드스테이츠 백엔드 부트캠프 41일차 - [Spring MVC] API 계층

wish17·2023년 2월 12일
0
post-thumbnail

Daily Coding - 20번

연속되는 숫자와 문자를 (연속하는 횟수 + 문자or숫자 형태로) 압축해서 리턴하라.

  • 3회이상 연속되는 경우만 압축하라.
public class CompressString {
    public String compressString(String str) {
        String result ="";
        char now = ' ';

        for(int i=0; i<str.length(); i++){
            if(str.charAt(i)!=now) {
                now = str.charAt(i);
                char nextCh = ' ';
                int j = i;
                int count = 1;
                if (j + 1 < str.length()) {
                    nextCh = str.charAt(j + 1);
                    while (now == nextCh) {
                        count++;
                        j++;
                        if (j + 1 < str.length()) nextCh = str.charAt(j + 1);
                        else break;
                    }
                }else result += str.charAt(i);

                if(count==1) result += now;
                else result = result + count + now;
            }
        }
        return result;
    }
}

//입력
compressString.compressString("qwweeerrrrtt")

//출력
q2w3e4r2t

//정답
qww3e4rtt

문제를 제대로 파악하자... 3회 이상 반복될 때만 압축해야 한다.

public String compressString(String str) {
        String result ="";
        char now = ' ';
        int count = 1;

        for(int i=0; i<str.length(); i++){
            if(str.charAt(i)!=now) {
                now = str.charAt(i);
                char nextCh = ' ';
                int j = i;
                count = 1;
                if (j + 1 < str.length()) {
                    nextCh = str.charAt(j + 1);
                    while (now == nextCh) {
                        count++;
                        j++;
                        if (j + 1 < str.length()) nextCh = str.charAt(j + 1);
                        else break;
                    }
                }else result += str.charAt(i);
---------------------------------수정된 부분---------------------------------
                if(count<3) result += now;
                else result = result + count + now;
            }else if(count<3) result += now;
---------------------------------수정된 부분---------------------------------
        }
        return result;
    }

//입력1
"qwweeerrrrtt"
//출력1
qww3e4rtt

//입력2
"EQTWVOQQQVDVRC"
//출력2
EQTWVO3QVDVRCC

일부 케이스는 되는데 일부 케이스는 압축이 제대로 안되고 있다.

마지막 요소가 연속되지 않고 종료할 경우 result에 값이 두번 들어가고 있다.
코드 작성중에 여러가지 시도를 해보다 남겨둔 코드였는데 지우는 것을 잊어버리고 실수했다.

package CodeStatesAlgorithms;

public class CompressString {
    public String compressString(String str) {
        String result ="";
        char now = ' ';
        int count = 1;

        for(int i=0; i<str.length(); i++){
            if(str.charAt(i)!=now) {
                now = str.charAt(i);
                char nextCh = ' ';
                int j = i;
                count = 1;
                if (j + 1 < str.length()) {
                    nextCh = str.charAt(j + 1);
                    while (now == nextCh) {
                        count++;
                        j++;
                        if (j + 1 < str.length()) nextCh = str.charAt(j + 1);
                        else break;
                    }
---------------------------------수정된 부분---------------------------------                    
                } //else result += str.charAt(i);
---------------------------------수정된 부분---------------------------------                
                if(count<3) result += now;
                else result = result + count + now;
            }else if(count<3) result += now;

        }
        return result;
    }
}

모든테스트 케이스 통과


[SEB BE] Section 3

[Spring MVC] API 계층

Spring MVC 아키텍처

Spring MVC = spring-webmvc

  • 서블릿(Servlet) API를 기반으로 클라이언트의 요청을 처리하는 모듈
  • 웹 프레임워크의 한 종류 (Spring MVC 프레임워크라고도 부름)
  • 클라이언트의 요청을 편리하게 처리해주는 프레임워크다.

서블릿(Servlet)

  • 웹 서버의 성능을 향상하기 위해 사용되는 자바 클래스의 일종
  • 클라이언트의 요청을 처리하도록 특정 규약에 맞추어서 Java 코드로 작성하는 클래스 파일

cf. 아파치 톰캣(Apache Tomcat)은 서블릿들이 웹 애플리케이션으로 실행이 되도록 해주는 서블릿 컨테이너(Servlet Container) 중 하나

Model

Spring MVC 중 M

Model

  • 클라이언트에게 응답으로 돌려주는 작업의 처리 결과 데이터

View

Spring MVC 중 V

View

  • Model 데이터를 이용해서 웹브라우저 같은 클라이언트 애플리케이션의 화면에 보여지는 리소스(Resource)를 제공하는 역할

다양한 View의 형태

  • HTML 페이지의 출력

    • 클라이언트 애플리케이션에 보여지는 HTML 페이지를 직접 렌더링해서 클라이언트 측에 전송하는 방식
    • 즉, 기본적인 HTML 태그로 구성된 페이지에 Model 데이터를 채워 넣은 후, 최종적인 HTML 페이지를 만들어서 클라이언트 측에 전송
    • Spring MVC에서 지원하는 HTML 페이지 출력 기술에는 Thymeleaf, FreeMarker, JSP + JSTL, Tiles 등이 있다.
  • PDF, Excel 등의 문서 형태로 출력

    • Model 데이터를 가공해서 PDF 문서나 Excel 문서를 만들어서 클라이언트 측에 전송하는 방식
    • 문서 내에서 데이터가 동적으로 변경되어야 하는 경우에 주로 사용
  • XML, JSON 등 특정 형식의 포맷으로의 변환

    • Model 데이터를 특정 프로토콜 형태로 변환해서 변환된 데이터를 클라이언트 측에 전송하는 방식

Java의 객체를 JSON 포맷으로 변환

public class Coffee {
    private String korName;
    private String engName;
    private int price;

    public Coffee(String korName, String engName, int price) {
        this.korName = korName;
        this.engName = engName;
        this.price = price;
    }
}

Gson이라는 라이브러리를 사용해서 Coffee 클래스의 객체를 JSON 포맷 형태로 출력

    public class JsonExample {
        public static void main(String[] args) {
            Coffee coffee = new Coffee("아메리카노", "Americano", 3500);
            Gson gson = new Gson();
            String jsonString = gson.toJson(coffee);
    
            System.out.println(jsonString);
        }
    }
    
//출력
{"korName":"아메리카노","engName":"Americano","price":3500}

JSON 포맷은 기본적으로{”속성”:”값”} 형태다.

위와 반대의 과정을 아래 툴로 연습해보며 JSON 형태에 익숙해질 수 있었다.

JSON 포맷의 문자열을 Java 클래스로 변경해주는 온라인 툴

Controller

Spring MVC 중 C

Controller

  • 클라이언트 측의 요청을 직접적으로 전달 받는 엔드포인트(Endpoint)로써 Model과 View의 중간에서 상호 작용을 해주는 역할
  • 클라이언트 측의 요청을 전달 받아서 비즈니스 로직을 거친 후에 Model 데이터가 만들어지면, 이 Model 데이터를 View로 전달하는 역할

Model, View, Controller 간의 처리 흐름

Client가 요청 데이터 전송
→ Controller가 요청 데이터 수신
→ 비즈니스 로직 처리
→ Model 데이터 생성
→ Controller에게 Model 데이터 전달
→ Controller가 View에게 Model 데이터 전달
→ View가 응답 데이터 생성


Spring MVC의 동작 방식과 구성 요소

Spring MVC의 요청 처리 흐름

  • 클라이언트의 요청을 제일 먼저 전달 받는 구성요소는 DispatcherServlet이다.
  • DispatcherServlet은 HandlerMapping 인터페이스에게 Controller의 검색을 위임한다.
  • DispatcherServlet은 검색된 Controller 정보를 토대로 HandlerAdapter 인터페이스에게 Controller 클래스내에 있는 Handler 메서드의 호출을 위임한다.
  • HandlerAdapter 인터페이스는 Controller 클래스의 Handler 메서드를 호출한다.
  • DispatcherServlet은 ViewResolver에게 View의 검색을 위임한다.
  • DispatcherServlet은 View에게 Model 데이터를 포함한 응답 데이터 생성을 위임한다.
  • DispatcherServlet은 최종 응답 데이터를 클라이언트에게 전달한다.

cf.
DispatcherServlet처럼 애플리케이션의 가장 앞단에 배치되어 다른 구성요소들과 상호작용하면서 클라이언트의 요청을 처리하는 패턴을 Front Controller Pattern이라고 한다.


Controller

Controller 클래스 설계 및 구조 생성

패키지 구조 생성

주로 사용하는 패키지 구조 두가지

기능 기반 패키지 구조(package-by-feature)

계층 기반 패키지 구조(package-by-layer)

테스트와 리팩토링이 용이하고, 향후에 마이크로 서비스 시스템으로의 분리가 상대적으로 용이한 기능 기반 패키지 구조 사용을 권장하기 때문에 학습과정에서는 기능 기반 패키지 구조를 따를 예정이다.


Controller 설계

  • Spring Boot 애플리케이션으로서 동작하기 위한 엔트리포인트에는 @SpringBootApplication을 추가한다.
  • main() 메서드 내에서 SpringApplication.run()을 호출하면 Spring Boot 기반의 애플리케이션으로 동작한다.
  • @RestController를 클래스에 추가함으로써 해당 클래스를 REST API의 리소스(자원, Resource)를 처리하기 위한 API 엔드포인트로 동작하게 해준다.
  • @RequestMapping을 Controller 클래스 레벨에 추가하여 클래스 전체에 사용되는 공통 URL(Base URL)을 설정할 수 있다.

사용한 기능 정리

@SpringBootApplication

  • 코드 상에서는 보이지 않지만 내부적으로 세가지 일 함
    • 자동 구성을 활성화
    • 애플리케이션 패키지 내에서 @Component가 붙은 클래스를 검색한 후(scan), Spring Bean으로 등록하는 기능을 활성화
    • @Configuration 이 붙은 클래스를 자동으로 찾아주고, 추가적으로 Spring Bean을 등록하는 기능을 활성화

SpringApplication.run(~~Application.class, args);

  • Spring 애플리케이션을 부트스트랩하고, 실행하는 역할
    • 부트스트랩(Bootstrap) : 애플리케이션이 실행되기 전에 여러가지 설정 작업을 수행하여 실행 가능한 애플리케이션으로 만드는 단계를 의미

@RestController

  • 클래스가 REST API의 리소스(자원, Resource)를 처리하기 위한 API 엔드포인트로 동작함을 정의한다.

  • 애플리케이션 로딩 시, Spring Bean으로 등록해준다.

@RequestMapping

  • 클라이언트의 요청과 클라이언트 요청을 처리하는 핸들러 메서드(Handler Method)를 매핑해준다.

핸들러 메서드(Handler Method)

기능 정리

produces애트리뷰트(Attribute)

  • 응답 데이터를 어떤 미디어 타입으로 클라이언트에게 전송할 지를 설정
@RequestMapping(value = "/v1/members", produces = {MediaType.APPLICATION_JSON_VALUE})
  • 위 예시의 MediaType.APPLICATION_JSON_VALUE 부분은 JSON 형식의 데이터를 응답 데이터로 전송하겠다는 의미다.

  • 위와 같이 produces값을 지정해주지 않으면 문자열 자체를 전송한다.

@PostMapping

  • 클라이언트의 요청 데이터(request body)를 서버에 생성할 때 사용하는 애너테이션

@RequestParam

  • 핸들러 메서드의 파라미터 종류 중 하나

map 메서드 put(), replace() 차이

Map<String, Integer> map = new HashMap<>();
   map.put("key1", 1);
   map.put("key2", 2);

   map.put("key1", 3); // key1의 value를 3으로 업데이트
   map.put("key3", 4); // key3의 value를 4로 추가

   map.replace("key2", 5); // key2의 value를 5로 업데이트
   map.replace("key4", 6); // key4가 존재하지 않기 때문에, 어떠한 동작도 수행하지 않음

put은 key가 해당하는 엔트리가 없어도 새로 만들어서라도 추가해줌
replace는 존재하지 않으면 아무것도 안함


ResponseEntity사용법

http 상태코드를 이용하면 됨.

// members + 200 OK
return new ResponseEntity(members, HttpStatus.OK);

// 204 No Content
return new ResponseEntity(HttpStatus.NO_CONTENT);

실습결과

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/v1/members")
public class MemberController {
    private final Map<Long, Map<String, Object>> members = new HashMap<>();

    @PostConstruct
    public void init() {
        Map<String, Object> member1 = new HashMap<>();
        long memberId = 1L;
        member1.put("memberId", memberId);
        member1.put("email", "hgd@gmail.com");
        member1.put("name", "홍길동");
        member1.put("phone", "010-1234-5678");

        members.put(memberId, member1);
    }

    // 1. 회원 정보 수정을 위한 핸들러 메서드 구현
    @PatchMapping("/{member-id}")
    public ResponseEntity patchMember(@PathVariable("member-id")long memberId, //  url에 있는 특정한 값을 뽑아오는 애너테이션 (http://localhost:8080/v1/members/1)
                                      @RequestParam("phone") String phone){ // requstParam = 사용자 입력 중 바디에 있는 값을 가져온다.
        members.get(memberId).replace("phone", phone);
        return new ResponseEntity(members, HttpStatus.OK);
    }


    // 2. 회원 정보 삭제를 위한 핸들러 메서드 구현
    @DeleteMapping("/{member-id}")
    public ResponseEntity deleteMember(@PathVariable("member-id")long memberId){
        members.remove(memberId);
        System.out.println("삭제성공");
        return new ResponseEntity(HttpStatus.NO_CONTENT);

    }

}

@PathVariable = url에 있는 특정한 값을 뽑아오는 애너테이션 http://localhost:8080/v1/members/1

@RequestParam = 사용자 입력 중 바디에 있는 값을 뽑아주는 애너테이션

package com.codestates.coffee;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/v1/coffees")
public class CoffeeController {
    private final Map<Long, Map<String, Object>> coffees = new HashMap<>();

    @PostConstruct
    public void init() {
        Map<String, Object> coffee1 = new HashMap<>();
        long coffeeId = 1L;
        coffee1.put("coffeeId", coffeeId);
        coffee1.put("korName", "바닐라 라떼");
        coffee1.put("engName", "Vanilla Latte");
        coffee1.put("price", 4500);

        coffees.put(coffeeId, coffee1);
    }

    //---------------- 여기서 부터 아래에 코드를 구현하세요! -------------------//
    // 1. 커피 정보 수정을 위한 핸들러 메서드 구현
    @PatchMapping("/{coffee-id}")
    public ResponseEntity patchMember(@PathVariable("coffee-id")long coffeeId,
                                      @RequestParam("korName") String korName,
                                      @RequestParam("price") int price){
        coffees.get(coffeeId).replace("korName", "바닐라 빈 라떼");
        coffees.get(coffeeId).replace("price", 5000);
        return new ResponseEntity(coffees,HttpStatus.OK);
    }
    // 2. 커피 정보 삭제를 위한 핸들러 서드 구현

    @DeleteMapping("/{coffee-id}")
    public ResponseEntity deleteMember(@PathVariable("coffee-id")long coffeeId){
        coffees.remove(coffeeId);
        return new ResponseEntity(HttpStatus.NO_CONTENT);

    }
}

이렇게 코드 내에서 직접적으로 수정해줄 수도 있다.

실습 중 @RequestParam, @PathVariable차이를 정확히 몰라서 고생했었다.

@PathVariable은 값을 하나만 받아올 수 있으므로, 여러 개 데이터를 받아올 때는 @RequestParam을 쓴다!

0개의 댓글