스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 : Spring MVC 기본 구조

jkky98·2024년 7월 21일
0

Spring

목록 보기
12/77

Logging

운영 시스템에서는 System.out.println()과 같은 시스템 콘솔 출력 대신 별도의 로깅 라이브러리를 사용한다. 스프링 부트는 기본적으로 인터페이스로 SLF4J, 구현체로는 Logback을 사용한다.
로깅 사용 방식은 여러 가지가 있지만, 일반적으로 @Slf4j 애노테이션을 클래스에 선언하여 log라는 인스턴스 변수를 사용하는 방식을 주로 채택한다. 이를 통해 코드가 간결해지고, 다양한 로깅 레벨(예: 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);

로그 레벨은 TRACE부터 ERROR까지 다양한 수준으로 제공되며, 각각 다음과 같은 의미를 가진다:

로그 레벨 설명

  • TRACE
    가장 상세한 로그 레벨로, 디버깅 용도로 사용된다. 코드의 특정 부분이 어떻게 동작하는지 상세히 추적하고 싶을 때 유용하며, 보통 개발 환경에서만 출력되도록 설정된다.

  • DEBUG
    디버깅에 필요한 정보를 제공하는 레벨이다. 예상치 못한 이벤트나 상태를 기록하고 문제 해결에 필요한 추가 정보를 출력한다.

  • INFO
    애플리케이션의 주요 이벤트나 상태 변경을 기록하는 레벨이다. 애플리케이션의 실행 상태를 추적하거나 주요 이벤트를 기록할 때 사용한다.

  • WARN
    잠재적인 문제를 나타내는 경고 메시지를 기록하는 레벨이다. 아직 문제가 발생하지 않았지만, 발생할 가능성이 있는 상황을 예방적으로 기록한다.

  • ERROR
    오류가 발생한 상황을 기록하는 레벨이다. 예기치 못한 예외나 치명적인 오류 상황을 기록할 때 사용한다.


로그 출력 기능과 활용

  • 부가 정보 출력
    로그에는 쓰레드 정보, 클래스 이름 등 부가 정보를 함께 출력할 수 있다.

  • 출력 형식 조절
    로그의 출력 모양을 설정 파일(예: Logback 설정)을 통해 원하는 대로 조절할 수 있다.

  • 로그 레벨 필터링
    개발 서버에서는 모든 로그 레벨(TRACE ~ ERROR)을 출력하고, 운영 서버에서는 특정 레벨만 출력하거나 로그를 기록하지 않도록 설정할 수 있다.

  • 로그 저장 위치
    로그는 파일, 네트워크, 데이터베이스 등 별도의 위치에 저장할 수 있다. 또한, 로그를 일별 또는 특정 용량 단위로 분할 저장할 수 있다.

  • 성능
    로그 라이브러리를 활용하면 System.out.println()보다 성능이 훨씬 뛰어나다. 이는 로그 처리가 비동기적으로 이루어질 수 있고, 필요하지 않은 레벨의 로그를 무시하기 때문이다.


로그 레벨과 설정을 통해 개발 환경과 운영 환경에서 적합한 로그를 효율적으로 관리할 수 있다. 상세한 로그 추적(TRACE, DEBUG)부터 운영에 필요한 주요 이벤트 및 오류 기록(INFO, WARN, ERROR)까지, 로그의 역할과 범위를 적절히 설정하여 시스템 성능과 안정성을 유지하는 것이 중요하다.

요청 매핑

@RestController
public class MappingController {

    private Logger log = LoggerFactory.getLogger(getClass());
	
    // (1)
    @RequestMapping(value = "/hello-basic")
    public String helloBasic() {
        log.info("helloBasic");
        return "ok";
    }
	// (2)
    @RequestMapping(value = "/mapping-get-v1", method = RequestMethod.GET)
    public String mappingGetV1() {
        log.info("mappingGetV1");
        return "ok";
    }
	// (3)
    @GetMapping("/mapping-get-v2")
    public String mappingGetV2() {
        log.info("mappingGetV2");
        return "ok";
    }
	// (4)
    @GetMapping("/mapping/{userId}")
    public String mappingPath(@PathVariable("userId") String data) {
        log.info("mappingPath userId={}", data);
        return "ok";

    }

    /**
     * PathVariable 다중 사용
     */
     // (5)
    @GetMapping("/mapping/users/{userId}/orders/{orderId}")
    public String mappingPath(@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" (! = )
     * params = {"mode=debug","data=good"} */
     // (6)
    @GetMapping(value = "/mapping-param", params = "mode=debug")
    public String mappingParam() {
        log.info("mappingParam");
        return "ok";
    }


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

    /**
     * Content-Type 헤더 기반 추가 매핑 Media Type * consumes="application/json"
     * consumes="!application/json"
     * consumes="application/*"
     * consumes="*\/*"
     * MediaType.APPLICATION_JSON_VALUE
     */
     // (8)
    @PostMapping(value = "/mapping-consume", consumes = "application/json")
    public String mappingConsumes() {
        log.info("mappingConsumes");
        return "ok";
    }
	// (9)
    @PostMapping(value = "/mapping-produce", produces = "text/html")
    public String mappingProduces() {
        log.info("mappingProduces");
        return "ok";
    }
}
  • @RestController = @Controller + @ResponseBody
    @Controller는 반환 값이 String일 경우, 뷰 이름으로 인식하여 뷰 리졸버(View Resolver)가 동작하고 해당 이름에 맞는 템플릿 파일을 찾는다.
    반면, @ResponseBody를 사용하면 반환 값이 뷰 이름이 아닌 HTTP 메시지 바디에 직접 쓰여진다.

즉, @RestController를 사용하면 별도로 @ResponseBody를 명시하지 않아도, 반환 값이 HTTP 메시지 바디에 직접 작성되는 RESTful 응답을 손쉽게 구현할 수 있다.

RequestMapping

실제로 @RequestMapping은 우리가 스프링MVC를 이용하여 백엔드를 구성할 때 가장 많이 사용하는 애노테이션 기반의 핸들러 매핑이다. 가장 높은 우선순위의 매핑 객체인 RequestMappingHandlerMapping@RequestMapping, @GetMapping, @PostMapping 등 애노테이션 기반 매핑을 처리하며, 스프링의 기본 동작에서 가장 먼저 검색된다.


@RequestMappingDispatcherServletRequestMappingHandlerMapping을 통해 작동하며, 적절한 어댑터를 찾아 우리가 작성한 비즈니스 로직 코드를 실행시킨다. 아래는 @RequestMapping의 다양한 사용 방법이다.

  1. 기본적인 RequestMapping 사용
    URI만 매핑하여, 모든 HTTP 메서드(GET, POST 등)에 대해 메서드가 실행된다.
    @RequestMapping("/example")

  2. HTTP 메서드 제한
    method 속성을 추가해 GET 요청만 처리하도록 제한한다.
    @RequestMapping(value = "/example", method = RequestMethod.GET)

  3. 축약형 애노테이션 사용
    @RequestMapping의 GET 요청 전용 축약형인 @GetMapping 사용.
    @GetMapping("/example")

  4. 동적 URI 매핑
    URI의 패스 변수를 사용하여 동적인 URI를 처리한다.
    @GetMapping("/example/{id}")

  5. 다중 패스 변수 사용
    여러 패스 변수를 선언해 동적으로 처리 가능하다.
    @GetMapping("/example/{category}/{id}")

  6. 필수 요청 파라미터 등록
    특정 요청 파라미터가 포함된 요청만 처리하도록 제한한다.
    @GetMapping(value = "/example", params = "key=value")

  7. 필수 요청 헤더 등록
    특정 헤더가 포함된 요청만 처리하도록 제한한다.
    @GetMapping(value = "/example", headers = "X-Custom-Header=custom-value")

  8. 요청 Content-Type 제한
    요청의 Content-Type을 제한하여 특정 타입만 처리 가능하도록 설정한다.
    @PostMapping(value = "/example", consumes = "application/json")

  9. 응답 Content-Type 고정
    응답 Content-Type을 설정하고, 요청의 Accept 헤더와 매칭해야 처리된다.
    @GetMapping(value = "/example", produces = "application/json")

헤더 조회

@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);
        log.info("httpMethod={}", httpMethod);
        log.info("locale={}", locale);
        log.info("headerMap={}", headerMap);
        log.info("header host={}", host);
        log.info("myCookie={}", cookie);

        return "ok";

    }
}
  • response, request
    기존에 사용하던 요청, 응답 객체로 서블릿에서 제공되는 기본 기능이다.

  • HttpMethod
    Spring MVC에서 지원하며, 요청 HTTP의 Method 정보만 따로 주입받아 사용할 수 있다.

  • Locale
    요청의 언어 정보를 제공한다.

  • @RequestHeader MultivalueMap<String, String>
    요청 헤더의 모든 정보를 MultivalueMap 형태로 주입받는다.

    • MultivalueMapkey: List<> 구조로, 하나의 키에 여러 값을 가질 수 있다.
  • @RequestHeader("host")
    특정한 헤더 정보를 따로 주입받는다.

  • @CookieValue(value, required)
    요청 쿠키 중 value에 해당하는 쿠키 값을 가져올 수 있다.

    • required 속성을 통해 쿠키의 필수 여부를 설정할 수 있다.

기존 서블릿 방식과 Spring MVC의 차이점

기본 서블릿으로 컨트롤러를 만들 때는 요청 객체인 request를 변수로 받아 사용해야 했다. 그러나 Spring MVC는 필요한 정보만 주입받을 수 있는 기능을 지원한다. 이를 통해 컨트롤러는 필요한 데이터만 접근할 수 있어 불필요한 비용이 줄어들고, 코드의 가독성과 유지보수성이 크게 향상된다.

요청 데이터 조회

위에서는 HTTP 요청에 대해 매핑하는 Spring MVC의 기능(@RequestMapping)과 헤더 조회에 관해 다루었다.

요청 데이터 조회는 크게 다음 3가지로 구분된다:

  1. GET 쿼리 파라미터와 POST 폼 데이터

    • HTTP 요청의 URL에 포함된 쿼리 파라미터 또는 POST 요청의 폼 데이터를 조회한다.
    • 예: /example?key=value 또는 POST 요청의 application/x-www-form-urlencoded 데이터.
  2. HTTP 요청 Body 데이터

    • 요청의 Body에 포함된 JSON, XML, 텍스트 등의 데이터를 조회한다.
    • 예: application/json 형태의 데이터로 전달된 요청.
  3. 파일 업로드 데이터

    • 멀티파트 형식의 요청 데이터를 처리하여 파일을 업로드한다.
    • 예: multipart/form-data를 통한 파일 전송.

Spring MVC는 각각의 데이터 조회 방식에 맞는 주입 및 처리를 간편하게 지원한다.

GET - 쿼리 파라미터 , HTML Form 쿼리 파라미터

URL의 쿼리 파라미터에 데이터를 포함해서 전달하는 방식과 HTML 폼에 데이터를 담아 전달하는 방식은 둘 다 메시지 바디를 사용하지 않고 쿼리 파라미터에 데이터를 담아 전송한다는 공통점이 있다.

이 두 방식을 묶어 말하는 이유는 요청 데이터가 메시지 바디가 아닌 URL 또는 폼 필드에 포함되어 전송되며, 주로 다음과 같은 형태를 가진다는 점이다:

  1. GET 요청 (쿼리 파라미터)

    • 데이터를 URL의 쿼리 스트링에 포함.
    • 예: /example?key=value&name=John
  2. POST 요청 (폼 데이터)

    • 데이터를 application/x-www-form-urlencoded 형식으로 전송.
    • 예: key=value&name=John (요청 본문에 포함되지만, 메시지 바디를 사용하는 JSON과는 다름).

이 방식들은 간단한 데이터 전달 및 조회 요청에 적합하며, 구조화된 대량 데이터 전송보다는 간단한 키-값 데이터 전송에 주로 사용된다.

@Slf4j
@Controller
public class RequestParamController {
	
    // (1)
    @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");
    }
    
	// (2)
    @ResponseBody
    @RequestMapping("/request-param-v2")
    public String requestParamV2(
            @RequestParam("username") String memberName,
            @RequestParam("age") int memberAge
    ) {
        log.info("username  = {}, age={}", memberName, memberAge);
        return "ok";
    }
	
    // (3)
    @ResponseBody
    @RequestMapping("/request-param-v3")
    public String requestParamV3(
            @RequestParam String username,
            @RequestParam int age
    ) {
        log.info("username  = {}, age={}", username, age);
        return "ok";
    }
	
    // (4)
    @ResponseBody
    @RequestMapping("/request-param-v4")
    public String requestParamV4 (String username, int age) {
        log.info("username  = {}, age={}", username, age);
        return "ok";
    }
	
    //(5)
    @ResponseBody
    @RequestMapping("/request-param-required")
    public String requestParamRequired(
            @RequestParam(required = true) String username,
            @RequestParam(required = false) Integer age // null이 들어와야 하므로 int는 불가능하다 -> 좋은 엣지 팁인데
            // "" , null 구분
    ) {
        log.info("username  = {}, age={}", username, age);
        return "ok";
    }
	//(6)
    @ResponseBody
    @RequestMapping("/request-param-default")
    public String requestParamDefualt(
            @RequestParam(required = true, defaultValue = "guest") String username,
            @RequestParam(required = false, defaultValue = "-1") int age
            // default -> 리콰이어드 필요 없어짐
            // "" -> 빈문자도 디폴트로 바꿔줌
    ) {
        log.info("username  = {}, age={}", username, age);
        return "ok";
    }
	//(7)
    @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";
    }
	//(8)
    @ResponseBody
    @RequestMapping("/model-attribute-v1")
    public String modelAttributeV1(@ModelAttribute HelloData helloData) {
        log.info("username  = {}, age={}", helloData.getUsername(), helloData.getAge());
        return "ok";
    }
	//(9)
    @ResponseBody
    @RequestMapping("/model-attribute-v2")
    public String modelAttributeV2(HelloData helloData) {
        log.info("username  = {}, age={}", helloData.getUsername(), helloData.getAge());
        return "ok";
    }
  1. 기존 서블릿 요청, 응답 객체를 이용한 컨트롤러

    • request.getParameter를 활용하여 요청 데이터를 수동으로 추출. (단순 서블릿 방식)
  2. @RequestParam을 사용한 요청 데이터 추출

    • @RequestParam을 통해 요청 전체 객체를 주입받는 것이 아니라, 쿼리 파라미터의 내용만 전달받는다.
  3. @RequestParam의 인자 생략

    • @RequestParam에 명시적으로 이름을 지정하지 않고, 변수 이름을 쿼리 파라미터 이름과 동일하게 구성하면 자동으로 매핑된다.
  4. @RequestParam 자체 생략 가능

    • 스프링은 @RequestParam을 생략해도 쿼리 파라미터를 매핑한다.
    • 다만, @RequestParam을 명시하면 해당 변수가 쿼리 파라미터임을 명확히 보여줄 수 있어 가독성과 유지보수에 유리하다.
  5. 필수 쿼리 파라미터 지정 (required 속성)

    • required 속성을 설정하여 필수 쿼리 파라미터를 지정할 수 있다. 기본값은 true.
    • 주의: 빈 문자열(예: key=)을 전달하면 여전히 required=true를 충족하기 때문에, null과 빈 문자열을 구분하여 활용해야 한다.
  6. 디폴트 값 설정 (defaultValue 속성)

    • 쿼리 파라미터가 전달되지 않을 경우, defaultValue를 통해 특정 값을 자동으로 적용할 수 있다.
    • 이 경우, required는 사실상 필요하지 않다. 또한, 빈 문자열로 쿼리 파라미터가 전달되어도 디폴트 값이 적용된다.
  7. 모든 쿼리 파라미터를 맵으로 가져오기

    • 요청에 포함된 모든 쿼리 파라미터를 Map 자료구조로 한꺼번에 받을 수 있다.
  8. 쿼리 파라미터와 객체 바인딩

    • 쿼리 파라미터를 특정 객체와 바인딩할 수 있다. 이 경우 객체의 클래스에는 @Data(롬복) 애노테이션이 적용되거나, 적절한 getter/setter 메서드가 정의되어 있어야 한다.
  9. @ModelAttribute 생략 가능

    • 인자로 전달된 클래스 타입이 기본형(int, String 등)이 아닌 경우, 별도로 명시하지 않아도 스프링은 자동으로 @ModelAttribute를 적용한다.
    • 단, argument resolver에 등록되지 않은 클래스만 해당된다.

API 요청 메시지 바디 - 단순 text, json

@Slf4j
@Controller
public class RequesBodyStringController {
	
    private ObjectMapper objectMapper = new ObjectMapper();
    
    // (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");
    }
	// (2)
    @PostMapping("/request-body-string-v3")
    public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) throws IOException {

        log.info("messageBody={}", httpEntity.getBody());

        return new HttpEntity<>("ok");
    }
	// (3)
    @ResponseBody
    @PostMapping("/request-body-string-v4")
    public String requestBodyStringV4(@RequestBody String messageBody) throws IOException {

        log.info("messageBody={}", messageBody);

        return "ok";
    }
}
// (4)
	@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 helloData = objectMapper.readValue(messageBody, HelloData.class);
        log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());

        response.getWriter().write("ok");
    }
	// (5)
    @ResponseBody
    @PostMapping("/request-body-json-v2")
    public String requestBodyJsonV2(@RequestBody String messageBody) throws IOException {

        log.info("messageBody={}", messageBody);
        HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
        log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());

        return "OK";
    }
	//(6)
    @ResponseBody
    @PostMapping("/request-body-json-v3")
    public String requestBodyJsonV3(@RequestBody HelloData helloData) {
        log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
        return "OK";
    }
	//(7)
    @ResponseBody
    @PostMapping("/request-body-json-v4")
    public String requestBodyJsonV4(HttpEntity<HelloData> data) {
        HelloData helloData = data.getBody();
        log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
        return "OK";
    }
	//(8)
    @ResponseBody
    @PostMapping("/request-body-json-v5")
    public HelloData requestBodyJsonV5(@RequestBody HelloData data) {
        log.info("username={}, age={}", data.getUsername(), data.getAge());
        return data;
    }
  1. 서블릿만을 이용한 메시지 바디 처리

    • 서블릿의 inputStream을 사용하여 요청 메시지 바디를 읽는다.
    • response 객체를 활용하여 응답 메시지 바디를 수동으로 구성한다.
  2. HttpEntity를 이용한 메시지 처리

    • 스프링이 지원하는 HttpEntity 객체를 메서드 인자로 사용하여 요청의 엔티티(헤더 및 바디 포함)를 자동으로 주입받는다.
    • 요청 메시지 바디를 수정하거나, 이를 응답 메시지 바디로 그대로 사용하여 반환할 수도 있다.
  3. @RequestBody를 이용한 메시지 바디 처리

    • 스프링의 HTTP 메시지 컨버터를 통해 요청 메시지 바디를 바로 읽어올 수 있다.
    • ObjectMapper를 사용하지 않고 스프링이 주입해준 객체를 자동으로 디코딩하여 바인딩할 수 있다.
  4. JSON 데이터 송신 컨트롤러 (서블릿 방식)

    • 서블릿의 요청, 응답 객체를 사용하여 JSON 데이터를 처리한다.
    • ObjectMapper를 사용해 JSON 데이터를 객체와 바인딩하거나 다시 JSON으로 변환한다.
  5. @RequestBody와 스프링의 메시지 컨버터

    • @RequestBody를 사용하면 별도의 ObjectMapper 없이도 메시지 바디를 읽고 자동으로 객체에 바인딩할 수 있다.
  6. 바인딩 객체 사용

    • 메시지 바디를 직접 읽지 않고, 바인딩될 객체를 작성하여 자동으로 바인딩된 객체를 주입받는다.
    • 스프링이 객체를 생성하고 데이터 매핑까지 처리해준다.
  7. HttpEntity를 이용한 객체 바인딩

    • 요청 메시지 바디를 HttpEntity로 받아 바인딩된 데이터를 활용할 수 있다.
  8. @ResponseBody를 통한 응답 데이터 처리

    • 컨트롤러 메서드에 @ResponseBody를 부여하여 응답 메시지 바디를 객체와 바인딩할 수 있다.
    • 요청 데이터를 받아 JSON -> 객체로 바인딩하고, 다시 객체 -> JSON으로 변환하여 응답 메시지로 반환할 수 있다.

HttpMessage컨버터

스프링 MVC는 다음과 같은 경우 메시지 컨버터(Message Converter)를 적용한다:

메시지 컨버터 적용 상황

  • 요청 처리:
    • @RequestBody
    • HttpEntity (또는 RequestEntity)
  • 응답 처리:
    • @ResponseBody
    • HttpEntity (또는 ResponseEntity)

HttpMessageConverter 인터페이스

스프링의 메시지 컨버터는 HttpMessageConverter 인터페이스를 기반으로 구현되며, 각 메시지 컨버터는 아래의 주요 메서드를 구현한다:

  1. canRead(Class<?> clazz, MediaType mediaType)

    • 이 컨버터가 주어진 클래스와 미디어 타입의 요청 메시지를 읽을 수 있는지 확인한다.
  2. canWrite(Class<?> clazz, MediaType mediaType)

    • 이 컨버터가 주어진 클래스와 미디어 타입의 응답 메시지를 쓸 수 있는지 확인한다.
  3. read(Class<?> clazz, HttpInputMessage inputMessage)

    • 요청 메시지의 데이터를 읽고, 이를 주어진 클래스 타입의 객체로 변환한다.
  4. write(Object t, MediaType contentType, HttpOutputMessage outputMessage)

    • 응답 메시지의 데이터를 주어진 객체를 기반으로 작성하고, 지정된 미디어 타입으로 출력한다.

주요 구현체

HttpMessageConverter의 다양한 구현체가 있으며, 각각 특정 미디어 타입과 데이터 형식을 처리한다:

  • MappingJackson2HttpMessageConverter: JSON 데이터를 처리. (기본적으로 Jackson 라이브러리 사용)
  • StringHttpMessageConverter: 텍스트 데이터를 처리.
  • ByteArrayHttpMessageConverter: 바이너리 데이터를 처리.
  • FormHttpMessageConverter: 폼 데이터를 처리.

스프링 MVC는 요청과 응답의 내용과 미디어 타입에 따라 적합한 메시지 컨버터를 자동으로 선택해 사용한다.

메시지 컨버터 동작 방식

우리는 이전에 @ModelAttribute를 공부하며 Argument Resolver의 존재를 확인했다. 컨트롤러 메서드의 인자로 객체를 주입받도록 설정하면, 해당 객체가 Argument Resolver에 등록되지 않은 경우 @ModelAttribute를 통해 커스텀 객체와 바인딩이 이루어진다는 것을 알게 되었다.


Argument Resolver의 역할

컨트롤러에서 다양한 파라미터를 사용할 수 있는 이유는 Argument Resolver 덕분이다.
핸들러 어댑터는 실제로 컨트롤러를 호출하기 전에 메서드의 파라미터를 살펴보고, 적절한 Argument Resolver를 호출하여 컨트롤러로 전달될 객체를 생성하거나 주입한다.

예:

  • 요청 객체 (HttpServletRequest, HttpSession)
  • 사용자 정의 객체 (@ModelAttribute)
  • 쿼리 파라미터 (@RequestParam)
  • 요청 본문 (@RequestBody)

Return Value Handler의 역할

컨트롤러의 반환값을 처리하는 것은 Return Value Handler가 담당한다.
이것은 Argument Resolver와 유사한 방식으로 동작하며, 컨트롤러의 반환값을 적절한 형식으로 변환하여 클라이언트로 전달한다.

  • 예:
    • 반환값이 객체인 경우 JSON으로 변환 (MappingJackson2HttpMessageConverter 활용)
    • 반환값이 뷰 이름인 경우 뷰 리졸버를 통해 렌더링
    • @ResponseBody가 사용된 경우 HTTP 메시지 컨버터로 처리

관계 요약

  1. Argument Resolver

    • 컨트롤러 메서드의 파라미터를 생성하거나 주입하는 역할.
  2. Return Value Handler

    • 컨트롤러 메서드의 반환값을 변환하여 클라이언트로 전달하는 역할.
    • Argument Resolver의 동작 원리와 유사하며, 컨트롤러 메서드의 반환값을 처리하는 또 다른 구성요소.

핸들러 어댑터는 이 두 구성 요소를 통해 요청 데이터를 컨트롤러로 전달하고, 응답 데이터를 클라이언트로 반환하는 전반적인 작업을 처리한다.

정리

  • 로깅 사용 이유, 로깅 사용법 - slf4j, @Slf4j
  • 요청 수용 컨트롤러 구성 방법
  • 헤더 꺼내오는 법
  • 요청 메시지 3가지 - get(QueryParameter), form(QueryParameter), API(json, text)
  • http메시지컨버터 개념, 작동방식, 구조

출처 : https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard

profile
자바집사의 거북이 수련법

0개의 댓글