-
Notifications
You must be signed in to change notification settings - Fork 16
Step4 파라미터 매핑 기능 추가 #62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: newvelop
Are you sure you want to change the base?
Changes from all commits
377e62b
2d4edda
698ea1c
d6cadc0
972d8ca
0125ce1
d0cc87c
95406e5
1c5220d
1e7a8eb
c17c1c5
3ac6694
8c874d8
491cccf
cc73030
5a6dd45
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,13 @@ | |
| import jakarta.servlet.http.HttpServletRequest; | ||
| import jakarta.servlet.http.HttpServletResponse; | ||
|
|
||
| import java.lang.reflect.Field; | ||
| import java.lang.reflect.Method; | ||
| import java.lang.reflect.Parameter; | ||
| import java.util.Arrays; | ||
| import java.util.Map; | ||
|
|
||
| import static com.interface21.webmvc.servlet.mvc.tobe.support.ParameterConverter.convertToMethodArg; | ||
|
|
||
| public class HandlerExecution { | ||
| private Method method; | ||
|
|
@@ -16,6 +22,22 @@ public HandlerExecution(Method method, Object object) { | |
| } | ||
|
|
||
| public ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response) throws Exception { | ||
| if (isParameterMappingMethod()) { | ||
| return (ModelAndView) method.invoke(object, convertToMethodArg(method, request.getRequestURI(), request.getParameterMap())); | ||
| } | ||
| return (ModelAndView) method.invoke(object, request, response); | ||
| } | ||
|
|
||
| private boolean isParameterMappingMethod() { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 지원하는 파라미터에 대해 HandlerExcution에서 관리하도록 정의해주셨는데 파라미터 순서와 수에 따라 매핑을 하지 않고 Application에서 지원하는 파라미터 타입을 관리해보도록 정의해보세요 😄 |
||
| Parameter[] parameters = method.getParameters(); | ||
| if (parameters.length != 2) { | ||
| return false; | ||
| } | ||
|
|
||
| if (!parameters[0].getType().equals(HttpServletRequest.class)) { | ||
| return false; | ||
| } | ||
|
|
||
| return parameters[0].getType().equals(HttpServletResponse.class); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,163 @@ | ||
| package com.interface21.webmvc.servlet.mvc.tobe.support; | ||
|
|
||
| import com.interface21.web.bind.annotation.PathVariable; | ||
| import com.interface21.web.bind.annotation.RequestMapping; | ||
|
|
||
| import java.lang.reflect.*; | ||
| import java.util.Arrays; | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
|
|
||
| public class ParameterConverter { | ||
| public static Object[] convertToMethodArg( | ||
| Method method, | ||
| String uri, | ||
| Map<String, String[]> parameterMap | ||
| ) { | ||
| if (isDtoParam(method.getParameters())) { | ||
| return convertToDto(method.getParameters(), parameterMap); | ||
| } | ||
|
|
||
| return convertToPrimitive(method, uri, parameterMap); | ||
| } | ||
|
|
||
| private static boolean isDtoParam(Parameter[] parameters) { | ||
| // dto는 하나만 사용할 수 있게 처리 | ||
| if (parameters.length > 1) { | ||
| return false; | ||
| } | ||
|
|
||
| // 만약 primitive 타입이거나 wrapper class, 배열이 아닐 경우, 필드 매핑을 해줘야하기때문에 dto인지 판단 | ||
| return !parameters[0].getClass().isArray() | ||
| && !(isPrimitiveOrPrimitiveWrapperOrString(parameters[0].getType())); | ||
| } | ||
|
|
||
| // 출처 https://stackoverflow.com/questions/25039080/java-how-to-determine-if-type-is-any-of-primitive-wrapper-string-or-something | ||
| private static boolean isPrimitiveOrPrimitiveWrapperOrString(Class<?> type) { | ||
| return (type.isPrimitive() && type != void.class) || | ||
| type == Double.class || type == Float.class || type == Long.class || | ||
| type == Integer.class || type == Short.class || type == Character.class || | ||
| type == Byte.class || type == Boolean.class || type == String.class; | ||
|
Comment on lines
+37
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Primitive Type을 관리할수 있는 클래스를 추출해보는건 어떨까요 ? |
||
| } | ||
|
|
||
| private static Object[] convertToDto(Parameter[] parameters, Map<String, String[]> parameterMap) { | ||
| Parameter parameter = parameters[0]; | ||
| Class<?> clazz = parameter.getType(); | ||
| Object[] args = new Object[1]; | ||
| Map<String, Object> convertedFieldByName = new HashMap<>(); | ||
| Map<String, Class<?>> fieldTypeByName = new HashMap<>(); | ||
|
|
||
| for (int i = 0; i < clazz.getDeclaredFields().length; i++) { | ||
| Field field = clazz.getDeclaredFields()[i]; | ||
| convertedFieldByName.put(field.getName(), convertVariable(field.getName(), field.getType(), parameterMap)); | ||
| fieldTypeByName.put(field.getName(), field.getType()); | ||
| } | ||
|
|
||
| args[0] = getNewInstance(convertedFieldByName, fieldTypeByName, clazz); | ||
|
|
||
| return args; | ||
| } | ||
|
|
||
| private static Object getNewInstance( | ||
| Map<String, Object> convertedFieldByName, | ||
| Map<String, Class<?>> fieldTypeByName, | ||
| Class<?> clazz | ||
| ) { | ||
| Constructor<?>[] constructors = clazz.getDeclaredConstructors(); | ||
| for (Constructor<?> constructor : constructors) { | ||
| Parameter[] parameters = constructor.getParameters(); | ||
| boolean isPossibleToGetNewInstance = true; | ||
| Object[] args = new Object[parameters.length]; | ||
| for (int i = 0; i < parameters.length; i++) { | ||
| Parameter parameter = parameters[i]; | ||
| if (!convertedFieldByName.containsKey(parameter.getName())) { | ||
| isPossibleToGetNewInstance = false; | ||
| break; | ||
| } | ||
|
|
||
| if (!fieldTypeByName.containsKey(parameter.getName())) { | ||
| isPossibleToGetNewInstance = false; | ||
| break; | ||
| } | ||
|
|
||
| if (!fieldTypeByName.get(parameter.getName()).equals(parameter.getType())) { | ||
| isPossibleToGetNewInstance = false; | ||
| break; | ||
| } | ||
|
|
||
| args[i] = convertedFieldByName.get(parameter.getName()); | ||
| } | ||
|
|
||
| if (isPossibleToGetNewInstance) { | ||
| try { | ||
| return constructor.newInstance(args); | ||
| } catch (InstantiationException e) { | ||
| e.printStackTrace(); | ||
| } catch (IllegalAccessException e) { | ||
| e.printStackTrace(); | ||
| } catch (InvocationTargetException e) { | ||
| e.printStackTrace(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| throw new RuntimeException("해당하는 생성자가 없습니다"); | ||
| } | ||
|
|
||
| private static Object[] convertToPrimitive(Method method, String uri, Map<String, String[]> parameterMap) { | ||
| Parameter[] parameters = method.getParameters(); | ||
| Object[] args = new Object[parameters.length]; | ||
|
|
||
| for (int i = 0; i < parameters.length; i++) { | ||
| Parameter parameter = parameters[i]; | ||
| PathVariable pathVariable = parameter.getAnnotation(PathVariable.class); | ||
| if (pathVariable != null) { | ||
| args[i] = convertPathVariable(method, uri, parameter); | ||
| } else if (parameter.getType().isArray()) { | ||
| args[i] = convertArray(parameter.getName(), parameter.getType().getComponentType(), parameterMap); | ||
| } else { | ||
| args[i] = convertVariable(parameter.getName(), parameter.getType(), parameterMap); | ||
| } | ||
| } | ||
| return args; | ||
| } | ||
|
|
||
| private static Object convertPathVariable(Method method, String uri, Parameter parameter) { | ||
| String uriPattern = method.getAnnotation(RequestMapping.class).value(); | ||
| return convertValue( | ||
| parameter.getType(), | ||
| PathPatternUtil.getUriValue(uriPattern, uri, parameter.getName()) | ||
| ); | ||
| } | ||
|
|
||
| private static Object[] convertArray( | ||
| String parameterName, | ||
| Class<?> compositionType, | ||
| Map<String, String[]> parameterMap | ||
| ) { | ||
| String[] values = parameterMap.get(parameterName); | ||
| return Arrays.stream(values) | ||
| .map(v -> convertValue(compositionType, v)) | ||
| .toArray(); | ||
| } | ||
|
|
||
| private static Object convertVariable( | ||
| String parameterName, | ||
| Class<?> clazz, | ||
| Map<String, String[]> parameterMap | ||
| ) { | ||
| String value = parameterMap.get(parameterName)[0]; | ||
| return convertValue(clazz, value); | ||
| } | ||
|
|
||
| private static Object convertValue(Class<?> clazz, String value) { | ||
| if( Boolean.class == clazz || Boolean.TYPE == clazz) return Boolean.parseBoolean( value ); | ||
| if( Byte.class == clazz || Byte.TYPE == clazz) return Byte.parseByte( value ); | ||
| if( Short.class == clazz || Short.TYPE == clazz) return Short.parseShort( value ); | ||
| if( Integer.class == clazz || Integer.TYPE == clazz) return Integer.parseInt( value ); | ||
| if( Long.class == clazz || Long.TYPE == clazz) return Long.parseLong( value ); | ||
| if( Float.class == clazz || Float.TYPE == clazz) return Float.parseFloat( value ); | ||
| if( Double.class == clazz || Double.TYPE == clazz) return Double.parseDouble( value ); | ||
| return value; | ||
|
Comment on lines
+153
to
+161
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 36-41 라인의 기능과 함꼐 별도의 클래스로 관리 해볼수 있을것 같습니다 😄 |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| package com.interface21.webmvc.servlet.mvc.tobe; | ||
|
|
||
| import com.interface21.web.bind.annotation.RequestMapping; | ||
| import org.junit.jupiter.api.Test; | ||
| import samples.TestUserController; | ||
|
|
||
| import java.lang.reflect.Method; | ||
| import java.lang.reflect.Parameter; | ||
| import java.lang.reflect.Type; | ||
| import java.util.List; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.*; | ||
|
|
||
| class ParametersTest { | ||
| @Test | ||
| void 파라미터를_추출해서_저장() throws NoSuchMethodException { | ||
| Class<?> classObj = TestUserController.class; | ||
| Method method = classObj.getDeclaredMethod("test", List.class); | ||
|
|
||
| Parameter[] parameters = method.getParameters(); | ||
|
|
||
| List<String> test = List.of("a"); | ||
| System.out.println(parameters[0].getParameterizedType().equals(test.getClass())); | ||
| System.out.println(); | ||
| } | ||
|
|
||
| @Test | ||
| void 파라미터를_추출해서_저장2() throws NoSuchMethodException { | ||
| Class<?> classObj = TestUserController.class; | ||
| Method method = classObj.getDeclaredMethod("create_string", String.class, String.class); | ||
|
|
||
| Parameter[] parameters = method.getParameters(); | ||
|
|
||
| List<String> test = List.of("a"); | ||
| System.out.println(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| package com.interface21.webmvc.servlet.mvc.tobe.support; | ||
|
|
||
| import org.junit.jupiter.api.Test; | ||
| import samples.TestUser; | ||
| import samples.TestUserController; | ||
|
|
||
| import java.lang.reflect.Method; | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
|
||
| class ParameterConverterTest { | ||
| @Test | ||
| void 파라미터_매핑이_성공적으로_동작() throws NoSuchMethodException { | ||
| // given | ||
| Class<?> clazz = TestUserController.class; | ||
| Method method = clazz.getMethod("create_javabean", TestUser.class); | ||
| Map<String, String[]> parameterMap = new HashMap<>(); | ||
| parameterMap.put("userId", new String[]{"test"}); | ||
| parameterMap.put("password", new String[]{"pw"}); | ||
| parameterMap.put("age", new String[]{"12"}); | ||
|
|
||
| // when | ||
| Object[] result = ParameterConverter.convertToMethodArg(method, "test", parameterMap); | ||
|
|
||
| assertEquals(1, result.length); | ||
| assertTrue(result[0] instanceof TestUser); | ||
| } | ||
|
|
||
| @Test | ||
| void 단일원시타입_파라미터_매핑이_성공적으로_동작() throws NoSuchMethodException { | ||
| Class<?> clazz = TestUserController.class; | ||
| Method method = clazz.getMethod("create_int_long", long.class, int.class); | ||
| Map<String, String[]> parameterMap = new HashMap<>(); | ||
| parameterMap.put("id", new String[]{"1"}); | ||
| parameterMap.put("age", new String[]{"12"}); | ||
|
|
||
| // when | ||
| Object[] result = ParameterConverter.convertToMethodArg(method, "test", parameterMap); | ||
|
|
||
| assertEquals(2, result.length); | ||
| } | ||
|
|
||
| @Test | ||
| void 경로변수_추출_성공() throws NoSuchMethodException { | ||
| Class<?> clazz = TestUserController.class; | ||
| Method method = clazz.getMethod("show_pathvariable", long.class); | ||
| Map<String, String[]> parameterMap = new HashMap<>(); | ||
|
|
||
| // when | ||
| Object[] result = ParameterConverter.convertToMethodArg(method, "/users/1", parameterMap); | ||
|
|
||
| assertEquals(1, result.length); | ||
| assertEquals(1L, result[0]); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HandlerMethodArgumentResolver를 구현하여 request, response 객체 또한 메서드 파라미터로 함꼐 전달할수 있도록 변경해보세요 😄