Spring

[Spring] MVC 12편 - 요청 매핑 완전 정복

dev-nadan 2025. 8. 14. 15:28

이번 편에서는 스프링 MVC에서 클라이언트 요청 URL과 컨트롤러 메서드를 연결하는 방법을 다룬다.

단순히 @RequestMapping 하나만 아는 것과, 다양한 속성과 어노테이션의 의미를 깊게 이해하는 것은 큰 차이가 있다. 실무에서는 이 매핑 설정이 잘못되면 API 호출이 아예 불가능해지거나, 잘못된 응답을 반환하는 경우가 생기므로 반드시 확실히 알아둬야 한다.


1. @RequestMapping 기본 사용

@RestController
public class MappingController {

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

    @RequestMapping("/hello-basic")
    public String helloBasic() {
        log.info("helloBasic");
        return "ok";
    }
}

 

핵심 개념

  • @RestController
    • @Controller + @ResponseBody 조합
    • 반환 값을 뷰로 해석하지 않고 HTTP 응답 바디에 그대로 작성
  • @RequestMapping("/hello-basic")
    • /hello-basic URL 호출 시 해당 메서드 실행
    • 배열 형태({"/path1", "/path2"})로 다중 매핑 가능

스프링 부트 3.0 이전과 이후 차이

  • 3.0 이전: /hello-basic/hello-basic/를 동일하게 처리
  • 3.0 이후: 두 경로를 서로 다른 요청으로 구분 (마지막 / 유지)

2. HTTP 메서드 지정

@RequestMapping은 기본적으로 모든 HTTP 메서드(GET, POST, PUT…)를 허용한다.

하지만 REST API에서는 보통 HTTP 메서드를 명확히 제한한다.

@RequestMapping(value = "/mapping-get-v1", method = RequestMethod.GET)
public String mappingGetV1() {
    log.info("mappingGetV1");
    return "ok";
}

 

  • method 속성으로 허용할 HTTP 메서드 지정
  • 지정하지 않으면 모든 메서드 허용
  • 잘못된 메서드로 요청 시 405 Method Not Allowed 반환

3. 축약 어노테이션

매번 method = RequestMethod.GET을 적는 건 번거롭다.

스프링은 이를 위해 축약형 어노테이션을 제공한다.

어노테이션 설명
@GetMapping GET 요청 전용
@PostMapping POST 요청 전용
@PutMapping PUT 요청 전용
@DeleteMapping DELETE 요청 전용
@PatchMapping PATCH 요청 전용
@GetMapping("/mapping-get-v2")
public String mappingGetV2() {
    log.info("mapping-get-v2");
    return "ok";
}

4. @PathVariable (경로 변수)

RESTful API에서 리소스 식별자는 URL 경로에 포함시키는 것이 일반적이다.

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

 

  • {userId} 부분이 경로 변수
  • @PathVariable("userId")로 바인딩
  • 변수명이 같다면 생략 가능: @PathVariable String userId

다중 경로 변수

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

5. params 속성 - 요청 파라미터 조건 매핑

@GetMapping(value = "/mapping-param", params = "mode=debug")
public String mappingParam() {
    log.info("mappingParam");
    return "ok";
}

 

  • params="mode" → 해당 파라미터가 존재해야 함
  • params="!mode" → 해당 파라미터가 없어야 함
  • params="mode=debug" → 값까지 일치해야 함
  • 여러 조건: params = {"mode=debug", "data=good"}

6. headers 속성 - HTTP 헤더 조건 매핑

@GetMapping(value = "/mapping-header", headers = "mode=debug")
public String mappingHeader() {
    log.info("mappingHeader");
    return "ok";
}

 

  • headers="mode=debug" → 요청 헤더 값이 일치해야 함
  • !로 부정 가능: headers="!mode"

7. consumes 속성 - Content-Type 조건 매핑

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

 

  • 요청 Content-Type과 매핑
  • 일치하지 않으면 415 Unsupported Media Type 발생

8. produces 속성 - Accept 조건 매핑

@PostMapping(value = "/mapping-produce", produces = "text/html")
public String mappingProduces() {
    log.info("mappingProduces");
    return "ok";
}
  • 요청 Accept 헤더와 매핑
  • 일치하지 않으면 406 Not Acceptable 발생

9. 클래스 레벨 매핑

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

    @GetMapping
    public String users() { 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;
    }
}

 

  • 클래스 레벨 @RequestMapping은 공통 URL prefix 지정
  • 메서드 레벨 매핑과 조합됨 (/mapping/users/{id})

회고

이번 장을 정리하면서 @RequestMapping 하나로도 수많은 매핑 조건을 만들 수 있다는 걸 다시 느꼈다.

과거에는 단순히 URL만 맞추면 된다고 생각했는데, 실제로는 HTTP 메서드, 파라미터, 헤더, 미디어 타입까지 세밀하게 조건을 걸 수 있어 API를 더 안전하고 명확하게 설계할 수 있다는 점이 인상 깊었다.

앞으로 REST API를 설계할 때 이런 매핑 조건을 적극 활용해서, 잘못된 요청이 아예 컨트롤러에 들어오지 않게 만드는 구조를 의식적으로 적용해야겠다.