From 048cf666904bf0909992efd54eef17341e3582d7 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Fri, 20 Sep 2024 12:17:57 +0900 Subject: [PATCH 01/13] test: solve servlet and filter --- .../servlet/com/example/CharacterEncodingFilter.java | 12 +++++++++--- .../test/java/servlet/com/example/ServletTest.java | 8 ++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/study/src/main/java/servlet/com/example/CharacterEncodingFilter.java b/study/src/main/java/servlet/com/example/CharacterEncodingFilter.java index cf4d886974..09df6d9011 100644 --- a/study/src/main/java/servlet/com/example/CharacterEncodingFilter.java +++ b/study/src/main/java/servlet/com/example/CharacterEncodingFilter.java @@ -1,16 +1,22 @@ package servlet.com.example; -import jakarta.servlet.*; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; import jakarta.servlet.annotation.WebFilter; - import java.io.IOException; +import java.nio.charset.StandardCharsets; @WebFilter("/*") public class CharacterEncodingFilter implements Filter { @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { request.getServletContext().log("doFilter() 호출"); + response.setCharacterEncoding(StandardCharsets.UTF_8.name()); chain.doFilter(request, response); } } diff --git a/study/src/test/java/servlet/com/example/ServletTest.java b/study/src/test/java/servlet/com/example/ServletTest.java index 75fbb10dd5..c5453f6949 100644 --- a/study/src/test/java/servlet/com/example/ServletTest.java +++ b/study/src/test/java/servlet/com/example/ServletTest.java @@ -1,10 +1,10 @@ package servlet.com.example; +import static org.assertj.core.api.Assertions.assertThat; + import org.junit.jupiter.api.Test; import support.HttpUtils; -import static org.assertj.core.api.Assertions.assertThat; - class ServletTest { private final String WEBAPP_DIR_LOCATION = "src/main/webapp/"; @@ -28,7 +28,7 @@ void testSharedCounter() { // expected를 0이 아닌 올바른 값으로 바꿔보자. // 예상한 결과가 나왔는가? 왜 이런 결과가 나왔을까? - assertThat(Integer.parseInt(response.body())).isEqualTo(0); + assertThat(Integer.parseInt(response.body())).isEqualTo(3); } @Test @@ -50,6 +50,6 @@ void testLocalCounter() { // expected를 0이 아닌 올바른 값으로 바꿔보자. // 예상한 결과가 나왔는가? 왜 이런 결과가 나왔을까? - assertThat(Integer.parseInt(response.body())).isEqualTo(0); + assertThat(Integer.parseInt(response.body())).isEqualTo(1); } } From ee6572e6e40390c3a0764f6a4ac60962f42d1441 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Wed, 25 Sep 2024 13:22:28 +0900 Subject: [PATCH 02/13] feat: add adapter and mapping interface for handler --- .../webmvc/servlet/mvc/tobe/HandlerAdapter.java | 11 +++++++++++ .../webmvc/servlet/mvc/tobe/HandlerMapping.java | 10 ++++++++++ 2 files changed, 21 insertions(+) create mode 100644 mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerAdapter.java create mode 100644 mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerMapping.java diff --git a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerAdapter.java b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerAdapter.java new file mode 100644 index 0000000000..6acc610728 --- /dev/null +++ b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerAdapter.java @@ -0,0 +1,11 @@ +package com.interface21.webmvc.servlet.mvc.tobe; + +import com.interface21.webmvc.servlet.ModelAndView; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public interface HandlerAdapter { + boolean supports(Object handler); + + ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; +} diff --git a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerMapping.java b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerMapping.java new file mode 100644 index 0000000000..b7db458b93 --- /dev/null +++ b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerMapping.java @@ -0,0 +1,10 @@ +package com.interface21.webmvc.servlet.mvc.tobe; + +import jakarta.servlet.http.HttpServletRequest; + +public interface HandlerMapping { + + void initialize(); + + Object getHandler(HttpServletRequest request); +} From 35ef83f922b06a3bd4c2ce6196ec1a5c6c685bb6 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Wed, 25 Sep 2024 13:23:33 +0900 Subject: [PATCH 03/13] feat: add handler adapter of controller interface and annotation --- .../mvc/tobe/ControllerHandlerAdapter.java | 22 +++++++++++++++++++ .../mvc/tobe/ExecutionHandlerAdapter.java | 19 ++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerHandlerAdapter.java create mode 100644 mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/ExecutionHandlerAdapter.java diff --git a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerHandlerAdapter.java b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerHandlerAdapter.java new file mode 100644 index 0000000000..2b6bee4f50 --- /dev/null +++ b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerHandlerAdapter.java @@ -0,0 +1,22 @@ +package com.interface21.webmvc.servlet.mvc.tobe; + +import com.interface21.webmvc.servlet.ModelAndView; +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 supports(Object handler) { + return handler instanceof Controller; + } + + @Override + public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) + throws Exception { + String viewName = ((Controller) handler).execute(request, response); + return new ModelAndView(new JspView(viewName)); + } +} diff --git a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/ExecutionHandlerAdapter.java b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/ExecutionHandlerAdapter.java new file mode 100644 index 0000000000..120a9ba614 --- /dev/null +++ b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/ExecutionHandlerAdapter.java @@ -0,0 +1,19 @@ +package com.interface21.webmvc.servlet.mvc.tobe; + +import com.interface21.webmvc.servlet.ModelAndView; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public class ExecutionHandlerAdapter implements HandlerAdapter { + + @Override + public boolean supports(Object handler) { + return handler instanceof HandlerExecution; + } + + @Override + public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) + throws Exception { + return ((HandlerExecution) handler).handle(request, response); + } +} From 45f9db3d032c57cb6940f3cb67fe9cbf127ffe90 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Wed, 25 Sep 2024 13:23:48 +0900 Subject: [PATCH 04/13] feat: add container of adapter and mapper --- .../mvc/tobe/HandlerAdapterContainer.java | 29 ++++++++++++++++ .../mvc/tobe/HandlerMappingContainer.java | 33 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerAdapterContainer.java create mode 100644 mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerMappingContainer.java diff --git a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerAdapterContainer.java b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerAdapterContainer.java new file mode 100644 index 0000000000..ecbfed4024 --- /dev/null +++ b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerAdapterContainer.java @@ -0,0 +1,29 @@ +package com.interface21.webmvc.servlet.mvc.tobe; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class HandlerAdapterContainer { + + private final List handlerAdapters; + + public HandlerAdapterContainer() { + this.handlerAdapters = new ArrayList<>(); + } + + public void initialize() { + addHandlerAdapter(new ExecutionHandlerAdapter()); + addHandlerAdapter(new ControllerHandlerAdapter()); + } + + public void addHandlerAdapter(HandlerAdapter handlerAdapter) { + handlerAdapters.add(handlerAdapter); + } + + public Optional getHandlerAdapter(Object handler) { + return handlerAdapters.stream() + .filter(adapter -> adapter.supports(handler)) + .findAny(); + } +} diff --git a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerMappingContainer.java b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerMappingContainer.java new file mode 100644 index 0000000000..b5c3e19505 --- /dev/null +++ b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerMappingContainer.java @@ -0,0 +1,33 @@ +package com.interface21.webmvc.servlet.mvc.tobe; + +import jakarta.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +public class HandlerMappingContainer { + + private static final String CONTROLLER_BASE_PACKAGE = "com.techcourse.controller"; + private final List handlerMappings; + + public HandlerMappingContainer() { + this.handlerMappings = new ArrayList<>(); + } + + public void initialize() { + addHandlerMapping(new AnnotationHandlerMapping(CONTROLLER_BASE_PACKAGE)); + handlerMappings.forEach(HandlerMapping::initialize); + } + + public void addHandlerMapping(HandlerMapping handlerMapping) { + handlerMappings.add(handlerMapping); + } + + public Optional getHandler(HttpServletRequest request) { + return handlerMappings.stream() + .map(handlerMapping -> handlerMapping.getHandler(request)) + .filter(Objects::nonNull) + .findAny(); + } +} From 26f018fd9f8ef0914f970afe19a186f00bbc8642 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Wed, 25 Sep 2024 13:24:20 +0900 Subject: [PATCH 05/13] refactor: merge register controller with get and post --- .../controller/RegisterController.java | 22 ++++++++++++++----- .../controller/RegisterViewController.java | 13 ----------- 2 files changed, 16 insertions(+), 19 deletions(-) delete mode 100644 app/src/main/java/com/techcourse/controller/RegisterViewController.java diff --git a/app/src/main/java/com/techcourse/controller/RegisterController.java b/app/src/main/java/com/techcourse/controller/RegisterController.java index 782abfb219..111c207cc2 100644 --- a/app/src/main/java/com/techcourse/controller/RegisterController.java +++ b/app/src/main/java/com/techcourse/controller/RegisterController.java @@ -1,21 +1,31 @@ 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 +@RequestMapping("/register") +public class RegisterController { - @Override - public String execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception { + @RequestMapping(value = "/view", method = RequestMethod.GET) + public ModelAndView show() { + return new ModelAndView(new JspView("/register.jsp")); + } + + @RequestMapping(method = RequestMethod.POST) + public ModelAndView save(final HttpServletRequest req) { final var user = new User(2, req.getParameter("account"), req.getParameter("password"), req.getParameter("email")); InMemoryUserRepository.save(user); - return "redirect:/index.jsp"; + return new ModelAndView(new JspView("redirect:/index.jsp")); } } 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 3077c51000e8f453cf0a4b7595bf60912a955220 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Wed, 25 Sep 2024 13:24:51 +0900 Subject: [PATCH 06/13] feat: impl handler mapping interface to all mapping classes --- .../com/techcourse/ManualHandlerMapping.java | 24 ++++++++++--------- .../mvc/tobe/AnnotationHandlerMapping.java | 10 +++++++- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/techcourse/ManualHandlerMapping.java b/app/src/main/java/com/techcourse/ManualHandlerMapping.java index 2116056394..a9a6c3cff1 100644 --- a/app/src/main/java/com/techcourse/ManualHandlerMapping.java +++ b/app/src/main/java/com/techcourse/ManualHandlerMapping.java @@ -1,15 +1,18 @@ package com.techcourse; -import com.techcourse.controller.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import com.interface21.webmvc.servlet.mvc.asis.Controller; import com.interface21.webmvc.servlet.mvc.asis.ForwardController; - +import com.interface21.webmvc.servlet.mvc.tobe.HandlerMapping; +import com.techcourse.controller.LoginController; +import com.techcourse.controller.LoginViewController; +import com.techcourse.controller.LogoutController; +import jakarta.servlet.http.HttpServletRequest; import java.util.HashMap; import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -public class ManualHandlerMapping { +public class ManualHandlerMapping implements HandlerMapping { private static final Logger log = LoggerFactory.getLogger(ManualHandlerMapping.class); @@ -20,15 +23,14 @@ public void initialize() { 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())); + log.info("Initialized ManualHandlerMapping!"); + controllers.forEach((path, controller) -> log.info("Path : {}, Controller : {}", path, controller.getClass())); } - public Controller getHandler(final String requestURI) { + @Override + public Object getHandler(HttpServletRequest request) { + String requestURI = request.getRequestURI(); log.debug("Request Mapping Uri : {}", requestURI); return controllers.get(requestURI); } 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 77720fae40..85b0a034d7 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 @@ -12,7 +12,7 @@ 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 String DELIMITER_PATH = "/"; @@ -27,10 +27,13 @@ public AnnotationHandlerMapping(final Object... basePackage) { this.handlerExecutions = new HashMap<>(); } + @Override public void initialize() { log.info("Initialized AnnotationHandlerMapping!"); Reflections reflections = new Reflections(basePackage); reflections.getTypesAnnotatedWith(Controller.class).forEach(this::assignHandlerByClass); + + handlerExecutions.forEach((key, execution) -> log.info("Key : {}, Execution : {}", key, execution)); } private void assignHandlerByClass(Class clazz) { @@ -64,6 +67,10 @@ private void assignHandlerByMethod(Class clazz, Method method, String basePat } private String generateEndpoint(String basePath, String subPath) { + if (subPath.isBlank()) { + return String.join(DELIMITER_PATH, DELIMITER_PATH, basePath) + .replaceAll(REGEX_MANY_PATH_DELIMITER, DELIMITER_PATH); + } return String.join(DELIMITER_PATH, DELIMITER_PATH, basePath, subPath) .replaceAll(REGEX_MANY_PATH_DELIMITER, DELIMITER_PATH); } @@ -85,6 +92,7 @@ private HandlerExecution findHandlerExecution(Class clazz, Method method) { return null; } + @Override public Object getHandler(final HttpServletRequest request) { RequestMethod requestMethod; try { From 36f94f3551eb52761575e05fc1d5e97ff5b42d17 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Wed, 25 Sep 2024 13:27:08 +0900 Subject: [PATCH 07/13] feat: override toString for logging on reflection --- .../webmvc/servlet/mvc/tobe/HandlerExecution.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerExecution.java b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerExecution.java index 57edfaa1a5..8b81f8dc91 100644 --- a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerExecution.java +++ b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerExecution.java @@ -42,4 +42,12 @@ private Object decideArgumentByParameter(Class param, List preparedAr .findAny() .orElse(null); } + + @Override + public String toString() { + return "HandlerExecution{" + + "method=" + method + + ", controllerInstance=" + controllerInstance + + '}'; + } } From 7ab2c1e9ba55223935b5b81236a6b86397848777 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Wed, 25 Sep 2024 13:28:50 +0900 Subject: [PATCH 08/13] feat: use mapper and adapter on DispatcherServlet --- .../com/techcourse/DispatcherServlet.java | 55 ++++++++++++++----- .../DispatcherServletInitializer.java | 7 ++- 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/techcourse/DispatcherServlet.java b/app/src/main/java/com/techcourse/DispatcherServlet.java index 2943e9ae55..1e9995efc0 100644 --- a/app/src/main/java/com/techcourse/DispatcherServlet.java +++ b/app/src/main/java/com/techcourse/DispatcherServlet.java @@ -1,51 +1,76 @@ package com.techcourse; +import com.interface21.webmvc.servlet.ModelAndView; +import com.interface21.webmvc.servlet.View; +import com.interface21.webmvc.servlet.mvc.tobe.HandlerAdapter; +import com.interface21.webmvc.servlet.mvc.tobe.HandlerAdapterContainer; +import com.interface21.webmvc.servlet.mvc.tobe.HandlerMapping; +import com.interface21.webmvc.servlet.mvc.tobe.HandlerMappingContainer; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.interface21.webmvc.servlet.view.JspView; 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 HandlerAdapterContainer handlerAdapterContainer; + private final HandlerMappingContainer handlerMappingContainer; public DispatcherServlet() { + handlerAdapterContainer = new HandlerAdapterContainer(); + handlerMappingContainer = new HandlerMappingContainer(); } @Override public void init() { - manualHandlerMapping = new ManualHandlerMapping(); - manualHandlerMapping.initialize(); + handlerAdapterContainer.initialize(); + handlerMappingContainer.initialize(); } @Override - protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException { + protected void service(final HttpServletRequest request, final HttpServletResponse response) + throws ServletException { final String requestURI = request.getRequestURI(); log.debug("Method : {}, Request URI : {}", request.getMethod(), requestURI); try { - final var controller = manualHandlerMapping.getHandler(requestURI); - final var viewName = controller.execute(request, response); - move(viewName, request, response); + Object handler = findHandler(request); + HandlerAdapter handlerAdapter = findHandlerAdapter(handler); + ModelAndView modelAndView = handlerAdapter.handle(request, response, handler); + renderViewWithModel(request, response, modelAndView); } catch (Throwable e) { log.error("Exception : {}", e.getMessage(), e); throw new ServletException(e.getMessage()); } } - private void move(final String viewName, final HttpServletRequest request, final HttpServletResponse response) throws Exception { - if (viewName.startsWith(JspView.REDIRECT_PREFIX)) { - response.sendRedirect(viewName.substring(JspView.REDIRECT_PREFIX.length())); - return; - } + private Object findHandler(HttpServletRequest request) { + return handlerMappingContainer.getHandler(request) + .orElseThrow(() -> new IllegalArgumentException( + "No handler found for request URI: " + request.getRequestURI())); + } + + private HandlerAdapter findHandlerAdapter(Object handler) { + return handlerAdapterContainer.getHandlerAdapter(handler).orElseThrow( + () -> new IllegalArgumentException( + "No handler adapter found for handler: " + handler.getClass())); + } + + private void renderViewWithModel(HttpServletRequest request, HttpServletResponse response, + ModelAndView modelAndView) + throws Exception { + Map model = modelAndView.getModel(); + View view = modelAndView.getView(); + view.render(model, request, response); + } - final var requestDispatcher = request.getRequestDispatcher(viewName); - requestDispatcher.forward(request, response); + public void addHandlerMapping(HandlerMapping handlerMapping) { + handlerMappingContainer.addHandlerMapping(handlerMapping); } } diff --git a/app/src/main/java/com/techcourse/DispatcherServletInitializer.java b/app/src/main/java/com/techcourse/DispatcherServletInitializer.java index d07ddf2033..cee825f502 100644 --- a/app/src/main/java/com/techcourse/DispatcherServletInitializer.java +++ b/app/src/main/java/com/techcourse/DispatcherServletInitializer.java @@ -1,13 +1,13 @@ 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} - * implementations that register a {@link DispatcherServlet} in the servlet context. + * Base class for {@link WebApplicationInitializer} implementations that register a {@link DispatcherServlet} in the + * servlet context. */ public class DispatcherServletInitializer implements 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) { From ba0280d7c7f349d75a01233d0478011572bb5bfe Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Wed, 25 Sep 2024 13:44:17 +0900 Subject: [PATCH 09/13] fix: test duplicated request controller --- .../DuplicateRequestController.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 mvc/src/test/java/com/interface21/webmvc/servlet/samples/duplicated/DuplicateRequestController.java diff --git a/mvc/src/test/java/com/interface21/webmvc/servlet/samples/duplicated/DuplicateRequestController.java b/mvc/src/test/java/com/interface21/webmvc/servlet/samples/duplicated/DuplicateRequestController.java new file mode 100644 index 0000000000..8419da9a2b --- /dev/null +++ b/mvc/src/test/java/com/interface21/webmvc/servlet/samples/duplicated/DuplicateRequestController.java @@ -0,0 +1,20 @@ +package com.interface21.webmvc.servlet.samples.duplicated; + +import com.interface21.context.stereotype.Controller; +import com.interface21.web.bind.annotation.RequestMapping; +import com.interface21.web.bind.annotation.RequestMethod; + +@Controller +public class DuplicateRequestController { + + public DuplicateRequestController() { + } + + @RequestMapping(value = "/api/dup", method = RequestMethod.GET) + public void test() { + } + + @RequestMapping(value = "/api/dup", method = RequestMethod.GET) + public void test2() { + } +} From f8ba7680e2a3a348698ea469e487bb33a29487fb Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Wed, 25 Sep 2024 14:24:28 +0900 Subject: [PATCH 10/13] test: return JspView from handling via adapter --- .../tobe/AnnotationHandlerMappingTest.java | 4 +-- .../tobe/ControllerHandlerAdapterTest.java | 32 +++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerHandlerAdapterTest.java 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 293330d1c1..bd48f4d7ac 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 @@ -88,7 +88,7 @@ void assignHandlerPrivate() { @Test @DisplayName("정의되지 않은 HTTP 메서드로 요청 시 핸들러를 찾을 수 없다.") - void requestWithInvalidMethod() throws Exception { + void requestWithInvalidMethod() { // given HttpServletRequest request = mock(HttpServletRequest.class); HttpServletResponse response = mock(HttpServletResponse.class); @@ -105,7 +105,7 @@ void requestWithInvalidMethod() throws Exception { @Test @DisplayName("등록되지 않은 엔드포인트로 요청 시 핸들러를 찾을 수 없다.") - void requestWithUnassignedEndpoint() throws Exception { + void requestWithUnassignedEndpoint() { // given HttpServletRequest request = mock(HttpServletRequest.class); HttpServletResponse response = mock(HttpServletResponse.class); diff --git a/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerHandlerAdapterTest.java b/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerHandlerAdapterTest.java new file mode 100644 index 0000000000..b55cd23885 --- /dev/null +++ b/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/ControllerHandlerAdapterTest.java @@ -0,0 +1,32 @@ +package com.interface21.webmvc.servlet.mvc.tobe; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.interface21.webmvc.servlet.ModelAndView; +import com.interface21.webmvc.servlet.mvc.asis.Controller; +import com.interface21.webmvc.servlet.view.JspView; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +class ControllerHandlerAdapterTest { + + @Test + @DisplayName("핸들러를 호출해 뷰 이름으로 JspView 만들어 반환한다.") + void handle() throws Exception { + // given + HandlerAdapter handlerAdapter = new ControllerHandlerAdapter(); + Controller loginViewController = (req, res) -> "/login.jsp"; + + // when + ModelAndView modelAndView = handlerAdapter.handle( + new MockHttpServletRequest(), + new MockHttpServletResponse(), + loginViewController); + + // then + assertThat(modelAndView.getModel()).isEmpty(); + assertThat(modelAndView.getView()).isInstanceOf(JspView.class); + } +} From e71aeeb617c5f6ca367d5fcc5ecaa10c2942b744 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Wed, 25 Sep 2024 17:43:51 +0900 Subject: [PATCH 11/13] refactor: rename container to registry --- .../com/techcourse/DispatcherServlet.java | 22 +++++++++---------- ...ainer.java => HandlerAdapterRegistry.java} | 4 ++-- ...ainer.java => HandlerMappingRegistry.java} | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) rename mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/{HandlerAdapterContainer.java => HandlerAdapterRegistry.java} (90%) rename mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/{HandlerMappingContainer.java => HandlerMappingRegistry.java} (92%) diff --git a/app/src/main/java/com/techcourse/DispatcherServlet.java b/app/src/main/java/com/techcourse/DispatcherServlet.java index 1e9995efc0..2ef2137eb0 100644 --- a/app/src/main/java/com/techcourse/DispatcherServlet.java +++ b/app/src/main/java/com/techcourse/DispatcherServlet.java @@ -3,9 +3,9 @@ import com.interface21.webmvc.servlet.ModelAndView; import com.interface21.webmvc.servlet.View; import com.interface21.webmvc.servlet.mvc.tobe.HandlerAdapter; -import com.interface21.webmvc.servlet.mvc.tobe.HandlerAdapterContainer; +import com.interface21.webmvc.servlet.mvc.tobe.HandlerAdapterRegistry; import com.interface21.webmvc.servlet.mvc.tobe.HandlerMapping; -import com.interface21.webmvc.servlet.mvc.tobe.HandlerMappingContainer; +import com.interface21.webmvc.servlet.mvc.tobe.HandlerMappingRegistry; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; @@ -19,18 +19,18 @@ public class DispatcherServlet extends HttpServlet { private static final long serialVersionUID = 1L; private static final Logger log = LoggerFactory.getLogger(DispatcherServlet.class); - private final HandlerAdapterContainer handlerAdapterContainer; - private final HandlerMappingContainer handlerMappingContainer; + private final HandlerAdapterRegistry handlerAdapterRegistry; + private final HandlerMappingRegistry handlerMappingRegistry; public DispatcherServlet() { - handlerAdapterContainer = new HandlerAdapterContainer(); - handlerMappingContainer = new HandlerMappingContainer(); + handlerAdapterRegistry = new HandlerAdapterRegistry(); + handlerMappingRegistry = new HandlerMappingRegistry(); } @Override public void init() { - handlerAdapterContainer.initialize(); - handlerMappingContainer.initialize(); + handlerAdapterRegistry.initialize(); + handlerMappingRegistry.initialize(); } @Override @@ -51,13 +51,13 @@ protected void service(final HttpServletRequest request, final HttpServletRespon } private Object findHandler(HttpServletRequest request) { - return handlerMappingContainer.getHandler(request) + return handlerMappingRegistry.getHandler(request) .orElseThrow(() -> new IllegalArgumentException( "No handler found for request URI: " + request.getRequestURI())); } private HandlerAdapter findHandlerAdapter(Object handler) { - return handlerAdapterContainer.getHandlerAdapter(handler).orElseThrow( + return handlerAdapterRegistry.getHandlerAdapter(handler).orElseThrow( () -> new IllegalArgumentException( "No handler adapter found for handler: " + handler.getClass())); } @@ -71,6 +71,6 @@ private void renderViewWithModel(HttpServletRequest request, HttpServletResponse } public void addHandlerMapping(HandlerMapping handlerMapping) { - handlerMappingContainer.addHandlerMapping(handlerMapping); + handlerMappingRegistry.addHandlerMapping(handlerMapping); } } diff --git a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerAdapterContainer.java b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerAdapterRegistry.java similarity index 90% rename from mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerAdapterContainer.java rename to mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerAdapterRegistry.java index ecbfed4024..78dd2bab7e 100644 --- a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerAdapterContainer.java +++ b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerAdapterRegistry.java @@ -4,11 +4,11 @@ import java.util.List; import java.util.Optional; -public class HandlerAdapterContainer { +public class HandlerAdapterRegistry { private final List handlerAdapters; - public HandlerAdapterContainer() { + public HandlerAdapterRegistry() { this.handlerAdapters = new ArrayList<>(); } diff --git a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerMappingContainer.java b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerMappingRegistry.java similarity index 92% rename from mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerMappingContainer.java rename to mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerMappingRegistry.java index b5c3e19505..e0590acd4c 100644 --- a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerMappingContainer.java +++ b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerMappingRegistry.java @@ -6,12 +6,12 @@ import java.util.Objects; import java.util.Optional; -public class HandlerMappingContainer { +public class HandlerMappingRegistry { private static final String CONTROLLER_BASE_PACKAGE = "com.techcourse.controller"; private final List handlerMappings; - public HandlerMappingContainer() { + public HandlerMappingRegistry() { this.handlerMappings = new ArrayList<>(); } From d34302801faa5eb2ba36a6170dc104f0d95df058 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Sun, 29 Sep 2024 01:04:14 +0900 Subject: [PATCH 12/13] test: able to exist two different handler mappings --- .../com/techcourse/DispatcherServlet.java | 3 +- .../mvc/tobe/HandlerMappingRegistry.java | 7 +-- .../mvc/tobe/HandlerMappingRegistryTest.java | 54 +++++++++++++++++++ .../mvc/tobe/ManualHandlerMapping.java | 22 ++++++++ .../test/java/samples/ManualController.java | 14 +++++ 5 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerMappingRegistryTest.java create mode 100644 mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/ManualHandlerMapping.java create mode 100644 mvc/src/test/java/samples/ManualController.java diff --git a/app/src/main/java/com/techcourse/DispatcherServlet.java b/app/src/main/java/com/techcourse/DispatcherServlet.java index 2ef2137eb0..4a2c4c46d3 100644 --- a/app/src/main/java/com/techcourse/DispatcherServlet.java +++ b/app/src/main/java/com/techcourse/DispatcherServlet.java @@ -16,6 +16,7 @@ public class DispatcherServlet extends HttpServlet { + private static final String BASE_PACKAGE = "com.techcourse.controller"; private static final long serialVersionUID = 1L; private static final Logger log = LoggerFactory.getLogger(DispatcherServlet.class); @@ -24,7 +25,7 @@ public class DispatcherServlet extends HttpServlet { public DispatcherServlet() { handlerAdapterRegistry = new HandlerAdapterRegistry(); - handlerMappingRegistry = new HandlerMappingRegistry(); + handlerMappingRegistry = new HandlerMappingRegistry(BASE_PACKAGE); } @Override diff --git a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerMappingRegistry.java b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerMappingRegistry.java index e0590acd4c..ae88841320 100644 --- a/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerMappingRegistry.java +++ b/mvc/src/main/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerMappingRegistry.java @@ -8,15 +8,16 @@ public class HandlerMappingRegistry { - private static final String CONTROLLER_BASE_PACKAGE = "com.techcourse.controller"; private final List handlerMappings; + private final String basePackage; - public HandlerMappingRegistry() { + public HandlerMappingRegistry(String basePackage) { this.handlerMappings = new ArrayList<>(); + this.basePackage = basePackage; } public void initialize() { - addHandlerMapping(new AnnotationHandlerMapping(CONTROLLER_BASE_PACKAGE)); + addHandlerMapping(new AnnotationHandlerMapping(basePackage)); handlerMappings.forEach(HandlerMapping::initialize); } diff --git a/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerMappingRegistryTest.java b/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerMappingRegistryTest.java new file mode 100644 index 0000000000..b97e73cd09 --- /dev/null +++ b/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerMappingRegistryTest.java @@ -0,0 +1,54 @@ +package com.interface21.webmvc.servlet.mvc.tobe; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import jakarta.servlet.http.HttpServletRequest; +import java.util.Optional; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import samples.ManualController; + +class HandlerMappingRegistryTest { + + private static final String BASE_PACKAGE = "samples"; + + @Test + @DisplayName("서로 다른 핸들러 매핑이 존재해도 메뉴얼 핸들러를 찾을 수 있다.") + void getManualHandler() { + // given + HttpServletRequest request = mock(HttpServletRequest.class); + HandlerMappingRegistry registry = new HandlerMappingRegistry(BASE_PACKAGE); + registry.addHandlerMapping(new ManualHandlerMapping()); + registry.initialize(); + + // when + when(request.getRequestURI()).thenReturn("/manual"); + when(request.getMethod()).thenReturn("GET"); + Optional handler = registry.getHandler(request); + + // then + assertThat(handler).isPresent(); + assertThat(handler.get()).isInstanceOf(ManualController.class); + } + + @Test + @DisplayName("서로 다른 핸들러 매핑이 존재해도 메뉴얼 핸들러를 찾을 수 있다.") + void getAnnotationHandler() { + // given + HttpServletRequest request = mock(HttpServletRequest.class); + HandlerMappingRegistry registry = new HandlerMappingRegistry(BASE_PACKAGE); + registry.addHandlerMapping(new ManualHandlerMapping()); + registry.initialize(); + + // when + when(request.getRequestURI()).thenReturn("/post-test"); + when(request.getMethod()).thenReturn("POST"); + Optional handler = registry.getHandler(request); + + // then + assertThat(handler).isPresent(); + assertThat(handler.get()).isInstanceOf(HandlerExecution.class); + } +} \ No newline at end of file diff --git a/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/ManualHandlerMapping.java b/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/ManualHandlerMapping.java new file mode 100644 index 0000000000..e0ed1752ae --- /dev/null +++ b/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/ManualHandlerMapping.java @@ -0,0 +1,22 @@ +package com.interface21.webmvc.servlet.mvc.tobe; + +import com.interface21.webmvc.servlet.mvc.asis.Controller; +import jakarta.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; +import samples.ManualController; + +public class ManualHandlerMapping implements HandlerMapping { + + private static final Map controllers = new HashMap<>(); + + public void initialize() { + controllers.put("/manual", new ManualController()); + } + + @Override + public Object getHandler(HttpServletRequest request) { + String requestURI = request.getRequestURI(); + return controllers.get(requestURI); + } +} diff --git a/mvc/src/test/java/samples/ManualController.java b/mvc/src/test/java/samples/ManualController.java new file mode 100644 index 0000000000..3990dd564c --- /dev/null +++ b/mvc/src/test/java/samples/ManualController.java @@ -0,0 +1,14 @@ +package samples; + + +import com.interface21.webmvc.servlet.mvc.asis.Controller; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public class ManualController implements Controller { + + @Override + public String execute(HttpServletRequest req, HttpServletResponse res) { + return "/manual.jsp"; + } +} From ca3464e0720677a44ae2ddb21f7541641e1923b5 Mon Sep 17 00:00:00 2001 From: Gyeongho Yang Date: Sun, 29 Sep 2024 01:05:42 +0900 Subject: [PATCH 13/13] test: fix typo on display name --- .../webmvc/servlet/mvc/tobe/HandlerMappingRegistryTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerMappingRegistryTest.java b/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerMappingRegistryTest.java index b97e73cd09..6068304c08 100644 --- a/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerMappingRegistryTest.java +++ b/mvc/src/test/java/com/interface21/webmvc/servlet/mvc/tobe/HandlerMappingRegistryTest.java @@ -34,7 +34,7 @@ void getManualHandler() { } @Test - @DisplayName("서로 다른 핸들러 매핑이 존재해도 메뉴얼 핸들러를 찾을 수 있다.") + @DisplayName("서로 다른 핸들러 매핑이 존재해도 어노테이션 핸들러를 찾을 수 있다.") void getAnnotationHandler() { // given HttpServletRequest request = mock(HttpServletRequest.class);