-
Notifications
You must be signed in to change notification settings - Fork 302
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
[MVC 구현하기 - 1단계] 푸우(백승준) 미션 제출합니다. #344
Changes from all commits
83cd4e7
f384afe
529d97e
c2b6eab
d9ef3a5
9bc9b97
c852c27
bb7f0b0
382863b
5d64428
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 |
---|---|---|
@@ -1,11 +1,20 @@ | ||
package webmvc.org.springframework.web.servlet.mvc.tobe; | ||
|
||
import context.org.springframework.stereotype.Controller; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.lang.reflect.InvocationTargetException; | ||
import java.lang.reflect.Method; | ||
import java.util.Arrays; | ||
import java.util.HashMap; | ||
import java.util.HashSet; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
import java.util.Set; | ||
import java.util.function.BinaryOperator; | ||
import org.reflections.Reflections; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import web.org.springframework.web.bind.annotation.RequestMapping; | ||
|
||
public class AnnotationHandlerMapping { | ||
|
||
|
@@ -15,15 +24,83 @@ public class AnnotationHandlerMapping { | |
private final Map<HandlerKey, HandlerExecution> handlerExecutions; | ||
|
||
public AnnotationHandlerMapping(final Object... basePackage) { | ||
validateBasePackage(basePackage); | ||
this.basePackage = basePackage; | ||
this.handlerExecutions = new HashMap<>(); | ||
} | ||
|
||
private void validateBasePackage(Object[] basePackage) { | ||
for (Object o : basePackage) { | ||
if (!(o instanceof String)) { | ||
throw new IllegalArgumentException("basePackage 는 String 으로 입력해야 합니다"); | ||
} | ||
} | ||
} | ||
|
||
public void initialize() { | ||
log.info("Initialized AnnotationHandlerMapping!"); | ||
handlerExecutions.putAll(extractHandler()); | ||
} | ||
|
||
private Map<HandlerKey, HandlerExecution> extractHandler() { | ||
Reflections reflections = new Reflections(basePackage); | ||
return reflections.getTypesAnnotatedWith(Controller.class).stream() | ||
.map(this::extractHandlerFromClass) | ||
.reduce(new HashMap<>(), migrateHandler()); | ||
} | ||
|
||
private Map<HandlerKey, HandlerExecution> extractHandlerFromClass(Class<?> targetClass) { | ||
Object handler = toInstance(targetClass); | ||
return Arrays.stream(targetClass.getMethods()) | ||
.filter(method -> method.isAnnotationPresent(RequestMapping.class)) | ||
.map(method -> extractHandlerFromMethod(method, handler)) | ||
.reduce(new HashMap<>(), migrateHandler()); | ||
} | ||
|
||
private Object toInstance(Class<?> targetClass) { | ||
try { | ||
return targetClass.getDeclaredConstructor().newInstance(); | ||
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException | | ||
IllegalAccessException e) { | ||
throw new IllegalArgumentException(e); | ||
} | ||
} | ||
|
||
private Map<HandlerKey, HandlerExecution> extractHandlerFromMethod(Method method, Object handler) { | ||
HandlerExecution handlerExecution = new HandlerExecution(handler, method); | ||
RequestMapping annotation = method.getAnnotation(RequestMapping.class); | ||
return Arrays.stream(annotation.method()) | ||
.map(requestMethod -> { | ||
Map<HandlerKey, HandlerExecution> extractedHandlerMapping = new HashMap<>(); | ||
extractedHandlerMapping.put(new HandlerKey(annotation.value(), requestMethod), handlerExecution); | ||
return extractedHandlerMapping; | ||
}).reduce(new HashMap<>(), migrateHandler()); | ||
} | ||
|
||
private BinaryOperator<Map<HandlerKey, HandlerExecution>> migrateHandler() { | ||
return (originHandler, migrateHandler) -> { | ||
checkDuplication(originHandler, migrateHandler); | ||
originHandler.putAll(migrateHandler); | ||
return originHandler; | ||
}; | ||
} | ||
Comment on lines
+80
to
+86
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. 오.. 생소한 방식이네요! |
||
|
||
private void checkDuplication(Map<HandlerKey, HandlerExecution> originHandlers, | ||
Map<HandlerKey, HandlerExecution> newHandlers) { | ||
Set<HandlerKey> duplicatedHandlerKeys = new HashSet<>(originHandlers.keySet()); | ||
duplicatedHandlerKeys.retainAll(newHandlers.keySet()); | ||
if (!duplicatedHandlerKeys.isEmpty()) { | ||
HandlerKey duplicatedHandlerKey = duplicatedHandlerKeys.iterator().next(); | ||
log.error("duplication handler : {}", duplicatedHandlerKey); | ||
throw new IllegalArgumentException("Duplicated HandlerKey"); | ||
} | ||
} | ||
|
||
public Object getHandler(final HttpServletRequest request) { | ||
return null; | ||
Optional<HandlerKey> findHandler = handlerExecutions.keySet().stream() | ||
.filter(handlerKey -> handlerKey.canHandle(request)) | ||
.findAny(); | ||
return findHandler.map(handlerExecutions::get) | ||
.orElseGet(null); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
package webmvc.org.springframework.web.servlet.mvc.tobe; | ||
|
||
import jakarta.servlet.http.HttpServletRequest; | ||
import web.org.springframework.web.bind.annotation.RequestMethod; | ||
|
||
import java.util.Objects; | ||
|
@@ -14,6 +15,11 @@ public HandlerKey(final String url, final RequestMethod requestMethod) { | |
this.requestMethod = requestMethod; | ||
} | ||
|
||
public boolean canHandle(HttpServletRequest httpServletRequest){ | ||
return httpServletRequest.getMethod().equals(requestMethod.name()) && | ||
httpServletRequest.getRequestURI().equals(url); | ||
} | ||
Comment on lines
+18
to
+21
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. equals가 재정의되어있는데 해당 메소드를 다시 정의한 이유가 있을까요? 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. 저는 HandlerKey 이 RequestMapping 가 객체화됨으로써 요청으로 전달된 HttpRequest 가 Mapping 되는지 확인하는 역할로 생각했습니다 ㅎㅎ
요 상황에서 만약
이런게 오면 해당 Request 로 만든 HandlerKey 로 equals 연산을 못하겠다고 생각해서 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. 오 contentType까지 고려한 로직이였군요! |
||
|
||
@Override | ||
public String toString() { | ||
return "HandlerKey{" + | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package duplicate.case1; | ||
|
||
import context.org.springframework.stereotype.Controller; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import web.org.springframework.web.bind.annotation.RequestMapping; | ||
import web.org.springframework.web.bind.annotation.RequestMethod; | ||
import webmvc.org.springframework.web.servlet.ModelAndView; | ||
|
||
@Controller | ||
public class TestController { | ||
|
||
private static final Logger log = LoggerFactory.getLogger(samples.TestController.class); | ||
|
||
@RequestMapping(value = "/get-test", method = RequestMethod.GET) | ||
public ModelAndView duplicatedMethod(final HttpServletRequest request, | ||
final HttpServletResponse response) { | ||
return null; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package duplicate.case2; | ||
|
||
import context.org.springframework.stereotype.Controller; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import web.org.springframework.web.bind.annotation.RequestMapping; | ||
import web.org.springframework.web.bind.annotation.RequestMethod; | ||
import webmvc.org.springframework.web.servlet.ModelAndView; | ||
|
||
@Controller | ||
public class TestController { | ||
|
||
private static final Logger log = LoggerFactory.getLogger(samples.TestController.class); | ||
|
||
@RequestMapping(value = "/get-test", method = RequestMethod.GET) | ||
public ModelAndView duplicatedMethod1(final HttpServletRequest request, | ||
final HttpServletResponse response) { | ||
return null; | ||
} | ||
|
||
@RequestMapping(value = "/get-test", method = RequestMethod.GET) | ||
public ModelAndView duplicatedMethod2(final HttpServletRequest request, | ||
final HttpServletResponse response) { | ||
return null; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package duplicate.case3; | ||
|
||
import context.org.springframework.stereotype.Controller; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import web.org.springframework.web.bind.annotation.RequestMapping; | ||
import web.org.springframework.web.bind.annotation.RequestMethod; | ||
import webmvc.org.springframework.web.servlet.ModelAndView; | ||
|
||
@Controller | ||
public class TestController { | ||
|
||
private static final Logger log = LoggerFactory.getLogger(samples.TestController.class); | ||
|
||
@RequestMapping(value = "/get-test", method = { | ||
RequestMethod.GET, | ||
RequestMethod.GET, | ||
RequestMethod.GET, | ||
RequestMethod.GET, | ||
RequestMethod.GET | ||
}) | ||
public ModelAndView duplicatedMethod1(final HttpServletRequest request, | ||
final HttpServletResponse response) { | ||
return null; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,25 @@ | ||
package reflection; | ||
|
||
import java.lang.reflect.Method; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
import org.junit.jupiter.api.Test; | ||
|
||
class Junit3TestRunner { | ||
|
||
@Test | ||
void run() throws Exception { | ||
Class<Junit3Test> clazz = Junit3Test.class; | ||
List<Method> methods = Arrays.stream(clazz.getMethods()) | ||
.filter(method -> method.getName().startsWith("test")) | ||
.collect(Collectors.toList()); | ||
|
||
// TODO Junit3Test에서 test로 시작하는 메소드 실행 | ||
Junit3Test junit3Test = clazz.getConstructor() | ||
.newInstance(); | ||
|
||
for (Method method : methods) { | ||
method.invoke(junit3Test); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,31 @@ | ||
package reflection; | ||
|
||
import java.lang.annotation.Annotation; | ||
import java.lang.reflect.Method; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
import org.junit.jupiter.api.Test; | ||
|
||
class Junit4TestRunner { | ||
|
||
@Test | ||
void run() throws Exception { | ||
Class<Junit4Test> clazz = Junit4Test.class; | ||
List<Method> methods = Arrays.stream(clazz.getMethods()) | ||
.filter(method -> haveTestAnnotation(method)) | ||
.collect(Collectors.toList()); | ||
|
||
// TODO Junit4Test에서 @MyTest 애노테이션이 있는 메소드 실행 | ||
Junit4Test junit4Test = clazz.getConstructor() | ||
.newInstance(); | ||
|
||
for (Method method : methods) { | ||
method.invoke(junit4Test); | ||
} | ||
} | ||
|
||
private boolean haveTestAnnotation(Method method) { | ||
return Arrays.stream(method.getDeclaredAnnotations()) | ||
.anyMatch(annotation -> annotation instanceof MyTest); | ||
} | ||
} |
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.
꼼꼼한 검증 좋네요! 👍
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.
감사합니다 ㅎㅎ