Notice
Recent Posts
Recent Comments
Link
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Tags
more
Archives
Today
Total
관리 메뉴

Kwon's Study Blog !

[Spring] 스프링 MVC 핵심 기술 - 스프링 MVC 기본 기능 본문

Spring

[Spring] 스프링 MVC 핵심 기술 - 스프링 MVC 기본 기능

순샤인 2022. 5. 11. 18:24

이글은 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 강의를 학습 후 
나중에 다시 복습하기 위해 정리한 글입니다.
문제시 비공개로 처리하겠습니다.
 

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 - 인프런 | 강의

웹 애플리케이션을 개발할 때 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 MVC의 핵심 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., -

www.inflearn.com

 

목차

  1. 로깅 간단히 알아보기
  2. 요청 매핑
  3. HTTP 요청
  4. HTTP 응답
  5. HTTP 메시지 컨버터
  6. 요청 매핑 핸들러 어댑터 구조

1. 로깅 간단히 알아보기

운영 시스템에서는 System.out.println() 같은 시스템 콘솔을 사용해서 필요한 정보를 출력하지 않고,

별도의 로깅 라이브러리를 사용해서 로그를 출력한다.

 

스프링 부트를 사용하면 기본 로깅 라이브러리가 함께 포함된다.

  • SLF4J
  • Logback

SLF4J는 통합적인 로깅 인터페이스이고, 이것의 구현체인 Logback 로그 라이브러리를 대부분 사용한다.

로그 예제

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

        log.debug("String concat log="+name);
        return "ok";
    }
}

2022-05-05 16:16:15.367  INFO 31196 --- [nio-8080-exec-1] hello.springmvc.basic.LogTestController  : info log=Spring
2022-05-05 16:16:15.372  WARN 31196 --- [nio-8080-exec-1] hello.springmvc.basic.LogTestController  : warn log=Spring
2022-05-05 16:16:15.372 ERROR 31196 --- [nio-8080-exec-1] hello.springmvc.basic.LogTestController  : error log=Spring

 매핑 정보

  • @RestController
    • @Controller 는 반환 값이 String 이면 뷰 이름으로 인식된다. 그래서 뷰를 찾고 뷰가 렌더링 된다.
    • @RestController 는 뷰를 찾는것이 아니라 HTTP 메시지 바디에 바로 입력한다.

테스트

  • 로그 출력
    • 시간, 로그 레벨, 프로세스 ID, 쓰레드명, 클레스 명, 로그 메시지
  • 로그 레벨
    • TRACE > DEBUG > INFO > WARN > ERROR
    • 개발 서버는 DEBUG 출력
    • 운영 서버는 INFO 출력
  • @Slf4j 를 사용하면 private final logger ... 를 주석해도 log 사용 가능 !

로그 레벨 설정

- application.properties 파일에

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

# hello.springmvc 패키지와 그 하위 로그 레벨 설정
logging.level.hello.springmvc = debug

로그 사용시 장점

  • 쓰레드 정보, 클래스 이름 같은 부가 정보를 함께 볼 수 있고, 출력 모양 조절 
  • 로그 레벨에 따라 개발 서버, 운영 서버를 독립적으로 로그를 상황에 맞게 조절
  • 파일, 네트워크 등 별도의 위치에 남길 수 있음
  • System.out 보다 성능이 좋음(내부 버퍼링, 멀티 쓰레드 등등)

 

참고 사이트 : 스프링 부트가 제공하는 로그 기능 


2. 요청 매핑

@Slf4j
@RestController
public class MappingController {
    /**
     * 기본 요청
     * 둘다 허용 /hello-basic , /hello-basic/
     * HTTP 메서드 모두 허용 GET, POST, PUT, PATCH, HEAD, DELETE
     * 매핑 URL 을 배열로 다중 설정 가능. {"/hello-basic","/hello-go"}
     * @return
     */
    @RequestMapping("/hello-basic")
    public String helloBasic(){
        log.info("helloBasic");
        return "ok";
    }

    /**
     * method 특정 HTTP 메서드 요청만 허용
     * 다른 Method 요청을 하면 HTTP 405 상태코드(Method Not Allowed) 를 반환
     */
    @RequestMapping(value = "/mapping-get-v1",method = RequestMethod.GET)
    public String mappingGetV1(){
        log.info("mappingGetV1");
        return "ok";
    }

    /**
     * 편리한 축약 애노테이션
     * @GetMapping
     * @PostMapping
     * @PutMapping ...
     * 내부 코드를 보면 @RequestMapping 과 method 를 지정해서 사용하는 것을 확인할 수 있다.
     * @return
     */
    @GetMapping(value = "/mapping-get-v2")
    public String mappingGetV2(){
        log.info("mappingGetV2");
        return "ok";
    }

    /**
     * PathVariable (경로 변수) 사용
     * 변수명이 같으면 생략 가능
     * @PathVariable("userId") String userId -> @PathVariable String userId
     */
    @GetMapping("/mapping/{userId}")
    public String mappingPath(@PathVariable("userId") String data){
        log.info("mappingPath userId={}",data);
        return "ok";
    }

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

    /**
     * 특정 파라미터로 조건 매핑
     * params="mode"
     * params="!mode"
     * params="mode=debug"
     * params={"mode=debug","data=good"}
     */
    @GetMapping(value = "/mapping-param", params = "mode=debug")
    public String mappingParam(){
        log.info("mappingParam");
        return "ok";
    }

    /**
     *  특정 헤더로 조건 매핑
     *  headers="mode"
     *  headers="mode=debug"
     */
    @GetMapping(value = "/mapping-header",headers = "mode=debug")
    public String mappingHeader(){
        log.info("mappingHeader");
        return "ok";
    }

    /**
     *  미디어 타입 조건 매핑 (HTTP 요청 Content-Type)
     *  Content-Type : 요청하는 표현 데이터 형식 (미디어 타입)
     *
     *  consumes="application/json"
     *  consumes="text/plain"
     *  consumes="application/*"
     *  consumes={"text/plain","application/*"}
     *  consumes="*\/*
     *  consumes = MediaType.APPLICATION_JSON_VALUE
     *  미디어 타입이 맞지 않으면 HTTP 415(UnSupported Media Type)을 반환
     */
    @PostMapping(value = "/mapping-consume",consumes = "application/json")
    public String mappingConsumes(){
        log.info("mappingConsumes");
        return "ok";
    }

    /**
     *  미디어 타입 조건 매핑 (HTTP 요청 Accept)
     *  Accept : 클라이언트가 선호하는 표현 데이터 형식 (미디어 타입)
     *
     *  produces="text/html"
     *  produces={"text/plain","application/*"}
     *  produces = MediaType.TEXT_PLAIN_VALUE
     *  produces = "text/plain;charset=UTF-8
     *  만약 맞지 않으면 HTTP 406 상태코드 (Not Acceptable)을 반환
     */
    @PostMapping(value = "/mapping-produce",produces = "text/html")
    public String mappingProduces(){
        log.info("mappingProduces");
        return "ok";
    }
}

 

참고 : @ResquestMapping("/mapping/users") 을 클래스 레벨에 두면 
메서드 레벨에서 해당 정보("/mapping/users") 를 조합해서 사용한다.

3. HTTP 요청

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){

	// MultiValueMap : map과 유사, 하나의 키에 여러 값을 받을 수 있음

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

 

참고 : @RestController -> 모든 메서드에 @ResponseBody 가 붙음.

HTTP 요청 파라미터

@Slf4j
@Controller
public class RequestParamController {

    /**
     * HTTP 파라미터 이름이 변수 이름과 같으면 @RequestParam(name="xx") 생략 가능
     */
    @ResponseBody
    @RequestMapping("/request-param-v3")
    public String requestParamV3(
            @RequestParam String username,
            @RequestParam int age){
        log.info("username={},age={}",username,age);
        return "ok";
    }

    /**
     * String, int 등이 단순 타입이면 @RequestParam 도 생략 가능
     */
    @ResponseBody
    @RequestMapping("/request-param-v4")
    public String requestParamV4(String username, int age){
        log.info("username={}, age={}",username,age);
        return "ok";
    }

    /**
     *  파라미터 필수 여부 - required
     *  주의 !
     *  /request-param -> username 이 없으므로 예외
     *  /request-param?username= -> 빈문자열로 통과
     */
    @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";
    }

    /**
     *  파라미터 기본 값 적용 - defaultValue
     *  빈 문자에도 적용됨
     */
    @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";
    }

    /**
     *  파라미터를 map 으로 조회
     * @RequestParam Map, MultiValueMap
     */
    @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";
    }

    /**
     * @ModelAttribute 사용
     * 파라미터 명만 일치한다면 마법처럼 helloData 객체 프로퍼티에 바인딩이 된다.
     *
     * 다음 과정이 자동화 된다.
     * @RequestParam String username
     * @RequestParam int age
     * HelloData data = new HelloData()
     * data.setUsername(username)
     * data.setAge(age)
     *
     * 참고 : model.addAttribute(helloData) 코드도 자동 적용됨.
     */
    @ResponseBody
    @RequestMapping("/model-attribute-v1")
    public String modelAttributeV1(@ModelAttribute HelloData helloData){
        log.info("username={}, age={}",helloData.getUsername(),helloData.getAge());
        return "ok";
    }

    /**
     * @ModelAttribute 도 생략 가능
     * @RequestParam 도 생략 가능 -> 너무 생략하다보면 혼란이 발생 가능
     * 
     * 스프링은 해당 생략시 다음과 같은 규칙 적용
     * String, int, Integer 같은 단순 타입은 @RequestParam
     * 나머지는 @ModelAttribute (argument resolver 로 지정해둔 타입 외 !)
     */
    @ResponseBody
    @RequestMapping("/model-attribute-v2")
    public String modelAttributeV2(HelloData helloData){
        log.info("username={}, age={}",helloData.getUsername(), helloData.getAge());
        return "ok";
    }
}

HTTP 요청 메시지

HTTP 메시지 바디에 데이터를 직접 담아서 요청.

  • HTTP API 에서 주로 사용 JSON, XML, TEXT
  • POST, PUT, PATCH

 

요청 파라미터와 다르게, HTTP 메시지 바디를 통해 데이터가 직접 넘어오는 경우는 

@RequestParam, @ModelAttribute를 사용할 수 없다.

위의 두 경우는 쿼리 파라미터로 들어와야 데이터를 담을 수 있다.

html form POST로 요청되는 경우 (Content Type = x-www-form-urlencoded)

쿼리 파라미터로 인정되고, 메시지 바디를 통해 들어오기 때문에

-> @RequestParam, @ModelAttribute 으로 데이터를 담을 수 있고, 메시지 바디의 데이터를 직접 읽어 들일 수 있다.

단순 텍스트

@Slf4j
@Controller
public class RequestBodyStringController {

    /*
        HTTP 메시지 바디의 데이터를 InputStream 을 사용해서 직접 읽을 수 있다.
     */
    @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");
    }

    /*
        스프링 MVC 는 다음 파라미터를 지원함.
        InputStream(Reader) : HTTP 요청 메시지 바디의 내용을 직접 조회
        OutputStream(Writer) : HTTP 응답 메시지 바디에 직접 결과 출력
     */
    @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");
    }

    /*
        HttpEntity : HTTP header, body 정보를 편리하게 조회
        요청
        - 메시지 바디 정보를 직접 조회 (@RequestParam x , @ModelAttribute x)
        - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
        응답
        - 메시지 바디 정보 직접 반환 (view 조회 x)
        - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용

        HttpEntity 를 상속받은 객체
        - RequestEntity -> HttpMethod, url 정보 추가, 요청에서 사용
        - ResponseEntity -> HTTP 상태 코드 설정 가능, 응답에서 사용

        참고 : 스프링 MVC 내부에서 HTTP 메시지 바디를 읽어서 문자나 객체를 변환해서 전달하는데
        이때 메시지 컨버터(HttpMessageConverter) 라는 기능을 사용한다.

     */
    @PostMapping("/request-body-string-v3")
    public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity){
        String messageBody = httpEntity.getBody();
        log.info("messageBody={}",messageBody);
        return new HttpEntity<>("ok");
    }

    /*
        @RequestBody
        - 메시지 바디 정보를 직접 조회 (@RequestParam x , @ModelAttribute x)
        - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용

        @ResponseBody
        - 메시지 바디 정보 직접 반환 (view 조회 x)
        - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
     */
    @ResponseBody
    @PostMapping("/request-body-string-v4")
    public String requestBodyStringV4(@RequestBody String messageBody){
        log.info("messageBody={}",messageBody);
        return "ok";
    }
}
  • InputStream : HTTP 메시지 바디의 내용을 직접 조회(인코딩 필요)
  • HttpEntity : 요청, 응답 처리
    • 요청 : HTTP Header, Body 정보를 직접 조회
    • 응답 : 메시지 바디 정보 직접 반환(view 조회 x)
  • RequestEntity : HttpEntity 를 상속받은 객체, HttpMethod, url 정보 조회 추가
  • ResponseEntity : HttpEntity 를 상속받은 객체,  HTTP 상태 코드 설정 가능
  • @RequestBody : HTTP 메시지 바디의 내용을 직접 조회
  • @ResponseBody : 메시지 바디 정보 직접 반환(view 조회 x)

JSON

HTTP API 에서 주로 사용하는 JSON 데이터 형식

@Slf4j
@Controller
public class RequestBodyJsonController {
    private ObjectMapper objectMapper = new ObjectMapper();

    /*
        HttpServletRequest 를 사용해서 HTTP 메시지 바디에서 데이터를 읽어와서, 문자로 변환
        -> 문자로된 JSON 데이터를 Jackson 라이브러리인 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 data = objectMapper.readValue(messageBody, HelloData.class);
        log.info("username={}, age={}",data.getUsername(),data.getAge());
        response.getWriter().write("ok");
    }

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

        참고 : @RequestBody 도 HttpMessageConverter 사용 -> StringHttpMessageConverter 적용

        @ResponseBody
        - 모든 메서드에 @ResponseBody 적용
        - 메시지 바디 정보 직접 반환 (view 조회 x)
        - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
     */
    @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";
    }

    /*
        @RequestBody 생략 불가능 (@ModelAttribute 가 적용되버림 ...)
        HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter 적용 (content-type: application/json)

        주의 ! : content-type 이 application/json 여야 JSON 을 처리할 수 있는 HTTP 메시지 컨버터가 실행된다.

        @ResponseBody
        - HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter 적용
        - Accept : application/json (클라가 선호하는 데이터 형식) 해줘야함.
     */
    @ResponseBody
    @PostMapping("/request-body-json-v3")
    public HelloData requestBodyJsonV3(@RequestBody HelloData data){
        log.info("username={}, age={}",data.getUsername(),data.getAge());
        return data;
    }

    /*
        위의 경우를 HttpEntity 로 적용
     */
    @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";
    }
}

4. HTTP 응답

스프링 (서버) 에서 응답 데이터를 만드는 방법은 크게 3가지이다.

  • 정적 리소스 : 웹 브라우저에 정적인 HTML, CSS, JS 를 제공할 때, 정적 리소스 사용
  • 뷰 템플릿 사용 : 웹 브라우저에 동적인 HTML 을 제공할 때, 뷰 템플릿 사용
  • HTTP 메시지 사용 : HTTP API를 제공하는 경우 HTML 이 아닌 데이터를 전달해야 하므로, HTTP 메시지 바디에 JSON 같은 형식으로 데이터를 실어 보낸다. 

정적 리소스

스프링 부트는 클래스 패스의 다음 디렉토리에 있는 정적 리소스를 제공

/static, /public, /resources, /META-INF/resources

 

src/main/resources 는 리소스를 보관하는 곳이고, 또 클래스 패스의 시작 경로임.

따라서 다음 디렉토리에 리소스를 넣어두면 스프링 부트가 정적 리소스로 서비스를 제공함.

ex) src/main/resources/static/basic/hello-form.html 이 경로에 파일이 들어있으면

웹 브라우저에서는 http://localhost:8080/basic/hello-form.html 과 같이 실행하면 됨.

뷰 템플릿

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

 

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

src/main/resources/templates

@Controller
public class ResponseViewController {

    /*
        스프링 MVC 의 기본적인 ModelAndView 를 반환하는 경우
        - new 할 때 view 값을 넣어준다.
        - addObject 로 Model 값을 넣어준다.
     */
    @RequestMapping("/response-view-v1")
    public ModelAndView responseViewV1(){
        ModelAndView mv = new ModelAndView("response/hello").addObject("data", "hello!");
        return mv;
    }

    /*
        String 을 반환하는 경우
        @ResponseBody 가 있으면 -> 뷰 리졸버를 실행하지 않고, HTTP 메시지 바디에 직접 response/hello 라는 문자가 입력됨.
        @ResponseBody 가 없으면 -> 뷰 리졸버를 실행(response/hello) 되어 뷰를 찾고 렌더링 됨.
     */
    @RequestMapping("/response-view-v2")
    public String responseViewV2(Model model){
        model.addAttribute("data","hello!!");
        return "response/hello";
    }

    /*
        @Controller 를 사용하고 HttpServletResponse, OutputStream(Writer) 같은 HTTP 메시지 바디를 처리하는
        파라미터가 없으면 요청 URL 을 참고하여 논리 뷰 이름으로 사용
        요청 URL : /response/hello -> 실행 : templates/response/hello.html

        참고 : 이 방식은 명시성이 너무 떨어지고, 이렇게 딱 맞는 경우도 많이 없어서 사용을 거의 안 한다.
     */
    @RequestMapping("/response/hello")
    public void responseViewV3(Model model){
        model.addAttribute("data","hello!!!");
    }
}

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

@Slf4j
@Controller
public class ResponseBodyController {

    /*
        서블릿을 직접 다룰 때 처럼
        HttpServletResponse 객체를 통해서 HTTP 메시지 바디에 직접 OK 응답 메시지를 전달한다.
     */
    @GetMapping("/response-body-string-v1")
    public void responseBodyV1(HttpServletResponse response) throws IOException {
        response.getWriter().write("ok");
    }

    /*
        HttpEntity, ResponseEntity(Http Status 추가)
        ResponseEntity 는 HttpEntity 를 상속 받았다. (HTTP 메시지의 헤더, 바디 정보를 가짐)
        ResponseEntity 는 여기에 HTTP 응답 코드를 설정할 수 있다.

     */
    @GetMapping("/response-body-string-v2")
    public ResponseEntity<String> responseBodyV2(){
        return new ResponseEntity<>("ok", HttpStatus.OK);
    }

    /*
        @ResponseBody 를 사용하면 view 를 사용하지 않고, HTTP 메시지 컨버터를 통해서 HTTP 메시지를 직접 입력할 수 있다.
        ResponseEntity 도 동일한 방식으로 동작한다.
     */
    @ResponseBody
    @GetMapping("/response-body-string-v3")
    public String responseBodyV3(){
        return "ok";
    }

    /*
        HTTP 메시지 컨버터를 통해서 JSON 형식으로 변환되어 반환된다.
     */
    @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 는 HTTP 응답 코드를 설정할 수 있는데,
        @ResponseBody 를 사용하면 이런 것을 설정하기 까다롭다.
        -> @ResponseStatus(HttpStatus.OK) 애노테이션을 사용해서 응답 코드를 설정할 수 있다.
     */
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    @GetMapping("/response-body-json-v2")
    public HelloData responseBodyJsonV2(){
        HelloData helloData = new HelloData();
        helloData.setUsername("userA");
        helloData.setAge(20);
        return helloData;
    }
}
참고 : @Controller 대신에 @RestController 를 사용하면 , 해당 컨트롤러에 모두 @ResponseBody 가 적용되는 효과가 있다. 따라서 뷰 템플릿을 사용하는 것이 아니라, HTTP 메시지 바디에 직접 데이터를 입력한다. 이름 그래도 Rest API 를 만들 때 사용하는 컨트롤러이다.

5. HTTP 메시지 컨버터

뷰 템플릿으로 HTML 을 생성해서 응답하는 것이 아닌, 단순 데이터 값이나 HTTP API 처럼 JSON 데이터를 HTTP 메시지 바디에서 직접 읽거나 쓰는 경우 HTTP 메시지 컨버터를 사용하면 편리하다.

@ResponseBody 

  • HTTP 의 BODY 에 문자 내용을 직접 반환
  • viewResolver 대신 HttpMessageConverter 가 동작
  • 기본 문자처리 : StringHttpMessageConverter
  • 기본 객체처리 : MappingJackson2HttpMessageConverter
  • byte 처리 등등 기타 여러 HttpMessageConverter 가 기본으로 등록되어 있음
참고 : 응답의 경우 클라이언트의 HTTP Accept 헤더와 서버의 컨트롤러 반환 타입 정보 둘을 조합해서 HttpMessageConverter 가 선택된다. 

스프링 MVC 의 HTTP 메시지 컨버터

스프링 MVC 는 다음의 경우 HTTP 메시지 컨버터를 적용한다.

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

HTTP 메시지 컨버터 인터페이스

package org.springframework.http.converter;
public interface HttpMessageConverter<T> {

	boolean canRead(Class<?> clazz, @Nullable MediaType mediaType); 
	boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);

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

HTTP 메시지 컨버터는 요청, 응답 둘 다 사용됨.

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

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

(일부 생략 ...)

0 = ByteArrayHttpMessageConverter
1 = StringHttpMessageConverter
2 = MappingJackson2HttpMessageConverter

스프링 부트는 다양한 메시지 컨버터를 제공하는데, 대상 클래스 타입미디어 타입 둘을 체크해서 사용여부를 결정한다. 만약 만족하지 않으면 다음 메시지 컨버터가 우선순위로 넘어간다.

 

- ByteArrayHttpMessageConverter : byte[] 데이터를 처리한다.

  • 클래스 타입 : byte[] , 미디어 타입 : */*
  • 요청 예) @RequestBody byte[] data
  • 응답 예) @ResponseBody return byte[] , 쓰기 미디어 타입 application/octet-stream

- StringHttpMessageConverter : String 문자로 데이터를 처리한다.

  • 클래스 타입 : String , 미디어 타입 : */*
  • 요청 예) @RequestBody String data
  • 응답 예) @ResponseBody return "ok" , 쓰기 미디어 타입 text/plain

- MappingJacksonHttpMessageConverter : application/json

  • 클래스 타입 : 객체 또는 HashMap , 미디어 타입 : application/json 관련
  • 요청 예) @RequestBody HelloData data 
  • 응답 예) @ResponseBody return helloData , 쓰기 미디어 타입 application/json 관련

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

HTTP 메시지 컨버터는 스프링 MVC 어디 쯤에서 사용될까?

HTTP 메시지 컨버터는 @RequestMapping 을 처리하는 핸들러 어댑터인

RequestMappingHandlerAdapter (요청 매핑 핸들러 어뎁터) 에서 사용된다.

- 요청의 경우

@RequestBody 를 처리하는 ArgumentResolver가 있고, HttpEntity 를 처리하는 ArgumentResolver 가 있다. 

이 ArgumentResolver 들이 HTTP 메시지 컨버터를 사용해서 필요한 객체를 생성하는 것이다.

 

- 응답의 경우

@ResponseBody 와 HttpEntity 를 처리하는 ReturnValueHandler 가 있다. 그리고 여기에서 HTTP 메시지 컨버터를 호출해서 응답 결과를 만든다.

 

참고 : 스프링 MVC는
@RequestBody, @ResponseBody 가 있으면 RequestResponseBodyProcessor(ArgumentResolver) ,
HttpEntity 가 있으면 HttpEntityMethodProcessor(ArgumentResolver) 를 사용한다.

확장

스프링이 제공하는 인터페이스

  • HandlerMethodArgumentResovler
  • HandlerMethodReturnValueHandler
  • HttpMessageConverter

스프링이 필요한 대부분의 기능들을 제공하기 때문에 실제 기능을 확장할 일은 많지 않다.

기능 확장WebMvcConfigure상속 받아서 스프링 빈으로 등록하면 된다.

실제론 자주 사용하지는 않는다.