본문 바로가기

Spring Boot

RESTful 구현 기본 : Controller에서 요청 처리

클라이언트 - 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 호출

한번 더 보는 클라이언트 - DispatcherServlet - Controller - DispatcherServlet - 클라이언트

 

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