로그 간단히 알아보기
SL4J는 인터페이스이고 구현체로 Logback을 사용한다. Logback말고도 여러 다른 구현체가 존재한다.
2021-03-29 14:18:13.367 INFO 9304 --- [nio-8080-exec-1] hello_mvc1.demo.basic.LogTestController : info log = spring
이런식으로 여러 정보를 한꺼번에 보여준다.
또한 log.info 말고도 trace, debug ,info, warn, error 등등 을 쓸 수 있다. 뒤로 갈수록 심각도 가 높아지게되고 application.properties를 통해 설정을 할 수가 있다.
log.trace("trace mylog{}, name) 이런 식으로 써야지 log.trace("trace mylog" + name) 이런 식으로 쓰면 출력도 하지 않는데 리소소를 잡아먹을 수 있다.
@RestController의 경우 String을 return 해줄때 view로인식하지않고 그냥 pure String 으로 인식해서 HTTP message body에 넣어서 return 해준다.
요청매핑
@RequestMapping( 배열이 들어갈 수 있음) , /basic 이나 /basic/ 을 같은 매핑이라고 인식한다. 하지만 실제로는 엄연히 다른 URL이다.
@GetMapping("/mapping/users/{userId}/orders/{orderId}")
public String mappingPath(@PathVariable String userId, @PathVariable Long orderId) {
log.info("mappingPath userId={}, orderId={}", userId, orderId);
return "ok";
}
pathvariable로 url의 특정부분을 parameter로 가져올 수 있다.
@PathVariable("userId") 로 변수명을 지정해야할 때도 있지만 String userId처럼 이미 변수명을 동일하게 해주면 따로 지정할 필요가 없다.
@GetMapping(value = "/mapping-param", params = "mode=debug")
public String mappingParam() {
log.info("mappingParam");
return "ok";
}
@GetMapping(value = "/mapping-header", headers = "mode=debug")
public String mappingHeader() {
log.info("mappingHeader");
return "ok";
}
RequestMapping URL옆에 옵션으로 제약을 걸 수 있다.
localhost:8080/mapping-param?mode=debug 처럼 parameter로 mode=debug를 써주지 않으면 해당 Controller호출을 하지 않는다. header도 마찬가지
하지만 거의 사용되지 않는다.
@PostMapping(value = "/mapping-consume", consumes = MediaType.APPLICATION_JSON_VALUE)
public String mappingConsumes() {
log.info("mappingConsumes");
return "ok";
}
Application 의 호출 request의 header에 content-type=application/json 이 존재하면 호출이 된다.
HTTP 요청 -기본 , 헤더 조회
@Slf4j
@RestController
public class RequestHeaderController {
@RequestMapping("/headers")
public String headers(HttpServletRequest request,
HttpServletRequest response,
HttpMethod httpMethod,
Locale locale,
@RequestHeader MultiValueMap<String, String> headerMap,
@RequestHeader("host") String host,
@CookieValue(value = "myCookie", required = false) String cookie
) {
return "ok";
}
}
Reqest header 에 들어있는 값과 쿠기 값을 손쉽게 조회가 가능하다.
MultiValueMap = 같은 key에 여러 value을 추가할떄 쓴다. key를 조회하면 List를 return해준다.
@RequestParam
@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";
}
@RequestParam 을 생략해도 단순 타입(int, String ,Integer등등) 이면 알아서 매핑을 해준다. 써주는것이 좋다한다.
여러개가 오면 Map , MutiValueMap으로 받아서 꺼내 쓸 수 있다. defaultValue또한 기본으로 줄 수 있다.
required=false 인경우에 int 로 할시 IllegalException이 뜬다. int에는 Null 이 들어갈수 없기 때문이다. Integer로 하거나 defaultvalue를 줘야한다.
@ModelAttribute
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData) {
log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
return "ok";
}
@ResponseBody
@RequestMapping("/model-attribute-v2")
public String modelAttributeV2(HelloData helloData) {
log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
return "ok";
}
Spring MVC는 @ModelAttribute 가 있으면 다음과 같이 실행한다.
주어진 클래스의 객체를 생성하고 요청된 Parameter와 매칭되는 getter setter(프로퍼티) 들을 찾아서 값들을 만들어진 객체에 바인딩 해준다.
@ModelAttribute는 생략이 가능하다
단순타입이면 @RequestParam , 그외 나머지는 ModelAttribute를 사용한다.
BUT argument resolver로 지정된(HttpServletResponse등등) 애들은 예외다 자동으로 안된다는것임.
HTTP 요청 메시지 - 단순 텍스트
@PostMapping("/request-body-string-v1")
public void requestBodyString(HttpServletRequest request, HttpServletResponse response) throws IOException {
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("messageBody={}", messageBody);
response.getWriter().write("ok");
}
@PostMapping("/request-body-string-v2")
public void requestBodyStringV2(InputStream inputStream, Writer responseWriter) throws IOException {
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("messageBody={}", messageBody);
responseWriter.write("ok");
}
@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) throws IOException {
String messageBody = httpEntity.getBody();
log.info("messageBody={}", messageBody);
return new HttpEntity<>("ok");
}
@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) {
log.info("messageBody={}", messageBody);
return "ok";
}
SpringMVC는 InputStream , OutputStream 파라미터를 지원한다
HttpServeletRequest에서 stream을 꺼낸것처럼 동작해준다.
HttpEntity = httpMessageConvert가 동작을해서 body를 손쉽게 꺼낼 수 있다. header 값도 가져올 수 있다.
@RequestBody String messagebody; 가 파라미터에 써있으면 body를 messagebody에 그냥 넣어준다.
메세지 바디를 조회하는것들은 @RequestParam , @ModelAttribute랑 전혀 관련이 없다. 애네둘은 query parameter 랑 연관이 있는 것이다.
@RequestBody HelloData 처럼 파라미터로 주어지면 알아서 HTTP메시지 컨버터가 HTTP 메시지 바디의 내용을 원하는 객체의 내용으로 바인딩 해준다. 생략이 불가능하다.
생략하면 @ModelAttriubte로 적용이 될텐데 body에 만 적었지 url에 parameter를 전달해주지 않았기때문에 기본값들이 나올 것이다.
@ResponseBody 를 통해 return 된 string이 Http Response body에 들어가게 된다.
HTTP 요청 메시지 - JSON
@PostMapping("/request-body-json-v1")
public void requestBodyJsonV1(HttpServletRequest request, HttpServletResponse response) throws IOException {
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("messageBody={}", messageBody);
HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
response.getWriter().write("ok");
}
@ResponseBody
@PostMapping("/request-body-json-v2")
public String requestBodyJsonV2(@RequestBody String messageBody) throws IOException {
log.info("messageBody={}", messageBody);
HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
return "ok";
}
@ResponseBody
@PostMapping("/request-body-json-v3")
public String requestBodyJsonV3(@RequestBody HelloData data) {
log.info("username={}, age={}", data.getUsername(), data.getAge());
return "ok";
}
@ResponseBody
@PostMapping("/request-body-json-v4")
public String requestBodyJsonV4(HttpEntity<HelloData> httpEntity) {
HelloData data = httpEntity.getBody();
log.info("username={}, age={}", data.getUsername(), data.getAge());
return "ok";
}
@ResponseBody
@PostMapping("/request-body-json-v5")
public HelloData requestBodyJsonV5(@RequestBody HelloData data) {
log.info("username={}, age={}", data.getUsername(), data.getAge());
return data;
}
ObjectMapper로 받아온 http body의 JSON 데이터를 클래스 데이터로 만들 수 있지만 스프링이 @RequestBody 하나로 알아서 알맞을 클래스로 매핑을 해준다.
@RequestBody를 생략하면 @ModelAttribute 라고 착각해서 동작을 하겠지만 Query param으로 아무것도 오지 않았기에 default값이 들어가게 된다.
@RequestBody를 쓰면 HttpMessageConverter를 사용하여 JSON응답을 생성한다.
응답 - 정적리소스 , 뷰 템플릿
정적 리소스 경로 = /static/ , /public/ , /resources , /META-INF/resources
src/main/resources 는 리소스를 보관하는 곳이고 , 클래스패스의 시작 경로이다.
해당 디렉토리에 넣어두면 스프링이 내장 톰캣을 이용하여 다 서비스해준다.
src/main/reosurces/static/basic/hello.html -> http://localhost:8080/basic/hello.html
@RequestMapping("/response-view-v1")
public ModelAndView responseViewV1() {
ModelAndView mav = new ModelAndView("response/hello")
.addObject("data", "hello!");
return mav;
}
@RequestMapping("/response-view-v2")
public String responseViewV2(Model model) {
model.addAttribute("data", "hello!");
return "response/hello";
}
@RequestMapping("/response/hello")
public void responseViewV3(Model model) {
model.addAttribute("data", "hello!");
}
뷰템플릿 = 뷰 템플릿을 거쳐서 html 을 생성한다.
뷰 템플릿 경로 = src/main/resources/templates
RequestMapping 인자와 뷰 경로가 같으면 따로 return 할 필요없이 void로 할 수 있다.
타임리프의 경우 depdency를 가져오면 ThyelafViewResolver를 빈으로 등록하고 application.properties에 suffix, prefix 를 알아서 등록해준다.
HTTP 응답 - HTTP API , Rest API , 메시지 바디에 직접 입력
@RestController = @Controller + @ResponseBody(모든 메소드에게)
HTML 이나 뷰 템플릿을 이용하는게 아니라, HTTP 메시지 바디에 직접 데이터를 입력한다.
@GetMapping("/response-body-string-v1")
public void responseBodyV1(HttpServletResponse response) throws IOException {
response.getWriter().write("ok");
}
@GetMapping("/response-body-string-v2")
public ResponseEntity<String> responseBodyV2() {
return new ResponseEntity<>("ok", HttpStatus.OK);
}
// @ResponseBody
@GetMapping("/response-body-string-v3")
public String responseBodyV3() {
return "ok";
}
@GetMapping("/response-body-json-v1")
public ResponseEntity<HelloData> responseBodyJsonV1() {
HelloData helloData = new HelloData();
helloData.setUsername("userA");
helloData.setAge(20);
return new ResponseEntity<>(helloData, HttpStatus.OK);
}
@ResponseStatus(HttpStatus.OK)
// @ResponseBody
@GetMapping("/response-body-json-v2")
public HelloData responseBodyJsonV2() {
HelloData helloData = new HelloData();
helloData.setUsername("userA");
helloData.setAge(20);
return helloData;
}
마지막의 경우는 상태코드를 지정하기 위해 @ResponseStatus 를 지원해준다.
HTTP 메시지 컨버터
스프링 MVC는 다음의 경우에 HTTP 메세지 컨버터를 적용한다.
@RequestBody, @ResponseBody , HttpEntity(RequestEntity) , HttpEntity(ResponseEntity) 가 붙어있는 경우
@Controller가 호출되기전에 컨버터가 처리를 해준다.
응답의 경우 클라이언트의 HTTP Accept header(e.g client는 json만 해석가능) 와 서버의 컨트롤러 반환타입 정보 등을 조합해서 HttpMessageConverter 가 선택이 된다.
ByteArrayHttpMessageConvert = byte[] 데이터를 처리한다.
- @RequestBody byte[] data; 요청예시
- @ResponseBody return byte[] 응답 예시 , 쓰기 미디어타입 application/octet-stream
StringHttpMessageConverter = String문자로 데이터를 처리한다.
- 요청 에) String data
- 응답 예) 쓰기 미디어 타입 text/plain
MappingJackson2HttpMessageConverter = application/json관련
- @RequestBody HelloData data
- @ResponseBody
return HelloData
요청 매핑 헨들러 어뎁터 구조
MVC 패턴전체에서 DispatcherServlet이 알맞은 핸들러 어댑터를 찾고 어댑터를 통해 Controller를 호출하기 전 모든일이 일어난다.
RequestMappingHandlerAdaptor 는 Argument Resolver를 호출함으로서 핸들러가 필요로 하는 (@Requestbody String data , @CookieValue, @RequestParam 등등) 을 만들어준다. 이후에 핸들러를 호출하면서 만들어진 것들을 전달해준다.
상당히 많은 ArugementReosolver가 존재한다.
ReturnValueHander가 반환값의 converting 작업을 다해준다. 컨트롤러에서 String을 반환해도 VIew로 처리되는 것이 이녀석 덕분이다.
ArugmentResolver 가 직접하는 것이 아니고 HTTP메시지 컨버터를 통해서 처리해준다. 여러개의 HTTP Message Converter가 존재한다.
요청의 경우 @RequestBody를 처리하는 ArugemntResolver가 있다. -> body 의 데이터를 처리해야하니 메시지 컨버터 사용
응답의 경우 @ResponseBody를 처리하는 ReturnValueHandler가 있다. -> body 의 데이터를 처리해야하니 메시지 컨버터 사용
WebMvcConfigurer 에서 여러 메소드를 통해 custom resolver를 추가할 수 있다.
'WEB > Spring MVC 1' 카테고리의 다른 글
스프링 MVC - 웹 페이지 만들기 (0) | 2022.07.08 |
---|---|
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술(스프링 MVC - 구조이해) (0) | 2021.03.29 |
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술(MVC 프레임워크 만들기) (0) | 2021.03.29 |
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술(서블릿,JSP,MVC패턴) (0) | 2021.03.28 |
스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술(서블릿) (0) | 2021.03.27 |