SpringMVCRequestParameter

1️⃣ 파라미터 바인딩 개요

파라미터 바인딩 어노테이션 비교 📋

어노테이션
데이터 출처
바인딩 방식
주요 용도

@RequestBody

HTTP 요청 바디

HTTP 메시지 컨버터

JSON/XML 등 바디 데이터 처리

@ModelAttribute

쿼리 파라미터, 폼 데이터

프로퍼티 접근법 (setter)

폼 제출, 단순 데이터 처리

@RequestParam

쿼리 파라미터, 폼 데이터

타입 변환기

단일 파라미터 처리

@PathVariable

URL 경로 변수

타입 변환기

경로 기반 데이터 처리

2️⃣ 요청 바디 처리 - @RequestBody

JSON 요청 바디 처리 📊

@Slf4j
@RestController
public class RequestBodyController {
    
    // 도메인 객체 예시
    @Data
    static class UserRequest {
        private String username;
        private int age;
    }
    
    // @RequestBody 객체 매핑
    @PostMapping("/users")
    public String createUser(@RequestBody UserRequest userRequest) {
        log.info("username={}, age={}", userRequest.getUsername(), userRequest.getAge());
        // 비즈니스 로직 처리
        return "사용자 생성 완료";
    }
    
    // @RequestBody + 검증(Validation)
    @PostMapping("/users/validate")
    public String createUserWithValidation(
            @RequestBody @Valid UserRequest userRequest,
            BindingResult bindingResult) {
        
        if (bindingResult.hasErrors()) {
            log.info("검증 오류 발생: {}", bindingResult);
            return "검증 오류";
        }
        
        log.info("검증 성공: username={}, age={}", 
                userRequest.getUsername(), 
                userRequest.getAge());
        return "검증된 사용자 생성 완료";
    }
    
    // JSON 배열 처리
    @PostMapping("/users/batch")
    public String createUsers(@RequestBody List<UserRequest> userList) {
        userList.forEach(user -> {
            log.info("username={}, age={}", user.getUsername(), user.getAge());
        });
        return "배치 사용자 생성 완료";
    }
}

@RequestBody 특징 🔍

  • HTTP 요청 바디(body)의 내용을 객체로 변환

  • JSON → 객체 변환에 HttpMessageConverter 사용 (MappingJackson2HttpMessageConverter 등)

  • 필수값으로 지정됨 (기본값: required=true) - 바디 없으면 400 오류

  • @Valid 또는 @Validated와 함께 사용하여 검증 가능

  • 주의: QueryString 파라미터는 처리하지 않음

  • modelAttribute 나 RequestParam 처럼 생략 불가능

    • 당연하지만, 미적용 시 RequestParam -> modelAttribute 순으로 인식하기 때문이며 대부분 body 부분은 생략 불가.

    • 해당 어노테이션은 HTTP 메세지 컨버터가 처리함.

3️⃣ 모델 파라미터 처리 - @ModelAttribute

@ModelAttribute 사용 예시 🏷️

@Controller
public class ModelAttributeController {
    
    // 도메인 객체 예시
    @Data 
    static class UserForm {
        private String username;
        private int age;
        private List<String> hobbies;
        private Address address;
    }
    
    @Data
    static class Address {
        private String city;
        private String street;
        private String zipcode;
    }
    
    // @ModelAttribute 명시적 사용
    @PostMapping("/signup")
    public String signup(@ModelAttribute("user") UserForm userForm, Model model) {
        // 비즈니스 로직 처리
        log.info("username={}, age={}", userForm.getUsername(), userForm.getAge());
        
        // Model에 추가 (생략 가능 - @ModelAttribute가 자동 추가)
        // model.addAttribute("user", userForm);
        
        return "user/signupComplete";  // 뷰 이름
    }
    
    // @ModelAttribute 생략 (객체)
    @PostMapping("/signup-simple")
    public String signupSimple(UserForm userForm) {  // @ModelAttribute 생략 가능
        log.info("간단 가입 처리: username={}, age={}", 
                userForm.getUsername(), userForm.getAge());
        return "user/signupComplete";
    }
    
    // 중첩된 객체 바인딩
    @PostMapping("/signup-address")
    public String signupWithAddress(@ModelAttribute UserForm userForm) {
        log.info("username={}, age={}", userForm.getUsername(), userForm.getAge());
        log.info("city={}, street={}, zipcode={}", 
                userForm.getAddress().getCity(),
                userForm.getAddress().getStreet(),
                userForm.getAddress().getZipcode());
        return "user/signupComplete";
    }
    
    // 컬렉션 바인딩 
    @PostMapping("/signup-hobbies")
    public String signupWithHobbies(@ModelAttribute UserForm userForm) {
        log.info("username={}, hobbies={}", 
                userForm.getUsername(), 
                userForm.getHobbies());
        return "user/signupComplete";
    }
    
    // 검증 추가
    @PostMapping("/signup-validate")
    public String signupWithValidation(
            @ModelAttribute @Valid UserForm userForm, 
            BindingResult bindingResult) {
        
        if (bindingResult.hasErrors()) {
            log.info("검증 오류 발생: {}", bindingResult);
            return "user/signupForm";  // 오류 발생 시 폼으로 돌아감
        }
        
        // 성공 로직
        return "user/signupComplete";
    }
    
    // 메서드 레벨 @ModelAttribute - 모든 핸들러에서 공통으로 사용
    @ModelAttribute("regions")
    public List<String> regions() {
        return List.of("서울", "경기", "강원", "제주");
    }
}

@ModelAttribute 특징 🔍

  • 요청 파라미터(쿼리 스트링, 폼 데이터)를 객체에 바인딩

  • 별도 어노테이션 없이 객체 타입 파라미터가 있으면 @ModelAttribute가 자동 적용

  • 바인딩 과정:

    1. 객체 생성 (기본 생성자 필요)

    2. 요청 파라미터 이름 = 객체 프로퍼티 이름인 경우 매핑

    3. 프로퍼티 타입에 맞게 값 변환 후 setter로 주입

  • 자동으로 Model에 추가됨 (뷰에서 바로 접근 가능)

  • 타입 변환 오류가 발생하면 BindingResult로 처리 가능

4️⃣ 단순 파라미터 처리 - @RequestParam

기본 사용법 📝

@RestController
@RequestMapping("/params")
public class RequestParamController {
    
    // 기본 사용법
    @GetMapping("/basic")
    public String basic(@RequestParam("name") String name,
                       @RequestParam("age") int age) {
        log.info("name={}, age={}", name, age);
        return "name=" + name + ", age=" + age;
    }
    
    // 파라미터명 생략 (변수명 = 파라미터명)
    @GetMapping("/simple")
    public String simple(@RequestParam String name, @RequestParam int age) {
        log.info("name={}, age={}", name, age);
        return "name=" + name + ", age=" + age;
    }
    
    // 어노테이션 자체 생략 (단순 타입만 가능)
    @GetMapping("/implicit")
    public String implicit(String name, int age) {
        log.info("name={}, age={}", name, age);
        return "name=" + name + ", age=" + age;
    }
    
    // 필수값 지정
    @GetMapping("/required")
    public String required(
            @RequestParam(required = true) String name,
            @RequestParam(required = false) Integer age) {
        log.info("name={}, age={}", name, age);
        return "name=" + name + ", age=" + age;
    }
    
    // 기본값 지정
    @GetMapping("/default")
    public String defaultValue(
            @RequestParam(defaultValue = "guest") String name,
            @RequestParam(defaultValue = "-1") int age) {
        log.info("name={}, age={}", name, age);
        return "name=" + name + ", age=" + age;
    }
    
    // 값이 없을 때 기본값 지정
    @GetMapping("/default-if-empty")
    public String defaultIfEmpty(
            @RequestParam(defaultValue = "guest") String name,
            @RequestParam(defaultValue = "-1") int age,
            @RequestParam(defaultValue = "EMPTY") String param) {
        log.info("name={}, age={}, param={}", name, age, param);
        return "name=" + name + ", age=" + age + ", param=" + param;
    }
    
    // Map으로 모든 파라미터 받기
    @GetMapping("/map")
    public String map(@RequestParam Map<String, Object> paramMap) {
        log.info("paramMap={}", paramMap);
        return "paramMap=" + paramMap;
    }
    
    // 다중값 처리 - 배열
    @GetMapping("/array")
    public String array(@RequestParam String[] names) {
        log.info("names={}", (Object) names);
        return "names=" + Arrays.toString(names);
    }
    
    // 다중값 처리 - 리스트
    @GetMapping("/list")
    public String list(@RequestParam List<String> names) {
        log.info("names={}", names);
        return "names=" + names;
    }
}

@RequestParam 고급 기능 📊

  • 필수값 설정: required=true (기본값) → 파라미터 없으면 400 오류

  • 기본값 설정: defaultValue="값" → 파라미터 없으면 기본값 사용

  • 빈 문자열 처리: defaultValue는 빈 문자열도 처리 가능

  • 다중값 처리:

    • 같은 이름의 파라미터가 여러 개일 때 배열/컬렉션으로 처리

    • 예: ?ids=1&ids=2&ids=3List<Integer> ids로 처리 가능

  • MultiValueMap: 파라미터별로 여러 값 처리

5️⃣ URL 경로 변수 처리 - @PathVariable

기본 사용법 🛣️

@RestController
@RequestMapping("/api")
public class PathVariableController {
    
    // 기본 사용법
    @GetMapping("/users/{userId}")
    public String getUserById(@PathVariable String userId) {
        log.info("userId={}", userId);
        return "조회된 회원 ID = " + userId;
    }
    
    // 변수명이 다를 경우
    @GetMapping("/orders/{orderId}")
    public String getOrderById(@PathVariable("orderId") String orderNumber) {
        log.info("orderId={}", orderNumber);
        return "조회된 주문 번호 = " + orderNumber;
    }
    
    // 다중 경로 변수
    @GetMapping("/users/{userId}/orders/{orderId}")
    public String getUserOrder(
            @PathVariable String userId,
            @PathVariable Long orderId) {
        log.info("userId={}, orderId={}", userId, orderId);
        return "회원 ID = " + userId + ", 주문 ID = " + orderId;
    }
    
    // 정규식 패턴 사용
    @GetMapping("/files/{filename:.+}")
    public String getFile(@PathVariable String filename) {
        log.info("filename={}", filename);
        return "파일명 = " + filename;
    }
    
    // PathVariable + RequestParam 함께 사용
    @GetMapping("/users/{userId}/settings")
    public String getUserSettings(
            @PathVariable String userId,
            @RequestParam(required = false) String theme,
            @RequestParam(defaultValue = "ko") String language) {
        
        log.info("userId={}, theme={}, language={}", userId, theme, language);
        return "회원 ID = " + userId + ", 테마 = " + theme + ", 언어 = " + language;
    }
}

@PathVariable 특징 🔍

  • URL 경로의 일부를 변수로 사용

  • RESTful API 설계에 필수적인 요소

  • 값이 반드시 존재해야 함 (required=true가 기본값)

  • 주의사항:

    • 경로 변수명과 메서드 파라미터명이 일치해야 함 (다르면 명시적으로 지정)

    • 타입 변환이 불가능한 경우 400 오류 발생

6️⃣ 쿠키 값 처리 - @CookieValue

쿠키 처리 예시 🍪

@RestController
public class CookieController {
    
    // 기본 사용법
    @GetMapping("/cookies/basic")
    public String getCookie(@CookieValue(name = "myCookie") String cookie) {
        log.info("myCookie={}", cookie);
        return "쿠키 값 = " + cookie;
    }
    
    // 필수 여부 설정
    @GetMapping("/cookies/optional")
    public String getOptionalCookie(
            @CookieValue(name = "myCookie", required = false) String cookie) {
        log.info("myCookie={}", cookie);
        return "쿠키 값 = " + (cookie != null ? cookie : "쿠키 없음");
    }
    
    // 기본값 설정
    @GetMapping("/cookies/default")
    public String getDefaultCookie(
            @CookieValue(name = "myCookie", defaultValue = "기본쿠키") String cookie) {
        log.info("myCookie={}", cookie);
        return "쿠키 값 = " + cookie;
    }
    
    // Cookie 객체로 직접 받기
    @GetMapping("/cookies/object")
    public String getCookieObject(@CookieValue(name = "myCookie") Cookie cookie) {
        log.info("쿠키 이름={}, 값={}, 도메인={}, 경로={}", 
                cookie.getName(), 
                cookie.getValue(), 
                cookie.getDomain(), 
                cookie.getPath());
        return "쿠키 객체 = " + cookie.getName() + ":" + cookie.getValue();
    }
    
    // 모든 쿠키 접근하기
    @GetMapping("/cookies/all")
    public String getAllCookies(HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();
        if (cookies == null) {
            return "쿠키 없음";
        }
        
        StringBuilder result = new StringBuilder();
        for (Cookie cookie : cookies) {
            result.append(cookie.getName()).append("=")
                .append(cookie.getValue()).append("; ");
        }
        
        return "모든 쿠키 = " + result.toString();
    }
    
    // 쿠키 생성 예시
    @GetMapping("/cookies/create")
    public String createCookie(HttpServletResponse response) {
        Cookie cookie = new Cookie("createdCookie", "hello-cookie");
        cookie.setMaxAge(60 * 60); // 1시간
        cookie.setPath("/");
        
        response.addCookie(cookie);
        return "쿠키 생성 완료";
    }
}

@CookieValue 특징 🔍

  • HTTP 요청의 쿠키 값을 파라미터로 받음

  • 필수값 설정: required=true (기본값)

  • 기본값 설정: defaultValue="값"

  • 쿠키는 문자열이지만 기본 타입 변환도 지원 (int, long 등)

  • Cookie 객체로 직접 받을 수도 있음

7️⃣ 헤더 값 처리 - @RequestHeader

헤더 처리 예시 📨

@RestController
public class HeaderController {
    
    // 단일 헤더 값 접근
    @GetMapping("/headers/basic")
    public String getHeader(@RequestHeader("User-Agent") String userAgent) {
        log.info("User-Agent={}", userAgent);
        return "User-Agent = " + userAgent;
    }
    
    // 필수 여부 설정
    @GetMapping("/headers/optional")
    public String getOptionalHeader(
            @RequestHeader(value = "X-Custom-Header", required = false) String customHeader) {
        log.info("X-Custom-Header={}", customHeader);
        return "X-Custom-Header = " + (customHeader != null ? customHeader : "헤더 없음");
    }
    
    // 기본값 설정
    @GetMapping("/headers/default")
    public String getDefaultHeader(
            @RequestHeader(value = "X-Custom-Header", defaultValue = "기본값") String customHeader) {
        log.info("X-Custom-Header={}", customHeader);
        return "X-Custom-Header = " + customHeader;
    }
    
    // 모든 헤더 맵으로 받기
    @GetMapping("/headers/map")
    public String getHeaderMap(@RequestHeader Map<String, String> headerMap) {
        headerMap.forEach((key, value) -> {
            log.info("header {}={}", key, value);
        });
        return "헤더 맵 크기 = " + headerMap.size();
    }
    
    // 다중값 헤더 처리
    @GetMapping("/headers/multi")
    public String getMultiValueHeader(
            @RequestHeader MultiValueMap<String, String> headerMap) {
        headerMap.forEach((key, values) -> {
            log.info("header {}={}", key, values);
        });
        return "다중값 헤더 맵 크기 = " + headerMap.size();
    }
    
    // 자주 사용하는 헤더 조합
    @GetMapping("/headers/common")
    public String getCommonHeaders(
            @RequestHeader("Host") String host,
            @RequestHeader("User-Agent") String userAgent,
            @RequestHeader(value = "Accept", required = false) String accept,
            @RequestHeader(value = "Accept-Language", required = false) String acceptLanguage,
            @RequestHeader(value = "Referer", required = false) String referer) {
        
        log.info("Host={}", host);
        log.info("User-Agent={}", userAgent);
        log.info("Accept={}", accept);
        log.info("Accept-Language={}", acceptLanguage);
        log.info("Referer={}", referer);
        
        return "일반적인 헤더 조회 완료";
    }
}

@RequestHeader 특징 🔍

  • HTTP 요청 헤더 값을 파라미터로 받음

  • 필수값 설정: required=true (기본값)

  • 기본값 설정: defaultValue="값"

  • 모든 헤더 정보: Map<String, String> 또는 MultiValueMap<String, String>로 받을 수 있음

  • 주의: 헤더명은 대소문자를 구분하지 않음 (HTTP 스펙)

Last updated