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
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,13 @@

### [STEP3] JSON View κ΅¬ν˜„ν•˜κΈ°
#### TODO
- [ ] JsonView render κ΅¬ν˜„ ν›„ UserControllerκ°€ μ •μƒλ™μž‘ν•˜λŠ”μ§€ ν…ŒμŠ€νŠΈ
- [X] JsonView render κ΅¬ν˜„ ν›„ UserControllerκ°€ μ •μƒλ™μž‘ν•˜λŠ”μ§€ ν…ŒμŠ€νŠΈ

### [STEP4] 4단계 - Controller λ©”μ„œλ“œ 인자 λ§€ν•‘
#### TODO
- [X] method의 parameter typesλ₯Ό 톡해 parsingν•  수 μžˆλŠ” parameter parser
- [X] query param parser -> λ©”μ†Œλ“œ νŒŒλΌλ―Έν„°μ˜ type, name 보고 request.getParameter μ—μ„œ μΆ”μΆœ
- [X] path variable parser -> @PathVariable이 μžˆμ„κ²½μš° λ©”μ†Œλ“œ νŒŒλΌλ―Έν„°μ˜ type, name 보고 /{}/{} path에 νŠΉμ •ν•œ 양식 ({})으둜 μΆ”μΆœ
- [X] parameter typed parser -> νŠΉμ • νƒ€μž…μ„ deserialization. request.getParameterμ—μ„œ μΆ”μΆœ
- [X] handlerExecutionμ—μ„œ invoke μ‹œμ μ— parser둜 parsing
- [X] testusercontroller둜 확인
4 changes: 4 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,7 @@ sourceSets {
java.destinationDirectory.set(file('src/main/webapp/WEB-INF/classes'))
}
}

compileJava {
options.compilerArgs << '-parameters'
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
@ControllerAdvice
public class DefaultExceptionHandlers {

@ExceptionHandler(IllegalArgumentException.class)
public void handle(IllegalArgumentException ex, HttpServletRequest request, HttpServletResponse response) {
response.setStatus(400);
}

@ExceptionHandler(NoHandlerFoundException.class)
public void handle(NoHandlerFoundException ex, HttpServletRequest request, HttpServletResponse response) {
response.setStatus(404);
Expand Down
4 changes: 4 additions & 0 deletions mvc/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,7 @@ dependencies {
test {
useJUnitPlatform()
}

tasks.withType(JavaCompile) {
options.compilerArgs.add("-parameters")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.interface21.web.parameter;

import jakarta.servlet.http.HttpServletRequest;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public interface ParameterParser {
Object parse(final Method method, final Parameter parameter, final HttpServletRequest request);

}
Comment on lines +8 to +11
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ParameterParser λ₯Ό μΈν„°νŽ˜μ΄μŠ€λ‘œ μ •μ˜ν•΄μ£Όμ‹œκ³ ,
이에 λŒ€ν•œ 3κ°€μ§€ κ΅¬ν˜„μ²΄λ₯Ό λ§Œλ“€μ–΄μ£Όμ‹ μ  μ’‹μŠ΅λ‹ˆλ‹€. πŸ‘
(ParameterTypedParser, PathVariableParser, QueryParamParser)

Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.interface21.web.parameter;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

public class ParameterParsers {

private final List<ParameterParser> parsers = new ArrayList<>(Arrays.asList(new PathVariableParser(), new QueryParamParser()));

public ParameterParsers() {

}

public ParameterParsers(ParameterParser... parsers) {
this.parsers.addAll(Arrays.asList(parsers));
}
Comment on lines +13 to +23
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ParameterParsers μΌκΈ‰μ»¬λ ‰μ…˜μ€ λ””ν΄νŠΈλ‘œ PathVariableParser 와 QueryParamParser λ₯Ό κ°€μ§€κ³ ,
νŒŒλΌλ―Έν„°λ‘œ λ„˜κ²¨λ°›μ€ parsers λ₯Ό μΆ”κ°€λ‘œ List 에 μΆ”κ°€λ₯Ό ν•˜λŠ” κ΅°μš”


public Object[] parse(final Method method, final HttpServletRequest request, final HttpServletResponse response) {
var parameters = method.getParameters();
var params = new Object[parameters.length];

for (int i = 0; i < parameters.length; i++) {
var parsedParam = parse(method, parameters[i], request, response);
params[i] = parsedParam;
}
return params;
}

private Object parse(final Method method, final Parameter parameter, final HttpServletRequest request, final HttpServletResponse response) {
if (parameter.getType().equals(HttpServletRequest.class)) {
return request;
}
if (parameter.getType().equals(HttpServletResponse.class)) {
return response;
}

return parsers.stream().map(parser -> parser.parse(method, parameter, request)).filter(Objects::nonNull).findFirst()
.orElseThrow(IllegalArgumentException::new);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.interface21.web.parameter;

import com.interface21.web.support.TypedParsers;
import jakarta.servlet.http.HttpServletRequest;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;

public class ParameterTypedParser implements ParameterParser {
private final Class<?> type;

public ParameterTypedParser(Class<?> type) {
this.type = type;
}

@Override
public Object parse(Method method, Parameter parameter, HttpServletRequest request) {
if (!parameter.getType().equals(type)) {
return null;
}
var constructors = type.getDeclaredConstructors();
var fullyFieldConstructor = Arrays.stream(constructors).filter(constructor -> constructor.getParameterCount() == type.getDeclaredFields().length).findFirst();
if (fullyFieldConstructor.isPresent()) {
return parseFully(fullyFieldConstructor.get(), request);
}
var defaultConstructor = Arrays.stream(constructors).filter(constructor -> constructor.getParameterCount() == 0).findFirst();
return defaultConstructor.map(constructor -> parseDefault(constructor, request)).orElse(null);
}

private Object parseFully(Constructor<?> constructor, HttpServletRequest request) {
var parameters = constructor.getParameters();
var args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
args[i] = TypedParsers.parse(parameters[i].getType(), request.getParameter(parameters[i].getName()));
}
try {
return constructor.newInstance(args);
} catch (Exception e) {
throw new IllegalArgumentException();
}
}

private Object parseDefault(Constructor<?> constructor, HttpServletRequest request) {
try {
var instance = constructor.newInstance();
for (Field field : type.getDeclaredFields()) {
field.setAccessible(true);
field.set(instance, TypedParsers.parse(field.getType(), request.getParameter(field.getName())));
}
return instance;
} catch (Exception e) {
throw new IllegalArgumentException();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.interface21.web.parameter;


import com.interface21.web.bind.annotation.PathVariable;
import com.interface21.web.bind.annotation.RequestMapping;
import com.interface21.web.support.PathPatternUtil;
import com.interface21.web.support.TypedParsers;
import jakarta.servlet.http.HttpServletRequest;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class PathVariableParser implements ParameterParser {

@Override
public Object parse(Method method, Parameter parameter, HttpServletRequest request) {
var variable = parameter.getAnnotation(PathVariable.class);
var requestMapping = method.getAnnotation(RequestMapping.class);
if (variable == null || requestMapping == null) {
return null;
}
var name = variable.name();
if (name.isBlank()) {
name = parameter.getName();
}
var param = PathPatternUtil.getUriValue(requestMapping.value(), request.getRequestURI(), name);
if (param == null) {
return null;
}
return TypedParsers.parse(parameter.getType(), param);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.interface21.web.parameter;


import com.interface21.web.support.TypedParsers;
import jakarta.servlet.http.HttpServletRequest;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class QueryParamParser implements ParameterParser {

@Override
public Object parse(Method method, Parameter parameter, HttpServletRequest request) {
var param = request.getParameter(parameter.getName());
if (param == null) {
return null;
}
return TypedParsers.parse(parameter.getType(), param);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.interface21.web.support;

import java.util.*;
import java.util.regex.Pattern;

public class PathPatternParser {

private final Pattern pattern;
private final List<String> variableNames = new ArrayList<>();

public PathPatternParser(String pattern) {
String regex = buildRegex(pattern);
this.pattern = Pattern.compile(regex);
}

private String buildRegex(String pattern) {
var 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 pattern.matcher(path).matches();
}

public Map<String, String> extractUriVariables(String path) {
var 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;
}
Comment on lines +27 to +41
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

PathPatternUtil 에 λŒ€ν•œ ν…ŒμŠ€νŠΈλŠ” μž‘μ„±ν•΄μ£Όμ…¨μ§€λ§Œ, μ •μž‘ PathPatternParser 객체에 λŒ€ν•΄μ„œλŠ” ν…ŒμŠ€νŠΈ μž‘μ„±ν•΄μ£Όμ‹œμ§€ μ•Šμ•˜λŠ”λ° 보완 λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.

}
24 changes: 24 additions & 0 deletions mvc/src/main/java/com/interface21/web/support/PathPatternUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.interface21.web.support;

import java.util.Map;

public class PathPatternUtil {

Comment on lines +5 to +6
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
public class PathPatternUtil {
public final class PathPatternUtil {
private PathPatternUtil() {}

μ €λŠ” μœ ν‹Έμ„± 클래슀의 경우 μΈμŠ€ν„΄μŠ€μ˜ 생성이 λΆˆκ°€λŠ₯ν•˜λ„λ‘,
μƒμ„±μžλ₯Ό private 으둜 μ„ μ–Έν•΄μ£ΌλŠ” 것을 μ’‹μ•„ν•©λ‹ˆλ‹€.

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

public static Map<String, String> getUriVariables(String pattern, String path) {
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) {
final PathPatternParser parser = new PathPatternParser(pattern);
return parser.matches(path);
}
}
40 changes: 40 additions & 0 deletions mvc/src/main/java/com/interface21/web/support/TypedParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.interface21.web.support;


import java.util.function.Function;

public class TypedParser {
protected static final TypedParser STRING = new TypedParser(String.class, (s) -> s);
protected static final TypedParser INT = new TypedParser(int.class, Integer::parseInt);
protected static final TypedParser WRAPPER_INTEGER = new TypedParser(Integer.class, Integer::parseInt);
protected static final TypedParser LONG = new TypedParser(long.class, Long::parseLong);
protected static final TypedParser WRAPPER_LONG = new TypedParser(Long.class, Integer::parseInt);
protected static final TypedParser BOOLEAN = new TypedParser(boolean.class, Boolean::parseBoolean);
protected static final TypedParser WRAPPER_BOOLEAN = new TypedParser(Boolean.class, Boolean::parseBoolean);
protected static final TypedParser SHORT = new TypedParser(short.class, Short::parseShort);
protected static final TypedParser WRAPPER_SHORT = new TypedParser(Short.class, Short::parseShort);
protected static final TypedParser FLOAT = new TypedParser(float.class, Float::parseFloat);
protected static final TypedParser WRAPPER_FLOAT = new TypedParser(Float.class, Float::parseFloat);
protected static final TypedParser DOUBLE = new TypedParser(double.class, Double::parseDouble);
protected static final TypedParser WRAPPER_DOUBLE = new TypedParser(Double.class, Double::parseDouble);
protected static final TypedParser CHAR = new TypedParser(char.class, s -> s.charAt(0));
protected static final TypedParser WRAPPER_CHAR = new TypedParser(Character.class, s -> s.charAt(0));
Comment on lines +6 to +21
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

TypedParser 내뢀에 μ„ μ–Έλœ static μƒμˆ˜λ“€μ€ TypedParsers μ—μ„œλ§Œ μ“°μ΄λŠ” λ“―ν•œλ°,
ꡳ이 μƒμˆ˜λͺ…κΉŒμ§€ ν•˜λ‚˜ν•˜λ‚˜ μ§€μ–΄κ°€λ©° protected ν•˜κ²Œ 곡개λ₯Ό ν•  ν•„μš”κ°€ μžˆμ„μ§€ κΆκΈˆν•©λ‹ˆλ‹€.

TypedParsers 클래슀 μ•ˆμ—μ„œ 읡λͺ…μœΌλ‘œ μ„ μ–Έν•  μˆ˜λ„ μžˆμ„ 것 κ°™μ•„μ„œμš”.


private final Class<?> type;
private final Function<String, Object> parser;

public TypedParser(Class<?> type, Function<String, Object> parser) {
this.type = type;
this.parser = parser;
}

protected boolean canParse(Class<?> type) {
return type.equals(this.type);
}

protected Object parse(String value) {
return parser.apply(value);
}


}
19 changes: 19 additions & 0 deletions mvc/src/main/java/com/interface21/web/support/TypedParsers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.interface21.web.support;

import java.util.ArrayList;
import java.util.List;

public class TypedParsers {
private static List<TypedParser> parserList = new ArrayList<>(List.of(TypedParser.STRING, TypedParser.INT, TypedParser.WRAPPER_INTEGER, TypedParser.LONG, TypedParser.WRAPPER_LONG, TypedParser.BOOLEAN, TypedParser.WRAPPER_BOOLEAN, TypedParser.SHORT, TypedParser.WRAPPER_SHORT, TypedParser.FLOAT, TypedParser.WRAPPER_FLOAT, TypedParser.DOUBLE, TypedParser.WRAPPER_DOUBLE, TypedParser.CHAR, TypedParser.WRAPPER_CHAR));


public static void add(TypedParser... parsers) {
parserList.addAll(List.of(parsers));
}
Comment on lines +10 to +12
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

μš” λ©”μ„œλ“œλŠ” 어디에 μ“°μ΄λŠ”μ§€, 무슨 μ˜λ„λ‘œ λ§Œλ“  것인지 κΆκΈˆν•΄μ„œ μ—¬μ­ˆμ–΄λ΄…λ‹ˆλ‹€.
λŸ°νƒ€μž„ 쀑에 TypedParser κ°€ μΆ”κ°€λ˜λŠ” κ²½μš°κ°€ 상상이 κ°€μ§€λ₯Ό μ•Šμ•„μ„œμš”.


public static Object parse(final Class<?> type, final String value) {
return parserList.stream().filter(parser -> parser.canParse(type)).findFirst()
.orElseThrow(IllegalArgumentException::new)
.parse(value);
}
Comment on lines +14 to +18
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ν˜„μž¬ TypeParser λ₯Ό 찾을 λ•Œ List μ—μ„œ findFirst λ₯Ό ν•˜λŠ”λ°,
μΌκΈ‰μ»¬λ ‰μ…˜μ„ ν™œμš©ν•˜κ³  μ‹ΆμœΌμ…¨λ‹€κ³  μ˜λ„λ₯Ό μ΄ν•΄ν–ˆμŠ΅λ‹ˆλ‹€.
μΌκΈ‰μ»¬λ ‰μ…˜ 객체에 λŒ€ν•œ ν…ŒμŠ€νŠΈ μ½”λ“œ μž‘μ„±λ„ 같이 ν•΄μ£Όμ‹œλ©΄ μ–΄λ–¨κΉŒ μ œμ•ˆν•΄λ΄…λ‹ˆλ‹€.

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@

import com.interface21.context.stereotype.ControllerAdvice;
import com.interface21.web.bind.annotation.ExceptionHandler;
import com.interface21.web.parameter.ParameterParsers;

import java.lang.reflect.Method;
import java.util.Arrays;

public class ControllerAdviceHandlerMapping implements HandlerMapping<ExceptionHandlers> {

private final ParameterParsers parsers;
private final Object[] basePackage;

public ControllerAdviceHandlerMapping(final Object... basePackage) {
public ControllerAdviceHandlerMapping(ParameterParsers parsers, final Object... basePackage) {
this.parsers = parsers;
this.basePackage = basePackage;
}

Expand All @@ -28,7 +31,7 @@ private void mappingHandler(final ExceptionHandlers handlers, final Object contr
return;
}
for (Class<? extends Throwable> exceptionClass : handler.value()) {
var execution = new HandlerExecution(controllerInstance, method);
var execution = new HandlerExecution(controllerInstance, method, parsers);
handlers.add(exceptionClass, execution);
}
}
Expand Down
Loading