월루를 꿈꾸는 대학생

[MVC-1] 스프링 MVC - 기본 기능 본문

Programing/Spring Boot

[MVC-1] 스프링 MVC - 기본 기능

하즈시 2023. 8. 1. 22:09
728x90
Welcome 페이지
  • Jar를 사용하면 /resources/static/index.html 위치를 기본 웰컴페이지로 인식함

로깅 간단히 알아보기

  • 운영 시스템이는 println으로 정보를 출력하는 것이 아니라 별도 로깅라이브러리를 써서 로그를 남긴다
로깅 라이브러리
@RestController  
public class LogTestController {  
    // 내 클래스를 지정  
    private final Logger log = LoggerFactory.getLogger(getClass());  

    @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);  
        //로그를 사용하지 않아도 a+b 계산 로직이 먼저 실행됨, 이런 방식으로 사용하면 X        log.debug("String concat log=" + name);  

        return "ok";  
        // 그냥 @Controller를 썼다면 리턴값으로 논리 경로를 반환한다고 생각을 할텐데  
        // restController 는 리턴값이 뷰리졸버로 가는게 아니라 스트링 그 값 그대로 화면에 출력시킴 즉 반환됨  
        // RestAPI만들 때 중요  
    }  
}

@RestController

  • 그냥 @Controller는 반환값이 String인 경우 view 이름으로 인식하고 뷰 랜더링이 되는데 @RestController는 반환값이 HTTP Body에 바로 입력되어짐 -> rest api 개발용

  • 시간 / 로그 레벨 / pid / 쓰레드명 / 클래스명 / 로그 메서지
  • trace -> debug -> info -> warn -> error
  • 멀티스레드에서 http요청오면 스레드풀에서 찾아서 처리함

application.properties

  • 코드를 건들이지 않고 설정으로 로그 레벨 설정 가능

    # 전체 로그 레벨 설정(기본 info)
    logging.level.root=info
    # hello.springmvc 패키지와 그 하위 로그 레벨 설정
    logging.level.hello.springmvc=debug

@Slf4j

  • 보다 간편하게 로그 사용 가능

    @Slf4j  
    @RestController  
    public class LogTestController {  
    Factory.getLogger(getClass());  
      @RequestMapping("/log-test")  
      public String logTest(){  
          log.trace("trace log={}", name);

로그를 출력할 때는 + 연산에 주의해야함

log.debug("data={}", data)

로그 사용시 장점
  • 단순 출력보다 더 많은 정보를 남길 수 있다 (쓰레드 정보, 클래스 이름 등등)
  • 로그 레벨을 용도에 맞게 조절
  • 실무에서는 꼭 로그를 사용하자 성능이 훨씬 좋음

요청 매핑

  • 요청이 올 때 어떤 컨트롤러가 요청이 되어야 하는 가?
  • url이외에 어떤 것이 있을까
@RestController  
public class MappingController {  
        private Logger log = LoggerFactory.getLogger(getClass());  
        @RequestMapping({"/hello-basic","/hello-go"})  
        public String helloBasic() {  
            log.info("helloBasic");  
            return "ok";  
        }

@ReqeustMapping

  • 해당 url이 요청되면 매핑된 메서드 호출
  • 매핑은 {} 배열로 여러개 매핑 가능
  • url요청시 끝에 '/'의 유무는 차이가 없음
@RequestMapping(value = "/mapping-get-v1", method = RequestMethod.GET)

@GetMapping(value = "/mapping-get-v2")
  • 무조건 get방식의 맞는 url요청만 응답
PathVariable 경로 변수 사용
@GetMapping("/mapping/users/{userId}/orders/{orderId}")  
public String mappingPath(@PathVariable("userId") String userId, @PathVariable Long  
        orderId) {  
    log.info("mappingPath userId={}, orderId={}", userId, orderId);  
    return "ok";  
}
  • 최근에 자주 사용하는 방식
  • @PathVariable 애노테이션 이용해서 요청에 있는 키-값을 가져와서 사용 가능
  • @PathVariable 이름이랑 파라미터 이름 같으면 괄호 생략가능
특정 파라미터 조건 매핑
@GetMapping(value = "/mapping-param", params = "mode=debug")
    public String mappingParam() {
    log.info("mappingParam");
    return "ok";
}
  • url에 ?mode=debug 가 붙어있는 경우에 동작함
  • 요즘 잘 안 쓰임
특정 헤더 조건 매핑
@GetMapping(value = "/mapping-header", headers = "mode=debug")
    public String mappingHeader() {
    log.info("mappingHeader");
    return "ok";
}
  • 해더 값에 mode=debug 값이 있는 경우에 동작
미디어 타입 조건 매핑 -HTTP 요청 Content-Type, consume
@PostMapping(value = "/mapping-consume", consumes = "application/json")
public String mappingConsumes() {
    log.info("mappingConsumes");
    return "ok";
}
  • 미디어 타입이 json 인 경우에 동작
  • 서버 입장에서 요청을 소비하니까 consumes 이라고 함 = 요청헤더의 컨텐트 타입
  • 타입이 안 맞으면 415에러
미디어 타입 조건 매핑 -HTTP 요청 Accept , produce
@PostMapping(value = "/mapping-produce", produces = "text/html")
    public String mappingProduces() {
    log.info("mappingProduces");
    return "ok";
}
  • 요청의 Accept헤더 기반으로 미디어타입 매핑
  • 타입이 안 맞으면 406에러
차이점

Content-type

  • http 메세지에 정의된 데이터 형식
  • 데이터를 싣어 보낼 때 해당 데이터가 어떤 타입인지 정의해서 보낸다 그래야 서버에서 별도 처리없이 아 json이구나 인식하고 데이터를 처리하는 것

Accept

  • 클라이언트가 서버한테 요청할 때 쓰는 거
  • 요청 메세지에 text/html이라고 쓴 거면 클라이언트는 해당 text/html 타입 아니면 화면에 출력하지 않겠다 그러니 꼭 text/html 타입으로 넘겨줘라고 서버에 요청을 하는것 지키든 말든 서버 마음이긴 함

참고
https://dololak.tistory.com/630


요청 매핑 - API 예시

  • 회원 목록 조회: GET /users
  • 회원 등록: POST /users
  • 회원 조회: GET /users/{userId}
  • 회원 수정: PATCH /users/{userId}
  • 회원 삭제: DELETE /users/{userId}
  • url은 같아도 방식으로 구분이 가능
@RestController  
@RequestMapping("/mapping/users")  
public class MappingClassController {  
    @GetMapping("/{userId}")  
    public String findUser(@PathVariable String userId) {  
        return "get userId=" + userId;  
    }  
    @PatchMapping("/{userId}")  
    public String updateUser(@PathVariable String userId) {  
        return "update userId=" + userId;  
    }
}

Http요청 - 기본 , 헤더 조회

헤더 조회 방법 알아보기

@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  
    ) {  
        log.info("request={}", request);  
        log.info("response={}", response);  
        //메서드 조회 get , post
        log.info("httpMethod={}", httpMethod); 
        // 위치 정보 : jp , kr 
        log.info("locale={}", locale);  
        //모든http 헤더를 맵형태로 만들어서 조회 
        log.info("headerMap={}", headerMap);  
        // 특정해더 조회 
        log.info("header host={}", host);  
        // 특정 쿠키 조회 
        log.info("myCookie={}", cookie);  
        return "ok";  
    }  
}
  • 애노테이션 기반 컨트롤러는 다양한 파라미터 받기 가능 정형화 된 거 아니니까

request=org.apache.catalina.connector.RequestFacade@5dc99ba7
response=org.apache.catalina.connector.ResponseFacade@425f4a2e
httpMethod=GET
locale=ko_KR
headerMap={host=[localhost:8080], connection=[keep-alive], cache-control=[max-age=0], sec-ch-ua=["Whale";v="3", "Not-A.Brand";v="8", "
header host=localhost:8080
myCookie=null

MultiValueMap

  • map과 유사 / 하나의 키에 여러 값을 배열 행태로 받음
  • 하나의 키에 여러 값 받을 때 사용 ex ) http header , 쿼리파라미터 등
  • keyA=value1&keyA=value2

리퀘스트 파라미터 목록
https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-methods/arguments.html

응답 값 목록
https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-methods/return-types.html


HTTP 요청 파라미터 - 쿼리파라미터 , html form

서블릿이 http요청 조회했던 방법을 스프링에서 어떻게 바뀌었는지 확인

http요청 메서지를 서버로 전달하는 방법 3가지

  1. GET - 쿼리 파라미터
    1. ?username=hello&age=20
    2. url에 직접 데이터를 넣어서 전달하는 방식
  2. Post - html form
    1. content-type: application/x-www-form-urlencoded
    2. 메세지 바디에 쿼리 파라미터 형식으로 전달 username=hello&age=20
  3. HTTP message body
    1. API에서 주로 사용 JSON

보통 이 3가지로 귀결되고 우리는 1,2 번을 알아보자
1,2 번은 방식은 다르지만 데이터를 보내는 형식은 같기에 같은 메서드로 요청에 있는 값을 꺼내서 확인할 수 있다

HttpServletRequest 의 request.getParameter()
이를 파라미터 조회 라고 칭함

@Slf4j  
@Controller  
public class ReqeustParamController {  
    @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");  
    }  
}

void타입으로 반환인데 이런 경우 응답에 직접 갑을 넣으면 view조회 하지않고 해당 값을 그대로 클라이언트에게 반환시켜준다
response.getWriter().write("ok");


HTTP 요청 파라미터 - @RequestParam

  • 스프링이 제공하는 애노테이션을 사용해서 편리하게 요청 데이터를 가져오자!
level1
@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 애노테이션 사용시 RestController랑 같은 의미 그냥 ok리턴하면 해당하는 물리적 위치 찾으려고 뷰리졸버가 찾는데 그걸 무시(view조회 무시)하고 문자열 그대로 반환시킴
  • @RequestParam 속성을 파라미터 이름 처럼 사용 해당 괄호는 파라미터 이름을 쓰면 알아서 요청에서 가지고 와서 씀
level2
@ResponseBody  
@RequestMapping("/request-param-v3")  
public String requestParamV3(  
        @RequestParam String username,  
        @RequestParam int age) {  
    log.info("username={}, age={}", username, age);  
    return "ok";  
}
  • 변수명과 파라미터 이름이 같으면 () 괄호 생략가능
level3
@ResponseBody  
@RequestMapping("/request-param-v4")  
public String requestParamV4(String username, int age) {  
    log.info("username={}, age={}", username, age);  
    return "ok";  
}
  • 단순 타입인 경우 (int, String, Integer)는 @RequestParam마저 생략이 가능
  • 다만 너무 없으면 좀 그러니 level2를 쓰도록 하자
  • @RequestParam생략시 required=false가 적용됨
파라미터 필수 여부
@ResponseBody  
@RequestMapping("/request-param-required")  
public String requestParamRequired(  
        @RequestParam(required = true) String username,  
        @RequestParam(required = false) Integer age) {  
    log.info("username={}, age={}", username, age);  
    return "ok";  
}
  • 디폴트는 true / true로 설정한 값이 요청에 없으면 400에러가 뜸
  • 기본값이 설정되어 있지 않는 경우 빈문자열도 그대로 통과 시킴
  • int로 설정한 파라미터에 요청 null(빈문자열)입력시 500에러 발생 null을 받을 수 있는 Integer같은 타입으로 변환이 필요 혹은 기본값 사용시 처리 가능
기본값 적용
@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";  
}
  • defaultValue로 기본값 설정 가능
  • 빈문자열 혹은 아무것도 입력하지 않으면 기본값으로 요청을 해석 후 처리
  • defaultValue가 설정되면 짜피 다 들어가니까 required가 없어도 오케
파라미터를 map으로 조회
@ResponseBody  
@RequestMapping("/request-param-map")  
public String requestParamMap(@RequestParam Map<String, Object> paramMap) {  
    log.info("paramap={}",paramMap);  
    log.info("username={}, age={}", paramMap.get("username"),  
            paramMap.get("age"));  
    return "ok";  
}
  • map을 파라미터로 설정하면 해당 쿼리 파라미터 모든 값이 map형태로 반환됨

    paramap={username=hello, age=20}

파라미터 값이 여러개인 경우 MultiValueMap을 사용해도 되지만 보통 파라미터 값은 1개인 경우가 많아서 Map으로도 충분


HTTP요청 파라미터 - @ModelAttribute

요청 파라미터 받아서 객체 만들어서 쓰는 경우가 많음
이걸 자동화 해준 것이 @ModelAttribute

@Data  
public class HelloData {  
    private String username;  
    private int age;  
}
  • @Data
    • getter , setter, toString 등등 필요한 거 대부분 자동 적용해줌
@ResponseBody  
@RequestMapping("/model-attribute-v1")  
public String modelAttributeV1(@ModelAttribute HelloData helloData) {  
    log.info("username={}, age={}", helloData.getUsername(),  
            helloData.getAge());  
    return "ok";  
}
  • 진짜... 그냥 알아서 다 만들어주네;; 놀랍다 놀라워

@ModelAttribute 실행 순서

  1. HelloData 객체 생성
  2. 요청 파라미터 이름으로 HelloData객체 프로퍼티 찾음
  3. 해당 프로퍼티의 setter 호출해서 파라미터 값 입력 (바인딩) 함
    ex) username이면 setUsername() 메서드 찾아서 값 입력

프로퍼티
객체에 getUsername , setUsername 등의 메서드가 있으면 이 객체는 username이라는 프로퍼티를 가지고 있다고 인식함

바인딩 오류

  • 숫자 넣을 곳에 문자 넣으면 BindException발생
@ResponseBody  
@RequestMapping("/model-attribute-v2")  
public String modelAttributeV2(HelloData helloData) {  
    log.info("username={}, age={}", helloData.getUsername(),  
            helloData.getAge());  
    return "ok";  
}
  • 이것도 RequestParam 처럼 생량 가능

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

  • 주로 json
  • 요청 파라미터와 다르게 http message body에 직접 데이터가 오는 경우 위에 썼던 @RequestParam , @ModelAttribute 사용 못함 ;;;
  • 쿼리데이터랑 html form의 방법 외에는 그냥 지금 밑에 방법 쓰면 됨 그 외는 데이터를 직접 가지고 와야함 !
  • 스트림으로 데이터 읽고 바꾸고 했던 거를 스프링의 http 메세지 컨버터가 자동으로 처리해줌
level 1
@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");  
}
  • 서블릿 처럼 처리
  • 서블릿 request객체의 getInsputStream 메서드를 호출해서 스트림을 반환하고 해당 스트림에 있는 바이트 데이터들을 utf-8로 인코딩해서 문자열로 반환
  • 해당 문자열을 보고 내부에서 처리하는 것
level2
@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");  
}
  • 서블릿이 필요없으니까 빼고 , 파라미터에 InputStream을 바로 받음
  • 위에서 request에 있는 거 쓰는 게 아니라 InputStream을 바로 파라미터에 받아서 해당 스트림 안에 http 메세지 바이트 코드가 있을테니까 해당 코드를 또 인코딩하고 내부에서 씀

스프링 mvc가 지원하는 파라미터

  • InputStream(Reader): HTTP 요청 메시지 바디의 내용을 직접 조회
  • OutputStream(Writer): HTTP 응답 메시지의 바디에 직접 결과 출력
level 3
@PostMapping("/request-body-string-v3")  
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) {  
    // http메세지 컨버터가 해당 코드 알아서 실행시켜주나봄 String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);    String messageBody = httpEntity.getBody(); // 메세지에 있는 바디를 끄냄  
    log.info("messageBody={}", messageBody);  
    return new HttpEntity<>("ok");  
}
  • HttpEntity
    • header , body 정보를 간편하게 조회
    • 요청 파라미터 @ReqeustParam @ModelAttribute 와 노관계
    • 응답에서도 사용 가능하며 헤더 정보 및 메세지 바디 정보를 반환 가능
    • 리턴할 때 view 정보를 반환하지 않음

HttpEntity를 상속한 다음 객체들도 같은 기능 제공

  1. RequestEntity
    • HttpMethod, url 정보가 추가, 요청에서 사용
  2. ResponseEntity
    • HTTP 상태 코드 설정 가능, 응답에서 사용
    • return new ResponseEntity<\String>("Hello World", responseHeaders,
      HttpStatus.CREATED)

스프링 내부에서 http 메세지 바디 읽어서 문자 혹은 객체 변환해주는 http 메세지 컨버터

현업에서 쓰는 거
@ResponseBody  
@PostMapping("/request-body-string-v4")  
public String requestBodyStringV4(@RequestBody String messageBody) {  
    log.info("messageBody={}", messageBody);  
    return "ok";  
}

@RequestBody

  • 편리하게 http 메세지 바디 정보 조회 가능
  • 헤더 정보가 필요한 경우 위에서 봤던 HttpEntity , @ReqeustHeader쓰자

요청 파라미터와 http 메세지 바디

  1. 요청 파라미터 조회 : @ReqeustParam , @ModelAttribute
  2. http 메세지 조회 : @RequestBody

@ResponseBody

  • 뷰 쓰는 게 아니라 바로 http 메세지 바디에 데이터 담아서 전달

HTTP 요청 메세지 JSON

level1
@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 data = objectMapper.readValue(messageBody, HelloData.class);  
    log.info("username={}, age={}", data.getUsername(), data.getAge());  
    response.getWriter().write("ok");  
}
  • 서블릿 request사용해서 http메세지 바디 데이터를 스트림으로 받아서 컨버팅후 읽음
  • json의 데이터를 objectMapper를 통해 HelloData객체로 변환
level2
@ResponseBody  
@PostMapping("/request-body-json-v2")  
public String requestBodyJsonV2(@RequestBody String messageBody) throws IOException {  
    HelloData data = objectMapper.readValue(messageBody, HelloData.class);  
    log.info("username={}, age={}", data.getUsername(), data.getAge());  
    return "ok";  
}
  • @ReqeustBody를 통해서 http메세지 취득후 ObjectMapper로 객체 변호나

문자 변환후 다시 json 변환 과정 불편..

level3
@ResponseBody  
@PostMapping("/request-body-json-v3")  
public String requestBodyJsonV3(@RequestBody HelloData data) {  
    log.info("username={}, age={}", data.getUsername(), data.getAge());  
    return "ok";  
}

@RequestBody 객체 파라미터

  • @RequestBody, @HttpEntity 사용 시 http메세지 컨버터가 http 바디 내용을 원하는 문자나 객체로 변환시켜서 반환해줌 -> json도 객체로 변환해준다 !

  • @RequestBody는 생략 불가 // 생략하면 1,2, 요청파라미터에서 했던 기본값 이외의 값 HelloData가 들어있으니 @ModelAttribute가 적용되어 에러가 뜬다

level 3 -2
@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";  
}
level4
@ResponseBody  
@PostMapping("/request-body-json-v5")  
public HelloData requestBodyJsonV5(@RequestBody HelloData data) {  
    log.info("username={}, age={}", data.getUsername(), data.getAge());  
    return data;  
}

@ResponseBody

  • 문자 뿐만 아니라 객체도 http 바디에 넣어서 반환 가능
  • HttpEntity 사용해도 괜찮

@RequestBody 요청

  • JSON 요청 HTTP 메시지 컨버터 객체
    @ResponseBody 응답
  • 객체 HTTP 메시지 컨버터 JSON 응답

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

  1. 정적 리소스
  • 예) 웹 브라우저에 정적인 HTML, css, js를 제공할 때는, 정적 리소스를 사용한다.
  1. 뷰 템플릿 사용
  • 예) 웹 브라우저에 동적인 HTML을 제공할 때는 뷰 템플릿을 사용한다.
  1. HTTP 메시지 사용
  • HTTP API를 제공하는 경우에는 HTML이 아니라 데이터를 전달해야 하므로, HTTP 메시지 바디에 JSON 같은 형식으로 데이터를 실어 보낸다
정적 리소스
  • 아래 디렉토리에 있는 정적 리소스 제공
    /static , /public , /resources , /META-INF/resources

  • src/main/resources 는 정적 리소스 보관 장소이며 클래스 패스의 시작 경로임

정적 리소스는 파일 변경없이 그대로 서비스 하는 것

뷰템플릿
  • 동적인 html 제공
  • 뷰템플릿을 거쳐서 html 생성되고 뷰가 응답 만들어서 전달
  • 기본 뷰 템플릿 경로
    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";  
    }  
    //비추  
    @RequestMapping("/response/hello")  
    public void responseViewV3(Model model) {  
        model.addAttribute("data", "hello!!");  
    }  
}

https://www.inflearn.com/questions/961287

String 반환하는 경우

  • @ResponseBody 없으면 리턴값으로 뷰리졸버 찾아서 뷰 찾고 랜더링
  • @ResponseBody 있으면 리턴값 그대로 바디에 넣어서 반환

Void 반환

  • @Controller 사용하고 HttpServletResponse ,OutputStreawm 같은 http 메세지 처리하는 파라미터가 없는 경우 요청URL 참고해서 논리뷰 이름으로 사용
  • 다만 비추임

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

  • 데이터를 메세지 바디에 JSON같은 형식으로 데이터를 실어 보냄
level1
@GetMapping("/response-body-string-v1")  
public void responseBodyV1(HttpServletResponse response) throws IOException  
{  
    response.getWriter().write("ok");  
}
  • 서블릿처럼 http객체를 사용해서 직접 응답을 넣어줌
level2
@GetMapping("/response-body-string-v2")  
public ResponseEntity<String> responseBodyV2() {  
    return new ResponseEntity<>("ok", HttpStatus.OK);  
}
  • 반환값이 ResponseEntity<\String> <- httpEntity 상속 메세지 헤더 및 바디 정보 가짐
  • http 응답 코드 설정 가능
level3
@ResponseBody  
@GetMapping("/response-body-string-v3")  
public String responseBodyV3() {  
    return "ok";  
}
  • @ResponseBod로 문자열 그대로 바디에 실어서 반환
json level1
@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);  
}
  • ResponseEntity<\HelloData> 객체를 반환
  • 상태코드도 넣어줄 수 있고 JSON그대로 넣어서 반환해줌
json level2
@ResponseStatus(HttpStatus.OK)  
@ResponseBody  
@GetMapping("/response-body-json-v2")  
public HelloData responseBodyJsonV2() {  
    HelloData helloData = new HelloData();  
    helloData.setUsername("userA");  
    helloData.setAge(20);  
    return helloData;  
}
  • HelloData 객체 반환
  • 응답 코드는 애노테이션을 사용해서 설정가능 = 정적

@RestController

  • 클래스에 해당 애노테이션 붙이면 하위 메서드에 @ResponseBody붙은 것과 같은 효과를 줌

HTTP 메세지 컨버터

뷰템플릿으로 html 만들어서 응답하는 것이 아니라 API처럼 json데이터를 바디에 직접 읽거나 쓰는 경우 HTTP 메세지 컨버터를 사용

@ResponseBody처리 순서
1. http 바디에 문자내용 직접 반환
2. viewReslover 대신 HttpMessageConverter가 동작
3. 반환에 따라 StringHttpMessagConverter / MappiongJackson2HttpMessageConverter 가 동작함
4. byte , string 객체 등 여러 httpmessageconverter가 기본으로 등록되어 제공

응답의 경우 accept헤더와 컨트롤러 타입 두개 다 비교하고 컨버터를 선택

아래의 상황에 http메세지컨버터가 동작

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

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

순서
읽기 : canRead() -> read()
쓰기 : canWrite -> write(

스프링 부트가 제공해주는 기본 메세지 컨버터


위에서부터 차례대로 우선순위 확인

  1. ByteArrayHttpMessageConverter : byte[] 데이터를 처리한다.
  • 클래스 타입: byte[] , 미디어타입: / ,
  • 요청 예) @RequestBody byte[] data
  • 응답 예) @ResponseBody return byte[] 쓰기 미디어타입 application/octet-stream
  1. StringHttpMessageConverter : String 문자로 데이터를 처리한다.
  • 클래스 타입: String , 미디어타입: /
  • 요청 예) @RequestBody String data
  • 응답 예) @ResponseBody return "ok" 쓰기 미디어타입 text/plain
  1. MappingJackson2HttpMessageConverter : application/json
  • 클래스 타입: 객체 또는 HashMap , 미디어타입 application/json 관련
  • 요청 예) @RequestBody HelloData data
  • 응답 예) @ResponseBody return helloData 쓰기 미디어타입 application/json 관련

StringHttpMessageConverter 사용됨

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

MappingJackson2HttpMessageConverter

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

그 외 전부 안 되면 탈락

content-type: text/html
@RequestMapping
void hello(@RequestBody HelloData data) {}
http 요청 데이터 읽기
  • 요청이 오면 컨트롤러에서 @RequestBody ,HttpEntity 파라미터를 사용
  • 메세지 컨버터가 메세지 읽을 웃 있는지 canRead()로 확인
    • 대상 클래스 타입을 지원하는지 bytes[], String , HelloData
    • 요청 content-type미디어 타입 지원하는지 text/plain , application/json , /
  • canRead()에서 조건 만족하면 read()를 호출해서 객체 생성 후 반환
http 응답 데이터 생성
  • 컨트롤러에서 @ResponseBody , HttpEntity 로 값이 반환
  • 메세지 컨버터가 메세지 쓸 수 있는지 carnWrite()로 확인
    • 대상 클래스 타입 지원하는가 return : byte[] , String , HelloData
    • Accept미디어 타입 지원하는가 ext/plain , application/json , /
  • canWrite() 조건을 만족하면 write() 호출해서 바디에 데이터 생성해줌

https://www.inflearn.com/questions/751430


요청 매핑 핸들러 어뎁터 구조

메세지 컨버터는 어디쯤에서 사용되는가??

핸들러 어댑터에서 관련

  • 애노테이션 기반 컨트롤러 (@ReqeustMapping)처리하는 핸들러 어댑터인 RequestMappingHandlerAdapter 요청 매핑 핸들러 어댑터 이 있음
RequestMappiongHandlerAdapter 동작방식

리졸버 = 일단 뭐든 처리해주는 친구

ArgumentResolver
  • 매우 다양한 파라미터를 사용하는 애노테이션 기반 컨트롤러에게 파라미터를 유연하게 처리하게 처리해주는 리졸버
  • 핸들러 어댑터는 컨트롤러를 호출하기 전에 해당 컨트롤러가 필요로 하는 파라미터들을 준비할 수 있는 ArgumentResolver에게 요청을 해서 파라미터 값을 생성하고 핸들러어댑터는 리졸버가 다 준비되면 그제서야 옛다하고 컨트롤러에게 파라미터 넘겨주고 실행시킴
  • 그러니까 파라미터 암거나 막 넣어도 임마가 알아서 넣어줬으니까 신경 안쓰고 유연하게 만들 수 있었던 것
  • 해당 리졸버는 인터페이스로 약 30개 넘는 스프링이 제공해주는 구현된 리졸버를 통해서 다양한 컨트롤러의 파라미터를 지원해줄 수 있음
ReturnValueHandler
  • 응답값을 변환하고 처리
  • 컨트롤러에서 String ,void 등등 반환해도 동작해주는 게 욤마 덕분

HTTP메세지 컨버터

메세지 컨버터를 사용하는 경우는 http 메세지 바디값을 직접 건드릴 때

@RequestBody , @ResponseBody를 사용할 때 메세지 컨버터 호출

요청
  • @RequestBody처리하는 ArgumentResolver가 있고 HttpEntity처리하는 리졸버가 있음 이 리졸버들이 http메세지 컨버터 사용할 때 필요한 객체를 생성해서 컨트롤러에 넣음
응답
  • @ResponseBody , HttpEntity 처리하는 ReutrnValueHandler가 있음 여기서 메세지 컨트롤러 호출해서 응답 결과를 만든다

https://www.inflearn.com/questions/944109

https://www.inflearn.com/questions/858726

https://www.inflearn.com/questions/825997

https://www.inflearn.com/questions/947991

https://www.inflearn.com/questions/947991


출처
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8

728x90

'Programing > Spring Boot' 카테고리의 다른 글

[MVC-2] 타임리프  (0) 2023.08.08
[MVC-1] 스프링 MVC -웹 페이지  (0) 2023.08.08
[MVC-1] 스프링 MVC - 구조 이해  (0) 2023.08.01
[MVC-1] MVC 프레임 워크 만들기  (0) 2023.08.01
[MVC-1] 서블릿 , JSP , MVC패턴  (0) 2023.07.25