자바 백엔드 입문 27편 — @Controller @RequestMapping

2026-05-16자바 백엔드 입문

자바 백엔드 입문 27편. @Controller·@RequestMapping의 표준 패턴, 클래스 레벨 + 메서드 레벨 매핑 합성, HTTP 메서드별 단축 어노테이션 5종을 우편함 비유로 풀어쓴 학습 노트.

📚 자바 백엔드 입문 · 27편 — @Controller @RequestMapping

이 글은 자바 백엔드 입문 시리즈 59편 중 27편이에요. 25편에서 DispatcherServlet의 9단계 흐름을 풀었다면, 이번 27편은 @Controller + @RequestMapping — 그 흐름의 가장 중요한 한 부품인 컨트롤러 작성의 표준 패턴을 풀어 가요.

@RequestMapping이 헷갈리는 이유

처음 Spring 컨트롤러 코드를 보면 @RequestMapping·@GetMapping·@PostMapping 등이 클래스에도 박히고 메서드에도 박혀 있어요. "둘이 합쳐지는 건가? 우선순위가 있나?" 가 안 잡혀요.

이 글에서는 우편함 비유로 풀어요. 클래스 @RequestMapping("/orders") = 우편함 주소(아파트 동), 메서드 @GetMapping("/{id}") = 우편함 호수(305호). 두 개가 합쳐져서 "/orders/{id}" 완성 주소. 끝까지 따라오시면 컨트롤러 매핑 5가지 옵션이 한 그림에 들어와요.

기본 컨트롤러 패턴

@Controller 가 박힌 클래스 안에 메서드 + @GetMapping 같은 HTTP 메서드 매핑. 이게 모든 컨트롤러의 기본 골격이에요.

@Controller
public class OrderController {

    @GetMapping("/orders/{id}")
    public String getOrder(@PathVariable Long id, Model model) {
        model.addAttribute("order", orderService.findById(id));
        return "order-view";   // 뷰 이름 반환 (HTML 렌더)
    }
}

이 한 메서드가 하는 일 — "GET /orders/123 요청이 오면 getOrder(123, ...) 호출, 결과를 order-view 라는 HTML 템플릿으로 렌더". 컨트롤러 한 클래스 안에 메서드 여러 개가 있으면 — 각 메서드마다 별도 URL 매핑 등록.

@RequestMapping vs @GetMapping 단축형

매핑 어노테이션의 원조는 @RequestMapping 이에요. "이 메서드를 이런 HTTP 요청에 매핑해라" 모든 옵션을 다 받는 만능 어노테이션.

@RequestMapping(value = "/orders", method = RequestMethod.GET)
public String list() { ... }

이게 너무 길어서 Spring 4.3부터 HTTP 메서드별 단축형 5종이 도입됐어요.

단축형원본
@GetMapping("/path")@RequestMapping(value="/path", method=GET)
@PostMapping("/path")@RequestMapping(value="/path", method=POST)
@PutMapping("/path")@RequestMapping(value="/path", method=PUT)
@PatchMapping("/path")@RequestMapping(value="/path", method=PATCH)
@DeleteMapping("/path")@RequestMapping(value="/path", method=DELETE)

현대 Spring 코드는 단축형이 거의 100%. @RequestMapping 단독은 "한 메서드에 여러 HTTP 메서드 묶음 처리" 같은 특수 케이스에만 봐요.

클래스 레벨 + 메서드 레벨 매핑 합성 — 우편함 주소

@RequestMapping 은 클래스에도 박을 수 있어요. 그러면 클래스 매핑이 모든 메서드 매핑의 접두사가 돼요.

@Controller
@RequestMapping("/orders")              // ← 클래스 레벨, "아파트 동 주소"
public class OrderController {

    @GetMapping                          // → /orders
    public String list() { ... }

    @GetMapping("/{id}")                 // → /orders/{id}
    public String detail(@PathVariable Long id) { ... }

    @PostMapping                         // → /orders (POST)
    public String create(...) { ... }

    @DeleteMapping("/{id}")              // → /orders/{id} (DELETE)
    public String delete(@PathVariable Long id) { ... }
}

클래스에 /orders 를 박으면 — 안의 모든 메서드 매핑이 자동으로 /orders 접두사를 갖게 돼요. 반복 제거 + 한눈에 보기 쉬움이 핵심 이점.

비유로 풀면 — 클래스 @RequestMapping("/orders") = "이 컨트롤러는 주문 관련 우편함 전체 담당", 메서드 @GetMapping("/{id}") = "이 메서드는 그 우편함 안에서 특정 호수 담당". 두 개가 합쳐져 "/orders/{id} 완성 주소".

@RequestMapping의 5가지 옵션

가장 자주 만나는 5가지 옵션. 단축형 어노테이션에도 다 적용 가능.

1. value (또는 path) — URL 패턴

@GetMapping("/orders/{id}")             // 단일
@GetMapping({"/orders", "/order"})      // 여러 경로
@GetMapping("/files/**")                 // 와일드카드

{id} 같은 PathVariable, ** 같은 와일드카드, 정규식까지 지원.

2. params — 쿼리 스트링 조건

@GetMapping(value = "/orders", params = "status=active")
public String activeOrders() { ... }
// /orders?status=active 만 매칭

특정 쿼리 파라미터가 있을 때만 매칭. 같은 URL에 여러 메서드를 분기할 때 유용.

3. headers — HTTP 헤더 조건

@PostMapping(value = "/api/orders", headers = "X-API-Version=v2")
public Order createV2(...) { ... }
// X-API-Version: v2 헤더가 박혀 있을 때만 매칭

API 버전 분기에 자주 쓰여요.

4. consumes — 요청 본문의 Content-Type

@PostMapping(value = "/orders", consumes = MediaType.APPLICATION_JSON_VALUE)
public Order create(@RequestBody OrderRequest req) { ... }
// Content-Type: application/json 요청만 매칭

JSON·XML·multipart 분기.

5. produces — 응답의 Content-Type

@GetMapping(value = "/orders/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public Order json(@PathVariable Long id) { ... }

@GetMapping(value = "/orders/{id}", produces = MediaType.APPLICATION_XML_VALUE)
public Order xml(@PathVariable Long id) { ... }

같은 URL인데 클라이언트의 Accept 헤더에 따라 분기. "콘텐츠 협상(Content Negotiation)" 이라고 불러요.

매핑 우선순위 — 더 구체적인 게 이김

같은 URL에 매칭되는 매핑이 여러 개 있으면 Spring이 가장 구체적인 매핑 을 골라요. 룰:

  1. 정확한 경로 > 와일드카드
  2. params/headers/consumes/produces 조건 많은 게 우선
  3. HTTP 메서드 명시 > 미명시

예를 들어:

@GetMapping("/orders/{id}")                              // 일반
public Order getById(@PathVariable Long id) { ... }

@GetMapping(value = "/orders/{id}", params = "preview=true")   // 더 구체적
public Order getPreview(@PathVariable Long id) { ... }

/orders/123?preview=true 요청이 오면 — 두 메서드 다 매칭되지만 params 조건이 있는 두 번째가 우선. /orders/123 만 요청이면 첫 번째 매칭.

@PathVariable — URL 안의 변수 추출

{id} 같은 PathVariable을 메서드 매개변수로 받는 패턴.

@GetMapping("/orders/{orderId}/items/{itemId}")
public Item getItem(
    @PathVariable Long orderId,
    @PathVariable Long itemId) { ... }

매개변수 이름이 URL 변수 이름과 같으면 자동 매핑. 다르면 명시:

@GetMapping("/orders/{id}")
public Order get(@PathVariable("id") Long orderId) { ... }

22편에서 깊이 다룰 부분.

💡 REST 컨벤션

REST API 표준 컨벤션은 명확해요. 리소스 단위 복수형 URL(/orders) + HTTP 메서드로 동작 분기(GET 조회·POST 생성·PUT 전체 수정·PATCH 부분 수정·DELETE 삭제). URL 안에 /getOrder·/createOrder 같은 동사를 박지 않아요.

실전 예 — REST API 컨트롤러 전체 골격

위 다 합쳐서 "주문 관리 REST API" 한 컨트롤러를 짜면 이렇게 돼요.

@RestController
@RequestMapping("/api/orders")
@RequiredArgsConstructor
public class OrderController {

    private final OrderService orderService;

    @GetMapping
    public List<Order> list(@RequestParam(required = false) String status) {
        return orderService.findAll(status);
    }

    @GetMapping("/{id}")
    public Order detail(@PathVariable Long id) {
        return orderService.findById(id);
    }

    @PostMapping
    public Order create(@RequestBody @Valid OrderRequest req) {
        return orderService.create(req);
    }

    @PatchMapping("/{id}")
    public Order update(@PathVariable Long id, @RequestBody OrderUpdateRequest req) {
        return orderService.update(id, req);
    }

    @DeleteMapping("/{id}")
    public void delete(@PathVariable Long id) {
        orderService.delete(id);
    }
}

5개 메서드로 "주문 CRUD 전체" 가 완성. 이게 한국 회사 백엔드의 표준 컨트롤러 패턴이에요.

한 줄 정리 — @Controller 클래스 안에 @GetMapping·@PostMapping 메서드. 클래스 레벨 @RequestMapping("/path") 가 모든 메서드의 접두사. 5가지 옵션(params·headers·consumes·produces)으로 정교한 분기.

시험 직전 한 번 더 — 컨트롤러 매핑 입문자가 매번 헷갈리는 것

  • @Controller = HTML 뷰 렌더 (ViewResolver 거침)
  • @RestController = @Controller + @ResponseBody, JSON 직접 반환
  • @RequestMapping = 매핑 어노테이션의 원조. 모든 옵션 받음
  • HTTP 메서드별 단축형 5종 = @GetMapping·@PostMapping·@PutMapping·@PatchMapping·@DeleteMapping
  • 현대 Spring = 단축형이 거의 100%
  • 클래스 + 메서드 매핑 합성 = 클래스 /orders + 메서드 /{id}/orders/{id}
  • 클래스 레벨 매핑 = 반복 제거 + 가독성 향상
  • 5가지 옵션 = value·params·headers·consumes·produces
  • params = 쿼리 스트링 조건, headers = HTTP 헤더 조건
  • consumes = 요청 Content-Type, produces = 응답 Content-Type
  • produces 활용 = 콘텐츠 협상 (JSON vs XML vs Protobuf)
  • 매핑 우선순위 = 더 구체적인 매핑이 우선
  • 정확한 경로 > 와일드카드, 조건 많은 게 > 적은 것
  • @PathVariable = URL 변수 추출 (/{id} → 메서드 매개변수)
  • 매개변수 이름이 URL 변수와 같으면 자동 매핑
  • 다르면 @PathVariable("id") 명시
  • REST 컨벤션 = 복수형 리소스 URL + HTTP 메서드로 동작 분기
  • URL에 /getOrder·/create 동사 박지 X
  • 같은 URL 여러 컨트롤러 매핑 충돌 시 — 시작 시 즉시 예외
  • 한 컨트롤러 = 한 리소스 도메인 (OrderController = /orders 영역)
  • @RequestMapping 단독은 거의 안 쓰임 — 다중 HTTP 메서드 묶음에만

시리즈 다른 편 (앞뒤 글 모음)

이전 글:

다음 글:

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!