클라이언트 - Controller로부터 요청 처리를 할 때 쓰는 애너테이션들.. 그리고 원리
@PathVariable
- URI 경로의 일부를 변수로 받아오는 애너테이션
- RESTful 스타일에서 리소스의 식별자(ID, 우리가 쓰는 UUID의 id)를 표현할 때 사용함
- URI 템플릿(/users/{userId})에 포함된 값을 자동으로 바인딩한다..
- 애너테이션을 사용하면, 스프링 내부적으로 PathVariableMethodArgumentResolver에서 처리한다.
- > GET /api/users/10/orders/77
여기에서 /api/가 전체를 감싸는 문서명이고, users/10이 userId이다. 이걸로 유저를 찾을 수 있음
그리고 orders/77이 orderId이다. 이 유저가 어떤 주문을 했는지 알 수 있음
따라서 저건 GET이니까 유저10이 order77을 주문한걸 READ해주는것을 알 수 있음(GET)
@RestController
@RequestMapping("/api/users")
public class OrderController {
@GetMapping("/{userId}/orders/{orderId}")
public OrderDto getOrder(
@PathVariable("userId") UUID userId,
@PathVariable("orderId") UUID orderId) {
return orderService.findOrder(userId, orderId);
}
}
// URI : /api/users/10/orders/77 , 숫자는 예시임
* 클래스 위에 단순히 Controller가 아닌 RestController를 명시해줌으로 RESTful한 설계인걸 알 수 있음
* 메서드 위에 @GetMapping("/{userId}/orders/{orderId}") 가 붙어있는데
1) @RestMapping 애너테이션을 통해 /api/users/에 대한 Controller를 명시해주고, 이에 대한 값을 처리하는 역할을 함
2) @GetMapping 애터네이션을 통해 읽어주는 메서드라는걸 명시해주고
{userId}와 {orderId}에 @PathVariable 애너테이션으로 받은 UUID 값을 각각 넣어주어서
userId와 orderId에 대한 값을 HTTP에 보여준다.
Spring MVC에서 요청 파라미터가 컨트롤러 메서드에 바인딩되는 흐름, 동작과정
Client - > DispatcherServlet - > HandlerMapping - > HandlerAdapter(RequestMappingHandlerAdapter)
- > ArgumentResolvers(HandlerMethodArgumentResolver) - > Controller Method 호출

1) DispatcherServlet에서 HTTP의 요청을 받음
이 서블릿은 web.xml 또는 SpringBootServletInitializer 등을 통해 등록되어있음
2) HandlerMapping으로 어떤 컨트롤러 메서드가 호출될지 결정한다 (HTTP 메서드 : POST, GET 등등)
@RequestMapping과 같은 애너테이션 맵핑 정보를 여기에 저장한다.
3) HandlerAdapter에게 메서드 호출을 준비하는데, 이 메서드가 컨트롤러 메서드의 파라미터 타입을 알아보고
ArgumentResolver들을 순차적으로 적용한다고 한다.
4) HandlerMethodArgumentResolver 단계에서 Spring 컨트롤러 메서드 파라미터에 대해 작업들을 수행하는데..
| Resolver명 | 처리 대상 | 설명 |
| PathVariableMethodArgumentResolver | @PathVariable | URI 템플릿에서 값을 추출함 /user/{id} 에서 id의 값을 추출함 |
| RequestParamMethodArgumentResolver | @RequestParam | 쿼리 파라미터(?page=1) 또는 폼 필드 |
| ModelAttributeMethodProcessor | @ModelAttribute 또는 DTO(생략) | form-data, x-www-form-urlencoded 방식 요청에서 DTO를 바인딩함 |
| RequestResponseBodyMethodProcessor | @RequestBody | application/json 요청바디를 Java 객체로 직렬화한다. |
| ServletRequestMethodArgumentResolver | HttpServletRequest, HttpSession 등.. | |
| RequestHeaderMethodArgumentResolver | @RequestHeader | 헤더값 추출 |
* 복수의 PathVariable을 사용할 경우 명시적으로 이름을 지정해주는게 매우 좋다..
1개여도 명시하는게 내 입장에선 보기 편함!
* Swagger 문서화시 경로 변수는 자동으로 {}으로 표현된다.
@RequestParam
- HTTP 요청 쿼리 파라미터 또는 form-data를 컨트롤러 메서드 파라미터로 바인딩한다
- required, defaultValue, name 속성으로 제어할 수 있다고 함..
- 스프링 내부적으로 RequestParamMethodArgumentResolver에서 처리한다고 한다
- > GET /api/products?category=book&page=2&size=10
public class ProductController {
public Page<ProductDto> search( // 리턴 타입이 Page<ProductDto>
@RequestParam(defaultValue = "all") String category,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size
) {
return productService.findAll(category, page, size);
// Service쪽에 findAll은 리턴타입이 분명 Page<ProductDto> 일 것이다
}
}
속성요약
| required | 필수여부(기본값 true) | @RequestParam(required = false) |
| defaultValue | 값이 없을 때, 요청값으로 주어지지 않았을 때 기본값 | @RequestParam(defaultValue = "1") |
| name | 요청 파라미터의 이름이 다를 경우 바인딩해줌(명시) | @RequestParam(name="p") String query |
* 여러가지 값이 전달될 경우, List<String> 혹은 MultiValueMap<String, String>을 사용할 수 있음
* Map<String, String> 또는 DTO로 파라미터를 캡슐화할 경우 복잡한 쿼리를 깔끔하게 처리할 수 있다고 한다..
@RequestBody
- HTTP Body(JSON, XML 등등..)를 객체로 역직렬화(Java)하여 컨트롤러에 전달
- 주로 HTTP 메서드 POST/PUT/PATCH 요청에서 사용한다.
- Jackson(ObjectMapper)을 통해 DTO로 변환된다.
- Bean Validation(@Valid 애너테이션)와 함께 사용하면 자동으로 유효성 검증을 할 수 있다.
// 회원가입 예제코드
public class SignRequest {
@NotBlank(message = "이름은 꼭 필요해요") // 비어선 안된다..
private String name;
@Email(message = "이메일 형식이 잘못되었습니다?")
private String email;
@Size(min = 8, message = "비밀번호는 8글자 이상이여야해요!") // 최소 길이에 대한 애너테이션
private String password;
}
@RestController
@RequestMapping("/api/members")
public class MemberController {
@PostMapping("/signup")
public ResponseEntity<MemberDto> siunup(
@Valid @ResponseEntity SignupRequest request) {
MemberDto saved = memberService.signup(request);
return ResponseEntity.status(HttpStatus.CREATED).body(saved);
}
}
글로벌 예외처리 @RestControllerAdvice, 예외처리 하는 클래스에 붙이는거임
@RestControllerAdvice // 글로벌 예외처리로 @RestControllerAdvice를 적어주며 명시해줌
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class) // @ExceptionHandler 적어야함!
public ResponseEntity<map<String, Object>> handleValidationErrors(
MethodArgumentNotValidException ex) {
List<String> errors = ex.getBindingResult().getFieldErrors().stream()
.map(e -> e.getField() + ":" + e.getDefaultMessage())
.toList();
return ResponseEntity.badRequest().body(Map.of("errors",errors));
// bad Request는 500번에서 자주 봤는데..
}
}
* JSON 구조가 깊은경우, 중첩 DTO 구조화로 복잡도를 분리한다?
* @Valid를 DTO필드에도 재귀적으로 적용하려면 @Valid의 중첩이 필요하다?
* 파일 업로드나 복합적인 요청은 @RequestPart, @MultipartFile, @ModelAttribute를 활용한다..
예시 : 디렉터리의 구조
src/main/java/com/example
src/main/java/com/example/api - > 컨트롤러
api/ProductController.java
api.MemberController.java
src/main/java/com/example/dto - > DTO
api/ProductSearchCondition.java
api/SignupRequest.java
src/main/java/com/example/service - > 서비스
service/ProductService.java
src/main/java/com/example/exception - > 예외
exception/GlobalExceptionHandler.java
src/main/java/com/example/config - > Swagger인데..
config/SwaggerConfig.java
정리
| 위치 | 용도 | 유효성 검증 | |
| @PathVariable | URI Path | 리소스 식별자 | X |
| @RequestParam | Query Param | 필터링, 검색조건에 쓰임 | 기본값 / 필수여부 |
| @RequestBody | HTTP Body | 복잡한 객체 데이터 | DTO + @Valid |
@_@
'Spring Boot' 카테고리의 다른 글
| Spring Data JPA (2) | 2025.07.22 |
|---|---|
| Entity 설계 (7) | 2025.07.22 |
| RESTful API 기본 : @RestController (6) | 2025.07.18 |
| REST : 제약조건 (0) | 2025.07.10 |
| REST : 탄생배경과 개념 (7) | 2025.07.10 |