Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions mvc/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,11 @@ dependencies {
test {
useJUnitPlatform()
}

compileJava {
options.compilerArgs << '-parameters'
}

compileTestJava {
options.compilerArgs << '-parameters'
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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()));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HandlerMethodArgumentResolver를 구현하여 request, response 객체 또한 메서드 파라미터로 함꼐 전달할수 있도록 변경해보세요 😄

}
return (ModelAndView) method.invoke(object, request, response);
}

private boolean isParameterMappingMethod() {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지원하는 파라미터에 대해 HandlerExcution에서 관리하도록 정의해주셨는데 HandlerMethodArgumentResolver의 구현을 경험해보는것이 어떨가 싶어 의견 남겨 놓습니다 😄

파라미터 순서와 수에 따라 매핑을 하지 않고 Application에서 지원하는 파라미터 타입을 관리해보도록 정의해보세요 😄
https://github.com/spring-projects/spring-framework/blob/main/spring-web/src/main/java/org/springframework/web/method/support/HandlerMethodArgumentResolver.java

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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

36-41 라인의 기능과 함꼐 별도의 클래스로 관리 해볼수 있을것 같습니다 😄

}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package com.interface21.webmvc.servlet.mvc.tobe.support;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class PathPatternParser {
Expand All @@ -16,14 +15,29 @@ public PathPatternParser(String pattern) {
}

private String buildRegex(String pattern) {
return null;
Matcher matcher = Pattern.compile("\\{([^}]+)}").matcher(pattern);
StringBuffer buffer = new StringBuffer();
while (matcher.find()) {
variableNames.add(matcher.group(1));
matcher.appendReplacement(buffer, "([^/]+)");
}
matcher.appendTail(buffer);
return buffer.toString();
}

public boolean matches(String path) {
return false;
return pattern.matcher(path).matches();
}

public Map<String, String> extractUriVariables(String path) {
return null;
Matcher matcher = pattern.matcher(path);
if (!matcher.matches()) {
return Collections.emptyMap();
}
Map<String, String> variables = new HashMap<>();
for (int i = 0; i < variableNames.size(); i++) {
variables.put(variableNames.get(i), matcher.group(i + 1));
}
return variables;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@
public class PathPatternUtil {

public static String getUriValue(String pattern, String path, String key) {
return null;
final Map<String, String> uriVariables = getUriVariables(pattern, path);
return uriVariables.get(key);
}

public static Map<String, String> getUriVariables(String pattern, String path) {
return null;
if (!isUrlMatch(pattern, path)) {
return Map.of();
}
final PathPatternParser parser = new PathPatternParser(pattern);
return parser.extractUriVariables(path);
}

public static boolean isUrlMatch(String pattern, String path) {
return false;
final PathPatternParser parser = new PathPatternParser(pattern);
return parser.matches(path);
}
}
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]);
}
}