From 5ffd82c1b819fb58d01ed3e5424fc2f2590ff63a Mon Sep 17 00:00:00 2001 From: yukong <92148749+kyukong@users.noreply.github.com> Date: Mon, 26 Sep 2022 17:12:16 +0900 Subject: [PATCH] =?UTF-8?q?[MVC=20=EA=B5=AC=ED=98=84=ED=95=98=EA=B8=B0=20-?= =?UTF-8?q?=202=EB=8B=A8=EA=B3=84]=20=EC=9C=A0=EC=BD=A9(=EA=B9=80=EC=9C=A0?= =?UTF-8?q?=EB=B9=88)=20=EB=AF=B8=EC=85=98=20=EC=A0=9C=EC=B6=9C=ED=95=A9?= =?UTF-8?q?=EB=8B=88=EB=8B=A4=20(#251)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: 2단계 체크리스트 작성 * refactor: controller 패키지 이동 * refactor: mapping 과 adapter 패키지 분리 * feat: ControllerScanner 로 @Controller 붙은 객체 조회 * feat: HandlerMappingRegistry 에서 HandlerMapping 처리 * feat: HandlerAdapterRegistry 에서 HandlerAdapter 처리 * test: 요청 및 응답 객체 픽스쳐 생성 * refactor: final 키워드 추가 및 불필요한 선언 삭제 * refactor: BDDMockito 메서드로 변경 * refactor: 테스트에서만 사용하는 생성자 제거 * refactor: getter 가 아닌 메서드 이용하여 render * refactor: 에러메시지 변경 --- README.md | 5 ++ .../AppWebApplicationInitializer.java | 5 +- .../com/techcourse/ManualHandlerMapping.java | 6 +- .../controller}/ForwardController.java | 3 +- .../controller/LoginController.java | 2 +- .../controller/LoginViewController.java | 2 +- .../controller/LogoutController.java | 2 +- .../controller/RegisterController.java | 2 +- .../controller/RegisterViewController.java | 2 +- .../java/nextstep/mvc/DispatcherServlet.java | 50 +++--------- .../mvc/controller/{asis => }/Controller.java | 2 +- .../mvc/controller/ControllerScanner.java | 41 ++++++++++ .../tobe/AnnotationHandlerMapping.java | 81 ------------------- .../AnnotationHandlerAdapter.java | 7 +- .../{ => handleradapter}/HandlerAdapter.java | 2 +- .../HandlerAdapterRegistry.java | 39 +++++++++ .../handleradapter}/ManualHandlerAdapter.java | 5 +- .../AnnotationHandlerMapping.java | 72 +++++++++++++++++ .../HandlerExecution.java | 6 +- .../tobe => handlermapping}/HandlerKey.java | 2 +- .../{ => handlermapping}/HandlerMapping.java | 2 +- .../HandlerMappingRegistry.java | 32 ++++++++ .../main/java/nextstep/mvc/view/JspView.java | 2 +- .../java/nextstep/mvc/view/ModelAndView.java | 15 ++-- mvc/src/test/java/fixture/RequestFixture.java | 42 ++++++++++ .../test/java/fixture/ResponseFixture.java | 17 ++++ .../mvc/controller/ControllerScannerTest.java | 25 ++++++ .../AnnotationHandlerMappingTest.java | 33 +++----- .../HandlerExecutionTest.java | 18 ++--- .../HandlerAdapterRegistryTest.java | 64 +++++++++++++++ .../HandlerMappingRegistryTest.java | 24 ++++++ ...ler.java => TestAnnotationController.java} | 9 +-- .../java/samples/TestInterfaceController.java | 19 +++++ 33 files changed, 450 insertions(+), 188 deletions(-) rename {mvc/src/main/java/nextstep/mvc/controller/asis => app/src/main/java/com/techcourse/controller}/ForwardController.java (85%) rename mvc/src/main/java/nextstep/mvc/controller/{asis => }/Controller.java (85%) create mode 100644 mvc/src/main/java/nextstep/mvc/controller/ControllerScanner.java delete mode 100644 mvc/src/main/java/nextstep/mvc/controller/tobe/AnnotationHandlerMapping.java rename mvc/src/main/java/nextstep/mvc/{controller/tobe => handleradapter}/AnnotationHandlerAdapter.java (72%) rename mvc/src/main/java/nextstep/mvc/{ => handleradapter}/HandlerAdapter.java (89%) create mode 100644 mvc/src/main/java/nextstep/mvc/handleradapter/HandlerAdapterRegistry.java rename {app/src/main/java/com/techcourse => mvc/src/main/java/nextstep/mvc/handleradapter}/ManualHandlerAdapter.java (84%) create mode 100644 mvc/src/main/java/nextstep/mvc/handlermapping/AnnotationHandlerMapping.java rename mvc/src/main/java/nextstep/mvc/{controller/tobe => handlermapping}/HandlerExecution.java (86%) rename mvc/src/main/java/nextstep/mvc/{controller/tobe => handlermapping}/HandlerKey.java (95%) rename mvc/src/main/java/nextstep/mvc/{ => handlermapping}/HandlerMapping.java (81%) create mode 100644 mvc/src/main/java/nextstep/mvc/handlermapping/HandlerMappingRegistry.java create mode 100644 mvc/src/test/java/fixture/RequestFixture.java create mode 100644 mvc/src/test/java/fixture/ResponseFixture.java create mode 100644 mvc/src/test/java/nextstep/mvc/controller/ControllerScannerTest.java rename mvc/src/test/java/nextstep/mvc/controller/{tobe => handlermapping}/AnnotationHandlerMappingTest.java (58%) rename mvc/src/test/java/nextstep/mvc/controller/{tobe => handlermapping}/HandlerExecutionTest.java (56%) create mode 100644 mvc/src/test/java/nextstep/mvc/handleradapter/HandlerAdapterRegistryTest.java create mode 100644 mvc/src/test/java/nextstep/mvc/handlermapping/HandlerMappingRegistryTest.java rename mvc/src/test/java/samples/{TestController.java => TestAnnotationController.java} (81%) create mode 100644 mvc/src/test/java/samples/TestInterfaceController.java diff --git a/README.md b/README.md index 507d3daec6..c796c08e14 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,8 @@ ## 🚀 1단계 - @MVC 프레임워크 구현하기 - [x] AnnotationHandlerMappingTest가 정상 동작한다. - [x] DispatcherServlet에서 HandlerMapping 인터페이스를 활용하여 AnnotationHandlerMapping과 ManualHandlerMapping 둘다 처리할 수 있다. + +## 🚀 2단계 - 점진적인 리팩터링 +- [x] ControllerScanner 클래스에서 @Controller가 붙은 클래스를 찾을 수 있다. +- [x] HandlerMappingRegistry 클래스에서 HandlerMapping을 처리하도록 구현했다. +- [x] HandlerAdapterRegistry 클래스에서 HandlerAdapter를 처리하도록 구현했다. diff --git a/app/src/main/java/com/techcourse/AppWebApplicationInitializer.java b/app/src/main/java/com/techcourse/AppWebApplicationInitializer.java index 431edcf4b3..b01cb6705f 100644 --- a/app/src/main/java/com/techcourse/AppWebApplicationInitializer.java +++ b/app/src/main/java/com/techcourse/AppWebApplicationInitializer.java @@ -2,8 +2,9 @@ import jakarta.servlet.ServletContext; import nextstep.mvc.DispatcherServlet; -import nextstep.mvc.controller.tobe.AnnotationHandlerAdapter; -import nextstep.mvc.controller.tobe.AnnotationHandlerMapping; +import nextstep.mvc.handleradapter.AnnotationHandlerAdapter; +import nextstep.mvc.handleradapter.ManualHandlerAdapter; +import nextstep.mvc.handlermapping.AnnotationHandlerMapping; import nextstep.web.WebApplicationInitializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/com/techcourse/ManualHandlerMapping.java b/app/src/main/java/com/techcourse/ManualHandlerMapping.java index 8a6fa9080f..6d9b1cc637 100644 --- a/app/src/main/java/com/techcourse/ManualHandlerMapping.java +++ b/app/src/main/java/com/techcourse/ManualHandlerMapping.java @@ -2,9 +2,9 @@ import com.techcourse.controller.*; import jakarta.servlet.http.HttpServletRequest; -import nextstep.mvc.HandlerMapping; -import nextstep.mvc.controller.asis.Controller; -import nextstep.mvc.controller.asis.ForwardController; +import nextstep.mvc.handlermapping.HandlerMapping; +import nextstep.mvc.controller.Controller; +import com.techcourse.controller.ForwardController; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/mvc/src/main/java/nextstep/mvc/controller/asis/ForwardController.java b/app/src/main/java/com/techcourse/controller/ForwardController.java similarity index 85% rename from mvc/src/main/java/nextstep/mvc/controller/asis/ForwardController.java rename to app/src/main/java/com/techcourse/controller/ForwardController.java index ed0f08d940..3a4d8657da 100644 --- a/mvc/src/main/java/nextstep/mvc/controller/asis/ForwardController.java +++ b/app/src/main/java/com/techcourse/controller/ForwardController.java @@ -1,7 +1,8 @@ -package nextstep.mvc.controller.asis; +package com.techcourse.controller; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import nextstep.mvc.controller.Controller; import java.util.Objects; diff --git a/app/src/main/java/com/techcourse/controller/LoginController.java b/app/src/main/java/com/techcourse/controller/LoginController.java index a07292e25b..113e735b2a 100644 --- a/app/src/main/java/com/techcourse/controller/LoginController.java +++ b/app/src/main/java/com/techcourse/controller/LoginController.java @@ -4,7 +4,7 @@ import com.techcourse.repository.InMemoryUserRepository; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import nextstep.mvc.controller.asis.Controller; +import nextstep.mvc.controller.Controller; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/com/techcourse/controller/LoginViewController.java b/app/src/main/java/com/techcourse/controller/LoginViewController.java index 85d64a73f8..55e558b3b6 100644 --- a/app/src/main/java/com/techcourse/controller/LoginViewController.java +++ b/app/src/main/java/com/techcourse/controller/LoginViewController.java @@ -2,7 +2,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import nextstep.mvc.controller.asis.Controller; +import nextstep.mvc.controller.Controller; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/app/src/main/java/com/techcourse/controller/LogoutController.java b/app/src/main/java/com/techcourse/controller/LogoutController.java index 9d1f099a98..ece27eefd4 100644 --- a/app/src/main/java/com/techcourse/controller/LogoutController.java +++ b/app/src/main/java/com/techcourse/controller/LogoutController.java @@ -2,7 +2,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import nextstep.mvc.controller.asis.Controller; +import nextstep.mvc.controller.Controller; public class LogoutController implements Controller { diff --git a/app/src/main/java/com/techcourse/controller/RegisterController.java b/app/src/main/java/com/techcourse/controller/RegisterController.java index 56bb436f6e..6b46c93313 100644 --- a/app/src/main/java/com/techcourse/controller/RegisterController.java +++ b/app/src/main/java/com/techcourse/controller/RegisterController.java @@ -4,7 +4,7 @@ import com.techcourse.repository.InMemoryUserRepository; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import nextstep.mvc.controller.asis.Controller; +import nextstep.mvc.controller.Controller; public class RegisterController implements Controller { diff --git a/app/src/main/java/com/techcourse/controller/RegisterViewController.java b/app/src/main/java/com/techcourse/controller/RegisterViewController.java index 052639134b..adb5406571 100644 --- a/app/src/main/java/com/techcourse/controller/RegisterViewController.java +++ b/app/src/main/java/com/techcourse/controller/RegisterViewController.java @@ -2,7 +2,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import nextstep.mvc.controller.asis.Controller; +import nextstep.mvc.controller.Controller; public class RegisterViewController implements Controller { diff --git a/mvc/src/main/java/nextstep/mvc/DispatcherServlet.java b/mvc/src/main/java/nextstep/mvc/DispatcherServlet.java index f05f2a88ae..e7c4360d78 100644 --- a/mvc/src/main/java/nextstep/mvc/DispatcherServlet.java +++ b/mvc/src/main/java/nextstep/mvc/DispatcherServlet.java @@ -4,32 +4,33 @@ import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import nextstep.mvc.handleradapter.HandlerAdapter; +import nextstep.mvc.handleradapter.HandlerAdapterRegistry; +import nextstep.mvc.handlermapping.HandlerMapping; +import nextstep.mvc.handlermapping.HandlerMappingRegistry; import nextstep.mvc.view.ModelAndView; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; -import java.util.List; import java.util.Map; -import java.util.Objects; public class DispatcherServlet extends HttpServlet { private static final long serialVersionUID = 1L; private static final Logger log = LoggerFactory.getLogger(DispatcherServlet.class); - private final List handlerMappings; - private final List handlerAdapters; + private final HandlerMappingRegistry handlerMappings; + private final HandlerAdapterRegistry handlerAdapters; public DispatcherServlet() { - this.handlerMappings = new ArrayList<>(); - this.handlerAdapters = new ArrayList<>(); + this.handlerMappings = new HandlerMappingRegistry(); + this.handlerAdapters = new HandlerAdapterRegistry(); } @Override public void init() { - handlerMappings.forEach(HandlerMapping::initialize); + handlerMappings.init(); } public void addHandlerMapping(final HandlerMapping handlerMapping) { @@ -45,39 +46,12 @@ protected void service(final HttpServletRequest request, final HttpServletRespon log.debug("Method : {}, Request URI : {}", request.getMethod(), request.getRequestURI()); try { - final Object handler = getHandler(request); - final ModelAndView modelAndView = getModelAndView(handler, request, response); - final Map model = modelAndView.getModel(); - modelAndView.getView().render(model, request, response); + final Object handler = handlerMappings.getHandler(request); + final ModelAndView modelAndView = handlerAdapters.getModelAndView(handler, request, response); + modelAndView.render(request, response); } catch (Throwable e) { log.error("Exception : {}", e.getMessage(), e); throw new ServletException(e.getMessage()); } } - - private Object getHandler(final HttpServletRequest request) { - return handlerMappings.stream() - .map(handlerMapping -> handlerMapping.getHandler(request)) - .filter(Objects::nonNull) - .findFirst() - .orElseThrow(); - } - - private ModelAndView getModelAndView(final Object handler, final HttpServletRequest request, final HttpServletResponse response) { - return handlerAdapters.stream() - .filter(handlerAdapter -> handlerAdapter.supports(handler)) - .map(handlerAdapter -> getModelAndView(handler, request, response, handlerAdapter)) - .findFirst() - .orElseThrow(); - } - - private ModelAndView getModelAndView(final Object handler, - final HttpServletRequest request, final HttpServletResponse response, final HandlerAdapter handlerAdapter - ){ - try { - return handlerAdapter.handle(request, response, handler); - } catch (Exception e) { - throw new IllegalArgumentException(String.format("적절하지 않은 handler 입니다. [%s]", handler)); - } - } } diff --git a/mvc/src/main/java/nextstep/mvc/controller/asis/Controller.java b/mvc/src/main/java/nextstep/mvc/controller/Controller.java similarity index 85% rename from mvc/src/main/java/nextstep/mvc/controller/asis/Controller.java rename to mvc/src/main/java/nextstep/mvc/controller/Controller.java index b0edabf3fb..ff6b8bcbfc 100644 --- a/mvc/src/main/java/nextstep/mvc/controller/asis/Controller.java +++ b/mvc/src/main/java/nextstep/mvc/controller/Controller.java @@ -1,4 +1,4 @@ -package nextstep.mvc.controller.asis; +package nextstep.mvc.controller; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; diff --git a/mvc/src/main/java/nextstep/mvc/controller/ControllerScanner.java b/mvc/src/main/java/nextstep/mvc/controller/ControllerScanner.java new file mode 100644 index 0000000000..b75d60f10f --- /dev/null +++ b/mvc/src/main/java/nextstep/mvc/controller/ControllerScanner.java @@ -0,0 +1,41 @@ +package nextstep.mvc.controller; + +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.reflections.Reflections; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import nextstep.web.annotation.Controller; + +public class ControllerScanner { + + private static final Logger log = LoggerFactory.getLogger(ControllerScanner.class); + + private ControllerScanner() { + } + + public static Map, Object> getControllers(final Object... basePackage) { + final Reflections reflections = new Reflections(basePackage); + final Set> controllers = reflections.getTypesAnnotatedWith(Controller.class); + + return controllers.stream() + .collect(Collectors.toMap( + Function.identity(), + ControllerScanner::initController + )); + } + + private static Object initController(final Class clazz) { + try { + return clazz.getDeclaredConstructors()[0] + .newInstance(); + } catch (Exception e) { + log.error(e.getMessage()); + throw new IllegalArgumentException(String.format("생성자가 존재하지 않습니다. [%s]", clazz.getName())); + } + } +} diff --git a/mvc/src/main/java/nextstep/mvc/controller/tobe/AnnotationHandlerMapping.java b/mvc/src/main/java/nextstep/mvc/controller/tobe/AnnotationHandlerMapping.java deleted file mode 100644 index aeb6a5c7e7..0000000000 --- a/mvc/src/main/java/nextstep/mvc/controller/tobe/AnnotationHandlerMapping.java +++ /dev/null @@ -1,81 +0,0 @@ -package nextstep.mvc.controller.tobe; - -import jakarta.servlet.http.HttpServletRequest; -import nextstep.mvc.HandlerMapping; -import nextstep.web.annotation.Controller; -import nextstep.web.annotation.RequestMapping; -import nextstep.web.support.RequestMethod; - -import org.reflections.Reflections; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -public class AnnotationHandlerMapping implements HandlerMapping { - - private static final Logger log = LoggerFactory.getLogger(AnnotationHandlerMapping.class); - - private final Object[] basePackage; - private final Map handlerExecutions; - - public AnnotationHandlerMapping(final Object... basePackage) { - this.basePackage = basePackage; - this.handlerExecutions = new HashMap<>(); - } - - @Override - public void initialize() { - final Reflections reflections = new Reflections(basePackage); - final Set> controllers = reflections.getTypesAnnotatedWith(Controller.class); - for (final Class controller : controllers) { - addRequestMappingMethod(controller); - } - log.info("Initialized AnnotationHandlerMapping!"); - } - - @Override - public Object getHandler(final HttpServletRequest request) { - final HandlerKey handlerKey = new HandlerKey(request.getRequestURI(), RequestMethod.from(request.getMethod())); - log.info("request handler [{}]", handlerKey); - if (!handlerExecutions.containsKey(handlerKey)) { - throw new IllegalArgumentException(String.format("요청한 핸들러가 존재하지 않습니다. [%s]", handlerKey)); - } - final HandlerExecution handlerExecution = handlerExecutions.get(handlerKey); - log.info("search handler [{}]", handlerExecution); - return handlerExecution; - } - - private void addRequestMappingMethod(final Class controller) { - final Method[] methods = controller.getMethods(); - for (final Method method : methods) { - if (!method.isAnnotationPresent(RequestMapping.class)) { - continue; - } - final RequestMapping requestMapping = method.getAnnotation(RequestMapping.class); - addHandlerExecution(controller, requestMapping, method); - } - } - - private void addHandlerExecution(final Class controller, final RequestMapping requestMapping, final Method method) { - for (final RequestMethod requestMethod : requestMapping.method()) { - final HandlerKey handlerKey = new HandlerKey(requestMapping.value(), requestMethod); - if (handlerExecutions.containsKey(handlerKey)) { - throw new IllegalArgumentException(String.format("중복적으로 매핑되었습니다. [%s]", handlerKey)); - } - handlerExecutions.put(handlerKey, getHandlerExecution(controller, method)); - } - } - - private HandlerExecution getHandlerExecution(final Class controller, final Method method) { - try { - final Object newInstance = controller.getConstructor().newInstance(); - return new HandlerExecution(newInstance, method); - } catch (Exception e) { - throw new IllegalArgumentException(String.format("기본 생성자가 존재하지 않습니다. [%s]", controller)); - } - } -} diff --git a/mvc/src/main/java/nextstep/mvc/controller/tobe/AnnotationHandlerAdapter.java b/mvc/src/main/java/nextstep/mvc/handleradapter/AnnotationHandlerAdapter.java similarity index 72% rename from mvc/src/main/java/nextstep/mvc/controller/tobe/AnnotationHandlerAdapter.java rename to mvc/src/main/java/nextstep/mvc/handleradapter/AnnotationHandlerAdapter.java index 1e8b91c668..054ad9107b 100644 --- a/mvc/src/main/java/nextstep/mvc/controller/tobe/AnnotationHandlerAdapter.java +++ b/mvc/src/main/java/nextstep/mvc/handleradapter/AnnotationHandlerAdapter.java @@ -1,16 +1,15 @@ -package nextstep.mvc.controller.tobe; +package nextstep.mvc.handleradapter; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import nextstep.mvc.HandlerAdapter; +import nextstep.mvc.handlermapping.HandlerExecution; import nextstep.mvc.view.ModelAndView; -import nextstep.web.annotation.Controller; public class AnnotationHandlerAdapter implements HandlerAdapter { @Override public boolean supports(final Object handler) { - return handler.getClass().isAnnotationPresent(Controller.class); + return handler instanceof HandlerExecution; } @Override diff --git a/mvc/src/main/java/nextstep/mvc/HandlerAdapter.java b/mvc/src/main/java/nextstep/mvc/handleradapter/HandlerAdapter.java similarity index 89% rename from mvc/src/main/java/nextstep/mvc/HandlerAdapter.java rename to mvc/src/main/java/nextstep/mvc/handleradapter/HandlerAdapter.java index b1666cb54e..d0aa99d58f 100644 --- a/mvc/src/main/java/nextstep/mvc/HandlerAdapter.java +++ b/mvc/src/main/java/nextstep/mvc/handleradapter/HandlerAdapter.java @@ -1,4 +1,4 @@ -package nextstep.mvc; +package nextstep.mvc.handleradapter; import nextstep.mvc.view.ModelAndView; diff --git a/mvc/src/main/java/nextstep/mvc/handleradapter/HandlerAdapterRegistry.java b/mvc/src/main/java/nextstep/mvc/handleradapter/HandlerAdapterRegistry.java new file mode 100644 index 0000000000..3215a241c4 --- /dev/null +++ b/mvc/src/main/java/nextstep/mvc/handleradapter/HandlerAdapterRegistry.java @@ -0,0 +1,39 @@ +package nextstep.mvc.handleradapter; + +import java.util.ArrayList; +import java.util.List; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import nextstep.mvc.view.ModelAndView; + +public class HandlerAdapterRegistry { + + private final List handlerAdapters; + + public HandlerAdapterRegistry() { + this.handlerAdapters = new ArrayList<>(); + } + + public void add(final HandlerAdapter handlerAdapter) { + handlerAdapters.add(handlerAdapter); + } + + public ModelAndView getModelAndView(final Object handler, final HttpServletRequest request, final HttpServletResponse response) { + return handlerAdapters.stream() + .filter(handlerAdapter -> handlerAdapter.supports(handler)) + .map(handlerAdapter -> getModelAndView(handler, request, response, handlerAdapter)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(String.format("적절한 handler 가 존재하지 않습니다. [%s]", handler))); + } + + private ModelAndView getModelAndView(final Object handler, + final HttpServletRequest request, final HttpServletResponse response, final HandlerAdapter handlerAdapter + ){ + try { + return handlerAdapter.handle(request, response, handler); + } catch (Exception e) { + throw new IllegalArgumentException(e.getMessage()); + } + } +} diff --git a/app/src/main/java/com/techcourse/ManualHandlerAdapter.java b/mvc/src/main/java/nextstep/mvc/handleradapter/ManualHandlerAdapter.java similarity index 84% rename from app/src/main/java/com/techcourse/ManualHandlerAdapter.java rename to mvc/src/main/java/nextstep/mvc/handleradapter/ManualHandlerAdapter.java index 38a529a359..71a6d84d1f 100644 --- a/app/src/main/java/com/techcourse/ManualHandlerAdapter.java +++ b/mvc/src/main/java/nextstep/mvc/handleradapter/ManualHandlerAdapter.java @@ -1,9 +1,8 @@ -package com.techcourse; +package nextstep.mvc.handleradapter; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import nextstep.mvc.HandlerAdapter; -import nextstep.mvc.controller.asis.Controller; +import nextstep.mvc.controller.Controller; import nextstep.mvc.view.ModelAndView; public class ManualHandlerAdapter implements HandlerAdapter { diff --git a/mvc/src/main/java/nextstep/mvc/handlermapping/AnnotationHandlerMapping.java b/mvc/src/main/java/nextstep/mvc/handlermapping/AnnotationHandlerMapping.java new file mode 100644 index 0000000000..75d32cec28 --- /dev/null +++ b/mvc/src/main/java/nextstep/mvc/handlermapping/AnnotationHandlerMapping.java @@ -0,0 +1,72 @@ +package nextstep.mvc.handlermapping; + +import jakarta.servlet.http.HttpServletRequest; +import nextstep.mvc.controller.ControllerScanner; +import nextstep.web.annotation.RequestMapping; +import nextstep.web.support.RequestMethod; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public class AnnotationHandlerMapping implements HandlerMapping { + + private static final Logger log = LoggerFactory.getLogger(AnnotationHandlerMapping.class); + + private final Object[] basePackage; + private final Map handlerExecutions; + + public AnnotationHandlerMapping(final Object... basePackage) { + this.basePackage = basePackage; + this.handlerExecutions = new HashMap<>(); + } + + @Override + public void initialize() { + ControllerScanner.getControllers(basePackage) + .forEach(this::addHandlerExecutions); + log.info("Initialized AnnotationHandlerMapping!"); + } + + @Override + public Object getHandler(final HttpServletRequest request) { + final HandlerKey handlerKey = new HandlerKey(request.getRequestURI(), RequestMethod.from(request.getMethod())); + log.info("request handler [{}]", handlerKey); + if (!handlerExecutions.containsKey(handlerKey)) { + throw new IllegalArgumentException(String.format("요청한 핸들러가 존재하지 않습니다. [%s]", handlerKey)); + } + final HandlerExecution handlerExecution = handlerExecutions.get(handlerKey); + log.info("search handler [{}]", handlerExecution); + return handlerExecution; + } + + private void addHandlerExecutions(final Class clazz, final Object controller) { + final Method[] methods = clazz.getMethods(); + Arrays.stream(methods) + .filter(method -> method.isAnnotationPresent(RequestMapping.class)) + .forEach(method -> addHandlerExecution(controller, method)); + } + + private void addHandlerExecution(final Object controller, final Method method) { + final RequestMapping requestMapping = method.getAnnotation(RequestMapping.class); + final RequestMethod[] requestMethods = requestMapping.method(); + final HandlerExecution handlerExecution = new HandlerExecution(controller, method); + + Arrays.stream(requestMethods) + .forEach(requestMethod -> { + final HandlerKey handlerKey = new HandlerKey(requestMapping.value(), requestMethod); + putHandlerExecution(handlerExecution, handlerKey); + }); + } + + private void putHandlerExecution(final HandlerExecution handlerExecution, final HandlerKey handlerKey) { + if (handlerExecutions.containsKey(handlerKey)) { + throw new IllegalArgumentException(String.format("중복적으로 매핑되었습니다. [%s]", handlerKey)); + } + handlerExecutions.put(handlerKey, handlerExecution); + } +} diff --git a/mvc/src/main/java/nextstep/mvc/controller/tobe/HandlerExecution.java b/mvc/src/main/java/nextstep/mvc/handlermapping/HandlerExecution.java similarity index 86% rename from mvc/src/main/java/nextstep/mvc/controller/tobe/HandlerExecution.java rename to mvc/src/main/java/nextstep/mvc/handlermapping/HandlerExecution.java index ccb987e89d..3f5b661f19 100644 --- a/mvc/src/main/java/nextstep/mvc/controller/tobe/HandlerExecution.java +++ b/mvc/src/main/java/nextstep/mvc/handlermapping/HandlerExecution.java @@ -1,4 +1,4 @@ -package nextstep.mvc.controller.tobe; +package nextstep.mvc.handlermapping; import java.lang.reflect.Method; @@ -16,8 +16,8 @@ public class HandlerExecution { private final Object clazz; private final Method method; - public HandlerExecution(final Object clazz, final Method method) { - this.clazz = clazz; + public HandlerExecution(final Object controller, final Method method) { + this.clazz = controller; this.method = method; } diff --git a/mvc/src/main/java/nextstep/mvc/controller/tobe/HandlerKey.java b/mvc/src/main/java/nextstep/mvc/handlermapping/HandlerKey.java similarity index 95% rename from mvc/src/main/java/nextstep/mvc/controller/tobe/HandlerKey.java rename to mvc/src/main/java/nextstep/mvc/handlermapping/HandlerKey.java index b6934407a1..66ac033f41 100644 --- a/mvc/src/main/java/nextstep/mvc/controller/tobe/HandlerKey.java +++ b/mvc/src/main/java/nextstep/mvc/handlermapping/HandlerKey.java @@ -1,4 +1,4 @@ -package nextstep.mvc.controller.tobe; +package nextstep.mvc.handlermapping; import nextstep.web.support.RequestMethod; diff --git a/mvc/src/main/java/nextstep/mvc/HandlerMapping.java b/mvc/src/main/java/nextstep/mvc/handlermapping/HandlerMapping.java similarity index 81% rename from mvc/src/main/java/nextstep/mvc/HandlerMapping.java rename to mvc/src/main/java/nextstep/mvc/handlermapping/HandlerMapping.java index 7da7184209..4eca7d46ff 100644 --- a/mvc/src/main/java/nextstep/mvc/HandlerMapping.java +++ b/mvc/src/main/java/nextstep/mvc/handlermapping/HandlerMapping.java @@ -1,4 +1,4 @@ -package nextstep.mvc; +package nextstep.mvc.handlermapping; import jakarta.servlet.http.HttpServletRequest; diff --git a/mvc/src/main/java/nextstep/mvc/handlermapping/HandlerMappingRegistry.java b/mvc/src/main/java/nextstep/mvc/handlermapping/HandlerMappingRegistry.java new file mode 100644 index 0000000000..a0f36b84a0 --- /dev/null +++ b/mvc/src/main/java/nextstep/mvc/handlermapping/HandlerMappingRegistry.java @@ -0,0 +1,32 @@ +package nextstep.mvc.handlermapping; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import jakarta.servlet.http.HttpServletRequest; + +public class HandlerMappingRegistry { + + private final List handlerMappings; + + public HandlerMappingRegistry() { + handlerMappings = new ArrayList<>(); + } + + public void init() { + handlerMappings.forEach(HandlerMapping::initialize); + } + + public void add(final HandlerMapping handlerMapping) { + handlerMappings.add(handlerMapping); + } + + public Object getHandler(final HttpServletRequest request) { + return handlerMappings.stream() + .map(handlerMapping -> handlerMapping.getHandler(request)) + .filter(Objects::nonNull) + .findFirst() + .orElseThrow(); + } +} diff --git a/mvc/src/main/java/nextstep/mvc/view/JspView.java b/mvc/src/main/java/nextstep/mvc/view/JspView.java index f04e3db037..2ed5bcfea7 100644 --- a/mvc/src/main/java/nextstep/mvc/view/JspView.java +++ b/mvc/src/main/java/nextstep/mvc/view/JspView.java @@ -21,7 +21,7 @@ public class JspView implements View { public JspView(final String viewName) { validateJsp(viewName); if (viewName.startsWith(REDIRECT_PREFIX)) { - this.viewName = viewName.substring(JspView.REDIRECT_PREFIX.length()); + this.viewName = viewName.substring(REDIRECT_PREFIX.length()); this.redirect = true; return; } diff --git a/mvc/src/main/java/nextstep/mvc/view/ModelAndView.java b/mvc/src/main/java/nextstep/mvc/view/ModelAndView.java index aeda8b3c77..5ab7a7a3b9 100644 --- a/mvc/src/main/java/nextstep/mvc/view/ModelAndView.java +++ b/mvc/src/main/java/nextstep/mvc/view/ModelAndView.java @@ -4,17 +4,16 @@ import java.util.HashMap; import java.util.Map; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + public class ModelAndView { private final View view; private final Map model; public ModelAndView(final String viewName) { - this(getView(viewName)); - } - - public ModelAndView(final View view) { - this.view = view; + this.view = getView(viewName); this.model = new HashMap<>(); } @@ -23,7 +22,11 @@ public ModelAndView addObject(final String attributeName, final Object attribute return this; } - private static View getView(String viewName) { + public void render(final HttpServletRequest request, final HttpServletResponse response) throws Exception { + view.render(model, request, response); + } + + private static View getView(final String viewName) { if (viewName.endsWith(".jsp")) { return new JspView(viewName); } diff --git a/mvc/src/test/java/fixture/RequestFixture.java b/mvc/src/test/java/fixture/RequestFixture.java new file mode 100644 index 0000000000..a01954db9c --- /dev/null +++ b/mvc/src/test/java/fixture/RequestFixture.java @@ -0,0 +1,42 @@ +package fixture; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +import jakarta.servlet.http.HttpServletRequest; + +public class RequestFixture { + + private RequestFixture() { + } + + public static HttpServletRequest getRequest() { + final HttpServletRequest request = mock(HttpServletRequest.class); + + given(request.getAttribute("id")).willReturn("gugu"); + given(request.getRequestURI()).willReturn("/get-test"); + given(request.getMethod()).willReturn("GET"); + + return request; + } + + public static HttpServletRequest getRequest(final String path) { + final HttpServletRequest request = mock(HttpServletRequest.class); + + given(request.getAttribute("id")).willReturn("gugu"); + given(request.getRequestURI()).willReturn(path); + given(request.getMethod()).willReturn("GET"); + + return request; + } + + public static HttpServletRequest postRequest() { + final HttpServletRequest request = mock(HttpServletRequest.class); + + given(request.getAttribute("id")).willReturn("gugu"); + given(request.getRequestURI()).willReturn("/post-test"); + given(request.getMethod()).willReturn("POST"); + + return request; + } +} diff --git a/mvc/src/test/java/fixture/ResponseFixture.java b/mvc/src/test/java/fixture/ResponseFixture.java new file mode 100644 index 0000000000..3bbb0ee2b8 --- /dev/null +++ b/mvc/src/test/java/fixture/ResponseFixture.java @@ -0,0 +1,17 @@ +package fixture; + +import static org.mockito.Mockito.mock; + +import jakarta.servlet.http.HttpServletResponse; + +public class ResponseFixture { + + private ResponseFixture() { + } + + public static HttpServletResponse response() { + final HttpServletResponse response = mock(HttpServletResponse.class); + + return response; + } +} diff --git a/mvc/src/test/java/nextstep/mvc/controller/ControllerScannerTest.java b/mvc/src/test/java/nextstep/mvc/controller/ControllerScannerTest.java new file mode 100644 index 0000000000..fd863e5b02 --- /dev/null +++ b/mvc/src/test/java/nextstep/mvc/controller/ControllerScannerTest.java @@ -0,0 +1,25 @@ +package nextstep.mvc.controller; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import java.util.Map; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import samples.TestAnnotationController; + +class ControllerScannerTest { + + @DisplayName("지정한 경로 내의 @Controller 가 있는 모든 객체를 찾는다") + @Test + void getControllers() { + final Map, Object> controllers = ControllerScanner.getControllers("samples"); + + assertAll( + () -> assertThat(controllers).containsOnlyKeys(TestAnnotationController.class), + () -> assertThat(controllers.get(TestAnnotationController.class)).isInstanceOf(TestAnnotationController.class) + ); + } +} diff --git a/mvc/src/test/java/nextstep/mvc/controller/tobe/AnnotationHandlerMappingTest.java b/mvc/src/test/java/nextstep/mvc/controller/handlermapping/AnnotationHandlerMappingTest.java similarity index 58% rename from mvc/src/test/java/nextstep/mvc/controller/tobe/AnnotationHandlerMappingTest.java rename to mvc/src/test/java/nextstep/mvc/controller/handlermapping/AnnotationHandlerMappingTest.java index c139e46b05..4b5c20ccee 100644 --- a/mvc/src/test/java/nextstep/mvc/controller/tobe/AnnotationHandlerMappingTest.java +++ b/mvc/src/test/java/nextstep/mvc/controller/handlermapping/AnnotationHandlerMappingTest.java @@ -1,15 +1,16 @@ -package nextstep.mvc.controller.tobe; +package nextstep.mvc.controller.handlermapping; + +import fixture.RequestFixture; +import fixture.ResponseFixture; +import nextstep.mvc.handlermapping.AnnotationHandlerMapping; +import nextstep.mvc.handlermapping.HandlerExecution; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; class AnnotationHandlerMappingTest { @@ -24,12 +25,8 @@ void setUp() { @DisplayName("GET 요청에 대한 handler 조회") @Test void get() throws Exception { - final var request = mock(HttpServletRequest.class); - final var response = mock(HttpServletResponse.class); - - when(request.getAttribute("id")).thenReturn("gugu"); - when(request.getRequestURI()).thenReturn("/get-test"); - when(request.getMethod()).thenReturn("GET"); + final var request = RequestFixture.getRequest(); + final var response = ResponseFixture.response(); final var handlerExecution = (HandlerExecution) handlerMapping.getHandler(request); final var modelAndView = handlerExecution.handle(request, response); @@ -40,12 +37,8 @@ void get() throws Exception { @DisplayName("POST 요청에 대한 handler 조회") @Test void post() throws Exception { - final var request = mock(HttpServletRequest.class); - final var response = mock(HttpServletResponse.class); - - when(request.getAttribute("id")).thenReturn("gugu"); - when(request.getRequestURI()).thenReturn("/post-test"); - when(request.getMethod()).thenReturn("POST"); + final var request = RequestFixture.postRequest(); + final var response = ResponseFixture.response(); final var handlerExecution = (HandlerExecution) handlerMapping.getHandler(request); final var modelAndView = handlerExecution.handle(request, response); @@ -56,11 +49,7 @@ void post() throws Exception { @DisplayName("추가하지 않은 handler 조회 시 예외 발생") @Test void getHandlerNotExist() { - final var request = mock(HttpServletRequest.class); - - when(request.getAttribute("id")).thenReturn("gugu"); - when(request.getRequestURI()).thenReturn("/not-exist-path"); - when(request.getMethod()).thenReturn("GET"); + final var request = RequestFixture.getRequest("/not-exist-path"); assertThatThrownBy(() -> handlerMapping.getHandler(request)) .isInstanceOf(IllegalArgumentException.class) diff --git a/mvc/src/test/java/nextstep/mvc/controller/tobe/HandlerExecutionTest.java b/mvc/src/test/java/nextstep/mvc/controller/handlermapping/HandlerExecutionTest.java similarity index 56% rename from mvc/src/test/java/nextstep/mvc/controller/tobe/HandlerExecutionTest.java rename to mvc/src/test/java/nextstep/mvc/controller/handlermapping/HandlerExecutionTest.java index a50bfa7b91..4adfd5f38f 100644 --- a/mvc/src/test/java/nextstep/mvc/controller/tobe/HandlerExecutionTest.java +++ b/mvc/src/test/java/nextstep/mvc/controller/handlermapping/HandlerExecutionTest.java @@ -1,31 +1,29 @@ -package nextstep.mvc.controller.tobe; +package nextstep.mvc.controller.handlermapping; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import java.lang.reflect.Method; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; +import fixture.RequestFixture; +import fixture.ResponseFixture; +import nextstep.mvc.handlermapping.HandlerExecution; import nextstep.mvc.view.ModelAndView; -import samples.TestController; +import samples.TestAnnotationController; class HandlerExecutionTest { @DisplayName("객체에 정의된 메서드 실행") @Test void handle() throws Exception { - final Object object = TestController.class.getConstructor().newInstance(); + final Object object = TestAnnotationController.class.getConstructor().newInstance(); final Method method = object.getClass().getMethods()[0]; final HandlerExecution handlerExecution = new HandlerExecution(object, method); - final var request = mock(HttpServletRequest.class); - final var response = mock(HttpServletResponse.class); - when(request.getAttribute("id")).thenReturn("gugu"); + final var request = RequestFixture.getRequest(); + final var response = ResponseFixture.response(); final ModelAndView modelAndView = handlerExecution.handle(request, response); assertThat(modelAndView.getObject("id")).isEqualTo("gugu"); diff --git a/mvc/src/test/java/nextstep/mvc/handleradapter/HandlerAdapterRegistryTest.java b/mvc/src/test/java/nextstep/mvc/handleradapter/HandlerAdapterRegistryTest.java new file mode 100644 index 0000000000..ed16ce1d2e --- /dev/null +++ b/mvc/src/test/java/nextstep/mvc/handleradapter/HandlerAdapterRegistryTest.java @@ -0,0 +1,64 @@ +package nextstep.mvc.handleradapter; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.lang.reflect.Method; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import fixture.RequestFixture; +import fixture.ResponseFixture; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import nextstep.mvc.handlermapping.HandlerExecution; +import nextstep.mvc.view.JspView; +import nextstep.mvc.view.ModelAndView; +import samples.TestInterfaceController; + +class HandlerAdapterRegistryTest { + + @DisplayName("annotation 기반 handler adapter 초기화") + @Test + void annotationInit() { + final HandlerAdapterRegistry registry = new HandlerAdapterRegistry(); + registry.add(new AnnotationHandlerAdapter()); + + final HttpServletRequest request = RequestFixture.getRequest(); + final HttpServletResponse response = ResponseFixture.response(); + + final ModelAndView modelAndView = registry.getModelAndView(getHandlerExecution(), request, response); + + assertThat(modelAndView.getView()) + .usingRecursiveComparison() + .isEqualTo(new JspView("/get-test.jsp")); + } + + private HandlerExecution getHandlerExecution() { + try { + final Class clazz = Class.forName("samples.TestAnnotationController"); + final Object controller = clazz.getConstructors()[0].newInstance(); + final Method method = clazz.getMethod("findUserId", HttpServletRequest.class, HttpServletResponse.class); + + return new HandlerExecution(controller, method); + } catch (Exception e) { + throw new IllegalArgumentException(); + } + } + + @DisplayName("controller interface 기반 handler adapter 초기화") + @Test + void controllerInterfaceInit() { + final HandlerAdapterRegistry registry = new HandlerAdapterRegistry(); + registry.add(new ManualHandlerAdapter()); + + final HttpServletRequest request = RequestFixture.getRequest(); + final HttpServletResponse response = ResponseFixture.response(); + + final ModelAndView modelAndView = registry.getModelAndView(new TestInterfaceController(), request, response); + + assertThat(modelAndView.getView()) + .usingRecursiveComparison() + .isEqualTo(new JspView("/test-path.jsp")); + } +} diff --git a/mvc/src/test/java/nextstep/mvc/handlermapping/HandlerMappingRegistryTest.java b/mvc/src/test/java/nextstep/mvc/handlermapping/HandlerMappingRegistryTest.java new file mode 100644 index 0000000000..0847156d18 --- /dev/null +++ b/mvc/src/test/java/nextstep/mvc/handlermapping/HandlerMappingRegistryTest.java @@ -0,0 +1,24 @@ +package nextstep.mvc.handlermapping; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import fixture.RequestFixture; +import jakarta.servlet.http.HttpServletRequest; + +class HandlerMappingRegistryTest { + + @DisplayName("annotation 기반 handler mapping 초기화") + @Test + void annotationInit() { + final HandlerMappingRegistry registry = new HandlerMappingRegistry(); + registry.add(new AnnotationHandlerMapping("samples")); + registry.init(); + + final HttpServletRequest request = RequestFixture.getRequest(); + + assertThat(registry.getHandler(request)).isInstanceOf(HandlerExecution.class); + } +} diff --git a/mvc/src/test/java/samples/TestController.java b/mvc/src/test/java/samples/TestAnnotationController.java similarity index 81% rename from mvc/src/test/java/samples/TestController.java rename to mvc/src/test/java/samples/TestAnnotationController.java index 7c14c3703e..d43c94e2a3 100644 --- a/mvc/src/test/java/samples/TestController.java +++ b/mvc/src/test/java/samples/TestAnnotationController.java @@ -2,7 +2,6 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import nextstep.mvc.view.JspView; import nextstep.mvc.view.ModelAndView; import nextstep.web.annotation.Controller; import nextstep.web.annotation.RequestMapping; @@ -11,14 +10,14 @@ import org.slf4j.LoggerFactory; @Controller -public class TestController { +public class TestAnnotationController { - private static final Logger log = LoggerFactory.getLogger(TestController.class); + private static final Logger log = LoggerFactory.getLogger(TestAnnotationController.class); @RequestMapping(value = "/get-test", method = RequestMethod.GET) public ModelAndView findUserId(final HttpServletRequest request, final HttpServletResponse response) { log.info("test controller get method"); - final var modelAndView = new ModelAndView(new JspView("/get-test.jsp")); + final var modelAndView = new ModelAndView("/get-test.jsp"); modelAndView.addObject("id", request.getAttribute("id")); return modelAndView; } @@ -26,7 +25,7 @@ public ModelAndView findUserId(final HttpServletRequest request, final HttpServl @RequestMapping(value = "/post-test", method = RequestMethod.POST) public ModelAndView save(final HttpServletRequest request, final HttpServletResponse response) { log.info("test controller post method"); - final var modelAndView = new ModelAndView(new JspView("/post-test.jsp")); + final var modelAndView = new ModelAndView("/post-test.jsp"); modelAndView.addObject("id", request.getAttribute("id")); return modelAndView; } diff --git a/mvc/src/test/java/samples/TestInterfaceController.java b/mvc/src/test/java/samples/TestInterfaceController.java new file mode 100644 index 0000000000..9e1325b8f2 --- /dev/null +++ b/mvc/src/test/java/samples/TestInterfaceController.java @@ -0,0 +1,19 @@ +package samples; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import nextstep.mvc.controller.Controller; + +public class TestInterfaceController implements Controller { + + private final String path; + + public TestInterfaceController() { + this.path = "/test-path.jsp"; + } + + @Override + public String execute(HttpServletRequest req, HttpServletResponse res) throws Exception { + return path; + } +}