[Spring Boot][4] 6-1. 스프링 MVC - 기본 기능

sorzzzzy·2021년 9월 10일
1

Spring Boot - RoadMap 1

목록 보기
32/46
post-thumbnail

🏷 프로젝트 생성

스프링 부트 스타터 사이트에서 프로젝트를 생성하는 부분은 이전에 여러번 다뤘기 때문에 이번에도!!!!! 과감히 넘어가겠다😅

✔️ 프로젝트 선택

  • Project: Gradle
  • Project Language: Java
  • Spring Boot: 2.4.x

✔️ Project Metadata

  • Group: hello
  • Artifact: springmvc
  • Name: springmvc
  • Package name: hello.springmvc
  • Packaging: Jar
  • Java: 11

✔️ Dependencies

  • Spring Web
  • Lombok
  • Thymeleaf

주의 🤚🏻

  • Packaging는 War가 아니라 Jar를 선택!(JSP를 사용하지 않기 때문에)
  • 앞으로 스프링 부트를 사용하면 이 방식을 주로 사용.
  • Jar를 사용하면 항상 내장 서버(톰캣등)을 사용하고, webapp 경로도 사용하지 않음.
  • 내장 서버 사용에 최적화 되어 있는 기능이며 최근에는 주로 이 방식을 사용.
    (War를 사용하면 내장 서버도 사용가능 하지만, 주로 외부 서버에 배포하는 목적으로 사용.)

📌 웰컴 페이지 만들기
스프링 부트에 Jar 를 사용하면 /resources/static/index.html 위치에 index.html 파일을 두면 Welcome 페이지로 처리해준다!!
(스프링 부트가 지원하는 정적 컨텐츠 위치에 /index.html 이 있으면 된다.)



🏷 로깅 간단히 알아보기

이제 우리는 System.out.println() 같은 시스템 콘솔을 사용해서 필요한 정보를 출력하지 않고, 별도의 로깅 라이브러리를 사용해서 로그를 출력한다❗️

✔️ LogTestController 생성

package hello.springmvc.basic;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

// @RestController 는 반환 값으로 뷰를 찾는 것이 아니라, HTTP 메시지 바디에 바로 입력한다.
// 따라서 실행 결과로 ok 메세지를 받을 수 있다.
// (@Controller 는 반환 값이 String 이면 뷰 이름으로 인식된다. 그래서 뷰를 찾고 뷰가 랜더링 된다.)
@RestController
public class LogTestController {

    private final Logger log = LoggerFactory.getLogger(getClass());

    @RequestMapping("/log-test")
    public String logTest() {

        String name = "Spring";
        log.trace("trace log={}", name);
        log.debug("debug log={}", name);
        log.info(" info log={}", name);
        log.warn(" warn log={}", name);
        log.error("error log={}", name);

        //로그를 사용하지 않아도 a+b 계산 로직이 먼저 실행됨, 이런 방식으로 사용하면 X
        log.debug("String concat log=" + name);
        return "ok";
    }
}

✔️ 로그 레벨 설정
application.properties

#전체 로그 레벨 설정(기본 info) 
logging.level.root=info

#hello.springmvc 패키지와 그 하위 로그 레벨 설정 
logging.level.hello.springmvc=debug

✔️ 로그 사용시 장점

  • 쓰레드 정보, 클래스 이름 같은 부가 정보를 함께 볼 수 있고, 출력 모양을 조정할 수 있다.
  • 로그 레벨에 따라 개발 서버에서는 모든 로그를 출력하고, 운영서버에서는 출력하지 않는 등 로그를 상황에 맞게 조절할 수 있다.
  • 시스템 아웃 콘솔에만 출력하는 것이 아니라, 파일이나 네트워크 등, 로그를 별도의 위치에 남길 수 있다.
    특히 파일로 남길 때는 일별, 특정 용량에 따라 로그를 분할하는 것도 가능하다.
  • 성능도 일반 System.out보다 좋다. (내부 버퍼링, 멀티 쓰레드 등등)
    ➡️ 그래서 실무에서는 꼭 로그를 사용해야 한다❗️


🏷 요청 매핑

요청이 왔을 때 어떤 컨트롤러가 매핑이 되어야 할까❓

✔️ MappingController 생성

package hello.springmvc.basic.requestmapping;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;

@RestController
public class MappingController {
    private Logger log = LoggerFactory.getLogger(getClass());

    /**
     * 1. 기본 요청
     * 둘다 허용 /hello-basic, /hello-basic/
     * HTTP 메서드 모두 허용 GET, HEAD, POST, PUT, PATCH, DELETE
     * */
    @RequestMapping("/hello-basic")
    public String helloBasic() {
        log.info("helloBasic");
        return "ok";
    }

    /**
     * 2. HTTP 메서드 매핑
     * GET, HEAD, POST, PUT, PATCH, DELETE
     */
    @RequestMapping(value = "/mapping-get-v1", method = RequestMethod.GET)
    public String mappingGetV1() {
        log.info("mappingGetV1");
        return "ok";
    }

    /**
     * 3. HTTP 메서드 매핑 축약
     * @GetMapping
     * @PostMapping
     * @PutMapping
     * @DeleteMapping
     * @PatchMapping
     */
    @GetMapping(value = "/mapping-get-v2")
    public String mappingGetV2() {
        log.info("mapping-get-v2");
        return "ok";
    }

    /**
     * 4. PathVariable(경로 변수) 사용
     * 변수명이 같으면 생략 가능
     * @PathVariable("userId") String userId -> @PathVariable userId
     * */
    @GetMapping("/mapping/{userId}")
    public String mappingPath(@PathVariable("userId") String data) {
        log.info("mappingPath userId={}", data);
        return "ok";
    }

    /**
     * 5. PathVariable 사용 - 다중
     */
    @GetMapping("/mapping/users/{userId}/orders/{orderId}")
    public String mappingPath(@PathVariable String userId, @PathVariable Long
            orderId) {
        log.info("mappingPath userId={}, orderId={}", userId, orderId);
        return "ok";
    }

    /**
     * 6. 특정 파라미터 조건 매핑
     * params="mode",
     * params="!mode"
     * params="mode=debug"
     * params="mode!=debug" (! = )
     * params = {"mode=debug","data=good"}
     */
    @GetMapping(value = "/mapping-param", params = "mode=debug")
    public String mappingParam() {
        log.info("mappingParam");
        return "ok";
    }

    /**
     * 7. 특정 헤더 조건 매핑
     * headers="mode",
     * headers="!mode"
     * headers="mode=debug"
     * headers="mode!=debug" (! = )
     */
    @GetMapping(value = "/mapping-header", headers = "mode=debug")
    public String mappingHeader() {
        log.info("mappingHeader");
        return "ok";
    }

    /**
     * 8. 미디어 타입 조건 매핑 - HTTP 요청 Content-Type, consume
     * consumes="application/json"
     * consumes="!application/json"
     * consumes="application/*"
     * consumes="*\/*"
     * MediaType.APPLICATION_JSON_VALUE
     */
    @PostMapping(value = "/mapping-consume", consumes = "application/json")
    public String mappingConsumes() {
        log.info("mappingConsumes");
        return "ok";
    }

    /**
     * 9. 미디어 타입 조건 매핑 - HTTP 요청 Accept, produce
     * produces = "text/html"
     * produces = "!text/html"
     * produces = "text/*"
     * produces = "*\/*"
     */
    @PostMapping(value = "/mapping-produce", produces = "text/html")
    public String mappingProduces() {
        log.info("mappingProduces");
        return "ok";
    }

}

1️⃣ 기본 매핑

  • @RestController 는 반환 값으로 뷰를 찾는 것이 아니라, HTTP 메시지 바디에 바로 입력한다.
  • @RequestMapping("/hello-basic")
    • /hello-basic URL 호출이 오면 이 메서드가 실행되도록 매핑한다.
      (대부분의 속성을 배열로 제공하므로 다중설정이 가능)

2️⃣ HTTP 메서드 매핑

  • @RequestMappingmethod 속성으로 HTTP 지정이 가능하다.
    • 만약 지정하지 않으면 HTTP 메서드와 무관하게 호출됨
      예) @RequestMapping("/hello-basic", method = RequestMethod.GET)
  • 만약 여기에 POST 요청을 하면 스프링 MVC는 HTTP 405 상태코드(Method Not Allowed)를 반환한다.

3️⃣ HTTP 메서드 매핑 축약

  • HTTP 메서드를 축약한 애노테이션을 사용하는 것이 더 직관적이다.
  • 코드를 보면 내부에서 @RequestMapping 과 method 를 지정해서 사용하는 것을 확인할 수 있다.

4️⃣ PathVariable(경로 변수) 사용

  • 최근 HTTP API는 아래와 같이 리소스 경로에 식별자를 넣는 스타일을 선호한다.
    • /mapping/userA
    • /users/1
  • @RequestMapping 은 URL 경로를 템플릿화 할 수 있는데, @PathVariable을 사용하면 매칭 되는 부분을 편리하게 조회할 수 있다.
  • @PathVariable 의 이름과 파라미터 이름이 같으면 생략할 수 있다.

5️⃣ PathVariable 사용 - 다중

6️⃣ 특정 파라미터 조건 매핑

  • 특정 파라미터가 있거나 없는 조건을 추가할 수 있다.(잘 사용하지는 않음)

7️⃣ 특정 헤더 조건 매핑

  • 파라미터 매핑과 비슷하지만, HTTP 헤더를 사용한다.

8️⃣ 미디어 타입 조건 매핑 - HTTP 요청 Content-Type, consume

  • HTTP 요청의 Content-Type 헤더를 기반으로 미디어 타입으로 매핑한다.
  • 만약 맞지 않으면 HTTP 415 상태코드(Unsupported Media Type)을 반환한다.

9️⃣ 미디어 타입 조건 매핑 - HTTP 요청 Accept, produce

  • HTTP 요청의 Accept 헤더를 기반으로 미디어 타입으로 매핑한다.
  • 만약 맞지 않으면 HTTP 406 상태코드(Not Acceptable)을 반환한다.


🏷 요청 매핑 - API 예시

회원 관리를 HTTP API로 만든다 생각하고 매핑을 어떻게 하는지 알아보자❗️
(실제 데이터가 넘어가는 부분은 생략하고 URL 매핑만!!)

✔️ 회원 관리 API

  • 회원 목록 조회 : GET /users
  • 회원 등록 : POST /users
  • 회원 조회 : GET /users/{userId}
  • 회원 수정 : PATCH /users/{userId}
  • 회원 삭제 : DELETE /users/{userId}

✔️ MappingClassController 생성

package hello.springmvc.basic.requestmapping;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/mapping/users")
public class MappingClassController {
    /**
     * GET /mapping/users
     */
    @GetMapping
    public String users() {
        return "get users";
    }

    /**
     * POST /mapping/users
     */
    @PostMapping
    public String addUser() {
        return "post user";
    }

    /**
     * GET /mapping/users/{userId}
     */
    @GetMapping("/{userId}")
    public String findUser(@PathVariable String userId) {
        return "get userId=" + userId;
    }

    /**
     * PATCH /mapping/users/{userId}
     */
    @PatchMapping("/{userId}")
    public String updateUser(@PathVariable String userId) {
        return "update userId=" + userId;
    }

    /**
     * DELETE /mapping/users/{userId}
     */
    @DeleteMapping("/{userId}")
    public String deleteUser(@PathVariable String userId) {
        return "delete userId=" + userId;
    }
}

➡️ @RequestMapping("/mapping/users") : 클래스 레벨에 매핑 정보를 두면 메서드 레벨에서 해당 정보를 조합해서 사용!



🏷 HTTP 요청 - 기본, 헤더 조회

애노테이션 기반의 스프링 컨트롤러는 다양한 파라미터를 지원한다.
이번 시간에는 HTTP 헤더 정보를 조회하는 방법을 알아보자 🙂

✔️ RequestHeaderController 생성

package hello.springmvc.basic.request;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

// 로그를 찍을 수 있는 기능
@Slf4j

// 응답값을 뷰가 아닌 문자 그대로 반환
@RestController
public class RequestHeaderController {

    @RequestMapping("/headers")
    // 다양한 파라미터를 가질 수 있음
    public String headers(HttpServletRequest request,
                          HttpServletResponse response,
                          // HTTP 메서드를 조회한다.
                          HttpMethod httpMethod,
                          // Locale 정보를 조회한다.
                          Locale locale,
                          // 모든 HTTP 헤더를 MultiValueMap 형식으로 조회한다.
                          // MAP과 유사한데, 하나의 키에 여러 값을 받을 수 있다.
                          @RequestHeader MultiValueMap<String, String> headerMap,
                          // 특정 HTTP 헤더를 조회한다.
                          @RequestHeader("host") String host,
                          // 특정 쿠키를 조회한다.
                          @CookieValue(value = "myCookie", required = false) String cookie
                        ) {

        log.info("request={}", request);
        log.info("response={}", response);
        log.info("httpMethod={}", httpMethod);
        log.info("locale={}", locale);
        log.info("headerMap={}", headerMap);
        log.info("header host={}", host);
        log.info("myCookie={}", cookie);
        return "ok";

    }
}


🏷 HTTP 요청 파라미터 - 쿼리 파라미터, HTML Form

HTTP 요청 메시지를 통해 클라이언트에서 서버로 데이터를 전달하는 방법을 알아보자 🙂
클라이언트에서 서버로 요청 데이터를 전달할 때는 주로 다음 3가지 방법을 사용한다❗️

1️⃣ GET - 쿼리 파라미터

  • /url?username=hello&age=20
  • 메시지 바디 없이, URL의 쿼리 파라미터에 데이터를 포함해서 전달
    예) 검색, 필터, 페이징등에서 많이 사용하는 방식

2️⃣ POST - HTML Form

  • content-type : application/x-www-form-urlencoded
  • 메시지 바디에 쿼리 파리미터 형식으로 전달 username=hello&age=20
    예) 회원 가입, 상품 주문, HTML Form 사용

3️⃣ HTTP message body에 데이터를 직접 담아서 요청

  • HTTP API에서 주로 사용, JSON, XML, TEXT
  • 데이터 형식은 주로 JSON 사용
  • POST, PUT, PATCH

하나씩 알아보자❗️❗️

1️⃣ GET, 쿼리 파라미터 전송

➡️ http://localhost:8080/request-param?username=hello&age=20


2️⃣ POST - HTML Form

⬆️ GET 쿼리 파리미터 전송 방식이든, POST HTML Form 전송 방식이든 둘다 형식이 같으므로 구분없이 조회할 수 있다!
이것을 간단히 요청 파라미터(request parameter) 조회라 한다.

우리는 이 요청 파라미터 조회 방식에 대해 지금부터 자세히 알아볼 예정이다^^


✔️ RequestParmaController 생성

package hello.springmvc.basic.request;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;

@Slf4j
@Controller
public class RequestParamController {
    /**
     * 반환 타입이 없으면서 이렇게 응답에 값을 직접 집어넣으면, view 조회X
     */
    @RequestMapping("/request-param-v1")
    public void requestParamV1(HttpServletRequest request, HttpServletResponse response) throws IOException {

        String username = request.getParameter("username");
        // 타입변환
        int age = Integer.parseInt(request.getParameter("age"));
        log.info("username={}, age={}", username, age);

        response.getWriter().write("ok");
    }
}

⬆️ http://localhost:8080/request-param-v1?username=hello&age=20 실행 결과 👍🏻


POST 전송도 해보자!!

(포스트맨으로 해도 되지만 우리는 /resources/static 아래에 새로운 테스트 폼 파일을 만들어 실습함)

⬆️ 실행 결과 👍🏻



🏷 HTTP 요청 파라미터 - @RequestParam

스프링이 제공하는 @RequestParam 을 사용하면 요청 파라미터를 매우 편리하게 사용할 수 있다👍🏻

✔️ requestParamV2

/**
     * @RequestParam 사용
     * - 파라미터 이름으로 바인딩
     * @ResponseBody 추가
     * - View 조회를 무시하고, HTTP message body에 직접 해당 내용 입력
     */
    @ResponseBody
    @RequestMapping("/request-param-v2")
    public String requestParamV2(
            //  파라미터 이름으로 바인딩
            @RequestParam("username") String memberName,
            @RequestParam("age") int memberAge) {
        log.info("username={}, age={}", memberName, memberAge);
        return "ok";
    }

✔️ requestParamV3

/**
     * @RequestParam 사용
     * HTTP 파라미터 이름이 변수 이름과 같으면 @RequestParam(name="xx") 생략 가능
     */
    @ResponseBody
    @RequestMapping("/request-param-v3")
    public String requestParamV3(
            @RequestParam String username,
            @RequestParam int age) {
        log.info("username={}, age={}", username, age);
        return "ok";
    }

✔️ requestParamV4

/**
     * @RequestParam 사용
     * String, int 등의 단순 타입이면 @RequestParam 도 생략 가능
     */
    @ResponseBody
    @RequestMapping("/request-param-v4")
    public String requestParamV4(String username, int age) {
        log.info("username={}, age={}", username, age);
        return "ok";
    }

✔️ 파라미터 필수 여부 - requestParamRequired

/**
     * @RequestParam.required
     * /request-param -> username이 없으므로 예외 *
     * 주의!
     * /request-param?username= -> 빈문자로 통과 *
     * 주의!
     * /request-param
     * int age -> null을 int에 입력하는 것은 불가능,
     * 따라서 Integer 변경해야 함(또는 다음에 나오는defaultValue 사용)
     */
    @ResponseBody
    @RequestMapping("/request-param-required")
    public String requestParamRequired(
            @RequestParam(required = true) String username,
            @RequestParam(required = false) Integer age) {
        log.info("username={}, age={}", username, age);
        return "ok";
    }

✔️ 기본 값 적용 - requestParamDefault

/**
     * @RequestParam
     * - defaultValue 사용 *
     * 참고: defaultValue는 빈 문자의 경우에도 적용 * /request-param?username=
     */
    @ResponseBody
    @RequestMapping("/request-param-default")
    public String requestParamDefault(
            @RequestParam(required = true, defaultValue = "guest") String username,
            @RequestParam(required = false, defaultValue = "-1") int age) {
        log.info("username={}, age={}", username, age);
        return "ok";
    }

⬆️ 파라미터에 값이 없는 경우 defaultValue 를 사용하면 기본 값을 적용할 수 있다.
이미 기본 값이 있기 때문에 required 는 의미가 없다.


✔️ 파라미터를 Map으로 조회하기 - requestParamMap

/**
     * @RequestParam Map, MultiValueMap
     * Map(key=value)
     * MultiValueMap(key=[value1, value2, ...] ex) (key=userIds, value=[id1, id2])
     */
    @ResponseBody
    @RequestMapping("/request-param-map")
    public String requestParamMap(@RequestParam Map<String, Object> paramMap) {
        log.info("username={}, age={}", paramMap.get("username"),
                paramMap.get("age"));
        return "ok";
    }

⬆️ 파라미터의 값이 1개가 확실하다면 Map 을 사용해도 되지만, 그렇지 않다면 MultiValueMap 을 사용하자!



넘 길어!!!!!!!!!!!!!!!!
양 jonna 방대해요 ㅋ

profile
Backend Developer

0개의 댓글