ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [스프링 MVC 1] 6. 스프링 MVC - 기본 기능
    INFLEARN/스프링 MVC 1편 2021. 11. 5. 17:45

    6. 스프링 MVC - 기본 기능

     

    [강의 정리] 김영한님 :: 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술

     

     

    1. 프로젝트 생성

    # 프로젝트 생성

    • Packaging은 Jar로 선택
      • 더 이상 JSP를 사용하지 않으므로
      • 앞으로 스프링 부트를 사용하면 이 방식 주로 사용
      • Jar를 사용하면 항상 내장 서버(톰캣 등)를 사용, 최적화 되어 있음
      • webapp 경로 사용하지 않음

    +) War는 내장 서버도 가능하지만, 주로 외부 서버 배포 목적

     

    # Welcome 페이지

    • 스프링 부트에서 Jar를 사용하기 위해 /resources/static 위치에 index.html파일을 두었음
    • 스프링 부트가 Welcome 페이지로 처리해줌

     

    2. 로깅 간단히 알아보기

    # 로깅 라이브러리

    • 스프링 부트 라이브러리를 사용하면 기본적으로 스프링 부트 로깅 라이브러리 spring-boot-starter-logging이 포함
    • SLF4J 라이브러리 (인터페이스)
      • Logback 라이브러리 (구현체)
      • Log4J 라이브러리
      • Log4J2 라이브러리

     

    # 로그 선언

    • private final Logger log = LoggerFactory.getLogger(getClass());
    • @sl4fj : 롬복 사용
    • 로그호출 : log.info("xxx");

     

    # @RestController

    • @Controller
      • 반환 값이 String이면 뷰 이름으로 인식
      • 뷰를 찾고 뷰를 렌더링
    • @RestController
      • 반환 값으로 뷰를 찾지 않음
      • HTTP 메시지 바디에 바로 입력
      • 즉, 실행 결과로 "ok"와 같은 String 메세지 받을 수 있음

     

    # 테스트

    • 로그 레벨
      • TRACE > DEBUG > INFO > WARN > ERROR
      • 개발 서버 : debug
      • 운영 서버 : info
    • 로그 레벨 설정 (application.properties)
      • 기본 설정은 루트 레벨의 info : logging.level.root=info
      • 패키지와 그 하위 로그 레벨 설정 가능 : logging.level.hello.springmvc=debug

     

    # 올바른 로그 사용법 

    • log.debug("data="+data) => 사용 X
      • 예를 들어, properties에서 특정 레벨을 적용한다고 가정해서 실제로 적용할 수 있는 레벨이 아님에도 더하기 연산자를 사용하면 실제로는 더하기 연산이 제일 먼저 일어나서 리소스를 사용하게 됨
    • log.debug("data={}", data)
      • parameter만 넘겨서 실행하여, 의미없는 연산이 발생하지 않음

     

    # 로그 사용시 장점 

    • 쓰레드 정보, 클래스 이름 같은 부가 정보 볼 수 있음
    • 출력 모양 조정 가능
    • 로그 레벨에 따라 개발/운영 서버 각각에 출력하고 싶은 로그만 출력 가능
    • 시스템 아웃은 콘솔에만 출력되지만, 로그는 파일, 네트워크 등의 위치에도 남길 수 있음
    • 파일에서 로그 분할도 가능
    • 성능(내부 버퍼링, 멀티 쓰레드)도 시스템 아웃보다 좋음

     

    3. 요청 매핑

    # 기본 요청 

    • URL 호출이 왔을 때 메서드가 실행될 수 있도록 매핑
    • @Controller가 아니라 @RestController를 붙여서, 반환 값으로 뷰를 찾는 것이 아니라 HTTP 메시지 바디에 바로 입력
    • 다중설정도 가능 (배열)
    • HTTP 메서드를 지정하지 않아서 모두 허용 가능 (GET, HEAD, POST, PUT, PATCH, DELETE)

     

    # HTTP 메서드 매핑

    • method 특정 HTTP 메서드 요청만 허용
    • 예시처럼 GET으로 세팅해놨는데 POST 요청을 하면 스프링 MVC는 405 반환

     

    # HTTP 메서드 매핑 축약

    • @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping

     

    # PathVariable(경로 변수) 사용

    • PathVariable 사용

    • 변수명이 같으면 생략 가능

    • 다중 사용 가능

     

    # 특정 파라미터 조건 매핑 (잘 사용하지 않음)

     

    # 특정 헤더 조건 매핑 

     

     

    # 미디어 타입 조건 매핑 - HTTP 요청 Content-Type, consume

    • 맞지 않으면 415 상태코드 반환

     

    # 미디어 타입 조건 매핑 - HTTP 요청 Accept, produce

    • 맞지 않으면 406 상태코드 반환

     

     

    4. 요청 매핑 - API 예시

    # 매핑

    • 회원 목록 조회 : GET /users
    • 회원 등록 : POST /users
    • 회원 조회 : GET /users/{userId}
    • 회원 수정 : PATCH /users/{userId}
    • 회원 삭제 : DELETE /users/{userId}

     

     

    5. HTTP 요청 - 기본, 헤더 조회

    # HTTP 헤더 정보 조회

    • HttpServletRequest
    • HttpServletResponse
    • HttpMethod
      • HTTP 메서드 조회
    • Locale
      • Locale 정보를 조회
    • @RequestHeader MultiValueMap<String, String> headerMap
      • 모든 HTTP 헤더를 MultiValueMap 형식으로 조회
      • MultiValueMap : 하나의 키에 여러 값을 받을 수 있음
    • @RequestHeader("host") String host
      • 특정 HTTP 헤더 조회
      • 속성 (필수 값 여부 : required, 기본 값 속성 : defaultValue)
    • @CookieValue(value = "myCookie", required = false) String cookie
      • 특정 쿠키 조회
      • 속성 (필수 값 여부 : required, 기본 값 속성 : defaultValue)

    출력

     

    +) 참고

     

     

    6. HTTP 요청 파라미터 - 쿼리 파라미터, HTML 조회

    # 클라이언트 -> 서버 (요청 데이터 전달)

    • GET - 쿼리 파라미터
      • 메세지 바디 없이 URL의 쿼리 파라미터에 데이터를 포함해서 전달
    • POST - HTML From
      • 메세지 바디에 쿼리 파라미터 형식으로 전달 
    • HTTP message body에 데이터 직접 담아서 요청
      • HTTP API에서 주로 사용, JSON

     

    # 요청 파라미터 - 쿼리 파라미터, HTML FORM

    • HttpServletRequest의 request.getParameter() 사용
    • GET 실행 (http://localhost:8080/request-param-v1?username=kim&age=20)
      • 아래 예시는 view 조회 x

    • POST Form 실행 (http://localhost:8080/basic/hello-form.html)
      • /resources/static 아래에 두면 스프링 부트가 자동으로 인식
      • Jar를 사용하면 webapp 경로 알 수 없어 정적 리소스도 클래스 경로에 포함해야함

     

     

    7. HTTP 요청 파라미터 - @RequestParam

     

    # requestParamV2

    • @RequestParam : 파라미터 이름으로 바인딩
      • @RequestParam("paramName") String name
      • -> request.getParameter("paramName")
    • @ResponseBody : View 조회를 무시하고, HTTP Message Body에 직접 해당 내용 입력

     

    # requestParamV3

    • HTTP 파라미터 이름이 변수 이름과 같은 경우
      • @RequestParam(name="xxx") 생략 가능

     

    # requestParamV4

    • String, int, Integer 등 단순 타입이면 @RequestParam 생략 가능
    • 하지만, @RequestParam이 있으면 명확하게 요청 파라미터에서 데이터를 읽는다는 것을 알 수 있으므로 살려두는 것을 권장

     

    # 파라미터 필수 여부 - requestParamRequired

    • @RequestParam.required
      • 파라미터 필수 여부
      • 기본값은 파라미터 필수(true)
    • /request-param 요청을 했을 때, 필수 값이 없으면 400 예외
    • (주의) /request-param?username=
      • 파라미터 이름만 있고 값이 없는 경우 필수값이어도 빈문자로 통과됨
      • null과 빈문자는 다른거
    • (주의) 기본형(primitive)에 null 입력
      • ex) @RequestParam(required = true) int age 일 때
      • null을 int에 입력하는 것은 불가능하므로 500 예외 발생
      • 따라서, null을 받을 수 있는 Integer로 변경하거나, defaultValue 사용

     

    # 기본 값 적용 - requestParamDefault

    • 파라미터에 값이 없는 경우 defaultValue를 사용하면 기본 값 적용 가능
    • 이미 기본 값이 있으므로 required는 의미 없음
    • 빈 문자의 경우에도 설정한 기본 값 적용

     

    # 파라미터를 Map으로 조회하기 - requestParamMap

    • 파라미터를 Map, MultiValueMap으로 조회할 수 있음
    • @RequestParam Map<String, Object> paramMap

     

     

     

    8. HTTP 요청 파라미터 - @ModelAttribute

    # modelAttributeV1

    • 요청 파라미터를 받아서 필요한 객체를 만들고, 그 객체에 값을 넣어줘야함
    • @ModelAttribute를 사용하면, HelloData 객체가 생성되고 요청 파라미터의 값도 들어있음

     

    # @ModelAttribute 흐름

    • 스프링 MVC는 @ModelAttribute가 있으면 다음을 실행
      • 해당 객체를 생성
      • 요청 파라미터의 이름으로 해당 객체의 프로퍼티를 찾음
      • 그리고 해당 프로피터의 setter를 호출해서 파라미터의 값을 입력(바인딩)
    • 프로퍼티
      • 객체에 getxxx(), setxxx() 메서드가 있으면, 이 객체는 xxx라는 프로퍼티를 가지고 있음
      • xxx 프로퍼티의 값을 변경하면 setxxx()가 호출되고, 조회하면 getxxx()가 호출됨
    • 바인딩 오류
      • age = abc 처럼 숫자가 들어가야할 곳에 문자를 넣으면 BindException 발생

     

    # modelAttributeV2 - @ModelAttribute 생략 가능

    • @ModelAttribute는 생략 할 수 있음
    • 하지만, @RequestParam도 생략 가능하여 혼란이 발생할 수 있음
      • String, int, Integer와 같은 단순 타입 = @RequestParam
      • 나머지 = @ModelAttribute (argument resolver로 지정해둔 타입 외)

     

     

    9. HTTP 요청 메시지 - 단순 텍스트

     

    # HTTP message body

    • 요청 파라미터와 다르게 HTTP 메세지 바디를 통해 직접 데이터가 넘어오는 경우 @RequestParam, @ModelAttribute를 사용할 수 없음 (HTML Form 형식 제외)

     

    # requestBodyStringV1

    • request.getInputStream()

     

    # requestBodyStringV2 - Input, Output Stream, Reader

    • InputStream(Reader) : HTTP 요청 메시지 바디의 내용을 직접 조회
    • OutputStream(Writer) : HTTP 응답 메시지의 바디에 직접 결과 출력

     

    # requestBodyStringV3 - HttpEntity

    • HttpEntity : HTTP header, body 정보를 편리하게 조회
      • httpEntity.getHeaders(), httpEntity.getBody()
      • 메시지 바디 정보를 직접 조회
      • 요청 파라미터를 조회하는 기능과 관계 없음 (@RequestParam X, @ModelAttribute X)
    • 응답에도 사용 가능
      • 메시지 바디 정보 직접 반환
      • 헤더 정보 포함 가능
      • view 조회 X (Body data -> HTTP 응답 메시지)

      • RequestEntity
        • HttpMethod, url 정보가 추가, 요청에서 사용
      • Responseentity
        • HTTP 상태 코드 설정 가능, 응답에서 사숑
        • return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED)
      • 메시지 컨버터 (HttpMessageConverter)
        • 스프링 MVC 내부에서 HTTP 메시지 바디를 읽어서 문자나 객체로 변환해서 전달할 때 사용되는 기능

     

    # requestBodyStringV4 - @RequestBody

    • @RequestBody
      • HTTP 메시지 바디 정보 직접 조회
      • 헤더 정보가 필요하다면 HttpEntity 또는 @RequestHeader 사용하면 됨
      • @RequestParam, @ModelAttribute 와 전혀 관계 없음! (-> 요청 파라미터 조회)
      • 전혀 다른 HttpMessageConverter라는 매커니즘이 존재
    • @ResponseBody
      • 응답 결과를 HTTP Message Body에 직접 담아서 전달
      • View X

     

     

    10. HTTP 요청 메시지 - JSON

     

    # requestBodyJsonV1

    • HttpServletRequest를 사용하여 직접 HTTP 메시지 바디에서 데이터 읽어서 문자로 변환
    • 문자로 된 JSON 데이터를 Jackson 라이브러리인 objectMapper 사용하여 자바 객체로 변환

     

     

    # requestBodyJsonV2 - @RequestBody 문자 변환

    • @RequestBody를 사용해서 HTTP 메시지를 꺼내고 메시지 바디에 저장
    • 문자로 된 JSON 데이터인 messageBody를 objectMapper를 통해서 자바 객체로 변환

     

    # requestBodyJsonV3 - @RequestBody 객체 변환

    • @RequestBody에 직접 만든 객체를 지정
      • HTTP 메시지 컨버터가 HTTP 메시지 바디의 내용을 우리가 원하는 문자나 객체(JSON 객체)로 변환 (HttpEntity, @RequestBody 사용시)
    • @RequestBody는 생략 불가능
      • 만약 생략하게 되면 @ModelAttribute로 인식하게 됨 (이러면 HTTP 메시지 바디가 아니라 요청 파라미터를 처리하게 됨)

     

     

    # requestBodyJsonV4 - HttpEntity

    • HttpEntity 가능

     

    # requestBodyJsonV5 

    • @ResponseBody 
      • 응답의 경우도 @ResponseBody를 사용하면 해당 객체를 HTTP 메시지 바디에 직접 넣을 수 있음 (물론 HttpEntity도 가능)
      • 이 때, 클라이언트에서 받아들일 수 있는 타입인지를 체크함 (Accept : application/json)
    • @RequestBody 요청
      • JSON 요청 -> HTTP 메시지 컨버터 -> 객체
    • @ResponseBody 응답
      • 객체 -> HTTP 메시지 컨버터 -> JSON 응답

     

     

    11. 응답 - 정적 리소스, 뷰 템플릿

     

    # 스프링(서버)에서 응답 데이터를 만드는 방법

    • 정적 리소스 (정적인 HTML, css, js)
    • 뷰 템플릿 사용 (동적인 HTML)
    • HTTP 메시지 사용 (JSON)

     

    # 정적 리소스

    • 스프링 부트는 클래스패스의 다음 디렉토리에 있는 정적 리소스를 제공
      • /static, /public, /resource, /META-INF/resources
    • src/main/resources는 리소스를 보관하는 곳이며, 클래스패스의 시작 경로
    • 스프링 부트가 정적 리소스로 서비스를 제공
      • src/main/resources/static/basic/hello-form.html
      • => http://localhost:8080/basic/hello-form.html

     

    # 뷰 템플릿

    • 뷰 템플릿을 거쳐서 HTML이 생성되고, 뷰가 응답을 만들어서 전달
    • 일반적으로는 HTML을 동적으로 생성하는 용도로 사용
    • src/main/resources/templates/response/hello.html

     

    # responseViewV1

    • ModelAndView

    # responseViewV2

      • String을 반환하는 경우, view or HTTP 메시지
      • @ResponseBody가 없으면 response/hello로 뷰 리졸버가 실행되어서 뷰를 찾고, 렌더링 함
        • 아래 예시에서는 templates/reponse/hello.html 실행하게됨
        • templates/xxx/xxx.html 은 미리 스프링부트가 세팅해놓음
          • build.gradleimplementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
          • 스프링 부트가 자동으로 ThymeleafViewResolver와 필요한 스프링 빈들을 등록
          • application.properties에 아래와 같이 default로 설정되어 있음
          • spring.thymeleaf.prefix=classpath:/templates/
            spring.thymeleaf.suffix=.html
    • @ResponseBody가 있으면 뷰 리졸버를 실행하지 않고, HTTP 메시지 바디에 response/hello 라는 문자가 입력

     

    # responseViewV3

    • void를 반환하는 경우 - 권장하지 않음
      • @Controller를 사용하고, HttpServletResponse, OutputStream(writer) 같은 HTTP 메시지 바디를 처리하는 파라미터가 없으면, 요청 URL을 참고해서 논리 뷰 이름으로 사용
      • 요청 URL : /response/hello
      • 실행 : templates/response/hello.html

     

    +) 참고 - 스프링 부트 타임리프 관련 추가 설정

    https://docs.spring.io/spring-boot/docs/2.4.3/reference/html/appendix-application-properties.html#common-application-properties-templating

     

     

    12. 응답 - HTTP API, 메시지 바디에 직접 입력

     

    + ) 정적 리소스나 뷰 템플릿을 거치지 않고, 직접 HTTP 응답 메시지를 전달하는 경우

    • HTML이나 뷰 템플릿을 사용해도 HTTP 응답 메시지 바디에 HTML 데이터가 담겨서 전달됨

     

    # responseBodyV1

    • (서블릿을 직접 다룰때처럼) HttpServletResponse 객체를 통해서 HTTP 메시지 바디에 직접 "ok" 응답 메시지 전달
    • response.getWriter().write("ok");

     

    # responseBodyV2

    • ResponseEntity는 HttpEntity를 상속 받음
    • HTTP 메시지의 헤더 바디 정보 + HTTP 응답 코드 설정 가능

     

    # responseBodyV3

    • @ResponseBody 사용하여 view를 사용하지 않고, HTTP 메시지 컨버터를 통해서 HTTP 메시지를 직접 입력

     

    # responseBodyJsonV1

    • HTTP 메시지 컨버터를 통해서 JSON 형식으로 변환되어서 반환

     

    # responseBodyJsonV2

    • ResponseEntity를 사용하지 않고 @ResponseBody를 사용하기 때문에 HTTP 응답 코드 설정이 까다로움
      • @ResponseStatus(HttpStatus.OK) 애노테이션 사용하여 응답 코드 설정
      • 애노테이션이므로 동적으로 변경은 불가능 (동적 변경이 필요하면 ResponseEntity 사용)

     

    # @RestController

    • @Controller 대신 @RestController 애노테이션을 사용하면, 해당 컨트롤러에 모두 @ResponseBody가 적용되는 효과가 있음
    • 따라서, 뷰 템플릿을 사용하는 것이 아니라 HTTP 메시지 바디에 직접 데이터를 입력
    • Rest API (HTTP API) 만들 때 사용하는 컨트롤러
    • @Controller + @ResponseBody

     

    13. HTTP 메시지 컨버터

     

    # @ResponseBody 사용 원리

    • HTTP의 BODY에 문자 내용을 직접 반환
    • viewResolver 대신에 HttpMessageConverter가 동작
    • 기본 문자 처리 : StringHttpMessageConverter
    • 기본 객체 처리 : MappingJackson2HttpMessageConverter

     

    # 스프링 MVC HTTP 메시지 컨버터 적용

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

    • canRead(), CanWrite() : 메시지 컨버터가 해당 클래스, 미디어타입을 지원하는지 체크
    • read(), write() : 메시지 컨버터를 통해서 메시지를 읽고 쓰느 기능

     

     

    # 스프링 부트 기본 메시지 컨버터

    • 스프링 부트는 대상 클래스 타입과 미디어 타입 툴을 체크해서 사용여부를 결정 (만족하지 않으면 우선순위에 따라 넘어감)

    • MappingJackson2HttpMessageConverter
      • 가능 (content-type, 클래스 타입 일치)
      • 불가능 (content-type이 text/html이면 안됨)

     

    # HTTP 요청 데이터 읽기

    • HTTP 요청이 오고, 컨트롤러에서 @RequestBody, HttpEntity 파라미터를 사용
    • 메시지 컨버터가 메시지를 읽을 수 있는지 확인하기 위해 canRead() 호출
      • 대상 클래스 타입을 지원하는가 
        • 예) @RequestBody의 대상 클래스 (byte[], String, HelloData)
      • HTTP 요청의 Content-Type 미디어 타입을 지원하는가
        • 예) text/plain, application/json, */*
    • canRead() 조건을 만족하면 read()를 호출해서 객체를 생성하고 반환

     

    # HTTP 응답 데이터 읽기

    • 컨트롤러에서 @ResponseBody, HttpEntity로 값이 반환
    • 메시지 컨버터가 메시지를 읽을 수 있는지 확인하기 위해 canWrite() 호출
      • 대상 클래스 타입을 지원하는가 
        • 예) return의 대상 클래스 (byte[], String, HelloData)
      • HTTP 요청의 Accept 미디어 타입을 지원하는가 (더 정확히는 @RequestMapping 의 produces)
        • 예) text/plain, application/json, */*
    • canWrite() 조건을 만족하면 write()를 호출해서 HTTP 응답 메시지 바디에 데이터 생성

     

     

    14. 요청 매핑 핸들러 어댑터 구조

     

    # @RequestMappingHandlerAdapter (요청 매핑 핸들러 어댑터) 동작 방식

    • ArgumentResolver
      • 애노테이션 기반의 컨트롤러는 매우 다양한 파라미터를 사용할 수 있고, 이렇게 파라미터를 유연하게 처리할 수 있는 이유는 ArgumentResolver 때문
        • 즉, 애노테이션 기반 컨트롤러를 처리하는 RequestMappingHandlerAdapter는 ArgumentResolver를 호출해서 컨트롤러(핸들러)가 필요로 하는 다양한 파라미터의 값(객체)를 생성
      • HandlerMethodArgumentResolver (줄여서 -> ArgumentResolver)
        • ArgumentResolver의 supportsParameter()를 호출해서 해당 파라미터를 지원하는지 체크
        • 지원한다면 resolveArgument()를 호출해서 실제 객체를 생성
        • 생성된 객체가 컨트롤러 호출시 넘어감

    + 참고) 가능한 파라미터 목록 : https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-arguments

    • HandlerMethodReturnValueHandler (줄여서 -> ReturnValueHandler)
      • 응답 값을 변환하고 처리
      • 컨트롤러에서 String으로 뷰 이름을 반환해도, 동작하는 이유

    + 참고) 가능한 응답 값 목록 : https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-return-types

    출처 : 김영한님 강의 자료

     

    # HTTP 메시지 컨버터 위치

    • HTTP 메시지 컨버터를 사용하는 @RequestBody도 컨트롤러가 필요로 하는 파라미터의 값에 사용되고, @ResponseBody도 컨트롤러의 반환 값을 이용
      • 요청 : @RequestBody, HttpEntity를 처리하는 ArgumentResolver가 있고,  이 ArgumentResolver들이 HTTP 메시지 컨버터를 사용해서 필요한 객체를 생성
      • 응답 : @ResponseBody, HttpEntity를 처리하는 ReturnValueHandler가 있고, 여기에서 HTTP 메시지 컨버터를 호출해서 응답 결과를 생성
    • 스프링 MVC는
      • @RequestBody, @ResponseBody => RequestResponseBodyMehtodProcessor(ArgumentResolver) 사용
      • @HttpEntity => HttpEntityMethodProcessor(ArgumentResolver) 사용

    출처 : 김영한님 강의 자료

     

    • 스프링은 다음을 모두 인터페이스로 제공하여, 필요하다면 언제든지 기능을 확장할 수 있음
      • HandlerMethodArgumentResolver
      • HandlerMethodReturnValueResolver
      • HttpMessageConverter

     

     

     

    댓글

Programming Diary