스프링 부트 스타터 사이트에서 프로젝트를 생성하는 부분은 이전에 여러번 다뤘기 때문에 이번에도!!!!! 과감히 넘어가겠다😅
✔️ 프로젝트 선택
✔️ 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-urlencoded
username=hello&age=20
3️⃣ 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 방대해요 ㅋ