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-Step 1] 베디 미션 제출합니다. #42

Merged
merged 30 commits into from
Oct 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
16624bd
feat: Java Reflection 실습
dpudpu Oct 3, 2019
bea6456
refactor: RequestMapping.method
dpudpu Oct 3, 2019
d55d63b
feat: 요구사항 1 - 애노테이션 기반 MVC 프레임워크
dpudpu Oct 3, 2019
d582303
feat: 요구사항 2 - 레거시 MVC와 애노테이션 기반 MVC 통합
dpudpu Oct 3, 2019
193e3c8
docs: README todo 작성
dpudpu Oct 3, 2019
ee565b2
feat: ControllerScanner.class
dpudpu Oct 5, 2019
a728a0c
feat: HandlerAdapter
dpudpu Oct 5, 2019
8d59a01
feat: HandlerAdapterNotSupported, HandlerNotFound 예외처리 추가
dpudpu Oct 5, 2019
9017e0f
feat: HandlerMethodArgumentResolver (리팩토링 필요)
dpudpu Oct 5, 2019
8fe1931
docs: README.md todo 작성
dpudpu Oct 5, 2019
a59b44b
feat: PathVariableHandlerMethodArgumentResolver.class
dpudpu Oct 6, 2019
6b5ea1c
feat: DefaultHandlerMethodArgumentResolver (javaBean, primitive, wrap…
dpudpu Oct 6, 2019
e64cb15
feat: HandlerMethodArgumentResolver HandlerAdapter 에 적용
dpudpu Oct 6, 2019
613f11c
feat: 요청 url PathPattern 적용
dpudpu Oct 6, 2019
9d17a10
refactor: rename DefaultHandlerAdapter -> HandlerExecutionAdapter
dpudpu Oct 6, 2019
e89aa58
refactor: PathVariableHandlerMethodArgumentResolver
dpudpu Oct 6, 2019
5abba0b
refactor: 패키지 재정의
dpudpu Oct 6, 2019
0691e1d
test: HandlerExecutionAdapterTest ModelAndView 변환 테스트 추가
dpudpu Oct 6, 2019
463f952
style: HandlerExecutionAdapter 메소드 추출 및 정리
dpudpu Oct 6, 2019
811d655
refactor: tobe.method -> tobe.resolver 패키지명 변경
dpudpu Oct 7, 2019
8218d13
refactor: ControllerScanner - Set<Class<?>> classes제거
dpudpu Oct 7, 2019
470eb98
test: 여러 RequestMapping 지원하는지 확인 테스트 추가
dpudpu Oct 7, 2019
e03f54b
feat: TypeConverter.class 형변환 enum
dpudpu Oct 7, 2019
801866e
test: @PathVariable 두개인 경우 테스트 추가
dpudpu Oct 7, 2019
8fd1d3a
refactor: PathUtils - PathPatternParser 상수로 변경
dpudpu Oct 7, 2019
fcebed4
refactor: PathUtils -> PathPatternUtils 이름 변경
dpudpu Oct 7, 2019
53417d9
refactor: MethodParameter - Method 필드 추가
dpudpu Oct 7, 2019
7dc3935
refactor: HandlerMethodArgumentResolver.resolveArgument() method 파라미터 삭제
dpudpu Oct 7, 2019
11275c6
refactor: PathVariableHandlerMethodArgumentResolver Long 외에도 형변환 지원 추가
dpudpu Oct 7, 2019
7c2b582
refactor: RequestMapping.method default 수정
dpudpu Oct 7, 2019
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
59 changes: 51 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,52 @@
# 프레임워크 구현
## 진행 방법
* 프레임워크 구현에 대한 요구사항을 파악한다.
* 요구사항에 대한 구현을 완료한 후 자신의 github 아이디에 해당하는 브랜치에 Pull Request(이하 PR)를 통해 코드 리뷰 요청을 한다.
* 코드 리뷰 피드백에 대한 개선 작업을 하고 다시 PUSH한다.
* 모든 피드백을 완료하면 다음 단계를 도전하고 앞의 과정을 반복한다.

## 우아한테크코스 코드리뷰
* [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md)
## 미션

#### 요구사항 1 - 애노테이션 기반 MVC 프레임워크

- AnnotationHandlerMappingTest 통과시키기
- @Controller 애노테이션이 설정되어 있는 클래스를 찾은 후 @RequestMapping 설정에 따라 요청 URL과 메소드를 연결

#### 요구사항 2 - 레거시 MVC와 애노테이션 기반 MVC 통합

- 지금까지 사용한 MVC 프레임워크(asis 패키지)와 새롭게 구현한 애노테이션 기반 MVC 프레임워크(tobe 패키지)가 공존이 가능한 구조로 MVC 프레임워크를 통합
- 기존코드 건들이지 말기



#### 미션 목표

- 레거시코드 수정 X
- stream 최대한 많이 쓰기 (연습)

#### 레거시 수정한 부분

- Controller.class Handler 상속
- ManualHandlerMapping.**getHandler(String)** -> **getHandler(HttpServletRequest)**

## TODO

- [x] 미션 요구사항 1,2 구현
- [x] 리턴타입 ModelAndView, String 둘 다 가능하게 하기
- [x] ComponentScanner
- [x] HandlerAdapter
- [x] Controller 메소드 인자 매핑 (java bean으로 매핑, PathVariable)
- [x] 역할별로 클래스 분리하기
- [x] 리팩토링하기
- [x] 누락된 기능 추가하기 (누락된 매핑 등)
- [x] 특정 기능들(형변환..) 재사용 가능하게 하기
- [x] DispatcherServlet 에 적용하기
- [ ] 성능 측정 ServletFilter 구현해보기
- [x] /users/{id} - url 요청 가능하게 하기

## 추가 미션
- [ ] 다른 템플릿 엔진 지원하기
- [ ] 인터셉터 구현하기

새로운 컨트롤러 (RequestMapping 스캔 기반)은 controller2 패키지에 위치



### 질문

- List<HandlerMapping> 도 포장하는 것이 좋을까?
- try-catch 는 필요한 부분만 묶는 것이 좋을까? 가독성을 위해서 전체를 묶는 것이 좋을까?
2 changes: 2 additions & 0 deletions nextstep-mvc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ dependencies {
api("com.fasterxml.jackson.core:jackson-databind:2.9.9.1")
implementation("com.github.jknack:handlebars:4.1.2")
implementation("org.apache.ant:ant:1.10.6")

testImplementation("org.mockito:mockito-junit-jupiter:2.17.0")
}
70 changes: 53 additions & 17 deletions nextstep-mvc/src/main/java/nextstep/mvc/DispatcherServlet.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
package nextstep.mvc;

import nextstep.mvc.asis.Controller;
import nextstep.mvc.tobe.exception.HandlerAdapterNotSupportedException;
import nextstep.mvc.tobe.exception.HandlerNotFoundException;
import nextstep.mvc.tobe.adapter.HandlerAdapter;
import nextstep.mvc.tobe.adapter.HandlerExecutionAdapter;
import nextstep.mvc.tobe.ModelAndView;
import nextstep.mvc.tobe.adapter.SimpleControllerAdapter;
import nextstep.mvc.tobe.mapping.HandlerMapping;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -11,46 +17,76 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;

@WebServlet(name = "dispatcher", urlPatterns = "/", loadOnStartup = 1)
public class DispatcherServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final Logger logger = LoggerFactory.getLogger(DispatcherServlet.class);
private static final String DEFAULT_REDIRECT_PREFIX = "redirect:";

private HandlerMapping rm;
private List<HandlerMapping> handlerMappings;
private List<HandlerAdapter> handlerAdapters;

public DispatcherServlet(HandlerMapping rm) {
this.rm = rm;
public DispatcherServlet(HandlerMapping... handlerMappings) {
this.handlerMappings = Arrays.asList(handlerMappings);
this.handlerAdapters = Arrays.asList(new SimpleControllerAdapter(), new HandlerExecutionAdapter());
}

@Override
public void init() throws ServletException {
rm.initialize();
handlerMappings.forEach(HandlerMapping::initialize);
}

@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String requestUri = req.getRequestURI();
logger.debug("Method : {}, Request URI : {}", req.getMethod(), requestUri);

Controller controller = rm.getHandler(requestUri);
try {
String viewName = controller.execute(req, resp);
move(viewName, req, resp);
} catch (Throwable e) {
logger.error("Exception : {}", e);
throw new ServletException(e.getMessage());
logger.debug("Method : {}, Request URI : {}", req.getMethod(), req.getRequestURI());

final Object handler = getHandler(req);

final HandlerAdapter handlerAdapter = getHandlerAdapter(handler);

final ModelAndView mav = handlerAdapter.handle(req, resp, handler);

// TODO ViewResolver (2단계)
move(mav, req, resp);

} catch (HandlerNotFoundException e) {
logger.error("not support uri: {} ", req.getRequestURI());
resp.sendError(SC_NOT_FOUND);
} catch (Exception e) {
logger.error(e.getMessage());
resp.sendError(SC_INTERNAL_SERVER_ERROR, e.getMessage());
}
}

private void move(String viewName, HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
private Object getHandler(final HttpServletRequest req) {
return handlerMappings.stream()
.map(handlerMapping -> handlerMapping.getHandler(req))
.filter(Objects::nonNull)
.findAny()
.orElseThrow(HandlerNotFoundException::new);
}

private HandlerAdapter getHandlerAdapter(final Object handler) {
return handlerAdapters.stream()
.filter(adapter -> adapter.supports(handler))
.findAny()
.orElseThrow(HandlerAdapterNotSupportedException::new);
}

private void move(ModelAndView mav, HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
final String viewName = mav.getViewName();
if (viewName.startsWith(DEFAULT_REDIRECT_PREFIX)) {
resp.sendRedirect(viewName.substring(DEFAULT_REDIRECT_PREFIX.length()));
return;
}

RequestDispatcher rd = req.getRequestDispatcher(viewName);
rd.forward(req, resp);
}
Expand Down
9 changes: 0 additions & 9 deletions nextstep-mvc/src/main/java/nextstep/mvc/HandlerMapping.java

This file was deleted.

This file was deleted.

10 changes: 0 additions & 10 deletions nextstep-mvc/src/main/java/nextstep/mvc/tobe/HandlerExecution.java

This file was deleted.

20 changes: 15 additions & 5 deletions nextstep-mvc/src/main/java/nextstep/mvc/tobe/ModelAndView.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
package nextstep.mvc.tobe;

import nextstep.mvc.tobe.view.View;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class ModelAndView {
private View view;
private Map<String, Object> model = new HashMap<String, Object>();
private Object view;
private Map<String, Object> model = new HashMap<>();

public ModelAndView() {
}

public ModelAndView(View view) {
public ModelAndView(final View view) {
this.view = view;
}

public ModelAndView(final String viewName) {
this.view = viewName;
}

public ModelAndView addObject(String attributeName, Object attributeValue) {
model.put(attributeName, attributeValue);
return this;
Expand All @@ -29,6 +35,10 @@ public Map<String, Object> getModel() {
}

public View getView() {
return view;
return view instanceof View ? (View) view : null;
}

public String getViewName() {
return view instanceof String ? (String) view : null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package nextstep.mvc.tobe.adapter;

import nextstep.mvc.tobe.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public interface HandlerAdapter {
ModelAndView handle(final HttpServletRequest req, final HttpServletResponse resp, final Object handler) throws Exception;

boolean supports(final Object handler);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package nextstep.mvc.tobe.adapter;

import nextstep.mvc.tobe.ModelAndView;
import nextstep.mvc.tobe.handler.HandlerExecution;
import nextstep.mvc.tobe.resolver.HandlerMethodArgumentResolverComposite;
import nextstep.mvc.tobe.resolver.MethodParameters;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class HandlerExecutionAdapter implements HandlerAdapter {
private HandlerMethodArgumentResolverComposite argumentResolvers;

public HandlerExecutionAdapter() {
argumentResolvers = new HandlerMethodArgumentResolverComposite();
}

@Override
public ModelAndView handle(final HttpServletRequest req, final HttpServletResponse resp, final Object handler) throws Exception {
final HandlerExecution handlerExecution = (HandlerExecution) handler;
final MethodParameters methodParameters = new MethodParameters(handlerExecution.getMethod());

final Object[] values = methodParameters.getMethodParams().stream()
.map(parameter -> {
if (parameter.isSameType(HttpServletRequest.class)) {
return req;
}
if (parameter.isSameType(HttpServletResponse.class)) {
return resp;
}
return argumentResolvers.resolveArgument(req, parameter);
})
.toArray();

final Object result = handlerExecution.execute(values);

return parseModelAndView(result);
}

private ModelAndView parseModelAndView(final Object result) {
if (result instanceof String) {
return new ModelAndView(String.valueOf(result));
}
return (ModelAndView) result;
}

@Override
public boolean supports(final Object handler) {
return handler instanceof HandlerExecution;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package nextstep.mvc.tobe.adapter;

import nextstep.mvc.asis.Controller;
import nextstep.mvc.tobe.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class SimpleControllerAdapter implements HandlerAdapter {

@Override
public ModelAndView handle(final HttpServletRequest req, final HttpServletResponse resp, final Object handler) throws Exception {
final Controller controller = (Controller) handler;
final String viewName = String.valueOf((controller.execute(req, resp)));
return new ModelAndView(viewName);
}

@Override
public boolean supports(final Object handler) {
return handler instanceof Controller;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package nextstep.mvc.tobe.exception;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ControllerScannerException extends RuntimeException {
private static final Logger logger = LoggerFactory.getLogger(ControllerScannerException.class);

public ControllerScannerException() {
super();
}

public ControllerScannerException(final String message) {
super(message);
logger.error(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package nextstep.mvc.tobe.exception;

public class HandlerAdapterNotSupportedException extends RuntimeException {
public HandlerAdapterNotSupportedException() {
this("not support HandlerAdapter");
}

public HandlerAdapterNotSupportedException(final String message) {
super(message);
}
}
Loading