Backend

스프링MVC 기본기능

연_우리 2021. 12. 3. 00:43
반응형

목차

     

     

    Welcome페이지

    /resources/static 에 index.html을 생성하면 스프링부트가 welcome페이지로 처리해준다.

     

     

    Logging 로깅

    스프링부트는 자동으로 spring-boot-starter-logging 라이브러리를 사용하고, 

    spring-boot-starter-logging라이브러리는 SLF4J, Logback 라이브러리를 사용한다.

    SLF4J는 수많은 로그 라이브러리를 인터페이스로 제공하고

    Logback은 로그 라이브러리를 구현하는 구현체이다.

     

    System.out.println은 콘솔에만 출력되지만

    로그는 콘솔뿐만 아니라 파일, 네트워크 등의 별도의 위치에 남길 수 있다.

    양이 많아지면 특정기준에 따라 로그를 분할할 수 있고, 성능도 System.out 보다 좋다.

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    //@Slf4j
    public class LogTestController {
        private final Logger log = LoggerFactory.getLogger(getClass()); // = @Slf4j 와 같다
    
        @RequestMapping("/log-test")
        public String logTest(){
            String name = "Spring";
            //System.out.println("name = " + name);
    
            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);
            //{}에 name이 치환된다.
            
            //아무 설정없이 실행하면 info, warn, error만 출력된다
            //2021-12-01 22:09:02.429  INFO 13728 --- [nio-8080-exec-1] hello.springmvc.basic.LogTestController  : info log=Spring
            //2021-12-01 22:09:02.433  WARN 13728 --- [nio-8080-exec-1] hello.springmvc.basic.LogTestController  : warn log=Spring
            //2021-12-01 22:09:02.433 ERROR 13728 --- [nio-8080-exec-1] hello.springmvc.basic.LogTestController  : error log=Spring
            //시간, 로그레벨, 프로세스ID, 쓰레드명, 클래스명, 로그메시지 순으로 출력된다.
    
            //resources/application.properties파일에 logging.level.hello.springmvc=trace 설정하면 trace, debug, info, warn, error 출력되고
            //resources/application.properties파일에 logging.level.hello.springmvc=debug 설정하면        debug, info, warn, error 출력된다.
            //개발서버는 debug, 로컬피시는 trace, 운영서버는 info로 설정한다.
    
            return "ok";
        }
    }

    ("log="+name)이 아닌 ("log={}", name)으로 사용하는 이유?

    ("log="+name)은 name값을 가져와서 + 연산하는 과정에서 리소스가 많이 사용되고

    연산이 모두 완료된 다음 로그 출력 레벨(trace인지 info인지...)을 확인하기때문에 헛수고 하는 일이 생길 수 있다.

    반면에 ("log={}", name)은 로그 출력 레벨을 먼저 확인하기때문에 헛수고를 방지할 수 있다.

     

     

    @RestController와 @Controller의 차이

    @Controller를 사용하면 @RequestMapping메서드의 반환값 문자열을 ViewName으로 판단해서 뷰를 찾고, 뷰가 렌더링된다.
    반면에 @RestController는 @RequestMapping메서드의 반환값 문자열을 HTML메시지 바디에 바로 입력한다.

     

     

    다양한 요청맵핑 @RequestMapping 

    @GetMapping("/url") = @RequestMapping(value = "/url", method = RequestMethod.GET)
    @PostMapping("/url") = @RequestMapping(value = "/url", method = RequestMethod.POST)
    @PatchMapping("/url") = @RequestMapping(value = "/url", method = RequestMethod.PATCH)
    @DeleteMapping("/url") = @RequestMapping(value = "/url", method = RequestMethod.DELETE)

     

     

    Request의 Header내용 출력하기

    @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
                              ){
    
            /*
            * HttpMethod : Http메서드를 조회한다
            * httpMethod=GET
            *
            * Locale : Locale정보(언어)를 조회한다
            * locale=ko_KR
            *
            * @RequestHeader MultiValueMap<String, String> headerMap : 모든 HTTP헤더를 MultiValueMap형식으로 조회한다
            *     MultiValueMap : 하나의 키에 여러 값을 받을 수 있다.
            * headerMap={content-type=[application/json], accept=[* / *], connection=[keep-alive], content-length=[32]} ...등등
            *
            * @RequestHeader("host") String host : 특정 HTTP헤더를 조회한다
            * header host=localhost:8080
            *
            * @CookieValue(value = "myCookie", required = false) String cookie : 특정 쿠기를 조회한다
            * myCookie=null
            * */
    
            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";
        }
    }

     

     

    GET, POST의 Query Parameter 내용 가져오기 : @RequestParam

    GET : /url?username=hello&age=20

    POST : Http Message Bodyusername=hello&age=20

    결국 GET이나 POST나 쿼리파라미터 형식으로 전달한다.

     

    @RequestParam의 required, defaultValue 속성

    @RequestParam으로 가져온 매개변수에 required 속성으로 필수여부를 지정할 수 있다. (true가 기본값이다)

    @RequestParam으로 가져온 매개변수에 defaultValue 속성으로 기본값을 지정할 수 있다.

    defaultValue는 "" 빈문자가 들어와도 적용된다.

    @RequestParam(required=true) String username
    /* username에 값이 필수로 들어와야 한다
     * /url (X) 
     * /url?username (OK) : username에 ""입력됨
    */
    
    @RequestParam(required=false) int age
    /* age에 값이 선택적으로 들어올 수 있다
     * /url (X)
     * 주의!! Integer는 객체라서 null이 들어올 수 있지만 int는 null이 들어올 수 없다
    */
    
    @RequestParam(required=false) Integer age
    /* age에 값이 선택적으로 들어올 수 있다
     * /url (O) : age에 null입력됨
    */
    
    @RequestParam(required=false, defaultValue=0) int age
    /* age에 값이 선택적으로 들어올 수 있고, 값이 없으면 기본으로 0을 넣는다.
     * /url (OK) : age에 0입력됨
    */
    
    @RequestParam(required=true, defaultValue=guest) String username
    /* username에 값이 필수로 들어와야 하고, 값이 없거나 "" 빈문자면 guest를 넣는다
     * /url (X) 
     * /url?username (OK) : username에 guest입력됨
    */

     

    @RequestParam의 모든 값 Map으로 가져오기

        @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";
        }
        // /request-param-map?username=aa&age=10
        // username=[aa], age=[10]
        
        @ResponseBody
        @RequestMapping("/request-param-map")
        public String requestParamMap(@RequestParam MultiValueMap<String, Object> paramMap) {
            log.info("username={}, age={}", paramMap.get("username"),paramMap.get("age"));
            return "ok";
        }
        // /request-param-map?username=aa&age=10&username=aa&age=20
        // username=[aa, aa], age=[10, 20]

     

     

    GET, POST의 Query Parameter를 클래스로 받기 : @ModelAttribute

    매번 request에서 parameter를 각각 받으면 클래스를 생성하고 set하는 과정이 발생한다.

    @ModelAttribute는 이 과정을 자동으로 처리해준다.

     

    @ModelAttribute는 HelloData 클래스를 확인한 후 모든 setter들을 가져와서 파라미터의 값을 바인딩한다.

    (@ModelAttribute HelloData helloData)를 (HelloData helloData)처럼 작성할 수 있다

     

    @Data
    public class HelloData {
        private String username;
        private int age;
    }

     

     

     

    HTTP API의 HTTP Message Body의 데이터 가져오기

    HTTP API에서 주로 사용하는 방식으로,

    HTTP Message Body에 데이터를 직접 담아서 전달한다

     

    단순 텍스트 조회

    Get, Post방식과는 다르기때문에 @RequestParam, @ModelAttribute를 사용할 수 없다!!

     

    헤더정보가 필요하면 HttpEntity

    메시지바디정보가 필요하면 @RequestBody, @ResponseBody를 사용하자.

     

    메시지 바디 조회(@RequestBody)와 요청 파라미터 조회(@RequestParam , @ModelAttribute)는 전혀 관계가 없다.

     

    JSON 조회

    HttpEntity, @RequestBody를 사용하면 HTTP Message ConverterBODY의 내용을 문자나 객체, JSON으로 자동변환해 준다.

    (그래서 "@RequestBody 객체타입"을 사용할 수 있고, 메서드 반환타입을 String이 아닌 HelloData로 사용할 수도 있다.)

    🔥🔥 스프링은 
    String, int, Integer과 같은 단순타입은 @RequestParam으로 처리
    나머지(객체.. 등)는 @ModelAttribute로 처리하기때문에
    여기서 @RequestBody를 생략하면 @ModelAttribute로 처리된다.

    @RequestBody : JSON 요청 → HTTP 메시지 컨버터 → 객체

    @ResponseBody : 객체 → HTTP 메시지 컨버터 → JSON 응답

     

     

    HTTP응답하기 : 정적리소스, 뷰 템플릿

    정적리소스 /resources/static
    /resources/public
    정적인 HTML, CSS, JS를 제공할 때 사용
    왼쪽 디렉토리명에 파일을 넣어두면 스프링 부트가 정적리소스로 서비스를 제공한다.
    뷰 템플릿 /resources/templates 동적인 HTML을 제공할 때 사용
    HTTP 메시지   HTTP API를 제공할 때 사용 (위에서 요청과 함께 살펴보았다)

     

    뷰 템플릿 응답하기

    @ResponseBody가 없기때문에 "response/hello"로 뷰 리졸버가 실행되어 뷰를 찾고 렌더링한다

    @ResponseBody가 있으면 HTTP 메시지 바디에 response/hello가 입력된다.

     

    HTTP API 응답하기

     

     

    HTTP 메시지 컨버터

    스프링은 @RequestBody, @ResponseBody, HttpEntity를 사용할 때 메시지 컨버터를 사용한다.

    package org.springframework.http.converter;
    public interface HttpMessageConverter<T> {
      boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
      boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
      // canRead, canWrite : 메시지컨버터가 해당 클래스, 미디어타입을 지원하는지 체크
      
      List<MediaType> getSupportedMediaTypes();
      
      T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
      throws IOException, HttpMessageNotReadableException;
      // 메시지컨버터를 통해서 메시지를 읽는다
      
      void write(T t, @Nullable MediaType contentType, HttpOutputMessage
      outputMessage)
      throws IOException, HttpMessageNotWritableException;
      // 메시지컨버터를 통해서 메시지를 작성한다
    }

     

    0순위 : ByteArrayHttpMessageConverter byte타입 데이터를 처리한다.

    @RequestBody byte[] data
    @ResponseBody return byte[]

    미디어타입 : application/octet-stream
    1순위 : StringHttpMessageConverter String타입 데이터를 처리한다.

    @RequestBody String data
    @ResponseBody return "ok"

    미디어타입 : text/plain
    2순위 : MappingJackson2HttpMessageConverter 객체, HashMap, Json 등..의 데이터를 처리한다.

    @RequestBody HelloData data
    @ResponseBody return helloData

    미디어타입 : application/json

     

     

    RequestMappingHandlerAdapter 요청 맵핑 핸들러 어댑터 동작방식

    애노테이션 기반의 컨트롤러는 다양한 파라미터(HttpServletRequest, Model, @RequestParam....)들을 사용할 수 있었다.

     

     

    1. RequestMappingHandlerAdapterHandlerMethodArgumentResolver(ArgumentResolver)를 호출하여 핸들러(각 컨트롤러)가 필요로하는 파라미터 객체를 생성한다.

    2. HandlerMethodArgumentResolver(ArgumentResolver)는 파라미터의 값이 모두 준비되면 핸들러(각 컨트롤러)에게 값을 넘겨준다.

    3. 핸들러(각 컨트롤러)는 로직을 수행하고 값을 반환하면서 HandlerMethodReturnValueHandler(ReturnValueHandle)을 호출한다.

    4. HandlerMethodReturnValueHandler(ReturnValueHandle)는 리턴값을 처리한 후 RequestMappingHandlerAdapter에게 값을 반환한다.

     

     

    HandlerMethodArgumentResolver(ArgumentResolver) 덕분에 HttpServletRequest 대신 @RequestParam으로 쉽게 값을 꺼낼 수 있고

    HandlerMethodReturnValueHandler(ReturnValueHandle) 덕분에 각 컨트롤러에서 String으로 viewName을 반환해도 동작할 수 있다.

     

    여기서 Http Message Converter는 ArgumentResolver, ReturnValueHandle에서 호출하게된다.

     

     

     

     

    인프런 - 스프링MVC 백엔드 웹개발 핵심기술
    반응형
    • 네이버 블러그 공유하기
    • 페이스북 공유하기
    • 트위터 공유하기
    • 구글 플러스 공유하기
    • 카카오톡 공유하기