
스프링 부트 스타터 사이트에서 프로젝트를 생성하는 부분은 이전에 여러번 다뤘기 때문에 이번에도!!!!! 과감히 넘어가겠다😅
✔️ 프로젝트 선택
✔️ Project Metadata
✔️ Dependencies
주의 🤚🏻
- 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
✔️ 로그 사용시 장점
요청이 왔을 때 어떤 컨트롤러가 매핑이 되어야 할까❓
✔️ 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 메서드 매핑
@RequestMapping 에 method 속성으로 HTTP 지정이 가능하다. @RequestMapping("/hello-basic", method = RequestMethod.GET)3️⃣ HTTP 메서드 매핑 축약
@RequestMapping 과 method 를 지정해서 사용하는 것을 확인할 수 있다.4️⃣ PathVariable(경로 변수) 사용
/mapping/userA/users/1@RequestMapping 은 URL 경로를 템플릿화 할 수 있는데, @PathVariable을 사용하면 매칭 되는 부분을 편리하게 조회할 수 있다.@PathVariable 의 이름과 파라미터 이름이 같으면 생략할 수 있다.5️⃣ PathVariable 사용 - 다중
6️⃣ 특정 파라미터 조건 매핑
7️⃣ 특정 헤더 조건 매핑
8️⃣ 미디어 타입 조건 매핑 - HTTP 요청 Content-Type, consume
9️⃣ 미디어 타입 조건 매핑 - HTTP 요청 Accept, produce
회원 관리를 HTTP API로 만든다 생각하고 매핑을 어떻게 하는지 알아보자❗️
(실제 데이터가 넘어가는 부분은 생략하고 URL 매핑만!!)
✔️ 회원 관리 API
/users/users/users/{userId}/users/{userId}/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 헤더 정보를 조회하는 방법을 알아보자 🙂
✔️ 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 요청 메시지를 통해 클라이언트에서 서버로 데이터를 전달하는 방법을 알아보자 🙂
클라이언트에서 서버로 요청 데이터를 전달할 때는 주로 다음 3가지 방법을 사용한다❗️
1️⃣ GET - 쿼리 파라미터
2️⃣ POST - HTML Form
application/x-www-form-urlencodedusername=hello&age=203️⃣ HTTP message body에 데이터를 직접 담아서 요청
하나씩 알아보자❗️❗️
➡️ http://localhost:8080/request-param?username=hello&age=20
⬆️ 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 아래에 새로운 테스트 폼 파일을 만들어 실습함)

⬆️ 실행 결과 👍🏻
스프링이 제공하는 @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 방대해요 ㅋ