Java/Spring

HandlerMethodArgumentResolver - 기본편

Reiphiel 2019. 7. 29. 00:14
반응형

Spring MVC를 이용하여 웹 어플리케이션을 개발할 경우 일반적인 MVC(Model View Controller)패턴과 동일하게 컨트롤러(Controller)를 이용하여 사용자의 입력을 처리하고 결과를 출력할 View로 연결합니다. 이때 컨트롤러의 메소드는 개발자가 직접 호출하지 않고 Dispatcher에 의해서 호출되므로 해당 메소드에 전달할 메소드의 Argument들을 해결(Resolve)해줄 방법이 필요합니다.

Spring에서는 HandlerMethodArgumentResolver를 이용하여 Argument를 해결하는 방법을 제공합니다. HandlerMethodArgumentResolver는 Argument들을 해결하기 위한 일종의 전략패턴으로 생각하시면 될 것 같습니다.

Spring에서는 컨트롤러의 Argument를 해결하기 위한 다양한 Built-in ArgumentResolver를 제공합니다. 다들 아시고 계시는 내용이겠지만 정리하는 차원에서 간단하게 정리해보았습니다.

RequestParamMethodArgumentResolver

Http요청의 파라미터를 해결해주는 ArgumentResolver입니다. @RequestParam 어노테이션을 지정했거나 MultipartFile 타입의 파라미터(MultipartResolver도 필요)를 해결해줍니다.

기본 사용법

@GetMapping("/params/simple")
public String getSimpleMapping(@RequestParam String test) {
    return test;
}

위의 예제와 같이 파라미터(매개변수)에 @RequestParam 어노테이션을 지정하면 파라미터명과 동일한 Http 파라미터를 받을 수 있습니다. 또한 문자열 뿐만 아니라 정수형, 실수형등 다양한 타입의 파라미터를 지정할 수 있습니다.

파라미터명 별도 지정

@GetMapping("/params/specific-parameter-name")
public String getSpecificNameMapping(@RequestParam(name = "test-2") String test) {
    return test;
}

Http의 파라미터명을 컨트롤러의 파라미터명과 별도로 지정해야할 필요성이 있는 경우 예를 들어 java의 변수명에 사용할 수 없거나 컨벤션에 맞지않는 문자를 지정해야 할 경우에 사용할 수 있습니다. 혹은 Spring의 설정에 따라 파라미터명이 Byte Code에 반영되지 않을 경우에 명시적으로 지정해야할 수 있습니다.

선택적 파라미터

@RequestParam을 지정한 파라미터의 경우 기본적으로 해당 파라미터가 Http 요청에 포함되어야 합니다. 지정한 파라미터가 존재하지 않을 경우 아래와 같이 예외가 발생하고 Http상태코드 400을 반환받게 됩니다.

 

지정한 파라미터가 존재하지 않는 경우

 

특정 파라미터를 선택적으로 받아야 할 경우에는 required 속성에 필수여부를 지정할 수 있습니다.

@GetMapping("/params/optional")
public String getOptionalParameter(@RequestParam(required = false) String test) {
    return test;
}

위와 같이 파라미터에 required = false를 지정하면 선택적으로 파라미터를 사용할 수 있습니다.

기본값

특정 파라미터에 값을 지정하지 않거나 파라미터가 존재하지 않을 경우 사용할 기본값을 지정할 수 있습니다.

@GetMapping("/params/default-value")
public String getDefaultValueMapping(@RequestParam(defaultValue = "not found") String test) {
    return test;
}

위와같이 defaultValue 속성에 지정한 값이 기본값으로 적용됩니다. 이 경우 required 속성을 지정하지 않더라도 암묵적으로 required = false와 동일하게 동작하게 됩니다. 이유는 파라미터가 존재하지 않더라도 지정한 기본값을 통해 파라미터에 값을 전달할 수 있기 때문입니다.

@GetMapping("/params/optional2")
public String getOptionalParameterMapping2(@RequestParam Optional<String> test) {
    return test.orElse("Not Found");
}

또한 파라미터의 타입으로 Optional을 지정할 경우 역시 선택적으로 파라미터를 사용할 수 있습니다.

Multi-Value 파라미터

동일한 파라미터를 다중으로 지정하거나 콤마(,)를 구분자로 지정된 파라미터를 Collection을 이용해서 파라미터를 받을 수 있습니다.

@GetMapping("/params/multi-value")
public List<String> getMultiValueMapping(@RequestParam List<String> test) {
    //Something to do 
}

RequestParamMapMethodArgumentResolver

RequestParamMethodArgumentResolver와 동일하게 Http 요청의 파라미터를 해결해주는 ArgumentResolver 입니다. 다른점은 파라미터의 타입이 Map인 경우에만 파라미터를 처리해 준다는 점입니다.

기본 사용법

파라미터의 타입으로 Map 혹은 MultiValueMap을 지정하고 @RequestParamname 속성을 별도로 지정하지 않은 경우 요청에 포함된 파라미터 전체를 한개의 파라미터로 받을 수 있습니다. 다만 Map을 지정한 경우에는 값을 여러개 가질 수 없으므로 요청된 동일한 이름의 파라미터중 HttpServletRequest에서 해당 파라미터명의 첫번째(0번) 인덱스의 값을 받을 수 있습니다.

@GetMapping("/map")
public Map<String, String> getMapMapping(@RequestParam Map<String, String> params) {
    return params;
}

@GetMapping("/multi-value-map")
public String getMultiValueMapMapping(@RequestParam MultiValueMap<String, String> params) {
    return params.toString();
}

파라미터 전체가 대상이므로 requireddefaultValue 속성도 반응하지 않는다는 점에 주의하시기 바랍니다.

PathVariableMethodArgumentResolver

@PathVariable을 파라메터에 지정하면 URI Template Variable(/some/{thing})에서 템플릿의('{', '}':Curly Brace로 감싸진 부분) 값을 파라메터로 받을 수 있습니다.

기본 사용법

@GetMapping("/orders/{orderNo}/lines/{lineNo}")
public void someController(@PathVariable Long orderNo, @PathVariable Long lineNo) {
    //Do Something
}

위와같이 파라메터명과 동일한 이름의 템플릿의 값을 파라미터로 받을 수 있으며 @PathVariablename 속성을 통해 템플릿 이름을 직접 지정할 수 있습니다. 또한 문자열뿐만 아니라 정수형, 실수형을 지정할 수 있습니다.

선택적 PathVariable

@PathVariable을 통해 PathVariable을 받을 경우에는 기본적으로 해당 값이 필수적으로 존재해야합니다. required 속성을 통해 @RequestParam과 동일하게 선택적으로 받도록 지정할 수 있습니다. 다만 PathVariable의 특성상 여러개의 PathVariable중 중간에 있는 값을 선택적으로 사용할 경우에는 사용할 수 없는 uri가 생성될 수 있으므로 주의가 필요합니다. 또한 마지막에 위치한 PathVariable을 선택적으로 사용할 경우에는 마지막 문자가 Path 구분자(/)인 uri 와 혼동될 수 있습니다.(Spring의 경우 런타임에 java.lang.IllegalStateException: Ambiguous handler methods mapped for 와 같은 예외를 발생합니다.)

@GetMapping("/optional/{path}")
public void getOptional(@PathVariable(required = false) String path){
    //Something to do
}

위와 같이 required = false를 지정하여 간단하게 선택적으로 사용할 수 있습니다.

Multi-Value Path Variable

Path Variable에 콤마(,)를 구분자로 지정된 한 값이 전달될 경우 Collection 타입의 파라미터로 받을 수 있습니다.

@GetMapping("/path/{variables}")
public List<String> getMultiValueMapping(@PathVariable List<String> variables) {
    return variables;
}

날짜타입

@PathVariable과 함께 @DateTimeFormat를 지정하면 날짜타입의 파라미터로도 받을 수 있습니다.

@GetMapping("/reservations/{date}")
public void getReservations(@PathVariable @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Date date) {
    //TODO
}

PathVariableMapMethodArgumentResolver

PathVariableMethodArgumentResolver와 동일하게 URI의 Path variable을 해결해주는 ArgumentResolver 입니다. 다른점은 파라미터의 타입이 Map인 경우에만 파라미터를 처리해 준다는 점입니다.

기본 사용법

파라미터의 타입으로 Map을 지정하고 @PathVariablename 속성을 별도로 지정하지 않은 경우 요청에 포함된 URI Path Variable 전체를 한개의 파라미터로 받을 수 있습니다.

@GetMapping("/{path}/{variable}")
public Map<String, String> getMapMapping(@PathVariable Map<String, String> variables) {
    return variables;
}

Path Variable 전체가 대상이므로 required는 속성은 적용되지 않습니다.

RequestHeaderMethodArgumentResolver

Http Request의 Header값을 해결해주는 ArgumentResolver로 @RequestHeader어노테이션을 파라메터에 지정하면 Http Request Header의 값을 파라미터로 받을 수 있습니다.

기본 사용법

@GetMapping("/something")
public String getSomething(@RequestHeader("user-agent") String userAgent) {
    return userAgent;
}

어노테이션의 name 속성에 지정한 이름의 Http Request Header의 값이 지정한 파라메터에 전달됩니다. 지정하지 않을 경우 변수명과 동일한 이름의 헤더의 키값이 지정되지만 변수에 사용할 수 없는 문자는 사용할 수 없으므로 지정하는 것을 추천합니다.(Http요청의 헤더에는 java의 변수명으로 사용할 수 없는 하이픈(-)이 포함되는 경우가 많습니다.)

선택적 Request Header

기본적으로 @RequestHeader를 통해 요청 헤더값을 받는 경우 해당 헤더가 존재하지 않을 경우 Http응답코드 400이 발생합니다. 이 경우 required 속성을 지정하면 요청 헤더값을 선택적으로 받을 수 있습니다.

@GetMapping("/something-optional")
public String getSomethingOptional(@RequestHeader(name = "optional-header", required = false) String header) {
    //Something to do
}

기본값

지정한 요청 헤더가 Http 요청에 포함되지 않는 경우 사용할 기본값을 지정할 수 있습니다.

@GetMapping("/something")
public String getHeaderDefaultValue(@RequestHeader(name = "some-header", defaultValue = "nothing") String header) {
    //Something to do
}

위와같이 defaultValue 속성에 지정한 값이 기본값으로 적용됩니다. 이 경우 required 속성을 지정하지 않더라도 파라미터에 값을 바인딩 할 수 있으므로 암묵적으로 required = false를 지정한 것처럼 예외가 발생하지 않습니다.

날짜타입

@RequestHeader과 함께 @DateTimeFormat를 지정하면 날짜타입의 헤더의 값도 받을 수 있습니다.

@GetMapping("/something")
public String getHeaderDateTime(@RequestHeader("some-header") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Date header) {
    //Something to do
}

RequestAttributeMethodArgumentResolver

Request Attribute의 속성을 해결해주는 ArgumentResolver 입니다. 서블릿 컨테이너에 의해서 설정된 속성이나 ServletRequest#setAttribute을 통해서 설정된 속성을 받을 수 있습니다.

기본 사용법

@GetMapping("/attribute/something")
public String getSomething(@RequestAttribute("something") String something) {
    //Something to do
}

위와 같이 파라미터에 @RequestAttribute 어노테이션에 지정한 이름 혹은 지정하지 않은 경우는 파라미터명과 동일한 속성값을 받을 수 있습니다.

선택적 Request Attribute

기본적으로 @RequestAttribute를 지정하여 Request Attribute를 받는 경우 해당 속성이 존재하지 않을 경우 예외가 발생하고 Http응답코드 400이 반환됩니다. 이 경우 required 속성을 지정하면 해당 속성값을 선택적으로 받을 수 있습니다.

@GetMapping("/attribute/something")
public String getSomething(@RequestAttribute(name = "something", required = false) String something) {
    //Something to do
}

SessionAttributeMethodArgumentResolver

HttpSession의 속성을 해결해주는 ArgumentResolver입니다. 컨트롤러의 파라미터에 @SessionAttribute 어노테이션을 지정하면 적용 가능합니다.

기본 사용법

@GetMapping("/session")
public void getSession(@SessionAttribute("someAttribute") String someAttribute) {
    //Something to do
}

위와 같이 파라미터에 @SessionAttribute 어노테이션에 지정한 이름 혹은 지정하지 않은 경우는 파라미터명과 동일한 세션의 속성값을 받을 수 있습니다.

선택적 Session Attribute

기본적으로 @SessionAttribute를 지정하여 Session Attribute를 받는 경우 해당 세션 속성이 존재하지 않을 경우 예외 처리되고 Http응답코드 400이 반환됩니다 이 경우 required 속성을 지정하면 해당 세션의 속성값을 선택적으로 받을 수 있습니다.

@GetMapping("/session")
public void getSession(@SessionAttribute(name = "someAttribute", required = false) String someAttribute) {
    //Something to do
}

ServletCookieValueMethodArgumentResolver

HttpServletRequest로부터 쿠키(Cookie)의 값을 해결하기 위한 ArgumentResolver입니다.

기본 사용법

@PostMapping
public void someController(@CookieValue(name="cookie_name") String cookie) {
    //Do Something
}

위와 같이 메소드의 파라메터에 @CookieVaule 어노테이션을 지정하면 HttpServletRequest에서 쿠키의 값을 파라미터로 받을 수 있습니다.

선택적 쿠키

@PostMapping
public void someController(@CookieValue(name="cookie_name", required = false) String cookie) {
    //Do Something
}

기본적으로 @CookieValue를 지저하여 쿠키값을 받는 경우 해당 쿠키가 존재하지 않을 경우 예외가 발생하고 Http응답코드 400이 반환됩니다. 이 경우 required 속성을 지정하면 해당 쿠키값을 선택적으로 받을 수 있습니다.

기본값

지정한 쿠키가 존재하지 않을 경우 사용할 기본값을 지정할 수 있습니다.

@GetMapping("/cookie")
public String getCookieDefaultValue(@Cookie(name = "some-cookie", defaultValue = "nothing") String header) {
    //Something to do
}

위와같이 defaultValue 속성에 지정한 값이 기본값으로 적용됩니다. 이 경우 required 속성을 지정하지 않더라도 파라미터에 값을 바인딩 할 수 있으므로 암묵적으로 required = false를 지정한 것처럼 예외가 발생하지 않습니다.

ModelAttributeMethodProcessor

Spring MVC 패턴에서 Model에 관한 속성을 처리해주는 ArgumentResolver입니다. 사용자의 입력 즉 Http요청의 파라미터를 Model에 바인딩해주는 역할을 수행합니다. 위에서 살펴본 RequestParamMethodArgumentResolver(@RequestParam)와 유사한 역할을 수행한다고 보면 되지만 POJO를 지정하여 여러개의 파라미터를 한개의 변수로 받을 수 있으므로 좀더 편리하게 사용하실 수 있습니다.

기본 사용법

@GetMapping("/model")
public void getModelFromParameter(@ModelAttribute("test") String test) {
    //Something to do
}

@GetMapping("/model")
public void getModel(@ModelAttribute TestDto test) {
    //Something to do
}

위와 같이 파라미터에 @ModelAttribute를 지정한 경우 해당 어노테이션에 지정한 이름에 해당하는 Http 파라미터를 받을 수 있습니다. 또한 POJO형태의 타입을 지정한 경우 해당 타입이 가진 프로퍼티에 Http 파라미터가 바인딩됩니다. 설정에 따라서 POJO를 지정한 경우 @ModelAttribute를 생략할 수 있으므로 편리하게 사용할 수 있습니다.

ExpressionValueMethodArgumentResolver

Spring의 표현식을 이용해서 값을 해결해주는 ArgurmentResolver입니다. @Value 어노테이션이 지정되어 있는 파라미터를 통해 값을 받을 수 있습니다. 보통 필드에 해당 어노테이션을 지정하는 방식을 많이 사용하므로 그다지 사용되지는 않지만 간단하게 알아보도록 하겠습니다.

기본 사용법

@GetMapping("/values/someValue")
public void getSomeValue(@Value("${someValue}") String someValue) {
    //Somthing to do
}

위와 같이 @Value 어노테이션에 지정한 표현식을 통해서 파라미터의 값이 바인딩 됩니다. 표현식에 대해서는 여기서는 별도로 다루지는 않겠습니다. 다만 위의 표현식의 경우 Spring의 환경변수 org.springframework.core.env.Enviroment#getProperty("someValue")을 호출한 결과를 얻게됩니다.

마치며

위에서 소개한 바와같이 HandlerMethodArgumentResolver를 이용하면 컨트롤러 내부에 작성해야할 보일러 플레이트 코드를 제거하여 코드를 깔끔하게 유지하여 비지니스 로직에만 집중할 수 있습니다. HandlerMethodArgumentResolver를 이용하여 공통화 가능한 중복코드가 있는지 확인해 보는 것을 추천합니다.