스프링 mvc1 - 기본 기능

2023. 1. 23. 23:42카테고리 없음

1/23

 

* 로깅 (system.pirnt.out X)

 

- 로그 선언

@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);

        return "ok";
    }
}

 

* private final Logger log = LoggerFactory.getLogger(getClass()) -> @Slf4j 로 변경 가능

 

* @RestController

 -  @Controller 반환 값이 String 이면 뷰 이름으로 인식된다. 그래서 뷰를 찾고 뷰가 랜더링 된다.

 - 하지만 @RestController은 HTTP 메시지 바디에 바로 입력된다.

 

* 로그 출력 포멧

2023-01-23 16:03:20.031  INFO 15124 --- [nio-8080-exec-1] hello.springmvc.basic.LogTestController  :  info log=Spring
2023-01-23 16:03:20.032  WARN 15124 --- [nio-8080-exec-1] hello.springmvc.basic.LogTestController  :  warn log=Spring
2023-01-23 16:03:20.032 ERROR 15124 --- [nio-8080-exec-1] hello.springmvc.basic.LogTestController  :  error log=Spring

 

* 로그 레벨

- TRACE > DEBUG > INFO > WARN > ERROR

- 개발 서버는 debug

- 운영 서버는 info

- 로그 설정 -> logging.level.hello.springmvc=debug

 

=========================================================================

 

* 경로 변수

@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable("userId") String data) {
    log.info("mappingPath userId={}", data);

    return "ok";
}

@PathVariable -> 매칭 부분 편리하게 사용

ex) mapping/userA

 -------> mappingPath userId=userA

 

* 경로변수 다중 사용

@GetMapping("/mapping/users/{userId}/orders/{orderId}")
public String mappingPath(@PathVariable String userId, @PathVariable Long
        orderId) {
    log.info("mappingPath userId={}, orderId={}", userId, orderId);
    return "ok";
}

--> users의 userId 의 주문중 주문번호 조회

=========================================================================

 

* consumes, produces

@PostMapping(value="/mapping-consume", consumes = "application/json")
public String mappingConsumes(){
    log.info("mappingConsumes");
    return "ok";
}

@PostMapping(value="/mapping-produce", produces = "text/html")
public String mappingProduces(){
    log.info("mappingProduces");
    return "ok";
}

consumes -> HTTP 요청의 Content-Type 헤더를 기반으로 미디어 타입으로 매핑

produces -> HTTP 요청의 Accept 헤더를 기반으로 미디어 타입으로 매핑

 

=========================================================================

* request 파라미터 받기 (단계별)

@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 requestParamV3(@RequestParam String username,
                                 @RequestParam int age){

        log.info("username={}, age={}", username, age);
        return "ok";
    }

    @ResponseBody
    @RequestMapping("/request-param-v4")
    public String requestParamV4(String userName, int age){

        log.info("username={}, age={}", userName, age);
        return "ok";
    }
}

* v1 -> request.getParameter(파라미터)

* v2 -> @RequestParam(파라미터) 타입 변수명

* v3 -> @RequestParam 타입 변수명  (변수명과 파라미터명이 같아야함)

* v4 -> 타입 변수명 (변수명과 파라미터명이 같아야함)

 

* @RequestParam(required = true) --> 기본값, 파라미터가 필수적으로 있어야 함 

 

@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData) {

    log.info("username={}, age={]", helloData.getUsername(), helloData.getAge());

    return "ok";
}

* @ModelAttribute -> 해당 객체의 getter, setter 조회해서 자동으로 모두 실행됨.

* @ModelAttribute 생략 가능...

 

=========================================================================

 

* 위의 경우에는 GET 쿼리 파라미터 , POST form 전송 방식의 경우 (RequestParam, ModelAttribute)

 

* 위를 제외한 나머지는 HttpEntity 사용

 

* 단계적으로

@Slf4j
@Controller
public class RequestBodyStringController {

    @PostMapping("/request-body-string-v1")
    public void requestBodyStringV1(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");
    }
}

 

- HTTP header, body 정보를 편리하게 조회 가능

- 응답에도 사용 가능 (view 조회 X)

 

* 최종

@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) throws IOException {
    log.info("messageBody={}", messageBody);

    return "ok";
}

=========================================================================

 

위와 같은 내용

String -> json

* @RequestBody 요청

-> JSON 요청 -> HTTP 메시지 컨버터 -> 객체

* @ResponseBody 응답

-> 객체 -> HTTP 메시지 컨버터 -> JSON 응답

 

@Slf4j
@Controller
public class RequestBodyJsonController {

    private ObjectMapper objectMapper = new ObjectMapper();

    @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());

    }

    @ResponseBody
    @PostMapping("/request-body-json-v2")
    //@RequestBody -> Http Body 를 바로 String 타입으로 받을 수 있음.

    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 helloData){
        log.info("username={}, age={}", helloData.getUsername(), helloData.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 helloData){
        log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
        return helloData;
    }
}

=========================================================================

 

*** 스프링 부트는 기본 뷰 템플릿 경로를 제공한다.

- src/main/resources/templates

 

* 뷰 템플릿을 호출하는 컨트롤러

@Controller
public class ResponseViewController {

    @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";
    }
}

=========================================================================

 

 

* HttpMessageConverter 용도

  • HTTP API처럼 JSON 데이터를 HTTP 메시지 바디 내 직접 읽거나 쓰는 경우 사용
  • @ResponseBody 어노테이션을 사용할 때 HTTP Body 내 문자 내용을 직접 반환하므로 HttpMessageConverter가 동작
  • String 문자 처리에는 StringHttpMessageConverter, 객체 처리에는 MappingJackson2HttpMessageConverter 사용
  • 이외에도 다양한 HttpMessageConverter가 존재

 

* 응답의 경우 클라이언트의 HTTP Accept 헤더 서버 컨트롤러의 반환 타입을 조합해 MessageConverter가 선택됨

 

* 스프링 MVC의 HttpMessageConverter 적용 사례

  • HTTP 요청: @RequestBody, HttpEntity(RequestEntity)
  • HTTP 응답: @ResponseBody, HttpEntity(ResponseEntity)

 

* 주로 사용하는 메시지 컨버터 세 가지 (우선순위 순)

- ByteArrayHttpMessageConverter

- StringHttpMessageConverter

- MappingJackson2HttpMessageConverter

 

* HTTP는 언제 사용될까 ? 

 

- 컨트롤러를 처리하는 RequestMappingHandlerAdapter가 ArgumentResolver를 호출하면서 필요로 하는 다양한 파라미터의 값을 생성한 후 컨트롤러를 호출하며 파라미터를 넘겨줌 (사진 내 1번과 2번 과정)

 

- ReturnValueHandler는 응답 값을 변환하고 처리하는데, 컨트롤러에서 String으로 뷰의 논리적 이름만 반환하더라도 동작하는 이유가 ReturnValueHandler 덕분 (사진 내 3번 과정)