개발일기
Spring Boot MVC 기본기능 모음집 회고- 1편 본문
스프링 부트 로깅 라이브러리
스프링 부트 로깅 라이브러리를 사용하기 위해서build.gradle 에다가 dependencies에 spring-boot-starter-logging을 포함해야 사용할 수 있다.
새롭게 알게 된 지식은 Logback, Log4J, Log4J2 등등 수 많은 라이브러리들이 있는데, 그것을 통합해준 것이 sl4j라이브러리이다.
인터페이스는 sl4j이고, 구현체는 Logback과 같은 로그 라이브러리를 선택하면 된다.
private final Logger log = Loggerfactory.getLogger(getClass());
@Slf4j : 롬복 사용 가능
추가적으로 좀 더 알게 된 사실은 log.info()로 호출하는 것과 System.out.println()사용하면 차이점이 있다는 것을 알게되었다.
System.out.println()을 사용하면 표준출력으로 입력이 남아 파일로 남지 않고 log는 로깅 설정을 통해서 파일로 남길 수 있다.
(로깅공부도 좀 더 해야될 것 같다.)
String name = "Spring";
log.trace("trace log = {}", name);
log.debufg("debug log = {}", name);
log.info("info log = {}", name);
log.warn("warn log= {}", name);
log.error("error log= {}", name);
// 로그를 사용하지 않아도 a + b로직 계산이 가장 먼저, 이루어지기 떄문에
log.info("이런식으로 x = " + name);
@RestContoller
restContoller는 @Controller 와 @ResponseBody 애노테이션이 합쳐져 구현되어있는 것을 ResController에 들어가서 확인할 수 있다.
- Contoller는 반환 값이 String이면 뷰이름으로 인식된다.
- RestController는 View가 반환 되는게 아니라 HTTP 메시지 바디에 데이터를 넣어준다.
요청 매핑
package hello.springmvc.basic.requestmapping;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
@RestController
public class MappingController {
private Logger log = LoggerFactory.getLogger(getClass());
@RequestMapping(value = {"/hello-basic", "/hello-go"}, method = RequestMethod.GET)
public String helloBasic(){
log.info("helloBasic");
return "ok";
}
@RequestMapping(value= "/mapping-get-v1", method=RequestMethod.GET)
public String mappingGetV1() {
log.info("mapping-get-v1");
return "ok";
}
/**
* 편리한 축약 애노테이션 (코드보기)
* @GetMapping
* @PostMapping
* @PutMapping
* @DeleteMapping
* @PatchMapping
* @return
*/
@GetMapping(value= "/mapping-get-v2")
public String mappingGetV2() {
log.info("mapping-get-v2");
return "ok";
}
/**
* PathVariable 사용
* 변수명이 같으면 생략 가능
* @PathVariable("userId") String userId -> @PathVariable userId
*/
@GetMapping("/mapping/users/{userId}/orders/{orderId}")
public String mappingPath(@PathVariable("userId") String userId,@PathVariable("orderId") Long orderId){
log.info("mappingPath userId= {}, orderId = {}", userId, orderId);
return "ok";
}
/**
* 파라미터로 추가 매핑
* params="mode"
* params="!mode"
* params="mode=debug"
* params="mode!=debug" ( != )
* params= {"mode=debug", "data=good"}
*/
@GetMapping(value= "/mapping-param", params= "mode=good")
public String mappingParam() {
log.info("mappingParam");
return "ok";
}
/**
* 특정 헤더로 추가 매핑
* 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";
}
/**
* Content-Type 헤더 기반 추가 매핑 MediaType
* consumes="application/json"
* consumes="!application/json"
* consumes="application/*"
* consumes="*\/*"
* MediaType.APPLICATION_JSON_VALUE
*/
@PostMapping(value="/mapping-consume", consumes = MediaType.APPLICATION_JSON_VALUE)
public String mappingConsumes(){
log.info("mappingConsumes");
return "ok";
}
/**
* Accept 헤더 기반 Media Type
* produces = "text/html"
* produces = "!text/html"
* produces = "text/*"
* produces = "*\/*"
*/
@PostMapping(value="/mapping-produce", produces = MediaType.TEXT_HTML_VALUE)
public String mappingProduces() {
log.info("mappingProduces");
return "ok";
}
}
{"/hello-basic", "/hello-go"} 처럼 다중 URL 설정이 가능하다.
Http 메서드
@RequestMapping에 method속성으로 HTTP 메서드를 지정하지 않으면 HTTP 메서드와 무관하게 호출됩니다.
이때, @RequestMapping은 url뒤에 콤마 찍고 method = RquestMethod.GET 또는 method = RequestMethod.POST이런식으로 구성 되어 있다.
이걸 어노테이션을 통해서 메서드 매핑을 엄청 축약할 수 있는데 , @GetMapping, @PostMapping이런식으로 사용할 수 있음.
현업에서는 대부분 이것만 씀.. 물론 @RequestMapping으로 되어있는 경우도 종종 보았음.
@PathVariable(경로변수) 사용
URL 경로에 /mapping/hanseu9839 이런식으로 되어있으면 @PathVariable("userId") String data라고 매개변수를 받으면 String data = hanseu9839라는 값을 매핑하여 받을 수 있다.
최근 방식을 보면 많은 사람들이 PathVariable을 사용하여 리소스 데이터를 찾는 방식을 선호하는 것 같다. 근데 만약에 회원정보나 이런걸 PathVariable로 한다면 /mapping/eunwha이런식으로 할 때, 다른 사용자들의 민감한 정보들이 다 보여진다면 이건 머리가 지끈거려 버리는 상황이 온다.
@GetMapping("/mapping/users/{userId}/orders/{orderId}")
{userId} , {orderId} 를 통해서 위에처럼 두개 또는 여러개의 PathVariable을 받아 사용 할 수 있다.
특정 파라미터 조건 매핑
위에서 보다시피 특정 파라미터가 들어와야 조건적으로 매핑되도록 설정 할 수 있다.
@GetMapping(value= "/mapping-param" , params = "mode=debug")이다.
해당 부분은 parameter로 mode=debug가 있어야 Mapping이된다.
URL 예시는 http://localhost:8080/mapping-param?mode=debug
이런식으로 특정 파라미터가 있거나 없거나의 조건을 넣어줄 수 있는데 사실 나는 실무에서 사용되는건 한번도 본적없는거 같다. 필자의 개발 경력이 짧은것도 한 몫 할 것이다.
특정 헤더 조건 매핑
@GetMapping(value = "/mapping-header" , headers = "mode=debug")
public String mappingHeader() {
}
해당 코드에 자세한 부분은 위에 있다. 파라미터 매핑가 비슷한 형태이며 HTTP 헤더를 사용하여 진행한다.
미디어 타입 조건 매핑 - HTTP 요청 Content-Type, consume
@PostMapping(value= "/mapping-consume" , consumes = "application/json")
public String mappingConsumes() {
}
HTTP 요청의 Content-Type 헤더를 기반으로 미디어 타입으로 매핑한다.
만약 맞지 않으면 HTTP 415 상태코드(Unsupported Media Type)을 반환한다.
예시 )
consumes = "text/plain"
comsumes = {"text/plain", "application/json"}
미디어 타입 조건 매핑 - HTTP 요청 Accept, produce
@PostMapping(value = "/mapping-produce" , produces = "text/html")
public String mappingProduces() {
}
HTTP 요청의 Accept 헤더를 기반으로 미디어 타입으로 매핑한다.
만약 맞지 않으면 HTTP 406 상태 코드(Not Acceptable)을 반환한다.
Http 요청 - 기본, 헤더 조회
애노테이션 기반의 스프링 컨트롤러는 다양한 파라미터를 지원한다.
이번 시간에는 HTTP 헤더 정보를 조회하는 방법을 알아보자.
RequestHeaderController
package hello.springmvc.basic.request;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Locale;
@Slf4j
@RestController
public class RequestHeaderController {
@RequestMapping("/headers")
public String headers(HttpServletRequest request,
HttpServletResponse response,
HttpMethod httpMethod,
Locale locale,
@RequestHeader MultiValueMap<String, String> headerMap,
@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";
}
}
- HttpMethod: HTTP 메서드를 조회한다. org.springframework.http.HttpMethod
- Locale: Locale 정보를 조회한다.
- @RequestHeader MultiValueMap<String, String> headerMap
- 모든 HTTP 헤더를 MultiValueMap형식으로 조회한다.
MultiValueMap은 중복이 허용된다. "A"라는 키가 있으면 150, 200, 340 등 여러개의 중복된 키값을 가지고 있다.
해당 부분은 스프링을 정리하는 부분이기 때문에 여기까지만 정리하겠다..!
@RequestHeader("host") => 특정 헤더를 조회한다.
속성에는 필수 값 여부 required , 기본 값 속성 : defaultValue 가 있다.
@CookieValue(value = "myCookie", required = false) String cookie
- 특정 쿠키를 조회한다.
value에 해당하는 부분의 myCookie에 해당 하는 부분
HTTP 요청 파라미터 - 쿼리 파라미터, HTML Form
클라이언트에서 서버로 요청 데이터 전달 시 3가지 방법
1. GET 쿼리 파라미터
특징)
- /url?username=hello&age=20
- 메시지 바디 없이, URL의 쿼리 파라미터에 데이터 포함해 전달
2. POST - HTML Form
특징)
- content-type : application/x-www-form-urlencoded
- 메시지 바디에 쿼리 파라미터 형식으로 전달
3. HTTP message body에 데이터 담아서 요청
JSON , XML, TEXT 주로 JSON데이터 사용
Form은 request.parameter를 post, get이든 상관없이 조회할 수 있음 이게 중요함..!
자세한 코드는 생략하겠다..!
페이지 생성
/resources/static 아래에 두면 스프링이 prefix, sufix를 입력하지 않아도 자동으로 인식한다.
추가적으로 jar를 이용하면 webapp경로를 사용할 수 없다.
HTTP 요청 파라미터 - @RequestParam
package hello.springmvc.basic.request;
import hello.springmvc.basic.HelloData;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.io.IOException;
import java.util.Map;
@Slf4j
@Controller
public class RequestParamController {
@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");
}
@ResponseBody
@RequestMapping("/request-param-v2")
public String requestParamv2(
@RequestParam("username") String memberName,
@RequestParam("age") int memberAge
){
log.info("username = {}, age = {} ", memberName, memberAge);
return "ok";
}
@ResponseBody
@RequestMapping("/request-param-v3")
public String requestParam3(
@RequestParam String username,
@RequestParam int age) {
log.info("username = {} , age = {}", username, age);
return "ok";
}
@ResponseBody
@RequestMapping("/request-param-v4")
public String requestParam4(
String username,
int age) {
log.info("username = {} , age = {}", username, age);
return "ok";
}
@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";
}
@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";
}
@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";
}
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData){
log.info("username = {} , age = {} ", helloData.getUsername(), helloData.getAge());
log.info("helloData = {} ", helloData);
return "ok";
}
@ResponseBody
@RequestMapping("/model-attribute-v2")
public String modelAttributeV2(HelloData helloData) {
log.info("username = {}, age= {}", helloData.getUsername(), helloData.getAge());
return "ok";
}
}
Servlet을 이용하는 방식은 위에 보이듯이 requestParamV1 버전이다.
request.getParameter를 통해서 각각의 parameter값을 가져와 준 모습이다.
requestParamV2버전은 @RequestParam을 사용해서 서블릿처럼 파라미터 이름으로 뽑아오는 코드부분이 굉장히 깔끔하게 사라져 극락인 것을 볼 수 있다.
requestParamV3는 @RequestParam의 이름이 매개변수로 받은 이름과 비슷하게 되어 name마저도 안 적어주는 대박박 현상이 보인다.
requestParam은 required를 통해서 필수 parameter 입력 , 필수가 아닌 파라미터 입력 그리고 아무런 값도 Mapping이 되지 않았을 경우 defaultValue를 설정하여 사용 할 수 있다.
@ResponseBody를 통해서 뷰가 아닌 데이터를 내보내는 모습도 추가적으로 참고하면 좋다.
@RequestParam Map<String, Obejct> paramMap 이런식으로 Map 형식에 parameter받아서 사용할 수 있다. 해당 방식은 실무에서 사용하는 것을 한번 본적이 있던거 같다. MultiValueMap도 가능함.
@ModelAttribute를 사용하여 요청 파라미터를 받아서 필요한 객체를 만들고 객체에 값을 넣어주는 Annotation이다.
이 어노테이션을 사용하지 않는다면
@RequestParam String username;
@RequestParam int age;
HelloData data = new HelloData();
data.setUsername(username);
data.setAge(age);
이런식으로 작성해야 할 것이다.
하지만 ModelAttribute 어노테이션이 HelloData 객체를 생성한 후, 요청 파라미터의 이름으로 HelloData 객체의 프로퍼티를 찾고 해당 프로퍼티의 Setter를 찾아서 입력을 바인딩해준다.
@ModelAttribute도 생략할 수 있다.
스프링은 생략 시 다음과 같은 규칙을 적용한다.
String, int, Integer => @RequestParam
@ModelAttribute (argument resolver에서 지정해둔 타입 외)
'Spring > (김영한님)스프링 MVC 1편 - 백엔드 웹 개발 핵심기술' 카테고리의 다른 글
Spring Boot MVC 기본기능 모음집 회고- 2편 (1) | 2024.03.27 |
---|---|
MVC 프레임워크 만들기 (V3, V4, V5) (1) | 2024.03.06 |
MVC 프레임워크 만들기 정리 및 회고 (V1, V2) (0) | 2024.03.06 |
서블릿, JSP , MVC 패턴 회고 및 정리 (0) | 2024.03.02 |
Session2. 서블릿 정리및 회고 ( request, response) (2) | 2024.02.27 |