@MVC Controller Parameter

February 07, 2021

HttpServletRequest, HttpServletResponse

대다수의 경우는 보다 상세한 정보를 포함하고 있는 파라미터를 사용할 수 있어 필요가 없다. 하지만 원한다면 서블릿의 이 오브젝트들을 파라미터로 받아 사용할 수 있다.

@PostMapping("/sample")
public ResponseEntity<Void> parameterSample(HttpServletRequest req, HttpServletResponse res) {
	...
}

HttpSession, @SessionAttribute

위에서 말한 것 처럼 이는 HttpServletRequest 를 통해 가져올 수 있다. 하지만 세션만 필요하다면 이렇게 바로 선언하여 사용할 수 있다. 주의할 점은 HttpSession 은 멀티스레드 환경에서 안전하지 않을 수 있다. 안전하게 사용하려면 HandlerAdapter의 synchronizeOnSession 프로퍼티를 true로 설정해야 한다.

@PostMapping("/sample")
public ResponseEntity<Void> parameterSample(HttpSession session) {
	...
}

세션 전체가 필요한 것이 아니라 세션의 특정 값만 READ하는 것이 필요하다면 @SessionAttribute 를 사용할 수 있다. 세션에서 직접 꺼냈을 때는 각 데이터들이 Object 타입인 것에 비해 자동으로 Type Conversion을 지원해준다.

@PostMapping("/sample")
public ResponseEntity<Void> parameterSample(@SessionAttribute("id") String id) {
	...
}

WebRequest, NativeWebRequest

HttpServeletRequest의 요청 정보의 다수를 가지고 있으나, 서블릿 API에 종속적이지 않는 범용적인 요청이다. NativeWebRequestWebReuqest 내부에 존재하는 환경 종속적인 오브젝트(HttpServeltRequest 같은)를 가져올 수 있다.

@PostMapping("/sample")
public ResponseEntity<Void> parameterSample(WebRequest webRequest, NativeWebRequest nativeWebRequest) {
	...
}

Locale, TimeZone, ZoneId

DispatcherServlet의 Locale Resolver가 결정한 Locale 오브젝트와 이와 관련된 TimeZone, ZoneId 오브젝트를 받아 올 수 있다.

@PostMapping("/sample")
public ResponseEntity<Void> parameterSample(
							Locale locale, 
							TimeZone timeZone, 
							ZoneId zoneId) 
{
	...
}

InputStream, Reader

HttpServletRequest 의 스트림, Reader 타입의 오브젝트를 처리할 수 있다.

OutputStream, Writer

HttpServletResponse 의 스트림, Writer 타입의 오브젝트를 처리할 수 있다.

HttpMethod

현재 요청의 Http Method를 파악할 수 있다.

@PostMapping("/sample")
public ResponseEntity<Void> parameterSample(HttpMethod httpMethod) {
	...
}

@PathVariable

id를 기반으로 고객 정보를 조회할 때는 다음과 같은 API를 설계할 수 있다.

/customers/{id}

여기서 파라미터에 해당하는 부분에 {}를 넣는 URI를 템플릿을 사용하면 파라미터를 가져올 수 있다. 물론 여러개를 동시에 사용하는 것도 가능하다.

@RequestMapping("/customers/{id}")
public String info(@PathVariable("id") int id){
	...
}

여기서는 id는 정수 형태로 받고 있는데 일치하는 포맷이 들어오지 않으면 별도의 처리가 없는 이상 클라이언트는 400(Bad Request)를 받게 된다. 필수 값으로 지정하지 않으려면 required 옵션을 false로 주거나 Optional로 처리할 수 있다.

@RequestMapping("/customers/{id}")
public String info(@PathVariable(value = "id", required = false) int id){
	...
}
@RequestMapping("/customers/{id}")
public String info(@PathVariable Optional<Integer> id){
	...
}

@RequestParam

쿼리 매개변수 및 폼 데이터(멀티파트 포함)을 메소드에서 사용할 수 있도록 한다.

@PostMapping("/sample")
public String view(@RequestParam("id") int id){
	...
}

@RequestParam이 지정되었을 때 해당 값은 요청에 반드시 포함되어야 한다. 설정 값으로 필수 요소가 아닐 때는 required 옵션을 false로 주고, 그 때의 디폴트 값도 설정이 가능하다.

@PostMapping("/sample")
public String view(@RequestParam(value="id", required=false, defaultValue="-1")){
	...
}

또한, 각각의 파라미터를 받는 것이 아니라 Map 또는 MultiValueMap을 통해 전체를 받을 수도 있다.

@PostMapping("/sample")
public String view(@RequestParam Map<String, String> params){
	...
}

@CookieValue

쿠키 값을 받아올 수 있다.

@PostMapping("/sample")
public String check(@CookieValue("auth") String auth){
	...
}

@RequestHeader

헤더 정보를 받아 올 수 있다.

@PostMapping("/sample")
public void header(@RequestHeader("Host") String host, 
				@RequestHeader("Keep-Alive") long keepAlive){
	...
} 

Map, Model, ModelMap

특정한 애노테이션 없이는 java.util의 Map 그리고 스프링의 Model, ModelMap 타입을 사용할 수 있다. 그리고 이 값들은 모델 정보를 담는데 사용할 수 있다.

@PostMapping("/sample")
public void hello(ModelMap model){
	User user = new User();
	model.addATtribute("user", user);
}

@ModelAttribute

사용자 정보를 등록하는 API를 예시로 생각해본다.
사용자 정보는 id, 이름, 이메일 주소 등으로 구성된다. API를 구성하기 위한 한 가지 방법으로는 @RequestParam을 통해 파라미터와 일대일로 매핑하여 처리 가능하다.

@RequestMapping("/user/{id}")
public String user(@PathVariable("id") int id, 
				@RequestParm("name") String name, 
				@RequestParam("email") String email){
	User user = new User(id, name, email);
	...
} 

하지만 이 방식은 문제점이 존재한다. 예제는 정말 간단한 형태로 3가지 파라미터가 존재하지만 실제 사용자의 데이터는 더 많은 필드로 구성될 것이다. 이 때 모든 값들을 파라미터로 작성해야하며 또 사용자 정보가 더 늘어난다면 계속해서 파라미터 수정 작업을 해줘야 할 것이다.

이렇게 받은 파라미터는 결국 하나의 객체로 구성된다. @ModelAttribute는 이러한 데이터들을 자동으로 오브젝트 바인딩해주며, 특정 형태의 포맷만 처리하는 것이 아니라 Path variable, Query Parameter, Form data, Session 등의 정보를 모아 바인딩 해준다.

@RequestMapping("/user/{id}")
public String user(@ModelAttribute User user){
	...
}

파라미터로 사용되는 @ModelAttribute는 기능이 하나 더 존재한다. 컨트롤러가 반환하는 Model에 넘어온 오브젝트를 자동으로 추가해주는 것이다. 이 때, 기본적으로 모델의 이름은 파라미터 타입의 이름을 따른다.

그리고, @ModelAttribute 는 위와 같이 파라미터로 사용할 수도 있지만 컨트롤러의 메소드 레벨에서도 사용할 수 있다. 메소드 레벨에 애노테이션을 적용하면 해당 컨트롤러 내의 모든 @RequestMapping 메소드의 Model에 삽입되어 반환된다.

@Controller
public class MyController {
    @ModelAttribute
	public void myModel(Model model) {
		mode.addAttribute("my", "my");
		...
	}
}

Errors, BindingResult

@ModelAttribute와 주로 같이 사용되는 파라미터이다. @RequestParam@ModelAttribute의 동작은 좀 다르다.

숫자 형태의 id 값을 받는 예를 들어보자.

@RequestMapping("/user")
public String user(@RequestParam("id") int id){
	...
}

이 때, id 값으로 문자열이 전달된다면 @RequestParam 같은 경우에는 형 변환 실패로 바로 400(Bad Reuqest)를 날린다.

@RequestMapping("/user")
public String user(@ModelAttribute User user){
	...
}

@ModelAttribute 같은 경우는 User의 id 필드가 정수형이라도 400(Bad Request)를 반환하지 않는다. 대신 별도 객체인 BindingResultErrors에 에러 정보가 담긴채로 진행이 된다.

@RequestMapping("/user")
public String user(@ModelAttribute User user, Errors errors){
	...
}

이런 구조는 좀 더 유연한 구조를 가질 수 있다. 예를 들어, 회원 가입 상황을 예로 들었을 때 수 많은 폼을 작성하여 제출을 한다. 그런데 뭐 하나를 잘못 기입했거나 누락되었을 때 바로 400을 받아버리고 다 날아가면 너무 가슴 아픈 일이 아닐 수 없다. 그래서 @ModelAttribute를 사용할 때는 에러 처리를 컨트롤러에 맡기며 개발자가 적절한 처리를 할 수 있다.

@RequestMapping("/user")
public String user(@ModelAttribute User user, Errors errors){
	if(errors.hasError()){
		...
	}
	...
}

그리고 주의할 점은 이 파라미터들은 @ModelAttribute 바로 뒤에 와야 한다는 것이다. 바로 자기 앞에 있는 @ModelAttribute에 대한 결과 값을 담아 주기 때문에 주의하도록 하자.

SessionStatus

모델 오브젝트를 세션에 저장해뒀다가 다음 페이지에서 다시 사용할 수 있게 해주는 기능이다. 이 값 사용했으면 setComplete() 를 통해 반드시 자원을 해지해주자.

public String user(SessionStatus sessionStatus){
	...
	sessionStatus.setComplete();
}

@RequestBody, HttpEntity

HTTP 요청의 Body 부분을 HttpMessageConverter를 통해 변환하여 받아 올 수 있다. 만약 JSON이나 XML 기반으로 데이터가 전달된다면 이를 사용하여 처리할 수 있다. 일반적으로 @ResponseBody와 주로 함께 사용된다.

@ResponseBody
public String message(@RequestBody String body){
	...
}

HttpEntity@RequestBody처럼 Body 정보를 담으면서 헤더 값과 같은 HTTP 요청 정보에 접근할 수 있다.

@ResponseBody
public String message(HttpEntity<String> req){
	...
}

@Value

Bean을 DI 해주는 @Value를 파라미터에서도 사용할 수 있다. 주로 시스템 프로퍼티, 다른 빈의 프로퍼티, 클래스 상수, 특정 메소드의 호출 결과, 조건식 등을 얻을 수 있다.

public String hello(@Value("#{systemProperties['os.name']}") String osName){
	...
}

근데 그냥 컨트롤러 필드에 선언하고 DI 받는게 보기가 편할듯 하다.

@Valid

JSR-303 : Bean Validation을 통해 모델 오브젝트를 검증할 수 있다. 일반적으로 @ModelAttribute와 주로 사용한다.

@RequestMapping("/user")
public String user(@ModelAttribute @Valid User user, Errors errors){
	if(errors.hasError()){
		...
	}
}

참고


songmk 🙁