Skip to content

simhani1/Spring-SpringMVC-1st-ver2

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

스프링 MVC 1편 - 기본기능 및 실습

로깅 간단히 알아보기

  • 실무에서는 sout을 사용하지 않고 별도의 로깅 라이브러리를 사용한다.

  • 로깅 라이브러리

    • 스프링 부트 라이브러리를 사용하면 스프링 부트 로깅 라이브러리(sping-boot-starter-logging)가 함께 포함된다.
    • 이 라이브러리는 기본으로 다음 로깅 라이브러리를 사용한다.
      • SLF4J : 인터페이스
      • Logback : 그 구현체로 Logback을 사용한다.

@RestControlelr vs @Controller

  • RestController : 컨트롤러에서 문자열을 반환하면 해당 문자열을 HTTP 메시지 바디에 바로 입력한다.
    • @ResponseBody와 관련이 있다.
  • Controller : 컨트롤러에서 문자열을 반환하면 해당 문자열을 이름으로 갖는 view를 찾아서 view가 렌더링된다.

log의 장점

  • 로그의 레벨을 지정할 수 있다. img.png

  • 프로세스 id, 스레드 id 부터 클래스명 등 많은 내용을 볼 수 있다. 2023-02-11 21:32:33.308 INFO 20728 --- [nio-8080-exec-1] hello.springmvc.basic.LogTestController : info log=spring

  • 로그를 콘솔에 출력도 할 수 있고 파일로도 기록을 남길 수 있다.

  • sout보다 성능이 좋다(내부 버퍼링, 멀티 쓰레드 등), 로그는 성능이 극한으로 설정되어 있다.

  • 모든 로그를 보고 싶은 경우

    • application.properties에 로그 레벨을 지정하면 된다.
    • 로그 레벨은 아래와 같은 순서, 상위 등급으로 설정할 수록 하위 등급 로그를 더 볼 수 있다.
    • LEVEL: TRACE > DEBUG > INFO > WARN > ERROR
    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);
  • 로그 레벨 설정

#전체 로그 레벨 설정(기본 info) 
logging.level.root=info

#hello.springmvc 패키지와 그 하위 로그 레벨 설정 
logging.level.hello.springmvc=debug
  • 운영서버와 개발서버에서 로그의 레벨을 다르게 설정해서 사용을 한다.
    • 설정만으로 로그 출력 모양을 조절할 수 있다는 점도 장점이다.
    • 그렇게 안하고 sout을 사용하여 출력을 시킨다면 이런 설정이 불가능하다.
    • 개발 서버는 debug 출력
    • 운영 서버는 info 출력
  • 올바른 로그 사용법
    • log.debug("data="+data
    • 로그 출력 레벨을 info로 하더라도 해당 문자열을 합치는 연산이 발생한다.
    • 즉 출력을 하지도 않는데 문자열을 합치는 연산이 일어나서 메모리가 낭비되는 것이 문제점이다.
    • 따라서 이는 사용은 가능하지만 절대 사용하면 안되는 방법이다.

HTTP 요청 - 기본, 헤더 조회

  • HttpServletResponse

  • HttpMethod : HTTP 메서드를 조회한다.

  • org.springframework.http.HttpMethod Locale : Locale 정보를 조회한다.

  • @RequestHeader MultiValueMap<String, String> headerMap

    • 모든 HTTP 헤더를 MultiValueMap 형식으로 조회한다.
  • @RequestHeader("host") String host

    • 특정 HTTP 헤더를 조회한다.
    • 속성
      • 필수 값 여부: required
      • 기본 값 속성: defaultValue
  • @CookieValue(value = "myCookie", required = false) String cookie

    • 특정 쿠키를 조회한다.
    • 속성
      • 필수 값 여부: required
      • 기본 값: defaultValue
  • MultiValueMap

    • MAP과 유사한데, 하나의 키에 여러 값을 받을 수 있다.
    • HTTP header, HTTP 쿼리 파라미터와 같이 하나의 키에 여러 값을 받을 때 사용한다.
    • keyA=value1 & keyA=value2
  • @slf4j

    • 아래 코드를 자동으로 생성해서 로그를 선언해준다.
    private static final org.slf4j.Logger log =
    org.slf4j.LoggerFactory.getLogger(RequestHeaderController.class);

@Controller의 사용 가능한 파라미터 목록 @Controller의 사용 가능한 응답 값 목록

HTTP 요청 파라미터 - 쿼리 파라미터, HTML Form

  • HTTP 요청 데이터 조회

  • 클라이언트에서 서버로 요청 데이터를 전달할 때는 주로 다음 3가지 방법을 사용한다.

  • GET - 쿼리 파라미터

    • 메시지 바디 없이 URL의 쿼리 파라미터에 데이터를 포함해서 전달
  • POST - HTML Form

    • content-type:application/x-www-form-urlencoded
    • 메시지 바디에 쿼리 파라미터 형식으로 전달
  • HTTP message body에 데이터를 직접 담아서 요청

    • HTTP API에서 주로 사용, JSON, XML, TEXT
    • 데이터 형식은 주로 JSON사용
    • POST, PUT, PATCH
  • 요청 파라미터 - 쿼리 파라미터, HTML Form

    • HttpServletRequest의 request.getParameter()를 사용하면 다음 두가지 요청 파라미터를 조회할 수 있다.

HTTP 요청 파라미터 - @RequestParam

  • 아래와 같이 4가지 방식으로 파라미터를 매개변수로 입력받을 수 있다.
    /**
     * 반환 타입이 없으면서 이렇게 응답에 값을 직접 집어넣으면, view 조회X
     */
    @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  // ok라는 문자열로 view를 조회하지 않고 그냥 문자열을 http응답에 넣어서 보내버린다.
    @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";
    }
  • @RequestPram.required

    • 파라미터 값 필수 여부를 지정할 수 있다.
    • 기본은 true로 설정되어 있다.
  • 주의

    • 파라미터 이름만 사용
      • 파라미터 이름만 있고 값이 없는 경우 -> 빈문자로 인식하여 통과된다.
    • 기본형(primitive type)에 null입력
      • null을 int에 입력하는 것은 불가능(500에러)
      • 따라서 null을 받을 수 있는 primitive type으로 지정해줘야 한다.
  • 기본 값 적용 - defaultValue

    • 값이 없는 경우 설정한 디폴트 값으로 대체된다.
    • defaultValue는 빈문자가 와도 기본값으로 대체한다.
  • 파라미터를 Map으로 조회하기 - requestParamMap

    • 모든 파라미터를 Map, MultiValueMap으로 조회할 수 있다.
    • 주의
      • 파라미터 값이 1개가 확실하면 Map을 사용해도 된다.
      • 그렇지 않다면 MultiValueMap을 사용하는 것이 옳다.
        • EX) 키워드 파라미터가 여러개 들어올 수도 있다.

HTTP 요청 파라미터 - @ModelAttribute

   @ResponseBody
    @RequestMapping("/model-attribute-v1")
    public String modelAttibuteV1(@ModelAttribute HelloData helloData) {
        log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
        log.info("hellodata={}", helloData);
        return "ok";
    }
  • @ModelAttribute

    1. HelloData객체를 생성한다.
    2. 요청 파라미터의 이름으로 HelloData 객체의 프로퍼티를 찾는다.
    3. setter를 호출하여 값을 바인딩한다.
  • 프로퍼티

    • 객체에 getUsername() 메서드가 존재하면, username이라는 프로퍼티를 갖고 있는 것이다.
  • 바인딩 오류

    • 프로퍼티의 타입에 맞지 않는 값이 들어온 경우 에러가 발생한다. img.png
  • @ModelAttribute, @RequestParam 모두 생략이 가능하다

  • 그렇다면 어떻게 동작하는 것인가?

    • 스프링은 해당 어노테이션들을 생략하는 경우 다음과 같은 규칙이 적용된다.
    • String, int, Interger 같은 단순 타입 = @RequestParam이 적용된다.
    • argument resolver로 지정한 타입 외 나머지 = @ModelAttribuete이 적용된다.

argument resolver는 뒤에서 학습할 예정

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

  • 요청 파라미터와 다르게, HTTP 메시지 바디를 통해 데이터가 직접 넘어오는 경우, @ModelAttribute, @RequestParam을 사용할 수 없다.

  • HTTP 바디의 메시지를 inputStream을 사용하여 읽을 수 있다.

  • 스프링 MVC는 다음 파라미터르 지원한다.

    • InputStream(Reader): HTTP 요청 메시지 바디의 내용을 직접 조회
    • OutPutStream(Writer): HTTP 응답 메시지의 바디에 직접 결과 출력
  • HttpEntity: HTTP header, body 정보를 편하게 조회

    • 메시지 바디 정보를 직접 조회
    • 요청 파라미터를 조회하는 기능과 관계없음
  • HttpEntity는 응답에도 사용 가능

    • 메시지 바디 정보 직접 반환
    • 헤더 정보 포함 가능
    • view 조회x
  • HttpEntity를 상속받은 다음 객체들도 같은 기능을 제공한다.

    • ReuqestEntity : HttpMethod, url 정보가 추가, 요청에서 사용
  • ResponseEntity : HTTP 상태 코드 설정 가능, 응답에서 사용

  • 스프링 MVC 내부에서 HTTP 메시지 바디를 읽어서 문자나 객체로 변환해서 전달해준다.

  • 이때 HTTP 메시지 컨버터라는 기능을 사용한다.

  • @ReqeuestBody

    • HTTP 메시지 바디 정보를 편리하게 조회할 수 있다.
    • 만약 헤더 정보가 필요하다면, HttpEntity를 사용하거나 @RequestHeader를 사용하면 된다.

HTTP 요청 메시지 - JSON

    @ResponseBody
    @PostMapping("request-body-json-v3")
    public String requestBodyJsonV2(@RequestBody HelloData helloData) throws IOException {
        log.info("messageBody={}", helloData);
        log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
        return "ok";
    }
  • 위와 같이 ObjectMapper를 사용하지 않고 JSON값을 바로 객체로 받을 수 있다.

  • HttpEntity, @RequestBody를 사용하면 HTTP 메시지 컨버터가 메시지 바디의 내용을 우리가 원하는 문자나 객체 등으로 변환해준다.

  • HTTP 메시지 컨버터는 문자 뿐만 아니라 JSON도 객체로 변환해준다.

    • 이는 V2에서 했던 작업을 우리 대신 처리해주고 있기 때문이다.
    HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
  • 단 @RequestBody를 생략할 수는 없다.

    • String, int, Integer 같은 단순 타입 = @RequestParam
    • 나머지 = @ModelAttribute
    • 즉 HTTP의 바디 메시지에 데이터가 담겨있는데 꺼내질 못하게 되는 것이다.
  • @ResponseBody

    • 응답의 경우에 해당 어노테이션을 작성하면 HTTP 메시지 바디에 값을 직접 넣을 수 있다.
  • @RequestBody 요청

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

    • 객체 -> HTTP 메시지 컨버터 -> JSON 응답
  • 즉 객체를 반환해도 메시지 컨버터가 자동으로 JSON 타입으로 형변환을 해준다.

    @ResponseBody
    @PostMapping("request-body-json-v5")
    public HelloData requestBodyJsonV2(@RequestBody HelloData helloData) throws IOException {
        log.info("messageBody={}", helloData);
        log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
        return helloData;
    }

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

  • 정적 리소스

    • HTML, CSS, js 등 정적 리소스
  • 뷰 템플릿 사용

    • 동적인 HTML
  • HTTP 메시지 사용

    • HTTP 메시지 바디에 JSON 형식의 데이터를 실어 보낸다.
  • 정적 리소스

    • /static, /public, /resources, /META-INF/resources에 있는 정적 리소스를 제공한다.
    • 정적 리소스는 해당 파일을 변경없이 그대로 서비스 하는 것이다.
  • 뷰 템플릿

  • 뷰 템플릿을 거쳐서 HTML이 생성되고 뷰가 응답을 만들어서 전달한다.

    • src/main/resources/templates
  • @ResponseBody가 없으면 뷰 리졸버가 실행되어서 뷰를 찾고 렌더링 한다.

    • 있다면 뷰 리졸버를 실행하지 않고, HTTP 메시지 바디에 직접 response/hello라는 문자가 입력된다.
  • void를 반환하는 경우

    • @Cotroller를 사용하고, HttpServletrequest. HttpServletResponse 같은 HTTP 메시지 바디를 처리하는 파라미터가 없으면 요청 URL을 참고해서 논리 뷰 이름으로 사용한다.
  • Thymeleaf 스프링 부트 설정

    • 스프링 부트가 자동으로 ThymeleafViewResolver 와 필요한 스프링 빈들을 등록한다.
    • 그리고 다음 설정도 사용한다. 이 설정은 기본 값 이기 때문에 변경이 필요할 때만 설정하면 된다.
      spring.thymeleaf.prefix=classpath:/templates/
      spring.thymeleaf.suffix=.html

타임리프 참고 링크

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

  • @ResponseBody 사용 원리

    • HTTP의 BODY에 문자 내용을 직접 반환
    • viewResolver 대신에 HttpMesssageConverter가 동작
    • 기본 문자처리: StringHttpMessageConverter
    • 기본 객체처리: MappingJackson2HttpMessageConverter
    • byte처리 등등 기타 여러 HttpMessageConverter가 기본적으로 등록되어 있음
  • HTP Accept 헤더와 서버의 컨트롤러 반환 타입 정보들을 조합해서 HttpMessageConverter가 선택된다

  • MessageConverter

    • canRead(), canWrite(): 메시지 컨버터가 해당 클래스, 미디어 타입을 지원하는지 체크
    • read(), write(): 메시지 컨버터를 통해서 메시지를 읽고 쓰는 기능
  • 스프링 부트는 다양한 메시지 컨버터를 제공하는데, 대상 클래스 타입과 미디어 타입 둘을 체크해서 사용여부를 결정한다. 만약 만족하지 않으면 다음 메시지 컨버터로 우선순위가 넘어간다.

  • 예시

  • StringMessageConverter

content-type: application/json

@RequestMapping
void hello(@RequestBody String data) {}
  • MappingJackson2HttpMessageConverter
content-type: application/json

@RequestMapping
void hello(@RequestBody Hellodata hellodata) {}
  • ?(컨버터가 실패하는 경우)
content-type: text/html

@RequestMapping
void hello(@RequestBody HelloData hellodata) {}
  • 요청 맵핑 핸들러 어댑터 구조
    • 그렇다면 HTTP 메시지 컨버터는 스프링 MVC 어디 쯤에서 사용되는 것일까
    • 컨트롤러를 호출하는 곳에서 사용이 될 것이다.
    • @RequestMapping을 처리하는 핸들러 어댑터인 RequestMappingHandlerAdapter(요청 맵핑 핸들러)에 있다.
  • RequestMappingHandlerAdapter 동작 방식 img.png
  • ArgumentResolver
    • 지금까지 어노테이션 기반의 컨트롤러는 매우 다양한 파라미터를 사용할 수 있었다.
    • 이는 ArgumentResolver를 호출해서 컨트롤러(핸들러)가 필요로 하는 다양한 파라미터 값(객체)을 생성한다.
    • 이렇게 파라미터의 값이 모두 준비되면 컨트롤러를 호출하면서 값을 넘겨준다.
    • 스프링은 30개가 넘는 resolver들을 제공해준다.
  • 동작 방식
    • ArgumentResolver의 supportsParameter()를 호출해서 해당 파라미터를 지원하는지 체크하고, 지원하면 resolverArgument()를 호출해서 실제 객체를 생성한다.
    • 이렇게 생성된 객체가 컨트롤러 호출 시 넘어가는 것이다.
  • ReturnValueHandler
    • 컨트롤러의 응답 값을 변환하고 처리한다.
  • HTTP 메시지 컨버터 img.png
    • HTTP 메시지 컨버터를 사용하는 @RequestBody도 컨트롤러가 필요로 하는 파라미터의 값에 사용된다.
    • @ResponseBody의 경우도 컨트롤러의 반환 값을 이용한다.
  • 요청의 경우
    • @RequestBody, HttpEntity를 처리하는 ArgumentResolver가 있다.
    • 이 ArgumentResolver들이 HTTP 메시지 컨버터를 사용해서 필요한 객체를 생성하는 것이다.
  • 응답의 경우
    • @ResponseBody, HttpEntity를 처리하는 ReturnValueResolver가 있다.
    • 여기서 HTTP 메시지 컨버터를 호출해서 응답 결과를 만든다.
  • 확장
    • 스프링은 다음을 모두 인터페이스로 제공한다. 따라서 필요하면 언제든지 기능을 확장할 수 있다.
      • HandlerMethodArgumentResolver
      • HandlerMethodReturnValueHandler
      • HttpMessageConverter
    • 스프링이 필요한 대부분의 기능을 제공하기 때문에 기능 확장은 WebMvcConfigure를 상속 받아서 스프링 빈으로 등록하면 된다.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published