본문 바로가기
Spring/스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술

스프링 MVC 요청 매핑

by hk27 2022. 1. 30.

 

스프링 MVC는 요청이 들어오면 어떤 컨트롤러에 매핑하나요?

 

안녕하세요.

스프링 MVC에서 요청이 왔을 때, 어떤 컨트롤러가 매핑되는지 알아봅시다. 

 

요청 매핑

http://localhost:8080/mapping 으로 들어온 요청을 처리하는 컨트롤러는 아래 코드처럼 만듭니다.

@Slf4j
@RestController
public class MappingController {

    @RequestMapping("/mapping")
    public String mapping(){
        log.info("mapping");
        return "ok";
    }
 }

첫 번째 줄의 @Slf4j는 로그를 찍기 위한 어노테이션으로, 요청 매핑과는 무관합니다. 

두 번째 줄의 @RestController와 다섯 번째 줄의 @RequestMapping이 요청 매핑과 관련된 어노테이션 입니다.

클래스 레벨에 붙이는 @RestController는 @Controller와 @ResponseBody가 합쳐진 어노테이션입니다. 

@Controller 어노테이션이 붙으면 해당 클래스가 컨트롤러임을 스프링에 알리며, 스프링 빈에 어노테이션 기반 컨트롤러로 등록됩니다. 

@ResponseBody는 원래는 메소드 레벨에 붙이는 어노테이션으로, return된 String이 HTTP 메시지 바디에 바로 입력됩니다. 

메소드 레벨에 붙은 @RequestMapping은 단어 그대로 요청을 매핑하기 위한 어노테이션으로, 괄호 속의 URL과 같은 요청이 들어오면 처리합니다.

예를 들어서 위의 메소드처럼 "/mapping" 경로를 받는 메소드는 http://localhost:8080/mapping으로 들어온 모든 요청을 받습니다. 

들어가 보면 아래 사진처럼 ok가 HTTP 바디에 잘 입력된 것을 확인할 수 있습니다. 

 

그러나 실제로는 @RequestMapping 어노테이션을 잘 사용하지 않습니다.

왜냐하면 @RequestMapping은 모든 HTTP 메소드를 허용하기 때문입니다. 

보통 웹을 만들 때 HTTP 메소드를 나눠서 만들기 때문에, 특정 메소드 요청만 허용하는 방식을 많이 사용합니다.

특정 HTTP 메소드를 매핑하는 방식은 2가지가 있습니다.

// 방법1	
    @RequestMapping(value="/mapping-get-v1", method={RequestMethod.GET, RequestMethod.PUT}) 
    public String mappingGetV1(){
        log.info("mapping-get-v1");
        return "ok";
    }
    
// 방법2
    @GetMapping("/mapping-get-v2")
    public String mappingGetV2(){
        log.info("mapping-get-v2");
        return "ok";
    }

첫 번째 방법은 @RequestMapping에 method 속성을 추가해서  HTTP 메소드를 지정하는 방법이고, 두 번째 방법은 @GetMapping, @PostMapping 등 특정 HTTP 메소드를 명시한 어노테이션을 사용하는 방법입니다.

두 번째 방법이 더 편리하고 직관적이므로 많이 사용됩니다.

그러나 위의 예시 코드처럼 두 개 이상의 HTTP 메소드를 매핑하고 싶다면 첫 번째 방법으로 method를 나열해서 사용해야 합니다. 

 

PathVariable(경로 변수)

다음으로 경로 변수를 사용해서 매핑하는 방법을 알아보겠습니다.

경로 변수는 단어 그대로 요청 경로에 변수가 있는 것입니다.

예를 들어서 /mapping/userA, /mapping/userB처럼 사용자의 이름을 변수로 갖는 요청 경로는 어떻게 처리할까요?

중괄호를 사용해서 /mapping/{userId}로 사용하면 됩니다.

예제 코드를 봅시다.

    @GetMapping("/mapping/{userId}")
    public String mappingPathV1(@PathVariable("userId") String data){
        log.info("mapping userId={}",data);
        return "ok";
    }

@GetMapping에서 경로를 받을 때 경로 변수는 중괄호로 나타냅니다.

그리고 메소드의 파라미터로 @PathVariable("pathVariableName") String parameterName 코드로 데이터를 받습니다.

예를 들어서 http://localhost:8080/mapping/userA로 요청이 들어오면 data 변수의 값은 userA입니다. 

 

더 편리하게 경로 변수를 받는 방법도 있습니다.

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

@PathVariable의 속성값이 사라졌습니다. 경로 변수 중괄호에서 지정한 변수 이름과 사용할 변수 이름이 같으면 @PathVariable의 속성 괄호를 생략할 수 있습니다.

예를 들어서 /mapping/users/userA/orders/34로 요청이 들어오면 userId는 userA로, orderId는 34로 저장됩니다.

참고로 orderId를 Long으로 받기 때문에, /mapping/users/userA/orders/OrderA와 같이 숫자가 아닌 값이 들어오면

Failed to convert value of type 'java.lang.String' to required type 'java.lang.Long' 에러가 납니다. 

 

조건 매핑

다음으로 특정 조건이 만족할 때 매핑되는 컨트롤러를 알아봅시다.

 

1. 특정 파라미터 조건 매핑

특정 파라미터가 있을 때만 매핑되도록 조건을 추가할 수 있습니다.

예를 들어서 쿼리 파라미터 "mode=debug"가 들어와야 매핑되는 컨트롤러는 아래와 같습니다.

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

위 메소드처럼 매핑 어노테이션에 params 속성으로 지정할 수 있습니다. 주석처럼 다양하게 활용할 수 있습니다.

위의 메소드는 /mapping-param?mode=debug 요청이 들어올 때만 호출됩니다.

만약 /mapping-param?mode=debugging처럼 다른 요청이 들어가면 400 Bad Request 에러가 납니다.

 

2. 특정 헤더 조건 매핑

다음으로 특정 헤더가 있을 때 매핑되는 컨트롤러입니다. 

위에서 본 파라미터 조건 매핑과 거의 같습니다.

어노테이션의 속성으로 headers 값을 지정해야 한다는 점만 다릅니다.

코드를 같이 봅시다

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

헤더에 mode=debug를 추가해야 호출되고, 찾는 헤더가 없으면 매핑이 되지 않아서 404 not found 에러가 뜹니다.

 

3. 미디어 타입 조건 매핑: HTTP 요청 Content-Type, consume

HTTP 요청 메시지의 Content-Type 헤더는 요청 데이터의 타입 정보를 담고 있습니다.

예를 들어서 요청 메시지에서 json 데이터가 전송되면 Content-Type은 application/json입니다.

특정 Content-Type을 갖는 요청만 받고 싶을 때는 consumes를 설정합니다.

서버 입장에서 consume 할 수 있는 데이터이기 때문에 consumes를 사용하는 것 같습니다. 

예시 코드를 봅시다. 

    @PostMapping(value="/mapping-consume", consumes={"application/json", MediaType.TEXT_PLAIN_VALUE})
    public String mappingConsumes(){
        log.info("mappingConsumes");
        return "ok";
    }

"application/json"처럼 스트링으로 지정하거나 MediaType.TEXT_PLAIN_VALUE처럼 MediaType 클래스의 상수로 지정할 수 있습니다.

위의 코드는 application/json이나 text/plain 타입의 데이터를 갖는 요청만 받습니다.

만약 다른 타입이거나 데이터가 없으면 415 Unsupported Media Type 에러가 납니다. 

 

 

4. 미디어 타입 조건 매핑: HTTP 요청 Accept, produce

HTTP 요청 메시지에 Accept 헤더가 포함되어 있을 수 있습니다. 

Accept는 요청한 클라이언트가 받고자 하는 데이터 타입을 의미합니다. 

예를 들어서 Accept가 application/json이면 서버에서 json 데이터를 전송해야 합니다. 

Accept 값을 조건으로 해서 매핑할 수도 있습니다. 

produces 속성을 지정하면 되는데요. 서버에서 만드는 데이터이므로 produces인 것 같습니다.

예시 코드를 봅시다. 

    @PostMapping(value="/mapping-produce", produces=MediaType.APPLICATION_JSON_VALUE)
    public String mappingProduces(){
        log.info("mappingProduces");
        return "ok";
    }

위의 메소드는 accept가 application/json이면 매핑되고, text/plain이면 org.springframework.web.HttpMediaTypeNotAcceptableException 에러를 냅니다. 

 

참고로 accept 값이 없을 때도 매핑이 됩니다. accept를 별도로 지정하지 않으면 모든 데이터 타입을 받는다는 의미이기 때문입니다. 그러나 위에서 본 consumes는 데이터가 없어서 Content-Type이 없을 때 매핑되지 않고 에러가 납니다. 

 

 

이번 게시글에서는 스프링 MVC의 요청 매핑을 알아보았습니다. 

다음 게시글에서는 스프링 MVC로 HTTP 요청 헤더와 데이터를 조회하는 방법을 알아보겠습니다.

 

인프런  '스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술' 강의를 듣고 공부하며 정리한 자료입니다. 

잘못된 부분은 피드백 주시면 감사하겠습니다. 

글 읽어주셔서 감사합니다 :-)

 

참고 자료

스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술, 섹션 6. 스프링 MVC - 기본 기능 https://www.inflearn.com/course/스프링-mvc-1

 

 

댓글