본문 바로가기

JAVA/Spring Framework

[Spring] RequestParam값 객체로 매핑하기, Custom Annotation 만들기

스프링 Controller에서 Request값을 도메인객체를 사용해서 받을 때는  Json으로 넘어오는 경우에는 requestBody로 받으면 손쉽게 해결할 수 있었다. 
 
위의 상황을 코드로 이야기하자면 다음과 같다. 
 
 
@RequestMapping(value = "/binding", method = RequestMethod.POST)
@ResponseBody
public TesUsrAdmA createUser(
        @RequestBody TestDomain testDomain
) {
    return testDomain;
}
 
Post 의 payload에 데이터가 json 형식으로 포함되어 다음의 형식으로 넘어온 것을 jackson library가 messageConvert 해줍니다.
 
{
    name : "이름",
    etc : "etc들"
}
 
 
하지만 QueryString으로 데이터가 넘어온다면? 
 
더욱 쉽게 설명하자면 Get에서 url 뒤에 값을 넣어서 보내준다면 이것에 대한 정보를 도메인 객체로 받을 수 있을까?
 
localhost/help/domain/get_req_domain?adm_hp=143&adm_nm=2222&admId=123
 
결과는 받을 수 없었습니다.
 
받기 위해서는 다음의 작업을 해줘야만 했습니다.
 
@RequestMapping(value = "/binding2", method = RequestMethod.POST)
@ResponseBody
public TesUsrAdmA createUser2(
        @RequestParam("adm_id") String admId
        @RequestParam("adm_nm") String admNm
        @RequestParam("adm_hp") String admHp
) {
    TestDomain testDomain = new TestDomain();
    testDomain.setAdmId("admId");
    testDomain.setAdmNm("admNm");
    ...
    return testDomain;
}
 
이를 해결하기 위한 Custom Annotation을 만들어보겠습니다 .
 
  1. 사용될 Annotation 을 생성합니다. 
 
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface QueryStringArgResolver {
}
 
앞으로는 @QueryStringArgResolver를 @RequestBody 대신에 사용하면 QueryString을 객체에 매핑시켜줄 수 있도록 만들 것 입니다.
 
  1. Annotation비교 후 처리 로직 만들기
@Component
public class QueryStringArgumentResolver implements HandlerMethodArgumentResolver {
 
    @Autowired
    private ObjectMapper mapper;
 
 
    @Override
    public boolean supportsParameter(final MethodParameter methodParameter) {
        return methodParameter.getParameterAnnotation(QueryStringArgResolver.class) != null;
    }
 
 
    @Override
    public Object resolveArgument(final MethodParameter methodParameter,
                                  final ModelAndViewContainer modelAndViewContainer,
                                  final NativeWebRequest nativeWebRequest,
                                  final WebDataBinderFactory webDataBinderFactory) throws Exception {
 
        final HttpServletRequest request = (HttpServletRequest) nativeWebRequest.getNativeRequest();
        final String json = qs2json(request.getQueryString());
        final Object a = mapper.readValue(json, methodParameter.getParameterType());
        return a;
    }
 
 
    private String qs2json(String a) {
        String res = "{\"";
 
        for (int i = 0; i < a.length(); i++) {
            if (a.charAt(i) == '=') {
                res += "\"" + ":" + "\"";
            } else if (a.charAt(i) == '&') {
                res += "\"" + "," + "\"";
            } else {
                res += a.charAt(i);
            }
        }
        res += "\"" + "}";
        return res;
    }
}
 
@QueryStringArgResolver 을 만나게 되면 request 값에서 getQueryString()을 얻을 수 있습니다.
 
얻어낸 값을 Json type으로 변경시켜주는 작업을 qs2json 이라는 메소드를 통해서 작업해주고 
 
리턴된 json 형식의 String값을 jackson을 이용하여 object에 매핑될 수 있도록 작업해줍니다.
 
이때 jackson은 @Autowired된 값을 사용하며 이유는 jackson과 관련된 설정이 @RequestBody에서 동작하는 것과 동일하게 동작할 수 있도록 하기 위함입니다.  (jackson 스프링부트에서 지원하는 기본 messageConverter 라이브러리, ObjectMapper가 이에 해당함)
 
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {
    private final QueryStringArgumentResolver argumentResolver;
 
 
    @Override
    public void addArgumentResolvers(
            final List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(argumentResolver);
    }
}
 
 
  1. 실전에서 사용하기, Controller에 적용
@RequestMapping(value = "/binding2", method = RequestMethod.POST)
@ResponseBody
public TesUsrAdmA createUser2(
        @QueryStringArgResolver TestDomain testDomain
) {
    return testDomain;
}
 
@RequestBody와 동일하게 QueryString으로 넘어오는 값들에 대해서는 @QueryStringArgResolver  을 적용하면 QueryString을 읽어서 Json형식의 String 값으로 바꿔준 뒤 Jackson(ObjectMapper)를 사용해서 객체에 매핑할 수 있게 도와줍니다.
 
만일 내용이 이해가 잘 안가신다면 Jackson동작 원리에 대해서 확인하시면 좋은 공부가 될 것으로 판단이 되며 
당장 문제를 해결하기 위해서라면 위의 코드를 복사해서 사용하시면 됩니다.
 
 
참고 자료 :