스프링 MVC - 기본 기능

2025. 8. 25. 17:13·Spring

로깅 간단히 알아보기

앞으로는 sout을 사용하지 않을 것이다. 별도의 로깅 라이브러리를 사용해서 로그를 출력할 것이다.

 

기본 로깅 라이브러리

- SLF4J

- Logback

 

로그 라이브러리는 Logback, Log4J, Log4J2 등등 많은 라이브러리가 있는데, 그것을 통합해서 인터페이스로 제공하는것이 SLF4J 라이브러리다. 쉽게 말해 SLF4J는 인터페이스, 그 구현체로 Logback같은 로그 라이브러리를 선택하면 된다.

실무에서는 Logback을 자주 사용한다고 한다.

@Slf4j
@RestController
public class LogTestController {

//  private final Logger log = LoggerFactory.getLogger(getClass());
//  @Slf4j 어노테이션 사용시 생략 가능. (롬복 사용 가능)

    @RequestMapping("/log-test")
    public String logTest() {
        String name = "Spring";
        
        log.trace(" trace my log= " + 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);
        
        return "ok";
    }
}

 

@RestController

- @Controller는 반환값이 String이면 뷰 이름으로 인식해서 뷰를 찾고 뷰가 렌더링 된다.

- @RestController는 반환 값으로 뷰를 찾는것이 아니라 HTTP 메세지 바디에 바로 입력한다.

 

로그 레벨은 TRACE > DEBUG > INFO > WARN > ERROR

- 개발 서버는 debug가 일반적

- 운영 서버는 info가 일반적

 

로그 호출 시 +를 사용하면 문자 더하기 연산이 발생한다. 따라서 사용하지 않는 호출에서 불필요한 리소스를 낭비하게 될 수 있으니 사용하지말자.

 

로그 사용시 장점

- 쓰레드 정보, 클래스 이름같은 부가 정보 확인가능, 출력 모양 조정 가능

- 로그 레벨에 따라 로그를 상황에 맞게 출력할 수 있음

- 파일이나 네트워크 등 로그를 별도 위치에 남길 수 있다.

- sout보다 성능이 좋다.

 

 

요청 매핑

@RestController
public class MappingController {

    private Logger log = LoggerFactory.getLogger(getClass());

    @RequestMapping(value = "/hello-basic", method = RequestMethod.GET)
    public String helloBasic() {
        log.info("helloBasic");
        return "ok";
    }

    @GetMapping(value = "/mapping-get-v2")
    public String mappingGetV2() {
        log.info("mapping-get-v2");
        return "ok";
    }

    @GetMapping("/mapping/{userId}")
    public String mappingPath(@PathVariable("userId") String data) {
//    public String mappingPath(@PathVariable String userId) 사용 가능. 변수명 맞추고 싶을 때
        log.info("mappingPath userId={}", data);
        return "ok";
    }

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

    // 파라미터로 추가 매핑
    // localhost:8080/mapping-param?mode=debug, 잘 사용하지 않음
    @GetMapping(value = "/mapping-param", params = "mode=debug")
    public String mappingParam() {
        log.info("mappingParam");
        return "ok";
    }

    // 특정 헤더로 추가 매핑
    @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
     */
    @PostMapping(value = "/mapping-consume", consumes = "application/json")
    public String mappingConsumes() {
        log.info("mappingConsumes");
        return "ok";
    }

    /**
     * Accept 헤더 기반 Media Type
     * produces = "text/html"
     * produces = "!text/html"
     * produces = "text/*"
     * produces = "*\/*"
     */
    @PostMapping(value = "/mapping-produce", produces = "text/html")
    public String mappingProduces() {
        log.info("mappingProduces");
        return "ok";
    }
}

(아는 내용이라 자세한 내용은 생략)

 

요청 매핑 - API 예시

package hello.springmvc.basic.requestmapping;

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/mapping/users")
public class MappingClassController {

    @GetMapping
    public String user() {
        return "get users";
    }

    @PostMapping
    public String addUser() {
        return "post user";
    }

    @GetMapping("/{userId}")
    public String findUser(@PathVariable String userId) {
        return "get userId= " + userId;
    }

    @PatchMapping("/{userId}")
    public String updateUser(@PathVariable String userId) {
        return "update userId= " + userId;
    }

    @DeleteMapping("/{userId}")
    public String deleteUser(@PathVariable String userId) {
        return "delete 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);
        log.info("httpMethod={}", httpMethod);
        log.info("locale={}", locale);
        log.info("headerMap={}", headerMap);
        log.info("header host={}", host);
        log.info("myCookie={}", cookie);

        return "ok";
    }
}

 

- MutliValueMap은 Map과 유사한데, 하나의 키에 여러 값을 받을 수 있다.

- @Controller의 사용가능한 파라미터 정보는 공식 문서에서 확인할 수 있다.

 

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

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

- GET, 쿼리 파라미터 전송 (http://localhost:8080/request-param?username=hello&age=20)

- POST, HTML Form 전송 (POST / request-param ... content-type: application/x-www-form-urlencoded

                                             username=hello&age=20)

GET 쿼리 파라미터 전송 방식이든, POST HTML Form 방식이든 둘 다 형식이 같으므로 구분없이 조회할 수 있다.

이것을 요청 파라미터 (request Parameter) 조회라고 한다.

 

@Slf4j
@Controller
public class RequestParamController {

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

 

getRequestParameter()를 사용했기 때문에 요청 파라미터 전송방식이다.

(쿼리 파라미터, HTML Form 둘 다 읽을 수 있기때문)

다만 json은 읽을 수 없다.

 

@RequestParam

스프링이 제공하는 @RequestParam을 사용하면 요청 파라미터를 쉽게 관리할 수 있다.

@Slf4j
@Controller
public class RequestParamController {

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

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

    @ResponseBody
    @RequestMapping("/request-param-default")
    public String requestParamDefault(@RequestParam(defaultValue = "guest") String username,
                                       @RequestParam(defaultValue = "-1") int age) {
        log.info("username={}, age={}", username, age);
        return "ok";
    }

    @ResponseBody
    @RequestMapping("/request-param-map")
    public String requestParamMap(@RequestParam Map<String, Objects> paramMap) {

        log.info("username={}, age={}", paramMap.get("username"), paramMap.get("age"));
        return "ok";
    }
}

 

v1부터 파라미터의 차이를 잘 비교해보자.

@ResponseBody를 사용하면 View조회를 무시 (코드에선 ok라는 이름의 view를 찾게됨.)하고 HTTP message body에 직접 입력

 

@RequestParam을 생략할 수 있으나, 명확하게 작성하는게 좋지 않을까 라는 강사의 의견이 있음.

 

 

@ModelAttribute

실제 개발에선 요청 파라미터를 받아서 필요한 객체를 만들고 그 객체에 값을 넣어주는 과정이 필요하다.

@ModelAttribute는 이 과정을 자동화해주는 어노테이션이다.

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


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

 

@ModelAttribute가 HelloData 객체를 생성 -> 요청 파라미터 이름으로 HelloData 객체의 프로퍼티 찾음 -> 해당 프로퍼티의 setter 호출해서 파라미터 값 입력 (바인딩)

 

프로퍼티

객체에 getUsername(), setUsername() 메서드가 있으면, 이 객체는 username이라는 프로퍼티를 가지고 있는 것이다.

 

@ModelAttribute를 생략하고 사용할 수 있다. 그렇다면 @RequestParam도 생략 가능한데 어떤 기준에 의해 생략되고 작동되는가?

- String, int, Integer같은 단순 타입 -> @RequestParam

- 나머지 타입은 @ModelAttribute. (argument resolver로 지정해둔 타입, 예를들어 HttpServletRequest같은것들은 제외)

 

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

HTTP message body에 데이터를 직접 담아서 요청

- HTTP API에 주로 사용, JSON, XML, TEXT (데이터 형식은 거의 JSON 사용함)

- POST, PATCH, PUT

 

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

 

@Slf4j
@Controller
public class RequestBodyStringController {

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

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

    @PostMapping("/request-body-string-v3")
    public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) throws IOException {

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

        return new HttpEntity<>("ok");
    }

    @ResponseBody
    @PostMapping("/request-body-string-v4")
    public String requestBodyStringV4(@RequestBody String messageBody) throws IOException {

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

        return "ok";
    }
}

 

v1 ~ v4의 변화를 잘 비교해보자.

1. inputStream으로 메시지 바디 내용을 조회할 수 있고,  Writer로 메세지 바디에 직접 출력할 수 있다.

 

2. HttpEntity를 사용해 코드를 간결하게 수정했다.

- HTTP header, body 등을 편리하게 조회할 수 있다.

- 요청 파라미터를 조회하는 기능과는 관계가 없다. (@RequestParam, @ModelAttribute)

- 메시지 바디에 직접 정보를 반환할 수 있다. 헤더도 마찬가지

* 스프링 MVC 내부에서 HTTP 메세지나 바디를 읽어서 문자나 객체로 변환해서 전달해주는데, 이 때 HttpMessateConverter라는 기능을 사용한다.

 

3. @RequestBody를 사용해서 HTTP 메시지 바디 정보를 편리하게 조회할 수 있다.

- 헤더 정보가 필요하면 @RequestHeader를 사용하면 된다.

- 메세지 바디를 직접 조회하는 기능은 요청 파라미터를 조회하는 @RequestParam, @ModelAttribute와는 전혀 관계 없다

- @ResponseBody를 사용하면 응답 결과를 HTTP 메세지 바디에 직접 담아서 전달할 수 있다.

 

HTTP 요청 메세지 - JSON

@Slf4j
@Controller
public class RequestBodyJsonController {

    private ObjectMapper objectMapper = new 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 helloData = objectMapper.readValue(messageBody, HelloData.class);
        log.info("username={}", helloData.getUsername(), helloData.getAge());

        response.getWriter().write("ok");
    }

    @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={}", helloData.getUsername(), helloData.getAge());

        return "ok";
    }

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

        return "ok";
    }

    @ResponseBody
    @PostMapping("/request-body-json-v4")
    public String requestBodyJsonV4(HttpEntity<HelloData> httpEntity) {
        HelloData helloData = httpEntity.getBody();
        log.info("username={}", helloData.getUsername(), helloData.getAge());

        return "ok";
    }

    @ResponseBody
    @PostMapping("/request-body-json-v5")
    public HelloData requestBodyJsonV5(@RequestBody HelloData data) {
        log.info("username={}", data.getUsername(), data.getAge());

        return data;
    }
}

 

v1

- HttpServletRequest를 사용해서 직접 HTTP 메세지 바디에서 데이터를 읽어오고, 문자를 변환

- 문자로 된 JSON 데이터를 Jackson 라이브러리인 objectMapper를 사용해서 자바 객체로 변환한다.

 

v2

- @RequestBody를 사용해서 HTTP 메세지 데이터를 꺼내고 messageBody에 저장

- 마찬가지로 objectMapper를 통해 자바 객체로 변환

 

v3

- @RequestBody Hello data 처럼 @RequestBody에 직접 만든 객체를 지정할 수 있다.

- HttpEntity, @RequestBody를 사용하면 HTTP 메세지 컨버터가 HTTP 메세지 바디의 내용을 우리가 원하는 문자나 객체로 변환해준다. 문자 뿐 아니라 JSON 객체도 변환할 수 있다.

- @RequestBody는 생략할 수 없음. 생략하게 된다면 @ModelAttribute가 적용된다.

 

v4

-  HttpEntity를 사용해서 httpEntity.getBody(); 부분이 자바 객체로 변환되는 부분이다.

 

v5

- 응답의 경우에도 @ResponseBody를 사용하면 해당 객체를 HTTP 메세지 바디에 직접 넣어줄 수 있다.

- @RequestBody 요청 : JSON 요청 -> HTTP 메세지 컨버터 -> 객체

- @ResponseBody 응답 : 객체 -> HTTP 메세지 컨버터 -> JSON 응답

 

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

 

응답 데이터를 만드는 방법은 크게 3가지가 있다.

- 정적 리소스 (정적인 HTML, css, js)

- 뷰 템플릿 사용 (동적 HTML 제공시)

- HTTP 메세지 사용 (HTTP API 제공 시 데이터를 전달해야하므로 JSON 형식이 데이터를 주로 보냄)

 

정적 리소스

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

/static, /public, /resources, /META_INF/resources 디렉토리에 리소스를 넣어두면 스프링 부트가 정적 리소스를 제공한다.

 

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

 

뷰 템플릿

뷰 템플릿을 거쳐서 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!");
    }
}

 

String을 반환하는 경우엔 뷰 리졸버가 실행되고 response/hello로 뷰를 찾고 렌더링한다.

만약 @ResponseBody가 있는경우엔 불가능 (String 그대로 반환하기때문)

 

void를 반환하는 경우엔 @Controller를 사용하고, HTTP 메세지 바디를 처리하는 파라미터가 없으면 요청 URL을 참고해서 논리 뷰 이름으로 사용된다. 하지만 이 방법은 명시성이 너무 떨어지기에 권장하지 않음.

 

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

HTTP API를 제공하는 경우엔 HTML이 아니라 데이터를 전달해야 하므로, HTTP 메세지 바디에 JSON같은 형식으로 데이터를 실어 보낸다.

@Slf4j
//@Controller
@RestController
public class ResponseBodyController {

    @GetMapping("/response-body-string-v1")
    public void responseBodyV1(HttpServletResponse response) throws IOException {
        response.getWriter().write("ok");
    }

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

//    @ResponseBody
    @GetMapping("/response-body-string-v3")
    public String responseBodyV3() {
        return "ok";
    }

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

    @ResponseStatus(HttpStatus.OK)
//    @ResponseBody
    @GetMapping("/response-body-json-v2")
    public HelloData responseBodyJsonV2() {
        HelloData helloData = new HelloData();
        helloData.setUsername("userA");
        helloData.setAge(20);

        return helloData;
    }
}

 

responseBodyV1

- 서블릿을 직접 다룰 때 처럼 HttpServletResponse 객체를 통해서 HTTP 메세지 바디에 직접 ok 응답 메세지를 전달

responseBodyV2

- ResponseEntity는 HttpEntity를 상속받았는데, HttpEntity는 HTTP 메세지의 헤더, 바디정보를 가지고 있다. ResponseEntity는 여기에 더해서 HTTP 응답코드를 설정할 수 있다.

responseBodyV3

- @ResponseBody를 사용하면 view를 사용하지 않고 HTTP 메세지 컨버터를 통해서 직접 입력할 수 있다. (ResponseEntity도 동일한 방식으로 동작함)

 

responseBodyJsonV1

- ResponseEntity를 반환한다. HTTP 메세지 컨버터를 통해 JSON으로 변환되어 반환

responseBodyJsonV2

- @ResponseBody를 사용해서 객체를 반환한다. 그러나 @ResponseBody는 응답 코드를 설정하기 까다롭기 때문에 @ResponseStatus 어노테이션을 사용해서 응답코드를 설정할 수 있다.

 

@RestController

- 쉽게 생각해서 @ResponseBody를 편하게 사용하기 위한 어노테이션이라고 생각하자. REST API를 만들때 사용하는 컨트롤러이다.

클래스 레벨에 @ResponseBody를 사용해도 되지만 굳이 그럴필요는 없다.

 

 

HttpMessageConverter

간단하게 내용정리

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

 

- HTTP 요청 : @RequestBody, HttpEntity(RequestEntity)

- HTTP 응답 : @ResponseBody, HttpEntity(ResponseEntity)

 

기본 메세지 컨버터는 크게 3가지가 있다.

 

1. ByteArrayHttpMessageConverter

2. StringHttpMessageConverter

3. MappingJackson2HttpMessageConverter

 

메세지 컨버터는 클래스 타입, 미디어 타입에 따라 호출된다.

ByteArrayHttpMessageConverter는 byte[]타입의 클래스

응답은 미디어타입이 application/octet-stream이어야 함

 

StringHttpMessageConverter는 String 타입의 클래스

응답은 미디어타입이 상관없음 (*/*)

 

MappingJackson2HttpMessageConverter는 객체 또는 HashMap.

응답은 미디어타입이 application/json이어야 함

 

클라이언트 요청에 따라 달라지는 것이다!!!. (데이터 형식은 content-type의 헤더에 따라 달라진다.)

예를들어 클라이언트에서 JSON을 HTTP Body에 담아보내면, 등록된 HttpMessageConverter를 순회하며 헤더를 보고 MappingJackson2HttpMessageConverter를 선택하게 된다.

그 후에 이 컨버터가 내부적으로 Jackson(ObjectMapper)를 사용해 JSON을 객체로 변환하게 되고, 변환된 객체가 @RequestBody HelloData data 파라미터에 주입 되는 것이다.

 

그렇다면 응답은?

객체를 return할 때, DispatcherServlet은 뷰를 찾아야되나? -> @ResponseBody를 보고 뷰 조회를 생략, 바로 HTTP Body로 내려보낸다. -> HttpMessageConverter쪽으로 넘어간다 -> 클라이언트가 보낸 Accept header 확인(application/json) -> 그에 맞는 컨버터 선택 -> 객체를 JSON 문자열로 변환 -> 변환된 문자열을 응답 바디에 작성해서 클라이언트에 반환

 

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

HTTP 메세지 컨버터는 스프링 MVC의 어느 시점에서 사용되는가?

 

 

어노테이션 기반의 컨트롤러, 즉 @RequestMapping을 처리하는 핸들러 어댑터인 RequestMappingHandlerAdapter에서 시작된다.

 

ArgumentResolver

어노테이션 기반의 컨트롤러는 매우 다양한 파라미터를 사용할 수 있었다.

HttpServletRequest, Model은 물론 @RequestParam, @ModelAttribute 같은 어노테이션, 그리고 @RequestBody, HttpEntity같은 HTTP 메세지를 처리하는 부분까지 매우 큰 유연함을 보여주었다.

이렇게 유연하게 처리할 수 있는 이유가 ArgumentResolver 덕분이다.

 

어노테이션 기반 컨트롤러를 처리하는 RequestMappingHandlerAdapter는 ArgumentResolver를 호출해서 컨트롤러(핸들러)가 필요로 하는 다양한 파라미터 값(객체)를 생성한다. 그리고 이렇게 파라미터의 값이 모두 준비되면 컨트롤러를 호출하면서 값을 넘겨준다.

 

ArgumentResolver 내부에는 supportsParameter() 메서드가 있는데, 이 메서드를 호출해서 해당 파라미터를 지원하는지 체크하고, resolveArgument()를 호출해서 실제 객체를 생성한다. 그 후에 컨트롤러 호출 시 넘어가는 것이다.

 

ReturnValueHandler

HandlerMethodReturnValueHandler를 줄여서 ReturnValueHandler라고 부른다.

ArgumentResolver와 비슷한데, 이것은 응답 값을 변환하고 처리한다.

 

컨트롤러에서 String으로 뷰 이름을 반환해도, 동작하는 이유가 바로 이것 때문이다.

요청의 경우 @RequestBody를 사용하는 ArgumentResolver가 있고, HttpEntity를 처리하는 ArgumentResolver가 있다.

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

 

응답의 경우도 마찬가지로 @ResponseBody, HttpEntity를 처리하는 ReturnValueHandler가 있다.

 

스프링 MVC는 @RequestBody, @ResponseBody가 있으면

RequestResponseBodyMethodProcessor(ArgumentResolver, ReturnValueHandler를 둘다 구현함)

HttpEntity가 있으면

HttpEntityMethodProcessor(ArgumentResolver, ReturnValueHandler를 둘 다 구현) 를 사용한다.

 

정리하자면

ArgumentResolver -> 컨트롤러 메서드에 파라미터를 어떻게 채울지 결정하는 곳

- 컨트롤러 실행 전 @RequestParam String username 누가 처리? -> RequestParamMethodArgumentResolver 호출해서 처리

- @RequestBody HelloData data 누가 처리? -> RequestResponseBodyMethodProceesor 호출 -> json이라면 변환이 필요하기 때문에 HTTP 메세지 컨버터 실행(컨버터에서 MappingJackson2HttpMessageConverter 호출) -> ObjectMapper로 json형식을 HelloData로 변환

 

1. ArgumentResolver가 흐름을 제어

2. HTTP 메세지 컨버터가 실제 변환을 수행

3. 변환 결과값을 ArgumentResolver가 반환해서 컨트롤러 파라미터에 주입

 

응답의 경우도 크게 다르지 않음

- ReturnValueHandler가 리턴값을 최종 처리, 그 과정에서 필요시 MessageConverter로 객체 -> 바디로 변환

'Spring' 카테고리의 다른 글

타임리프 - 기본 기능  (1) 2025.08.31
상품 도메인 개발 - 뷰 템플릿, 타임리프  (1) 2025.08.28
Spring MVC - 구조 이해  (0) 2025.06.19
프론트 컨트롤러  (1) 2025.05.19
서블릿, JSP, MVC 패턴  (1) 2025.04.16
'Spring' 카테고리의 다른 글
  • 타임리프 - 기본 기능
  • 상품 도메인 개발 - 뷰 템플릿, 타임리프
  • Spring MVC - 구조 이해
  • 프론트 컨트롤러
공부처음하는사람
공부처음하는사람
  • 공부처음하는사람
    lazzzykim
    공부처음하는사람
  • 전체
    오늘
    어제
    • 분류 전체보기 (159)
      • Kotlin (31)
      • Java (56)
      • Spring (44)
      • JPA (6)
      • Algorithm (3)
      • TroubleShooting (1)
      • 내일배움캠프 프로젝트 (14)
      • Setting (2)
      • ... (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 글쓰기
  • 링크

  • 인기 글

  • 태그

    김영한
    언체크예외
    트랜잭션
    제네릭
    spring
    중첩클래스
    빈 생명주기
    kotlin
    예외처리
    다형성
    김영한의 실전자바
    캡슐화
    Di
    배열
    jpa
    내일배움캠프
    OCP
    싱글톤
    java
    김영한의 실전 자바
  • hELLO· Designed By정상우.v4.10.3
공부처음하는사람
스프링 MVC - 기본 기능
상단으로

티스토리툴바