PathVariable ๊ฐ์ RequestBody ์ค๋ธ์ ํธ์ ์ฃผ์ ํ๊ธฐ
์ค๋ ์ ๋ฆฌํ๊ณ ๊ณต์ ํ๊ณ ์ ํ๋ ๋ด์ฉ์ @PathVariable๋ก ์ง์ ๋ ํ๋ผ๋ฏธํฐ๊ฐ ์์ ๊ฒฝ์ฐ @RequestBody๊ฐ ์ง์ ๋ ์ค๋ธ์ ํธ ํ๋์ ๋ฐ์ดํฐ๋ฅผ ์ฃผ์ ํ ์ ์๋๋ก ํ๋ ๊ตฌํ ๋ฐฉ์์ด๋ค. @PathVariable๋ก ์ง์ ๋ ํ๋ผ๋ฏธํฐ๊ฐ ์์ ๊ฒฝ์ฐ @ModelAttribute์ ์ฃผ์ ๋๋๋ก ServletModelAttributeMethodProcessor๊ฐ ๊ตฌํ๋์ด์๋ ๋ฐ๋ฉด์ @RequestBody์ ๋ํด์ ๋ณํ์ ๋ด๋นํ๋ MappingJackson2HttpMessageConverter ์์๋ ObjectMapper ์ ์ํด ๋ณํ๋ง ์ํํ๋๋ก ๋์ด์๋ค.
๋ฐ๋ผ์, ๊ธฐ๋ณธ์ ์ผ๋ก๋ @PathVariable ํ๋ผ๋ฏธํฐ ๊ฐ์ด @RequestBody ์ค๋ธ์ ํธ์ ์ฃผ์ ๋์ง ์๋๋ค.
RequestBodyAdviceAdapter
ChatGPT ์๊ฒ ํํธ๋ฅผ ์ป์ด RequestBodyAdvice
์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ์ฌ RequestBody๋ฅผ ์ ํ์ฒ๋ฆฌ๋ฅผ ์ํํ ์ ์๋ค๋ ๊ฒ์ ์์๋๋ค. ๋๋ถ๋ถ @RestControllerAdvice์ ํจ๊ป ๊ณตํต ์ค๋ฅ์ฒ๋ฆฌ๋ฅผ ์ํ ํธ๋ค๋ฌ๋ฅผ ์์ฑํด์์ํ
๋ฐ RequestBodyAdviceAdapter๋ฅผ ํ์ฅํ์ฌ ServletModelAttributeMethodProcessor ์์์ ๊ตฌํ์ฒ๋ผ @PathVariable๋ก ์ง์ ๋ ํ๋ผ๋ฏธํฐ์ ๊ฐ์ RequestBody ์ค๋ธ์ ํธ์ ์ฃผ์
ํ ์ ์์ง ์์๊น ์๋ํด๋ณด์๋ค.
PathVariableRequestBodyBinder@RestControllerAdvice public class PathVariableRequestBodyBinder extends RequestBodyAdviceAdapter { private static final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); @SuppressWarnings("unchecked") @Override public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { Object object = super.afterBodyRead(body, inputMessage, methodParameter, targetType, converterType); Method method = methodParameter.getMethod(); RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes(); Map<String, ?> pathVariables = (Map<String, ?>) requestAttributes.getAttribute( HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); if (method == null || pathVariables == null || pathVariables.isEmpty()) { return object; } Class<?> clazz = object.getClass(); String[] parameterNames = parameterNameDiscoverer.getParameterNames(method); if (parameterNames != null && parameterNames.length > 0) { Parameter[] parameters = method.getParameters(); for (int index = 0; index < parameterNames.length; index++) { String parameterName = parameterNames[index]; Parameter parameter = parameters[index]; // NOTE: Set PathVariable into RequestBody. if (parameter.isAnnotationPresent(PathVariable.class) && pathVariables.containsKey(parameterName)) { try { Field field = clazz.getDeclaredField(parameterName); ReflectionUtils.makeAccessible(field); Object o = ReflectionUtils.getField(field, object); if (o == null) { ReflectionUtils.setField(field, object, pathVariables.get(parameterName)); } } catch (NoSuchFieldException ignored) { // ignored } } } } return object; } @Override public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { Method method = methodParameter.getMethod(); if (method != null) { Parameter[] parameters = method.getParameters(); for (Parameter parameter : parameters) { if (parameter.isAnnotationPresent(PathVariable.class)) { return true; } } } return false; } }
์ปจํธ๋กค๋ฌ ํธ๋ค๋ฌ ํจ์์ ํ๋ผ๋ฏธํฐ์ @PathVariable ์ด ์กด์ฌํ๋ค๋ฉด ์ฒ๋ฆฌํ๋๋ก supports
๊ฒฐ๊ณผ๋ฅผ ๊ตฌํํ์๋ค. RequestBodyAdviceAdapter ์์ฒด๊ฐ RequestBody๋ฅผ ์ฒ๋ฆฌํจ์ผ๋ก @PathVariable ํ๋ผ๋ฏธํฐ๊ฐ ์กด์ฌํ๋์ง๋ง ์ฒดํฌํด๋ ๋ฌด๋ฐฉํด๋ณด์ธ๋ค. RequestBody๊ฐ ์ค๋ธ์ ํธ๋ก ๋ณํ๋๊ณ ๋์ ์ฃผ์
์ ์๋ํ๊ธฐ ์ํด์ afterBodyRead
ํจ์๋ฅผ ์ค๋ฒ๋ผ์ด๋ฉํ์ฌ ๊ตฌํํ์๋๋ฐ HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE
๋ก PathVariable ๊ฐ์ ๊ฐ์ ธ์ค๊ณ ํ๋ผ๋ฏธํฐ ์ด๋ฆ์ ์ถ๋ก ํ๊ธฐ ์ํ์ฌ DefaultParameterNameDiscoverer
๋ฅผ ์ฌ์ฉํ๋๋ฐ ๊ทธ ์ด์ ์๋ @PathVariable
์ฒ๋ผ ์ด๋ฆ์ ๋ณ๋๋ก ์ง์ ํ์ง ์๋ ๊ฒฝ์ฐ arg0
๊ณผ ๊ฐ์ด ์ด๋ฆ์ด ๋ถ์ฌ๋๊ธฐ ๋๋ฌธ์ ๊ธฐ๋ณธ์ ์ผ๋ก๋ ๊ฐ์ ธ์ฌ ์ ์๋ค.
@RequestBody๋ก ๋ณํ๋์ด์ผํ๋ ์ค๋ธ์ ํธ ํ๋ผ๋ฏธํฐ์ ์ค์ ๋ก ํ๋๊ฐ ์กด์ฌํ๋์ง์ ๋ํด์๋ ๋ฆฌํ๋ ์
API์ ๋ํด์ ๊น์ ์ดํด๊ฐ ์๋ ๊ด๊ณ๋ก ์คํ๋ง ํ๋ ์์ํฌ์ ํฌํจ๋์ด์๋ ReflectionUtils
ํด๋์ค๋ฅผ ์ด์ฉํ์๋ค. ์๋ฐ์์ ๋๋ถ๋ถ์ ์ค๋ธ์ ํธ ํ๋์๋ private ์ ๊ทผ ์ ์ด์๋ฅผ ์ ์ธํ๋ฏ๋ก ์ ๊ทผํ ์ ์๋๋ก ํ ์ดํ์ ํ๋์ ๊ฐ์ด ์๋ ๊ฒฝ์ฐ์ ํํด์๋ง PathVariable์ ๋ํ ๊ฐ์ ๋ฐ์ธ๋ฉํ์๋ค. ๊ฒฐ๊ณผ์ ์ผ๋ก ์๋์ ์ด๋ฏธ์ง์ ๊ฐ์ด ์์ฒญ ๋ฐ์ดํฐ์ ์์ด๋๊ฐ ํฌํจ๋์ง ์์๋ PathVariable์ ํด๋น๋๋ 111 ๊ฐ์ด ํฌํจ๋จ์ ์ ์ ์๋ค.
DefaultParameterNameDiscoverer
์คํ๋ง ๋ถํธ 3.2 ๋ถํฐ๋ LocalVariableTableParameterNameDiscoverer
๊ฐ ์๋ StandardReflectionParameterNameDiscoverer
๊ฐ ์ฌ์ฉ๋๋ฏ๋ก -parameters
์ปดํ์ผ ์ต์
์ ํ์ฑํด์ผํ๋ค. ์คํ๋ง ๋ถํธ 2.7.18 ๋ฒ์ ๊ธฐ์ค์ DefaultParameterNameDiscoverer
๋ฅผ ๋ณด๋ฉด ์๋์ ๊ฐ์ด ๋์ด์์์ ํ์ธํ ์ ์๋ค.
DefaultParameterNameDiscovererpublic class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer { public DefaultParameterNameDiscoverer() { if (KotlinDetector.isKotlinReflectPresent() && !NativeDetector.inNativeImage()) { this.addDiscoverer(new KotlinReflectionParameterNameDiscoverer()); } this.addDiscoverer(new StandardReflectionParameterNameDiscoverer()); this.addDiscoverer(new LocalVariableTableParameterNameDiscoverer()); } }