From 5370cdff55e14577db75000ecc69ab61a22327dd Mon Sep 17 00:00:00 2001 From: HyungUk Ryu Date: Mon, 23 Sep 2024 13:19:03 +0900 Subject: [PATCH 01/25] =?UTF-8?q?refactor:=20RegisterController=EB=A5=BC?= =?UTF-8?q?=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EA=B8=B0?= =?UTF-8?q?=EB=B0=98=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD=ED=95=98?= =?UTF-8?q?=EA=B3=A0=20=EB=AA=85=EC=8B=9C=EC=A0=81=20=EB=A7=A4=ED=95=91=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/techcourse/ManualHandlerMapping.java | 13 ++++++----- .../controller/RegisterController.java | 22 ++++++++++++++----- .../controller/RegisterViewController.java | 13 ----------- 3 files changed, 25 insertions(+), 23 deletions(-) delete mode 100644 app/src/main/java/com/techcourse/controller/RegisterViewController.java diff --git a/app/src/main/java/com/techcourse/ManualHandlerMapping.java b/app/src/main/java/com/techcourse/ManualHandlerMapping.java index 2116056394..d88f947f95 100644 --- a/app/src/main/java/com/techcourse/ManualHandlerMapping.java +++ b/app/src/main/java/com/techcourse/ManualHandlerMapping.java @@ -1,6 +1,8 @@ package com.techcourse; +import com.interface21.webmvc.servlet.mvc.HandlerMapping; import com.techcourse.controller.*; +import jakarta.servlet.http.HttpServletRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.interface21.webmvc.servlet.mvc.asis.Controller; @@ -9,27 +11,28 @@ import java.util.HashMap; import java.util.Map; -public class ManualHandlerMapping { +public class ManualHandlerMapping implements HandlerMapping { private static final Logger log = LoggerFactory.getLogger(ManualHandlerMapping.class); private static final Map controllers = new HashMap<>(); + @Override public void initialize() { controllers.put("/", new ForwardController("/index.jsp")); controllers.put("/login", new LoginController()); controllers.put("/login/view", new LoginViewController()); controllers.put("/logout", new LogoutController()); - controllers.put("/register/view", new RegisterViewController()); - controllers.put("/register", new RegisterController()); log.info("Initialized Handler Mapping!"); controllers.keySet() .forEach(path -> log.info("Path : {}, Controller : {}", path, controllers.get(path).getClass())); } - public Controller getHandler(final String requestURI) { - log.debug("Request Mapping Uri : {}", requestURI); + @Override + public Controller getHandler(HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.debug("(Manual) Request Mapping Uri : {}", requestURI); return controllers.get(requestURI); } } diff --git a/app/src/main/java/com/techcourse/controller/RegisterController.java b/app/src/main/java/com/techcourse/controller/RegisterController.java index 782abfb219..ec96dd1861 100644 --- a/app/src/main/java/com/techcourse/controller/RegisterController.java +++ b/app/src/main/java/com/techcourse/controller/RegisterController.java @@ -1,21 +1,33 @@ package com.techcourse.controller; +import com.interface21.context.stereotype.Controller; +import com.interface21.web.bind.annotation.RequestMapping; +import com.interface21.web.bind.annotation.RequestMethod; +import com.interface21.webmvc.servlet.ModelAndView; +import com.interface21.webmvc.servlet.view.JspView; import com.techcourse.domain.User; import com.techcourse.repository.InMemoryUserRepository; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import com.interface21.webmvc.servlet.mvc.asis.Controller; -public class RegisterController implements Controller { +@Controller +public class RegisterController { - @Override - public String execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception { + @RequestMapping(value = "/register", method = RequestMethod.GET) + public ModelAndView view(HttpServletRequest req, HttpServletResponse res) { + JspView jspView = new JspView("/register.jsp"); + return new ModelAndView(jspView); + } + + @RequestMapping(value = "/register", method = RequestMethod.POST) + public ModelAndView save(HttpServletRequest req, HttpServletResponse res) { final var user = new User(2, req.getParameter("account"), req.getParameter("password"), req.getParameter("email")); InMemoryUserRepository.save(user); - return "redirect:/index.jsp"; + JspView jspView = new JspView("redirect:/index.jsp"); + return new ModelAndView(jspView); } } diff --git a/app/src/main/java/com/techcourse/controller/RegisterViewController.java b/app/src/main/java/com/techcourse/controller/RegisterViewController.java deleted file mode 100644 index c88dc2814b..0000000000 --- a/app/src/main/java/com/techcourse/controller/RegisterViewController.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.techcourse.controller; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import com.interface21.webmvc.servlet.mvc.asis.Controller; - -public class RegisterViewController implements Controller { - - @Override - public String execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception { - return "/register.jsp"; - } -} From e8cec5f28193430e2f3a9e928f6f85cdb7aa935a Mon Sep 17 00:00:00 2001 From: HyungUk Ryu Date: Mon, 23 Sep 2024 13:22:47 +0900 Subject: [PATCH 02/25] =?UTF-8?q?refactor(HandlerMapping):=20HandlerMappin?= =?UTF-8?q?g=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../webmvc/servlet/mvc/HandlerMapping.java | 10 ++++++++++ .../servlet/mvc/tobe/AnnotationHandlerMapping.java | 12 +++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 mvc/src/main/java/com/interface21/webmvc/servlet/mvc/HandlerMapping.java diff --git a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/HandlerMapping.java b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/HandlerMapping.java new file mode 100644 index 0000000000..8318b1dad2 --- /dev/null +++ b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/HandlerMapping.java @@ -0,0 +1,10 @@ +package com.interface21.webmvc.servlet.mvc; + +import jakarta.servlet.http.HttpServletRequest; + +public interface HandlerMapping { + + void initialize(); + + Object getHandler(HttpServletRequest request); +} diff --git a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/AnnotationHandlerMapping.java b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/AnnotationHandlerMapping.java index a4421fca1b..7077f7f78b 100644 --- a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/AnnotationHandlerMapping.java +++ b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/AnnotationHandlerMapping.java @@ -3,19 +3,19 @@ import com.interface21.context.stereotype.Controller; import com.interface21.web.bind.annotation.RequestMapping; import com.interface21.web.bind.annotation.RequestMethod; +import com.interface21.webmvc.servlet.mvc.HandlerMapping; import jakarta.servlet.http.HttpServletRequest; import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; import org.reflections.Reflections; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class AnnotationHandlerMapping { +public class AnnotationHandlerMapping implements HandlerMapping { private static final Logger log = LoggerFactory.getLogger(AnnotationHandlerMapping.class); private static final Class CONTROLLER_TYPE = Controller.class; @@ -29,6 +29,7 @@ public AnnotationHandlerMapping(final Object... basePackage) { this.handlerExecutions = new HashMap<>(); } + @Override public void initialize() { Reflections reflections = new Reflections(basePackage); Set> classes = reflections.getTypesAnnotatedWith(CONTROLLER_TYPE); @@ -75,9 +76,10 @@ private boolean hasNoHttpMethods(RequestMethod[] requestMethods) { return requestMethods.length == 0; } - public Object getHandler(final HttpServletRequest request) { + @Override + public HandlerExecution getHandler(HttpServletRequest request) { + log.debug("(Annotation) Request Mapping Uri : {}", request.getRequestURI()); HandlerKey handlerKey = HandlerKey.from(request); - return Optional.ofNullable(handlerExecutions.get(handlerKey)) - .orElseThrow(() -> new IllegalArgumentException(handlerKey + "과(와) 매칭되는 핸들러를 찾을 수 없습니다.")); + return handlerExecutions.get(handlerKey); } } From 7ab73a991f2b6fcd7407d6003634eeec40a08f8f Mon Sep 17 00:00:00 2001 From: HyungUk Ryu Date: Mon, 23 Sep 2024 13:26:02 +0900 Subject: [PATCH 03/25] =?UTF-8?q?feat(HandlerAdapter):=20HandlerAdapter=20?= =?UTF-8?q?=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../webmvc/servlet/mvc/HandlerAdapter.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 mvc/src/main/java/com/interface21/webmvc/servlet/mvc/HandlerAdapter.java diff --git a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/HandlerAdapter.java b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/HandlerAdapter.java new file mode 100644 index 0000000000..9d8d6c76b7 --- /dev/null +++ b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/HandlerAdapter.java @@ -0,0 +1,12 @@ +package com.interface21.webmvc.servlet.mvc; + +import com.interface21.webmvc.servlet.ModelAndView; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public interface HandlerAdapter { + + ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; + + boolean canHandle(Object handler); +} From 22504fccb369eb26429bdb0ffbf2929b568ff853 Mon Sep 17 00:00:00 2001 From: HyungUk Ryu Date: Mon, 23 Sep 2024 13:29:16 +0900 Subject: [PATCH 04/25] =?UTF-8?q?feat(ControllerHandlerAdapter):=20?= =?UTF-8?q?=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=EB=A5=BC=20=EB=8B=B4?= =?UTF-8?q?=EB=8B=B9=ED=95=98=EB=8A=94=20HandlerAdapter=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=EC=B2=B4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../techcourse/ControllerHandlerAdapter.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 app/src/main/java/com/techcourse/ControllerHandlerAdapter.java diff --git a/app/src/main/java/com/techcourse/ControllerHandlerAdapter.java b/app/src/main/java/com/techcourse/ControllerHandlerAdapter.java new file mode 100644 index 0000000000..061dc13fe5 --- /dev/null +++ b/app/src/main/java/com/techcourse/ControllerHandlerAdapter.java @@ -0,0 +1,24 @@ +package com.techcourse; + +import com.interface21.webmvc.servlet.ModelAndView; +import com.interface21.webmvc.servlet.mvc.HandlerAdapter; +import com.interface21.webmvc.servlet.mvc.asis.Controller; +import com.interface21.webmvc.servlet.view.JspView; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public class ControllerHandlerAdapter implements HandlerAdapter { + + @Override + public boolean canHandle(Object handler) { + return handler instanceof Controller; + } + + @Override + public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + Controller controller = (Controller) handler; + String viewName = controller.execute(request, response); + JspView jspView = new JspView(viewName); + return new ModelAndView(jspView); + } +} From 4dfbbe1d8532386e3d00c1fa60ff472d2e6b9fae Mon Sep 17 00:00:00 2001 From: HyungUk Ryu Date: Mon, 23 Sep 2024 13:30:09 +0900 Subject: [PATCH 05/25] =?UTF-8?q?feat(HandlerExecutionHandlerAdapter):=20H?= =?UTF-8?q?andlerExecution=EC=9D=84=20=EB=8B=B4=EB=8B=B9=ED=95=98=EB=8A=94?= =?UTF-8?q?=20HandlerAdapter=20=EA=B5=AC=ED=98=84=EC=B2=B4=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../HandlerExecutionHandlerAdapter.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 app/src/main/java/com/techcourse/HandlerExecutionHandlerAdapter.java diff --git a/app/src/main/java/com/techcourse/HandlerExecutionHandlerAdapter.java b/app/src/main/java/com/techcourse/HandlerExecutionHandlerAdapter.java new file mode 100644 index 0000000000..17d3883b3b --- /dev/null +++ b/app/src/main/java/com/techcourse/HandlerExecutionHandlerAdapter.java @@ -0,0 +1,21 @@ +package com.techcourse; + +import com.interface21.webmvc.servlet.ModelAndView; +import com.interface21.webmvc.servlet.mvc.HandlerAdapter; +import com.interface21.webmvc.servlet.mvc.tobe.HandlerExecution; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public class HandlerExecutionHandlerAdapter implements HandlerAdapter { + + @Override + public boolean canHandle(Object handler) { + return handler instanceof HandlerExecution; + } + + @Override + public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) { + HandlerExecution handlerExecution = (HandlerExecution) handler; + return handlerExecution.handle(request, response); + } +} From 45dfa16557dc9dbb5623c28494a1e354cdd5d0f6 Mon Sep 17 00:00:00 2001 From: HyungUk Ryu Date: Mon, 23 Sep 2024 13:43:24 +0900 Subject: [PATCH 06/25] =?UTF-8?q?feat:=20HandlerMapping=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=EC=B2=B4=EC=97=90=EC=84=9C=20=ED=95=B8=EB=93=A4?= =?UTF-8?q?=EB=9F=AC=20=EC=B4=88=EA=B8=B0=ED=99=94=20=EC=8B=9C=20=EB=A1=9C?= =?UTF-8?q?=EA=B9=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/techcourse/ManualHandlerMapping.java | 2 +- .../webmvc/servlet/mvc/tobe/AnnotationHandlerMapping.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/techcourse/ManualHandlerMapping.java b/app/src/main/java/com/techcourse/ManualHandlerMapping.java index d88f947f95..65c8a177dd 100644 --- a/app/src/main/java/com/techcourse/ManualHandlerMapping.java +++ b/app/src/main/java/com/techcourse/ManualHandlerMapping.java @@ -24,7 +24,7 @@ public void initialize() { controllers.put("/login/view", new LoginViewController()); controllers.put("/logout", new LogoutController()); - log.info("Initialized Handler Mapping!"); + log.info("Initialized ManualHandlerMapping!"); controllers.keySet() .forEach(path -> log.info("Path : {}, Controller : {}", path, controllers.get(path).getClass())); } diff --git a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/AnnotationHandlerMapping.java b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/AnnotationHandlerMapping.java index 7077f7f78b..1caf434e46 100644 --- a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/AnnotationHandlerMapping.java +++ b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/AnnotationHandlerMapping.java @@ -41,6 +41,8 @@ public void initialize() { } log.info("Initialized AnnotationHandlerMapping!"); + handlerExecutions.keySet() + .forEach(handlerKey -> log.info("HandlerKey : {}", handlerKey)); } private Object instantiate(Class clazz) { From 01edf41313d794fdfd0a79cba81fc4defd052759 Mon Sep 17 00:00:00 2001 From: HyungUk Ryu Date: Mon, 23 Sep 2024 13:48:54 +0900 Subject: [PATCH 07/25] =?UTF-8?q?feat(DispatcherServlet):=20=EC=96=B4?= =?UTF-8?q?=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EA=B8=B0=EB=B0=98=20?= =?UTF-8?q?=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=EC=99=80=20=EC=9D=B8?= =?UTF-8?q?=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EA=B8=B0=EB=B0=98=20?= =?UTF-8?q?=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=EB=A5=BC=20=EB=AA=A8?= =?UTF-8?q?=EB=91=90=20=EC=A7=80=EC=9B=90=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/techcourse/DispatcherServlet.java | 48 ++++++++++++++----- .../webmvc/servlet/ModelAndView.java | 6 +++ 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/techcourse/DispatcherServlet.java b/app/src/main/java/com/techcourse/DispatcherServlet.java index cdd3b3d14d..ca769dbbd7 100644 --- a/app/src/main/java/com/techcourse/DispatcherServlet.java +++ b/app/src/main/java/com/techcourse/DispatcherServlet.java @@ -1,12 +1,17 @@ package com.techcourse; -import com.interface21.webmvc.servlet.view.JspView; +import com.interface21.webmvc.servlet.ModelAndView; +import com.interface21.webmvc.servlet.mvc.HandlerAdapter; +import com.interface21.webmvc.servlet.mvc.HandlerMapping; +import com.interface21.webmvc.servlet.mvc.tobe.AnnotationHandlerMapping; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.Serial; -import java.util.HashMap; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,30 +21,51 @@ public class DispatcherServlet extends HttpServlet { private static final long serialVersionUID = 1L; private static final Logger log = LoggerFactory.getLogger(DispatcherServlet.class); - private ManualHandlerMapping manualHandlerMapping; + private final List handlerMappings = new ArrayList<>(); + private final List handlerAdapters = new ArrayList<>(); public DispatcherServlet() { } @Override public void init() { - manualHandlerMapping = new ManualHandlerMapping(); - manualHandlerMapping.initialize(); + handlerMappings.add(new ManualHandlerMapping()); + handlerMappings.add(new AnnotationHandlerMapping("com.techcourse.controller")); + handlerMappings.forEach(HandlerMapping::initialize); + + handlerAdapters.add(new ControllerHandlerAdapter()); + handlerAdapters.add(new HandlerExecutionHandlerAdapter()); } @Override protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException { - final String requestURI = request.getRequestURI(); - log.debug("Method : {}, Request URI : {}", request.getMethod(), requestURI); + String method = request.getMethod(); + String requestURI = request.getRequestURI(); + log.debug("Method : {}, Request URI : {}", method, requestURI); try { - final var controller = manualHandlerMapping.getHandler(requestURI); - final var viewName = controller.execute(request, response); - JspView jspView = new JspView(viewName); - jspView.render(new HashMap<>(), request, response); + Object handler = findHandlerOf(request); + HandlerAdapter adapter = findHandlerAdapterOf(handler); + ModelAndView modelAndView = adapter.handle(request, response, handler); + modelAndView.render(request, response); } catch (Exception e) { log.error("Exception : {}", e.getMessage(), e); throw new ServletException(e.getMessage()); } } + + private Object findHandlerOf(HttpServletRequest request) { + return handlerMappings.stream() + .map(handlerMapping -> handlerMapping.getHandler(request)) + .filter(Objects::nonNull) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("요청에 해당하는 핸들러를 찾을 수 없습니다.")); + } + + private HandlerAdapter findHandlerAdapterOf(Object handler) { + return handlerAdapters.stream() + .filter(adapter -> adapter.canHandle(handler)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("요청에 해당하는 핸들러 어댑터를 찾을 수 없습니다.")); + } } diff --git a/mvc/src/main/java/com/interface21/webmvc/servlet/ModelAndView.java b/mvc/src/main/java/com/interface21/webmvc/servlet/ModelAndView.java index d5a3eadeac..b80a58ba18 100644 --- a/mvc/src/main/java/com/interface21/webmvc/servlet/ModelAndView.java +++ b/mvc/src/main/java/com/interface21/webmvc/servlet/ModelAndView.java @@ -1,5 +1,7 @@ package com.interface21.webmvc.servlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -14,6 +16,10 @@ public ModelAndView(final View view) { this.model = new HashMap<>(); } + public void render(HttpServletRequest request, HttpServletResponse response) throws Exception { + view.render(model, request, response); + } + public ModelAndView addObject(final String attributeName, final Object attributeValue) { model.put(attributeName, attributeValue); return this; From 945487942a4892f68bb3917a123142f93f31d4e3 Mon Sep 17 00:00:00 2001 From: HyungUk Ryu Date: Mon, 23 Sep 2024 14:04:29 +0900 Subject: [PATCH 08/25] =?UTF-8?q?refactor(JspView):=20=EB=82=B4=EB=B6=80?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../interface21/webmvc/servlet/view/JspView.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/mvc/src/main/java/com/interface21/webmvc/servlet/view/JspView.java b/mvc/src/main/java/com/interface21/webmvc/servlet/view/JspView.java index 585c3df477..c16a4be9d8 100644 --- a/mvc/src/main/java/com/interface21/webmvc/servlet/view/JspView.java +++ b/mvc/src/main/java/com/interface21/webmvc/servlet/view/JspView.java @@ -20,15 +20,22 @@ public JspView(String viewName) { @Override public void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception { - if (viewName.startsWith(REDIRECT_PREFIX)) { - response.sendRedirect(viewName.substring(JspView.REDIRECT_PREFIX.length())); + if (shouldRedirect()) { + response.sendRedirect(parseRedirectViewName()); return; } - setModelAttribute(model, request); request.getRequestDispatcher(viewName).forward(request, response); } + private boolean shouldRedirect() { + return viewName.startsWith(REDIRECT_PREFIX); + } + + private String parseRedirectViewName() { + return viewName.substring(REDIRECT_PREFIX.length()); + } + private void setModelAttribute(Map model, HttpServletRequest request) { model.keySet().forEach(key -> { log.debug("attribute name : {}, value : {}", key, model.get(key)); From d6eb8a806f8f4b356091485bb2a977c8488aa279 Mon Sep 17 00:00:00 2001 From: HyungUk Ryu Date: Mon, 23 Sep 2024 14:25:21 +0900 Subject: [PATCH 09/25] =?UTF-8?q?test(JspViewTest):=20redirect/forward=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../webmvc/servlet/view/JspViewTest.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 mvc/src/test/java/com/interface21/webmvc/servlet/view/JspViewTest.java diff --git a/mvc/src/test/java/com/interface21/webmvc/servlet/view/JspViewTest.java b/mvc/src/test/java/com/interface21/webmvc/servlet/view/JspViewTest.java new file mode 100644 index 0000000000..da668a5114 --- /dev/null +++ b/mvc/src/test/java/com/interface21/webmvc/servlet/view/JspViewTest.java @@ -0,0 +1,52 @@ +package com.interface21.webmvc.servlet.view; + + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import jakarta.servlet.RequestDispatcher; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class JspViewTest { + + @DisplayName("redirect 접두사가 존재하는 경우 Redirec 처리한다.") + @Test + void redirect() throws Exception { + // given + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + String viewName = "redirect:/index.jsp"; + JspView jspView = new JspView(viewName); + + // when + jspView.render(Map.of(), request, response); + + // then + verify(response).sendRedirect("/index.jsp"); + } + + @DisplayName("redirect 접두사가 존재하지 않는 경우 모델 데이터를 request attribute로 설정하고 forward 처리한다.") + @Test + void forward() throws Exception { + // given + String viewName = "/index.jsp"; + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + RequestDispatcher requestDispatcher = mock(RequestDispatcher.class); + when(request.getRequestDispatcher(viewName)).thenReturn(requestDispatcher); + + JspView jspView = new JspView(viewName); + + // when + jspView.render(Map.of("id", "pedro"), request, response); + + // then + verify(request).setAttribute("id", "pedro"); + verify(requestDispatcher).forward(request, response); + } +} From c1e936ae863b12237e69058a840ebd7de054a50b Mon Sep 17 00:00:00 2001 From: HyungUk Ryu Date: Mon, 23 Sep 2024 14:32:02 +0900 Subject: [PATCH 10/25] =?UTF-8?q?refactor(AnnotationHandlerMappingTest):?= =?UTF-8?q?=20=ED=95=B8=EB=93=A4=EB=9F=AC=20=EB=B6=80=EC=9E=AC=20=EC=8B=9C?= =?UTF-8?q?=20=EB=8D=94=20=EC=9D=B4=EC=83=81=20=EC=98=88=EC=99=B8=EB=A5=BC?= =?UTF-8?q?=20=EB=8D=98=EC=A7=80=EC=A7=80=20=EC=95=8A=EC=9C=BC=EB=AF=80?= =?UTF-8?q?=EB=A1=9C=20=EA=B4=80=EB=A0=A8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mvc/tobe/AnnotationHandlerMappingTest.java | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/AnnotationHandlerMappingTest.java b/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/AnnotationHandlerMappingTest.java index e420406457..3053831451 100644 --- a/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/AnnotationHandlerMappingTest.java +++ b/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/AnnotationHandlerMappingTest.java @@ -1,7 +1,6 @@ package com.interface21.webmvc.servlet.mvc.tobe; 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; @@ -90,16 +89,4 @@ void allMethod(RequestMethod httpMethod) { assertThat(modelAndView.getObject("id")).isEqualTo("holymoly"); } - - @DisplayName("핸들러가 존재하지 않으면 예외가 발생한다.") - @Test - void throwsWhenHandlerNotFound() { - final var request = mock(HttpServletRequest.class); - - when(request.getRequestURI()).thenReturn("/not-found"); - when(request.getMethod()).thenReturn("GET"); - - assertThatThrownBy(() -> handlerMapping.getHandler(request)) - .isInstanceOf(IllegalArgumentException.class); - } } From 2a5c2eef84269c822af37fff22754bf545d1d36d Mon Sep 17 00:00:00 2001 From: HyungUk Ryu Date: Mon, 23 Sep 2024 14:57:27 +0900 Subject: [PATCH 11/25] =?UTF-8?q?refactor(HandlerExecutionHandlerAdapter):?= =?UTF-8?q?=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=9C=84=EC=B9=98=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/techcourse/DispatcherServlet.java | 1 + .../servlet/mvc/tobe}/HandlerExecutionHandlerAdapter.java | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) rename {app/src/main/java/com/techcourse => mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe}/HandlerExecutionHandlerAdapter.java (88%) diff --git a/app/src/main/java/com/techcourse/DispatcherServlet.java b/app/src/main/java/com/techcourse/DispatcherServlet.java index ca769dbbd7..2d39bd54c5 100644 --- a/app/src/main/java/com/techcourse/DispatcherServlet.java +++ b/app/src/main/java/com/techcourse/DispatcherServlet.java @@ -4,6 +4,7 @@ import com.interface21.webmvc.servlet.mvc.HandlerAdapter; import com.interface21.webmvc.servlet.mvc.HandlerMapping; import com.interface21.webmvc.servlet.mvc.tobe.AnnotationHandlerMapping; +import com.interface21.webmvc.servlet.mvc.tobe.HandlerExecutionHandlerAdapter; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; diff --git a/app/src/main/java/com/techcourse/HandlerExecutionHandlerAdapter.java b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerExecutionHandlerAdapter.java similarity index 88% rename from app/src/main/java/com/techcourse/HandlerExecutionHandlerAdapter.java rename to mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerExecutionHandlerAdapter.java index 17d3883b3b..7cc1404936 100644 --- a/app/src/main/java/com/techcourse/HandlerExecutionHandlerAdapter.java +++ b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerExecutionHandlerAdapter.java @@ -1,8 +1,7 @@ -package com.techcourse; +package com.interface21.webmvc.servlet.mvc.tobe; import com.interface21.webmvc.servlet.ModelAndView; import com.interface21.webmvc.servlet.mvc.HandlerAdapter; -import com.interface21.webmvc.servlet.mvc.tobe.HandlerExecution; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; From f501d947650f526e338913b5cd48ab682ea7eb6f Mon Sep 17 00:00:00 2001 From: HyungUk Ryu Date: Mon, 23 Sep 2024 15:27:07 +0900 Subject: [PATCH 12/25] =?UTF-8?q?test(HandlerExecutionTest):=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=A0=91=EA=B7=BC=20=EB=B6=88=EA=B0=80=20?= =?UTF-8?q?=EC=8B=9C=20=EC=98=88=EC=99=B8=20=EB=B0=9C=EC=83=9D=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mvc/tobe/HandlerExecutionTest.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerExecutionTest.java diff --git a/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerExecutionTest.java b/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerExecutionTest.java new file mode 100644 index 0000000000..c71e5d0fd4 --- /dev/null +++ b/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerExecutionTest.java @@ -0,0 +1,35 @@ +package com.interface21.webmvc.servlet.mvc.tobe; + + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class HandlerExecutionTest { + + static class TestController { + + private void testMethod() { + } + } + + @DisplayName("Controller의 메서드에 접근할 수 없는 경우 예외가 발생한다.") + @Test + void handle() { + // given + TestController controller = new TestController(); + HandlerExecution handlerExecution = new HandlerExecution(controller, + controller.getClass().getDeclaredMethods()[0]); + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + + // when & then + assertThatThrownBy(() -> handlerExecution.handle(request, response)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("핸들러 메서드 호출 중 오류가 발생했습니다."); + } +} From c5b430fe6f08d794c716b8aad72eac6c2f703f5f Mon Sep 17 00:00:00 2001 From: HyungUk Ryu Date: Mon, 23 Sep 2024 15:50:28 +0900 Subject: [PATCH 13/25] =?UTF-8?q?refactor(ControllerScanner):=20Reflection?= =?UTF-8?q?s=EC=97=90=EC=84=9C=20Controller=EB=A5=BC=20=EC=B0=BE=EA=B3=A0?= =?UTF-8?q?=20=EC=9D=B8=EC=8A=A4=ED=84=B4=EC=8A=A4=ED=99=94=EB=A5=BC=20?= =?UTF-8?q?=EB=8B=B4=EB=8B=B9=ED=95=98=EB=8A=94=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mvc/tobe/AnnotationHandlerMapping.java | 24 +++++--------- .../servlet/mvc/tobe/ControllerScanner.java | 33 +++++++++++++++++++ 2 files changed, 41 insertions(+), 16 deletions(-) create mode 100644 mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerScanner.java diff --git a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/AnnotationHandlerMapping.java b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/AnnotationHandlerMapping.java index 1caf434e46..7033534cbc 100644 --- a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/AnnotationHandlerMapping.java +++ b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/AnnotationHandlerMapping.java @@ -1,6 +1,5 @@ package com.interface21.webmvc.servlet.mvc.tobe; -import com.interface21.context.stereotype.Controller; import com.interface21.web.bind.annotation.RequestMapping; import com.interface21.web.bind.annotation.RequestMethod; import com.interface21.webmvc.servlet.mvc.HandlerMapping; @@ -10,15 +9,15 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; import org.reflections.Reflections; +import org.reflections.scanners.Scanners; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class AnnotationHandlerMapping implements HandlerMapping { + private static final Logger log = LoggerFactory.getLogger(AnnotationHandlerMapping.class); - private static final Class CONTROLLER_TYPE = Controller.class; private static final Class REQUEST_MAPPING_MARKER = RequestMapping.class; private final Object[] basePackage; @@ -31,12 +30,13 @@ public AnnotationHandlerMapping(final Object... basePackage) { @Override public void initialize() { - Reflections reflections = new Reflections(basePackage); - Set> classes = reflections.getTypesAnnotatedWith(CONTROLLER_TYPE); + Reflections reflections = new Reflections(basePackage, Scanners.TypesAnnotated); + ControllerScanner controllerScanner = new ControllerScanner(reflections); + Map, Object> controllers = controllerScanner.getControllers(); - for (Class clazz : classes) { - Object instance = instantiate(clazz); - List handlerMethods = getRequestMappingMethods(clazz); + for (Map.Entry, Object> entry : controllers.entrySet()) { + Object instance = entry.getValue(); + List handlerMethods = getRequestMappingMethods(entry.getKey()); handlerMethods.forEach(method -> putAllMatchedHandlerExecutionOf(instance, method)); } @@ -45,14 +45,6 @@ public void initialize() { .forEach(handlerKey -> log.info("HandlerKey : {}", handlerKey)); } - private Object instantiate(Class clazz) { - try { - return clazz.getDeclaredConstructor().newInstance(); - } catch (Exception e) { - throw new IllegalArgumentException("컨트롤러를 인스턴스화 할 수 없습니다.: " + clazz, e); - } - } - private List getRequestMappingMethods(Class clazz) { return Arrays.stream(clazz.getDeclaredMethods()) .filter(method -> method.isAnnotationPresent(REQUEST_MAPPING_MARKER)) diff --git a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerScanner.java b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerScanner.java new file mode 100644 index 0000000000..a9f08915ca --- /dev/null +++ b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerScanner.java @@ -0,0 +1,33 @@ +package com.interface21.webmvc.servlet.mvc.tobe; + +import static java.util.stream.Collectors.toMap; + +import com.interface21.context.stereotype.Controller; +import java.util.Map; +import java.util.Set; +import org.reflections.Reflections; + +public class ControllerScanner { + + private static final Class CONTROLLER_TYPE = Controller.class; + + private final Reflections reflections; + + public ControllerScanner(Reflections reflections) { + this.reflections = reflections; + } + + public Map, Object> getControllers() { + Set> classes = reflections.getTypesAnnotatedWith(CONTROLLER_TYPE); + return classes.stream() + .collect(toMap(clazz -> clazz, this::instantiate)); + } + + private Object instantiate(Class clazz) { + try { + return clazz.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new IllegalArgumentException("컨트롤러를 인스턴스화 할 수 없습니다.: " + clazz, e); + } + } +} From a5d79bbef658d181b60f61cfa55a43f9da480f02 Mon Sep 17 00:00:00 2001 From: HyungUk Ryu Date: Mon, 23 Sep 2024 15:55:59 +0900 Subject: [PATCH 14/25] =?UTF-8?q?Test(ControllerScannerTest):=20=EC=BB=A8?= =?UTF-8?q?=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=EC=9D=98=20=EC=9D=B8=EC=8A=A4=ED=84=B4=EC=8A=A4=ED=99=94=20?= =?UTF-8?q?=EC=8B=A4=ED=8C=A8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mvc/tobe/ControllerScannerTest.java | 24 +++++++++++++++++++ .../servlet/mvc/tobe/FakeController.java | 11 +++++++++ 2 files changed, 35 insertions(+) create mode 100644 mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerScannerTest.java create mode 100644 mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/FakeController.java diff --git a/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerScannerTest.java b/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerScannerTest.java new file mode 100644 index 0000000000..c26956d474 --- /dev/null +++ b/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerScannerTest.java @@ -0,0 +1,24 @@ +package com.interface21.webmvc.servlet.mvc.tobe; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.reflections.Reflections; + +class ControllerScannerTest { + + @DisplayName("@Controller 어노테이션이 붙은 클래스를 인스턴스화할수 없는 경우 예외가 발생한다.") + @Test + void throwsWhenCannotInstantiateController() { + // given + Reflections reflections = new Reflections("com.interface21.webmvc.servlet.mvc.tobe"); + ControllerScanner controllerScanner = new ControllerScanner(reflections); + + // when & then + assertThatThrownBy(controllerScanner::getControllers) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining( + "컨트롤러를 인스턴스화 할 수 없습니다."); + } +} diff --git a/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/FakeController.java b/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/FakeController.java new file mode 100644 index 0000000000..f0804ca753 --- /dev/null +++ b/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/FakeController.java @@ -0,0 +1,11 @@ +package com.interface21.webmvc.servlet.mvc.tobe; + +import com.interface21.context.stereotype.Controller; + +@Controller + +public class FakeController { + + private FakeController() { + } +} From 0309308431d1bb530bb0e84ff34d6de8fb1b9575 Mon Sep 17 00:00:00 2001 From: HyungUk Ryu Date: Mon, 23 Sep 2024 16:18:17 +0900 Subject: [PATCH 15/25] =?UTF-8?q?refactor(AnnotationHandlerMapping):=20?= =?UTF-8?q?=EB=A3=A8=ED=94=84=20=EB=82=B4=EB=B6=80=EC=9D=98=20forEach=20?= =?UTF-8?q?=EA=B5=AC=EB=AC=B8=EC=9D=84=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../servlet/mvc/tobe/AnnotationHandlerMapping.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/AnnotationHandlerMapping.java b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/AnnotationHandlerMapping.java index 7033534cbc..29830234e4 100644 --- a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/AnnotationHandlerMapping.java +++ b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/AnnotationHandlerMapping.java @@ -34,11 +34,10 @@ public void initialize() { ControllerScanner controllerScanner = new ControllerScanner(reflections); Map, Object> controllers = controllerScanner.getControllers(); - for (Map.Entry, Object> entry : controllers.entrySet()) { - Object instance = entry.getValue(); - List handlerMethods = getRequestMappingMethods(entry.getKey()); - handlerMethods.forEach(method -> putAllMatchedHandlerExecutionOf(instance, method)); - } + controllers.forEach((clazz, instance) -> { + List handlerMethods = getRequestMappingMethods(clazz); + registerAllHandlerMethods(instance, handlerMethods); + }); log.info("Initialized AnnotationHandlerMapping!"); handlerExecutions.keySet() @@ -51,6 +50,10 @@ private List getRequestMappingMethods(Class clazz) { .toList(); } + private void registerAllHandlerMethods(Object instance, List handlerMethods) { + handlerMethods.forEach(method -> putAllMatchedHandlerExecutionOf(instance, method)); + } + private void putAllMatchedHandlerExecutionOf(Object instance, Method method) { RequestMapping requestMapping = method.getAnnotation(REQUEST_MAPPING_MARKER); String path = requestMapping.value(); From 849d79fe527b9c950095641542b8c3998040982d Mon Sep 17 00:00:00 2001 From: HyungUk Ryu Date: Mon, 23 Sep 2024 16:20:31 +0900 Subject: [PATCH 16/25] =?UTF-8?q?refactor(DispatcherServlet):=20=EC=96=B4?= =?UTF-8?q?=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EA=B8=B0=EB=B0=98=20?= =?UTF-8?q?=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=EC=9D=98=20=EB=B2=A0?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EC=83=81=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/techcourse/DispatcherServlet.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/techcourse/DispatcherServlet.java b/app/src/main/java/com/techcourse/DispatcherServlet.java index 2d39bd54c5..6eccc9cae7 100644 --- a/app/src/main/java/com/techcourse/DispatcherServlet.java +++ b/app/src/main/java/com/techcourse/DispatcherServlet.java @@ -20,6 +20,7 @@ public class DispatcherServlet extends HttpServlet { @Serial private static final long serialVersionUID = 1L; + private static final String ANNOTATION_BASED_CONTROLLER_BASE = "com.techcourse.controller"; private static final Logger log = LoggerFactory.getLogger(DispatcherServlet.class); private final List handlerMappings = new ArrayList<>(); @@ -31,7 +32,7 @@ public DispatcherServlet() { @Override public void init() { handlerMappings.add(new ManualHandlerMapping()); - handlerMappings.add(new AnnotationHandlerMapping("com.techcourse.controller")); + handlerMappings.add(new AnnotationHandlerMapping(ANNOTATION_BASED_CONTROLLER_BASE)); handlerMappings.forEach(HandlerMapping::initialize); handlerAdapters.add(new ControllerHandlerAdapter()); From 753154a1239f1bdeeadf93b7350087cc73effdbf Mon Sep 17 00:00:00 2001 From: HyungUk Ryu Date: Mon, 23 Sep 2024 18:41:43 +0900 Subject: [PATCH 17/25] =?UTF-8?q?test(DispatcherServletTest):=20Dispatcher?= =?UTF-8?q?Servlet=20=EC=98=88=EC=99=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/techcourse/DispatcherServlet.java | 10 ++- .../com/techcourse/DispatcherServletTest.java | 68 +++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 app/src/test/java/com/techcourse/DispatcherServletTest.java diff --git a/app/src/main/java/com/techcourse/DispatcherServlet.java b/app/src/main/java/com/techcourse/DispatcherServlet.java index 6eccc9cae7..9608effcf3 100644 --- a/app/src/main/java/com/techcourse/DispatcherServlet.java +++ b/app/src/main/java/com/techcourse/DispatcherServlet.java @@ -40,7 +40,7 @@ public void init() { } @Override - protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException { + protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException { String method = request.getMethod(); String requestURI = request.getRequestURI(); log.debug("Method : {}, Request URI : {}", method, requestURI); @@ -70,4 +70,12 @@ private HandlerAdapter findHandlerAdapterOf(Object handler) { .findFirst() .orElseThrow(() -> new IllegalArgumentException("요청에 해당하는 핸들러 어댑터를 찾을 수 없습니다.")); } + + public void addHandlerMapping(HandlerMapping handlerMapping) { + handlerMappings.add(handlerMapping); + } + + public void addHandlerAdapter(HandlerAdapter handlerAdapter) { + handlerAdapters.add(handlerAdapter); + } } diff --git a/app/src/test/java/com/techcourse/DispatcherServletTest.java b/app/src/test/java/com/techcourse/DispatcherServletTest.java new file mode 100644 index 0000000000..86f2975885 --- /dev/null +++ b/app/src/test/java/com/techcourse/DispatcherServletTest.java @@ -0,0 +1,68 @@ +package com.techcourse; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.interface21.webmvc.servlet.mvc.HandlerMapping; +import jakarta.servlet.ServletException; +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; + +class DispatcherServletTest { + + private DispatcherServlet dispatcherServlet; + + @BeforeEach + void setUp() { + dispatcherServlet = new DispatcherServlet(); + dispatcherServlet.init(); + } + + @DisplayName("요청에 해당하는 핸들러를 찾을 수 없으면 예외가 발생한다.") + @Test + void throwsWhenHandlerNotFound() { + // given + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + when(request.getMethod()).thenReturn("GET"); + when(request.getRequestURI()).thenReturn("/not-found"); + + // when & then + assertThatThrownBy(() -> dispatcherServlet.service(request, response)) + .isInstanceOf(ServletException.class) + .hasMessageContaining("요청에 해당하는 핸들러를 찾을 수 없습니다."); + } + + @DisplayName("요청에 해당하는 핸들러 어댑터를 찾을 수 없으면 예외가 발생한다.") + @Test + void throwsWhenHandlerAdapterNotFound() { + // given + registerFakeHandlerMapping(); + HttpServletRequest request = mock(HttpServletRequest.class); + HttpServletResponse response = mock(HttpServletResponse.class); + when(request.getMethod()).thenReturn("GET"); + when(request.getRequestURI()).thenReturn("/test"); + + // when & then + assertThatThrownBy(() -> dispatcherServlet.service(request, response)) + .isInstanceOf(ServletException.class) + .hasMessageContaining("요청에 해당하는 핸들러 어댑터를 찾을 수 없습니다."); + } + + private void registerFakeHandlerMapping() { + dispatcherServlet.addHandlerMapping(new HandlerMapping() { + @Override + public void initialize() { + } + + @Override + public Object getHandler(HttpServletRequest request) { + return "test"; + } + }); + } +} From 8e6e237a11d349487fc2eedaafff2bb152e9b6da Mon Sep 17 00:00:00 2001 From: HyungUk Ryu Date: Mon, 23 Sep 2024 19:11:58 +0900 Subject: [PATCH 18/25] =?UTF-8?q?refactor(DispatcherServlet):=20HandlerMap?= =?UTF-8?q?ping=EA=B3=BC=20HandlerAdapter=EB=A5=BC=20=EC=9D=BC=EA=B8=89=20?= =?UTF-8?q?=EC=BB=AC=EB=A0=89=EC=85=98=EC=9C=BC=EB=A1=9C=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/techcourse/DispatcherServlet.java | 33 +++++------------ .../techcourse/HandlerAdapterRegistry.java | 31 ++++++++++++++++ .../techcourse/HandlerMappingRegistry.java | 37 +++++++++++++++++++ .../mvc/tobe/AnnotationHandlerMapping.java | 3 +- 4 files changed, 79 insertions(+), 25 deletions(-) create mode 100644 app/src/main/java/com/techcourse/HandlerAdapterRegistry.java create mode 100644 app/src/main/java/com/techcourse/HandlerMappingRegistry.java diff --git a/app/src/main/java/com/techcourse/DispatcherServlet.java b/app/src/main/java/com/techcourse/DispatcherServlet.java index 9608effcf3..d64bed8c8e 100644 --- a/app/src/main/java/com/techcourse/DispatcherServlet.java +++ b/app/src/main/java/com/techcourse/DispatcherServlet.java @@ -3,16 +3,11 @@ import com.interface21.webmvc.servlet.ModelAndView; import com.interface21.webmvc.servlet.mvc.HandlerAdapter; import com.interface21.webmvc.servlet.mvc.HandlerMapping; -import com.interface21.webmvc.servlet.mvc.tobe.AnnotationHandlerMapping; -import com.interface21.webmvc.servlet.mvc.tobe.HandlerExecutionHandlerAdapter; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.Serial; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,23 +15,20 @@ public class DispatcherServlet extends HttpServlet { @Serial private static final long serialVersionUID = 1L; - private static final String ANNOTATION_BASED_CONTROLLER_BASE = "com.techcourse.controller"; private static final Logger log = LoggerFactory.getLogger(DispatcherServlet.class); - private final List handlerMappings = new ArrayList<>(); - private final List handlerAdapters = new ArrayList<>(); + private final HandlerMappingRegistry handlerMappingRegistry; + private final HandlerAdapterRegistry handlerAdapterRegistry; public DispatcherServlet() { + handlerMappingRegistry = new HandlerMappingRegistry(); + handlerAdapterRegistry = new HandlerAdapterRegistry(); } @Override public void init() { - handlerMappings.add(new ManualHandlerMapping()); - handlerMappings.add(new AnnotationHandlerMapping(ANNOTATION_BASED_CONTROLLER_BASE)); - handlerMappings.forEach(HandlerMapping::initialize); - - handlerAdapters.add(new ControllerHandlerAdapter()); - handlerAdapters.add(new HandlerExecutionHandlerAdapter()); + handlerMappingRegistry.initialize(); + handlerAdapterRegistry.initialize(); } @Override @@ -57,25 +49,20 @@ protected void service(HttpServletRequest request, HttpServletResponse response) } private Object findHandlerOf(HttpServletRequest request) { - return handlerMappings.stream() - .map(handlerMapping -> handlerMapping.getHandler(request)) - .filter(Objects::nonNull) - .findFirst() + return handlerMappingRegistry.getHandler(request) .orElseThrow(() -> new IllegalArgumentException("요청에 해당하는 핸들러를 찾을 수 없습니다.")); } private HandlerAdapter findHandlerAdapterOf(Object handler) { - return handlerAdapters.stream() - .filter(adapter -> adapter.canHandle(handler)) - .findFirst() + return handlerAdapterRegistry.getHandlerAdapter(handler) .orElseThrow(() -> new IllegalArgumentException("요청에 해당하는 핸들러 어댑터를 찾을 수 없습니다.")); } public void addHandlerMapping(HandlerMapping handlerMapping) { - handlerMappings.add(handlerMapping); + handlerMappingRegistry.addHandlerMapping(handlerMapping); } public void addHandlerAdapter(HandlerAdapter handlerAdapter) { - handlerAdapters.add(handlerAdapter); + handlerAdapterRegistry.addHandlerAdapter(handlerAdapter); } } diff --git a/app/src/main/java/com/techcourse/HandlerAdapterRegistry.java b/app/src/main/java/com/techcourse/HandlerAdapterRegistry.java new file mode 100644 index 0000000000..6b044c2919 --- /dev/null +++ b/app/src/main/java/com/techcourse/HandlerAdapterRegistry.java @@ -0,0 +1,31 @@ +package com.techcourse; + +import com.interface21.webmvc.servlet.mvc.HandlerAdapter; +import com.interface21.webmvc.servlet.mvc.tobe.HandlerExecutionHandlerAdapter; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class HandlerAdapterRegistry { + + private final List handlerAdapters; + + public HandlerAdapterRegistry() { + this.handlerAdapters = new ArrayList<>(); + } + + public void initialize() { + handlerAdapters.add(new ControllerHandlerAdapter()); + handlerAdapters.add(new HandlerExecutionHandlerAdapter()); + } + + public void addHandlerAdapter(HandlerAdapter adapter) { + handlerAdapters.add(adapter); + } + + public Optional getHandlerAdapter(Object handler) { + return handlerAdapters.stream() + .filter(adapter -> adapter.canHandle(handler)) + .findFirst(); + } +} diff --git a/app/src/main/java/com/techcourse/HandlerMappingRegistry.java b/app/src/main/java/com/techcourse/HandlerMappingRegistry.java new file mode 100644 index 0000000000..6115d02d97 --- /dev/null +++ b/app/src/main/java/com/techcourse/HandlerMappingRegistry.java @@ -0,0 +1,37 @@ +package com.techcourse; + +import com.interface21.webmvc.servlet.mvc.HandlerMapping; +import com.interface21.webmvc.servlet.mvc.tobe.AnnotationHandlerMapping; +import jakarta.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +public class HandlerMappingRegistry { + + private static final String ANNOTATION_BASED_CONTROLLER_BASE = "com.techcourse.controller"; + + private final List handlerMappings; + + public HandlerMappingRegistry() { + handlerMappings = new ArrayList<>(); + } + + public void initialize() { + handlerMappings.add(new ManualHandlerMapping()); + handlerMappings.add(new AnnotationHandlerMapping(ANNOTATION_BASED_CONTROLLER_BASE)); + handlerMappings.forEach(HandlerMapping::initialize); + } + + public void addHandlerMapping(HandlerMapping mapping) { + handlerMappings.add(mapping); + } + + public Optional getHandler(HttpServletRequest request) { + return handlerMappings.stream() + .map(handlerMapping -> handlerMapping.getHandler(request)) + .filter(Objects::nonNull) + .findFirst(); + } +} diff --git a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/AnnotationHandlerMapping.java b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/AnnotationHandlerMapping.java index 29830234e4..58743dd936 100644 --- a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/AnnotationHandlerMapping.java +++ b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/AnnotationHandlerMapping.java @@ -15,8 +15,7 @@ import org.slf4j.LoggerFactory; public class AnnotationHandlerMapping implements HandlerMapping { - - + private static final Logger log = LoggerFactory.getLogger(AnnotationHandlerMapping.class); private static final Class REQUEST_MAPPING_MARKER = RequestMapping.class; From 3b52a1bc66a49f5f4d254bb4d8aef78556110d2a Mon Sep 17 00:00:00 2001 From: HyungUk Ryu Date: Mon, 23 Sep 2024 19:29:08 +0900 Subject: [PATCH 19/25] =?UTF-8?q?refactor:=20Registry=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=EB=93=A4=EC=9D=84=20=ED=94=84=EB=A0=88=EC=9E=84?= =?UTF-8?q?=EC=9B=8C=ED=81=AC=20=ED=8C=A8=ED=82=A4=EC=A7=80=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/techcourse/DispatcherServlet.java | 2 ++ .../java/com/techcourse/DispatcherServletInitializer.java | 3 ++- .../webmvc/servlet/mvc}/HandlerAdapterRegistry.java | 4 ++-- .../webmvc/servlet/mvc}/HandlerMappingRegistry.java | 4 +--- .../webmvc/servlet/mvc/tobe}/ControllerHandlerAdapter.java | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) rename {app/src/main/java/com/techcourse => mvc/src/main/java/com/interface21/webmvc/servlet/mvc}/HandlerAdapterRegistry.java (87%) rename {app/src/main/java/com/techcourse => mvc/src/main/java/com/interface21/webmvc/servlet/mvc}/HandlerMappingRegistry.java (88%) rename {app/src/main/java/com/techcourse => mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe}/ControllerHandlerAdapter.java (94%) diff --git a/app/src/main/java/com/techcourse/DispatcherServlet.java b/app/src/main/java/com/techcourse/DispatcherServlet.java index d64bed8c8e..2055c13f84 100644 --- a/app/src/main/java/com/techcourse/DispatcherServlet.java +++ b/app/src/main/java/com/techcourse/DispatcherServlet.java @@ -2,7 +2,9 @@ import com.interface21.webmvc.servlet.ModelAndView; import com.interface21.webmvc.servlet.mvc.HandlerAdapter; +import com.interface21.webmvc.servlet.mvc.HandlerAdapterRegistry; import com.interface21.webmvc.servlet.mvc.HandlerMapping; +import com.interface21.webmvc.servlet.mvc.HandlerMappingRegistry; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; diff --git a/app/src/main/java/com/techcourse/DispatcherServletInitializer.java b/app/src/main/java/com/techcourse/DispatcherServletInitializer.java index d07ddf2033..eae1d640c2 100644 --- a/app/src/main/java/com/techcourse/DispatcherServletInitializer.java +++ b/app/src/main/java/com/techcourse/DispatcherServletInitializer.java @@ -1,9 +1,9 @@ package com.techcourse; +import com.interface21.web.WebApplicationInitializer; import jakarta.servlet.ServletContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.interface21.web.WebApplicationInitializer; /** * Base class for {@link WebApplicationInitializer} @@ -18,6 +18,7 @@ public class DispatcherServletInitializer implements WebApplicationInitializer { @Override public void onStartup(final ServletContext servletContext) { final var dispatcherServlet = new DispatcherServlet(); + dispatcherServlet.addHandlerMapping(new ManualHandlerMapping()); final var registration = servletContext.addServlet(DEFAULT_SERVLET_NAME, dispatcherServlet); if (registration == null) { diff --git a/app/src/main/java/com/techcourse/HandlerAdapterRegistry.java b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/HandlerAdapterRegistry.java similarity index 87% rename from app/src/main/java/com/techcourse/HandlerAdapterRegistry.java rename to mvc/src/main/java/com/interface21/webmvc/servlet/mvc/HandlerAdapterRegistry.java index 6b044c2919..5e41708863 100644 --- a/app/src/main/java/com/techcourse/HandlerAdapterRegistry.java +++ b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/HandlerAdapterRegistry.java @@ -1,6 +1,6 @@ -package com.techcourse; +package com.interface21.webmvc.servlet.mvc; -import com.interface21.webmvc.servlet.mvc.HandlerAdapter; +import com.interface21.webmvc.servlet.mvc.tobe.ControllerHandlerAdapter; import com.interface21.webmvc.servlet.mvc.tobe.HandlerExecutionHandlerAdapter; import java.util.ArrayList; import java.util.List; diff --git a/app/src/main/java/com/techcourse/HandlerMappingRegistry.java b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/HandlerMappingRegistry.java similarity index 88% rename from app/src/main/java/com/techcourse/HandlerMappingRegistry.java rename to mvc/src/main/java/com/interface21/webmvc/servlet/mvc/HandlerMappingRegistry.java index 6115d02d97..37e90ee08c 100644 --- a/app/src/main/java/com/techcourse/HandlerMappingRegistry.java +++ b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/HandlerMappingRegistry.java @@ -1,6 +1,5 @@ -package com.techcourse; +package com.interface21.webmvc.servlet.mvc; -import com.interface21.webmvc.servlet.mvc.HandlerMapping; import com.interface21.webmvc.servlet.mvc.tobe.AnnotationHandlerMapping; import jakarta.servlet.http.HttpServletRequest; import java.util.ArrayList; @@ -19,7 +18,6 @@ public HandlerMappingRegistry() { } public void initialize() { - handlerMappings.add(new ManualHandlerMapping()); handlerMappings.add(new AnnotationHandlerMapping(ANNOTATION_BASED_CONTROLLER_BASE)); handlerMappings.forEach(HandlerMapping::initialize); } diff --git a/app/src/main/java/com/techcourse/ControllerHandlerAdapter.java b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerHandlerAdapter.java similarity index 94% rename from app/src/main/java/com/techcourse/ControllerHandlerAdapter.java rename to mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerHandlerAdapter.java index 061dc13fe5..2dfa038554 100644 --- a/app/src/main/java/com/techcourse/ControllerHandlerAdapter.java +++ b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerHandlerAdapter.java @@ -1,4 +1,4 @@ -package com.techcourse; +package com.interface21.webmvc.servlet.mvc.tobe; import com.interface21.webmvc.servlet.ModelAndView; import com.interface21.webmvc.servlet.mvc.HandlerAdapter; From 10d5980230af170d8707d5452fb256dd1f6c4610 Mon Sep 17 00:00:00 2001 From: HyungUk Ryu Date: Mon, 23 Sep 2024 19:32:35 +0900 Subject: [PATCH 20/25] =?UTF-8?q?style(ControllerScannerTest):=20=EB=B6=88?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20=EA=B0=9C=ED=96=89=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../webmvc/servlet/mvc/tobe/ControllerScannerTest.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerScannerTest.java b/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerScannerTest.java index c26956d474..9a8c8ac03d 100644 --- a/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerScannerTest.java +++ b/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerScannerTest.java @@ -8,7 +8,7 @@ class ControllerScannerTest { - @DisplayName("@Controller 어노테이션이 붙은 클래스를 인스턴스화할수 없는 경우 예외가 발생한다.") + @DisplayName("@Controller 어노테이션이 붙은 클래스를 인스턴스화 할 수 없는 경우 예외가 발생한다.") @Test void throwsWhenCannotInstantiateController() { // given @@ -18,7 +18,6 @@ void throwsWhenCannotInstantiateController() { // when & then assertThatThrownBy(controllerScanner::getControllers) .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining( - "컨트롤러를 인스턴스화 할 수 없습니다."); + .hasMessageContaining("컨트롤러를 인스턴스화 할 수 없습니다."); } } From 1d66e3fb8844bae42083fdb4f9128234cc03141a Mon Sep 17 00:00:00 2001 From: HyungUk Ryu Date: Mon, 23 Sep 2024 19:47:39 +0900 Subject: [PATCH 21/25] =?UTF-8?q?refactor(FakeController):=20=EB=AF=B8=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=20=EA=B2=BD=EA=B3=A0=20=EC=A0=9C=EA=B1=B0=20?= =?UTF-8?q?=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/interface21/webmvc/servlet/mvc/tobe/FakeController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/FakeController.java b/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/FakeController.java index f0804ca753..37caa05f64 100644 --- a/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/FakeController.java +++ b/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/FakeController.java @@ -4,6 +4,7 @@ @Controller +@SuppressWarnings("unused") public class FakeController { private FakeController() { From 5fd05951ba2096276b998927cbf226236467744d Mon Sep 17 00:00:00 2001 From: HyungUk Ryu Date: Fri, 27 Sep 2024 21:58:54 +0900 Subject: [PATCH 22/25] =?UTF-8?q?fix(JspViewTest):=20`@DisplayName`=20?= =?UTF-8?q?=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/interface21/webmvc/servlet/view/JspViewTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mvc/src/test/java/com/interface21/webmvc/servlet/view/JspViewTest.java b/mvc/src/test/java/com/interface21/webmvc/servlet/view/JspViewTest.java index da668a5114..f9870d3d34 100644 --- a/mvc/src/test/java/com/interface21/webmvc/servlet/view/JspViewTest.java +++ b/mvc/src/test/java/com/interface21/webmvc/servlet/view/JspViewTest.java @@ -14,7 +14,7 @@ class JspViewTest { - @DisplayName("redirect 접두사가 존재하는 경우 Redirec 처리한다.") + @DisplayName("redirect 접두사가 존재하는 경우 Redirect 처리한다.") @Test void redirect() throws Exception { // given From 894da3c150d12e0aacba6cc2b0913411a2a45e17 Mon Sep 17 00:00:00 2001 From: HyungUk Ryu Date: Sat, 28 Sep 2024 13:36:45 +0900 Subject: [PATCH 23/25] =?UTF-8?q?test(ControllerScannerTest):=20=EC=BB=A8?= =?UTF-8?q?=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EC=9D=B8=EC=8A=A4=ED=84=B4=EC=8A=A4=ED=99=94=20=EC=84=B1?= =?UTF-8?q?=EA=B3=B5=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mvc/tobe/ControllerScannerTest.java | 25 ++++++++++++++++++- .../servlet/mvc/tobe/FakeController.java | 12 --------- .../java/samples/error/ErrorController.java | 12 +++++++++ 3 files changed, 36 insertions(+), 13 deletions(-) delete mode 100644 mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/FakeController.java create mode 100644 mvc/src/test/java/samples/error/ErrorController.java diff --git a/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerScannerTest.java b/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerScannerTest.java index 9a8c8ac03d..7bde227b67 100644 --- a/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerScannerTest.java +++ b/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerScannerTest.java @@ -1,18 +1,41 @@ package com.interface21.webmvc.servlet.mvc.tobe; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import java.util.Map; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.reflections.Reflections; +import org.reflections.util.ClasspathHelper; +import org.reflections.util.ConfigurationBuilder; +import org.reflections.util.FilterBuilder; +import samples.TestController; class ControllerScannerTest { + @DisplayName("@Controller 어노테이션이 붙은 클래스를 인스턴스화하여 반환한다.") + @Test + void scanAndInstantiateController() { + // given + ConfigurationBuilder configBuilder = new ConfigurationBuilder() + .setUrls(ClasspathHelper.forPackage("samples")) + .filterInputsBy(new FilterBuilder().includePackage("samples").excludePackage("samples.error")); + Reflections reflections = new Reflections(configBuilder); + ControllerScanner controllerScanner = new ControllerScanner(reflections); + + // when + Map, Object> controllers = controllerScanner.getControllers(); + + // then + assertThat(controllers).containsKey(TestController.class); + } + @DisplayName("@Controller 어노테이션이 붙은 클래스를 인스턴스화 할 수 없는 경우 예외가 발생한다.") @Test void throwsWhenCannotInstantiateController() { // given - Reflections reflections = new Reflections("com.interface21.webmvc.servlet.mvc.tobe"); + Reflections reflections = new Reflections("samples.error"); ControllerScanner controllerScanner = new ControllerScanner(reflections); // when & then diff --git a/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/FakeController.java b/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/FakeController.java deleted file mode 100644 index 37caa05f64..0000000000 --- a/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/FakeController.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.interface21.webmvc.servlet.mvc.tobe; - -import com.interface21.context.stereotype.Controller; - -@Controller - -@SuppressWarnings("unused") -public class FakeController { - - private FakeController() { - } -} diff --git a/mvc/src/test/java/samples/error/ErrorController.java b/mvc/src/test/java/samples/error/ErrorController.java new file mode 100644 index 0000000000..63fdc0b868 --- /dev/null +++ b/mvc/src/test/java/samples/error/ErrorController.java @@ -0,0 +1,12 @@ +package samples.error; + +import com.interface21.context.stereotype.Controller; + +@Controller + +@SuppressWarnings("unused") +public class ErrorController { + + private ErrorController() { + } +} From 13e946d5f4964007ef7047f7180c9d3c97d5af3c Mon Sep 17 00:00:00 2001 From: HyungUk Ryu Date: Sat, 28 Sep 2024 15:20:00 +0900 Subject: [PATCH 24/25] =?UTF-8?q?test(HandlerMappingRegistry):=20HandlerMa?= =?UTF-8?q?ppingRegistry=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mvc/HandlerMappingRegistryTest.java | 51 +++++++++++++++++++ .../mvc/tobe/ControllerScannerTest.java | 4 +- .../ErrorController.java | 2 +- 3 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 mvc/src/test/java/com/interface21/webmvc/servlet/mvc/HandlerMappingRegistryTest.java rename mvc/src/test/java/{samples/error => errorsamples}/ErrorController.java (88%) diff --git a/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/HandlerMappingRegistryTest.java b/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/HandlerMappingRegistryTest.java new file mode 100644 index 0000000000..99c7aad128 --- /dev/null +++ b/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/HandlerMappingRegistryTest.java @@ -0,0 +1,51 @@ +package com.interface21.webmvc.servlet.mvc; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.interface21.webmvc.servlet.mvc.tobe.AnnotationHandlerMapping; +import jakarta.servlet.http.HttpServletRequest; +import java.util.Optional; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class HandlerMappingRegistryTest { + + @DisplayName("핸들러 목록에서 핸들러를 찾는다.") + @Test + void getHandlerTest() { + // given + AnnotationHandlerMapping annotationHandlerMapping = new AnnotationHandlerMapping("samples"); + annotationHandlerMapping.initialize(); + HandlerMappingRegistry handlerMappingRegistry = new HandlerMappingRegistry(); + handlerMappingRegistry.addHandlerMapping(annotationHandlerMapping); + + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getRequestURI()).thenReturn("/get-test"); + when(request.getMethod()).thenReturn("GET"); + + // when + Optional handler = handlerMappingRegistry.getHandler(request); + + // then + assertThat(handler).isPresent(); + } + + @DisplayName("핸들러 목록에서 핸들러를 찾을 수 없는 경우 빈 Optional을 반환한다.") + @Test + void getHandlerNotFoundTest() { + // given + HandlerMappingRegistry handlerMappingRegistry = new HandlerMappingRegistry(); + + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getRequestURI()).thenReturn("/"); + when(request.getMethod()).thenReturn("PUT"); + + // when + Optional handler = handlerMappingRegistry.getHandler(request); + + // then + assertThat(handler).isEmpty(); + } +} diff --git a/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerScannerTest.java b/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerScannerTest.java index 7bde227b67..031b9d735d 100644 --- a/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerScannerTest.java +++ b/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerScannerTest.java @@ -20,7 +20,7 @@ void scanAndInstantiateController() { // given ConfigurationBuilder configBuilder = new ConfigurationBuilder() .setUrls(ClasspathHelper.forPackage("samples")) - .filterInputsBy(new FilterBuilder().includePackage("samples").excludePackage("samples.error")); + .filterInputsBy(new FilterBuilder().includePackage("samples")); Reflections reflections = new Reflections(configBuilder); ControllerScanner controllerScanner = new ControllerScanner(reflections); @@ -35,7 +35,7 @@ void scanAndInstantiateController() { @Test void throwsWhenCannotInstantiateController() { // given - Reflections reflections = new Reflections("samples.error"); + Reflections reflections = new Reflections("errorsamples"); ControllerScanner controllerScanner = new ControllerScanner(reflections); // when & then diff --git a/mvc/src/test/java/samples/error/ErrorController.java b/mvc/src/test/java/errorsamples/ErrorController.java similarity index 88% rename from mvc/src/test/java/samples/error/ErrorController.java rename to mvc/src/test/java/errorsamples/ErrorController.java index 63fdc0b868..cf79dc49d8 100644 --- a/mvc/src/test/java/samples/error/ErrorController.java +++ b/mvc/src/test/java/errorsamples/ErrorController.java @@ -1,4 +1,4 @@ -package samples.error; +package errorsamples; import com.interface21.context.stereotype.Controller; From 8f72eaf5bc25a8998341daf660d21e8a08c5add4 Mon Sep 17 00:00:00 2001 From: HyungUk Ryu Date: Sat, 28 Sep 2024 15:40:28 +0900 Subject: [PATCH 25/25] =?UTF-8?q?test(DispatcherServletTest):=20=EC=96=B4?= =?UTF-8?q?=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98/=EC=9D=B8=ED=84=B0?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EA=B8=B0=EB=B0=98=20=EC=BB=A8?= =?UTF-8?q?=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EC=B2=98=EB=A6=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/techcourse/DispatcherServletTest.java | 61 ++++++++++++++++--- 1 file changed, 54 insertions(+), 7 deletions(-) diff --git a/app/src/test/java/com/techcourse/DispatcherServletTest.java b/app/src/test/java/com/techcourse/DispatcherServletTest.java index 86f2975885..ff0f53066c 100644 --- a/app/src/test/java/com/techcourse/DispatcherServletTest.java +++ b/app/src/test/java/com/techcourse/DispatcherServletTest.java @@ -1,33 +1,82 @@ package com.techcourse; +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; import com.interface21.webmvc.servlet.mvc.HandlerMapping; +import com.techcourse.controller.LoginViewController; +import jakarta.servlet.RequestDispatcher; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; class DispatcherServletTest { private DispatcherServlet dispatcherServlet; + private HttpServletRequest request; + private HttpServletResponse response; @BeforeEach void setUp() { dispatcherServlet = new DispatcherServlet(); dispatcherServlet.init(); + request = mock(HttpServletRequest.class); + response = mock(HttpServletResponse.class); + } + + @DisplayName("어노테이션 기반 컨트롤러를 찾아서 처리한다.") + @Test + void processAnnotationBasedController() throws ServletException { + // given + when(request.getMethod()).thenReturn("GET"); + when(request.getRequestURI()).thenReturn("/register"); + + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(String.class); + RequestDispatcher requestDispatcher = mock(RequestDispatcher.class); + when(request.getRequestDispatcher(argumentCaptor.capture())) + .thenReturn(requestDispatcher); + + // when + dispatcherServlet.service(request, response); + + // then + assertThat(argumentCaptor.getValue()) + .isEqualTo("/register.jsp"); + } + + @DisplayName("Controller 인터페이스 기반 컨트롤러를 찾아서 처리한다.") + @Test + void processInterfaceBasedController() throws ServletException { + // given + when(request.getMethod()).thenReturn("GET"); + when(request.getRequestURI()).thenReturn("/login/view"); + when(request.getSession()).thenReturn(mock(HttpSession.class)); + registerFakeHandlerMapping(new LoginViewController()); + + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(String.class); + RequestDispatcher requestDispatcher = mock(RequestDispatcher.class); + when(request.getRequestDispatcher(argumentCaptor.capture())) + .thenReturn(requestDispatcher); + + // when + dispatcherServlet.service(request, response); + + // then + assertThat(argumentCaptor.getValue()) + .isEqualTo("/login.jsp"); } @DisplayName("요청에 해당하는 핸들러를 찾을 수 없으면 예외가 발생한다.") @Test void throwsWhenHandlerNotFound() { // given - HttpServletRequest request = mock(HttpServletRequest.class); - HttpServletResponse response = mock(HttpServletResponse.class); when(request.getMethod()).thenReturn("GET"); when(request.getRequestURI()).thenReturn("/not-found"); @@ -41,9 +90,7 @@ void throwsWhenHandlerNotFound() { @Test void throwsWhenHandlerAdapterNotFound() { // given - registerFakeHandlerMapping(); - HttpServletRequest request = mock(HttpServletRequest.class); - HttpServletResponse response = mock(HttpServletResponse.class); + registerFakeHandlerMapping("test"); when(request.getMethod()).thenReturn("GET"); when(request.getRequestURI()).thenReturn("/test"); @@ -53,7 +100,7 @@ void throwsWhenHandlerAdapterNotFound() { .hasMessageContaining("요청에 해당하는 핸들러 어댑터를 찾을 수 없습니다."); } - private void registerFakeHandlerMapping() { + private void registerFakeHandlerMapping(Object retVal) { dispatcherServlet.addHandlerMapping(new HandlerMapping() { @Override public void initialize() { @@ -61,7 +108,7 @@ public void initialize() { @Override public Object getHandler(HttpServletRequest request) { - return "test"; + return retVal; } }); }