Skip to content
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단계] 후디(조동현) 미션 제출합니다. #163

Merged
merged 8 commits into from
Sep 22, 2022

Conversation

devHudi
Copy link

@devHudi devHudi commented Sep 19, 2022

안녕하세요, 찬 🙌 후디입니다!
쉽지 않은 미션이었어요. 특히 처음에 실수로 Reflections 클래스를 사용하지 않고 구현하다가 후반부에 많이 갈아 엎었습니다 😓
HandlerAdapter 를 사용하는 것은 2단계 요구사항이므로 지금은 일반 Controller와 어노테이션 기반의 컨트롤러를 타입체크로 구분하였습니다. 이 부분은 2단계에서 개선해볼게요.
또, 테스트코드를 하나도 작성하지 못했는데요 ㅠㅠ 2단계 진행하면서 작성하지 못한 테스트 코드 열심히 채워보겠습니다. 자세한 내용은 코멘트로 달아둘게요!

@devHudi devHudi requested a review from kimchan123 September 19, 2022 17:10
@devHudi devHudi self-assigned this Sep 19, 2022
Comment on lines +46 to +55
// TODO: 다형성을 통해 해결해야함
if (controller instanceof HandlerExecution) {
HandlerExecution handlerExecution = (HandlerExecution) controller;
ModelAndView modelAndView = handlerExecution.handle(request, response);
Map<String, Object> model = modelAndView.getModel();
modelAndView.getView().render(model, request, response);
return;
}

final var viewName = ((Controller) controller).execute(request, response);
Copy link
Author

Choose a reason for hiding this comment

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

나중에 HandlerAdapter가 구현이되면, 자연스럽게 해결이 되지 않을까 하는 부분입니다. 사실 아직 HandlerAdapter 의 역할을 잘 알고있지는 않아서 아닐수도 있어요... 찬의 의견이 궁금합니다!

Choose a reason for hiding this comment

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

나중에 각 HandlerMapping을 처리할 수 있는 HandlerAdapter를 구현하면 자연스럽게 해결될 것 같아요~!


private void addHandlerExecutions(final Method method) {
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
List<HandlerKey> handlerKeys = HandlerKey.from(requestMapping);
Copy link
Author

Choose a reason for hiding this comment

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

HandlerKeyfromrequestMapping 어노테이션 객체(?)를 전달합니다. 내부에서 value() (URL)와 method() (RequestMethod[]) 를 requestMapping 에서 꺼내와서 List<HandlerKey> 를 반환합니다. 리스트를 반환하는 이유는 method() 의 반환값이 RequestMethod 의 배열이기 때문입니다. URL 하나에 여러 HTTP Method를 매핑하는 것이 의도된 것 같아서 이렇게 정적 팩토리 메소드로 만들었어요.

Comment on lines +14 to +17
public HandlerExecution(final Method method) {
this.controller = method.getDeclaringClass();
this.method = method;
}
Copy link
Author

Choose a reason for hiding this comment

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

Method 만 넣어주면, 알아서 컨트롤러의 Class 도 가져옵니다.

Choose a reason for hiding this comment

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

이건 저도 변경해야 할 부분인데 나중에 controller에 필드들이 추가가 된다면 지금과 같은 방법을 사용하면 대응을 할 수 없을 것 같아요! 추후 DI를 통해 생성된 Object를 필드로 갖고 있는 것이 좋을 것 같은데 후디의 생각은 어떠신가요?!

Comment on lines 19 to +23
public ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response) throws Exception {
return null;
Constructor<?> constructor = controller.getConstructor();
Object instance = constructor.newInstance();

return (ModelAndView) method.invoke(instance, request, response);
Copy link
Author

Choose a reason for hiding this comment

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

handle() 이 호출될 때 컨트롤러의 Class 를 사용해서 컨트롤러 인스턴스를 생성합니다.
HandlerExecution 이 초기화 되는 시점에서 인스턴스를 필드에 넣어주지 않은 이유는 handle 에서 throws 를 사용해 외부로 예외 처리를 미루는게 코드가 깔끔해지기 때문입니다.

Copy link

@kimchan123 kimchan123 left a comment

Choose a reason for hiding this comment

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

안녕하세요 후디! 블로그 포스팅 정말 잘 보고 있어요! 👍 리뷰하게 되어 영광입니다..!
전체적으로 코드를 깔끔하게 작성해주셔서 이해하기 정말 쉬웠어요 ㅎㅎ
궁금한 것 몇가지 리뷰로 남겼어요. 이해가 잘 안되거나 다른 의견있으시면 언제든 댓글이나 DM 주세요!
고생하셨습니다 ㅎㅎ

Comment on lines +46 to +55
// TODO: 다형성을 통해 해결해야함
if (controller instanceof HandlerExecution) {
HandlerExecution handlerExecution = (HandlerExecution) controller;
ModelAndView modelAndView = handlerExecution.handle(request, response);
Map<String, Object> model = modelAndView.getModel();
modelAndView.getView().render(model, request, response);
return;
}

final var viewName = ((Controller) controller).execute(request, response);

Choose a reason for hiding this comment

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

나중에 각 HandlerMapping을 처리할 수 있는 HandlerAdapter를 구현하면 자연스럽게 해결될 것 같아요~!

.findFirst()
.orElseThrow();
}

private void move(final String viewName, final HttpServletRequest request, final HttpServletResponse response) throws Exception {
private void move(final String viewName, final HttpServletRequest request, final HttpServletResponse response)

Choose a reason for hiding this comment

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

나중에 이부분도 DispatcherServlet이 아닌 View 쪽으로 로직을 넘기면 좋을 것 같아요!

Copy link
Author

Choose a reason for hiding this comment

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

HandlerAdapter 를 구현하며 자연스럽게 ModelAndView가 처리하도록 분리하였습니다!

Comment on lines +23 to +27
String url = request.getRequestURI();
RequestMethod requestMethod = RequestMethod.valueOf(request.getMethod());

this.url = url;
this.requestMethod = requestMethod;

Choose a reason for hiding this comment

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

이 부분 inline 처리하면 어떨까요?

this.url = request.getRequestURI();
this.requestMethod = RequestMethod.valueOf(request.getMethod()); 

이렇게요!

Copy link
Author

Choose a reason for hiding this comment

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

엇 ㅋㅋㅋ 제가 왜 이렇게 했을까요! 수정하겠습니다 ㅎㅎ

Comment on lines +69 to +71
if (handlerExecution == null) {
throw new ControllerNotFoundException();
}

Choose a reason for hiding this comment

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

request가 들어오면 DispatcherServlet에 있는 모든 HandlerMapping을 돌며 handle이 가능한지 여부를 확인할텐데 이렇게 특정 HandlerMapping에서 NotFoundException을 터트려버리면 다른 HandlerMapping에서 해당 request를 처리할 수 있는 여지를 남길 수 없을 것 같아요. 모든 HandlerMapping을 전부 순회했는데도 불구하고 찾지 못한다면 그 때 ControllerNotFoundException을 터트리는 것이 어떨까요?

Comment on lines +27 to +28
final var requestDispatcher = request.getRequestDispatcher(viewName);
requestDispatcher.forward(request, response);

Choose a reason for hiding this comment

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

model에 저장되어 있는 값을 request에 setting 한 후에 forward가 일어나야 할 것 같아요!

Comment on lines +14 to +17
public HandlerExecution(final Method method) {
this.controller = method.getDeclaringClass();
this.method = method;
}

Choose a reason for hiding this comment

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

이건 저도 변경해야 할 부분인데 나중에 controller에 필드들이 추가가 된다면 지금과 같은 방법을 사용하면 대응을 할 수 없을 것 같아요! 추후 DI를 통해 생성된 Object를 필드로 갖고 있는 것이 좋을 것 같은데 후디의 생각은 어떠신가요?!

Comment on lines +35 to +41
List<HandlerKey> handlerKeys = new ArrayList<>();
for (RequestMethod requestMethod : requestMethods) {
HandlerKey handlerKey = new HandlerKey(url, requestMethod);
handlerKeys.add(handlerKey);
}

return handlerKeys;

Choose a reason for hiding this comment

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

사소하지만 이부분을 메서드로 추출하면 더 깔끔할 것 같아요 ㅎㅎ!

@kimchan123 kimchan123 merged commit b04bd9d into woowacourse:devhudi Sep 22, 2022
@kimchan123
Copy link

후디 첫 PR 머지가 오늘까지라 머지하겠습니다! 2단계 화이팅이에요 ㅎㅎ

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants