From 9d8f40a9505ac6ee9f0a1b0cf12974ae748c8384 Mon Sep 17 00:00:00 2001 From: DEOKWOO KIM Date: Fri, 22 Sep 2023 17:58:33 +0900 Subject: [PATCH] =?UTF-8?q?[mvc=EA=B5=AC=ED=98=84=20/=202=EB=8B=A8?= =?UTF-8?q?=EA=B3=84]=20=EB=A1=9C=EC=9D=B4(=EA=B9=80=EB=8D=95=EC=9A=B0)=20?= =?UTF-8?q?=EB=AF=B8=EC=85=98=20=EC=A0=9C=EC=B6=9C=ED=95=A9=EB=8B=88?= =?UTF-8?q?=EB=8B=A4.=20(#520)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 핸들러 매핑 추상화 * feat: 핸들러 어댑터 인터페이스 및 구현체 생성 * feat: HandlerAdapterRegistry 및 HandlerMappingRegistry 구현 * feat: DispatcherServlet 내 핸들러매핑 및 핸들러어댑터 설정 * refactor: 메서드 파라미터 리팩터링 --- .../com/techcourse/DispatcherServlet.java | 34 +++++++++++++++---- .../DispatcherServletInitializer.java | 11 ++++++ .../com/techcourse/ManualHandlerAdapter.java | 24 +++++++++++++ .../com/techcourse/ManualHandlerMapping.java | 10 ++++-- .../mvc/tobe/AnnotationHandlerAdapter.java | 19 +++++++++++ .../mvc/tobe/AnnotationHandlerMapping.java | 10 +++--- .../web/servlet/mvc/tobe/HandlerAdapter.java | 12 +++++++ .../mvc/tobe/HandlerAdapterRegistry.java | 26 ++++++++++++++ .../web/servlet/mvc/tobe/HandlerMapping.java | 10 ++++++ .../mvc/tobe/HandlerMappingRegistry.java | 34 +++++++++++++++++++ .../web/servlet/view/JspView.java | 9 ++++- 11 files changed, 185 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/com/techcourse/ManualHandlerAdapter.java create mode 100644 mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerAdapter.java create mode 100644 mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerAdapter.java create mode 100644 mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerAdapterRegistry.java create mode 100644 mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerMapping.java create mode 100644 mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerMappingRegistry.java diff --git a/app/src/main/java/com/techcourse/DispatcherServlet.java b/app/src/main/java/com/techcourse/DispatcherServlet.java index 277d8eed9a..d1dee5a0cd 100644 --- a/app/src/main/java/com/techcourse/DispatcherServlet.java +++ b/app/src/main/java/com/techcourse/DispatcherServlet.java @@ -6,6 +6,11 @@ import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import webmvc.org.springframework.web.servlet.ModelAndView; +import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerAdapter; +import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerAdapterRegistry; +import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerMapping; +import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerMappingRegistry; import webmvc.org.springframework.web.servlet.view.JspView; public class DispatcherServlet extends HttpServlet { @@ -13,15 +18,25 @@ public class DispatcherServlet extends HttpServlet { private static final long serialVersionUID = 1L; private static final Logger log = LoggerFactory.getLogger(DispatcherServlet.class); - private ManualHandlerMapping manualHandlerMapping; + private HandlerMappingRegistry handlerMappingRegistry; + private HandlerAdapterRegistry handlerAdapterRegistry; public DispatcherServlet() { + this.handlerMappingRegistry = new HandlerMappingRegistry(); + this.handlerAdapterRegistry = new HandlerAdapterRegistry(); } @Override public void init() { - manualHandlerMapping = new ManualHandlerMapping(); - manualHandlerMapping.initialize(); + handlerMappingRegistry.init(); + } + + public void addHandlerMapping(HandlerMapping handlerMapping) { + handlerMappingRegistry.addHandlerMapping(handlerMapping); + } + + public void addHandlerAdapter(HandlerAdapter handlerAdapter) { + handlerAdapterRegistry.addHandlerAdapter(handlerAdapter); } @Override @@ -30,9 +45,16 @@ protected void service(final HttpServletRequest request, final HttpServletRespon 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 = handlerMappingRegistry.getHandler(request) + .orElse(new IllegalArgumentException("URI에 해당하는 핸들러가 존재하지 않습니다.")); + + HandlerAdapter handlerAdapter = handlerAdapterRegistry.getHandlerAdapter(handler); + + ModelAndView modelAndView = handlerAdapter.handle(request, response, handler); + + JspView view = (JspView) modelAndView.getView(); + move(view.getView(), request, response); + } catch (Throwable e) { log.error("Exception : {}", e.getMessage(), e); throw new ServletException(e.getMessage()); diff --git a/app/src/main/java/com/techcourse/DispatcherServletInitializer.java b/app/src/main/java/com/techcourse/DispatcherServletInitializer.java index 6e814cdd25..ba6abbfcf1 100644 --- a/app/src/main/java/com/techcourse/DispatcherServletInitializer.java +++ b/app/src/main/java/com/techcourse/DispatcherServletInitializer.java @@ -4,6 +4,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import web.org.springframework.web.WebApplicationInitializer; +import webmvc.org.springframework.web.servlet.mvc.tobe.AnnotationHandlerAdapter; +import webmvc.org.springframework.web.servlet.mvc.tobe.AnnotationHandlerMapping; /** * Base class for {@link WebApplicationInitializer} @@ -19,7 +21,16 @@ public class DispatcherServletInitializer implements WebApplicationInitializer { public void onStartup(final ServletContext servletContext) { final var dispatcherServlet = new DispatcherServlet(); + dispatcherServlet.addHandlerMapping(new AnnotationHandlerMapping("com.techcourse")); + dispatcherServlet.addHandlerMapping(new ManualHandlerMapping()); + + dispatcherServlet.addHandlerAdapter(new AnnotationHandlerAdapter()); + dispatcherServlet.addHandlerAdapter(new ManualHandlerAdapter()); + + dispatcherServlet.init(); + final var registration = servletContext.addServlet(DEFAULT_SERVLET_NAME, dispatcherServlet); + if (registration == null) { throw new IllegalStateException("Failed to register servlet with name '" + DEFAULT_SERVLET_NAME + "'. " + "Check if there is another servlet registered under the same name."); diff --git a/app/src/main/java/com/techcourse/ManualHandlerAdapter.java b/app/src/main/java/com/techcourse/ManualHandlerAdapter.java new file mode 100644 index 0000000000..b65357d88b --- /dev/null +++ b/app/src/main/java/com/techcourse/ManualHandlerAdapter.java @@ -0,0 +1,24 @@ +package com.techcourse; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import webmvc.org.springframework.web.servlet.ModelAndView; +import webmvc.org.springframework.web.servlet.mvc.asis.Controller; +import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerAdapter; +import webmvc.org.springframework.web.servlet.view.JspView; + +public class ManualHandlerAdapter implements HandlerAdapter { + + @Override + public boolean isSupport(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); + return new ModelAndView(new JspView(viewName)); + } + +} diff --git a/app/src/main/java/com/techcourse/ManualHandlerMapping.java b/app/src/main/java/com/techcourse/ManualHandlerMapping.java index a54863caf8..a8e3bb7a36 100644 --- a/app/src/main/java/com/techcourse/ManualHandlerMapping.java +++ b/app/src/main/java/com/techcourse/ManualHandlerMapping.java @@ -1,20 +1,23 @@ package com.techcourse; import com.techcourse.controller.*; +import jakarta.servlet.http.HttpServletRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import webmvc.org.springframework.web.servlet.mvc.asis.Controller; import webmvc.org.springframework.web.servlet.mvc.asis.ForwardController; +import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerMapping; 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()); @@ -28,8 +31,9 @@ public void initialize() { .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 Object getHandler(final HttpServletRequest request) { + String requestURI = request.getRequestURI(); return controllers.get(requestURI); } } diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerAdapter.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerAdapter.java new file mode 100644 index 0000000000..9ec9a9fddb --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerAdapter.java @@ -0,0 +1,19 @@ +package webmvc.org.springframework.web.servlet.mvc.tobe; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import webmvc.org.springframework.web.servlet.ModelAndView; + +public class AnnotationHandlerAdapter implements HandlerAdapter { + + @Override + public boolean isSupport(Object handler) { + return handler instanceof HandlerExecution; + } + + @Override + public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + HandlerExecution handlerExecution = (HandlerExecution) handler; + return handlerExecution.handle(request, response); + } +} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMapping.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMapping.java index a3e3f37fe6..74b11c0313 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMapping.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMapping.java @@ -13,7 +13,7 @@ import java.util.Map; import java.util.Set; -public class AnnotationHandlerMapping { +public class AnnotationHandlerMapping implements HandlerMapping{ private static final Logger log = LoggerFactory.getLogger(AnnotationHandlerMapping.class); @@ -25,6 +25,7 @@ public AnnotationHandlerMapping(final Object... basePackage) { this.handlerExecutions = new HashMap<>(); } + @Override public void initialize() { log.info("Initialized AnnotationHandlerMapping!"); @@ -46,18 +47,19 @@ private void mapHandlerForAnnotatedMethod(Method declaredMethod) { if (declaredMethod.isAnnotationPresent(RequestMapping.class)) { RequestMapping annotation = declaredMethod.getAnnotation(RequestMapping.class); RequestMethod[] method = annotation.method(); - mapHandlerForRequestMethods(declaredMethod, annotation, method); + HandlerExecution handlerExecution = new HandlerExecution(declaredMethod); + mapHandlerForRequestMethods(declaredMethod, annotation, method, handlerExecution); } } - private void mapHandlerForRequestMethods(Method declaredMethod, RequestMapping annotation, RequestMethod[] method) { + private void mapHandlerForRequestMethods(Method declaredMethod, RequestMapping annotation, RequestMethod[] method, HandlerExecution handlerExecution) { for (RequestMethod requestMethod : method) { HandlerKey handlerKey = new HandlerKey(annotation.value(), requestMethod); - HandlerExecution handlerExecution = new HandlerExecution(declaredMethod); handlerExecutions.put(handlerKey, handlerExecution); } } + @Override public Object getHandler(final HttpServletRequest request) { String requestURI = request.getRequestURI(); String method = request.getMethod(); diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerAdapter.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerAdapter.java new file mode 100644 index 0000000000..993bc13e5c --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerAdapter.java @@ -0,0 +1,12 @@ +package webmvc.org.springframework.web.servlet.mvc.tobe; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import webmvc.org.springframework.web.servlet.ModelAndView; + +public interface HandlerAdapter { + + boolean isSupport(Object handler); + + ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; +} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerAdapterRegistry.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerAdapterRegistry.java new file mode 100644 index 0000000000..df99b85c65 --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerAdapterRegistry.java @@ -0,0 +1,26 @@ +package webmvc.org.springframework.web.servlet.mvc.tobe; + +import java.util.ArrayList; +import java.util.List; + +public class HandlerAdapterRegistry { + + private final List handlerAdapters; + + public HandlerAdapterRegistry() { + this.handlerAdapters = new ArrayList<>(); + } + + public void addHandlerAdapter(HandlerAdapter handlerAdapter) { + this.handlerAdapters.add(handlerAdapter); + } + + public HandlerAdapter getHandlerAdapter(Object handler) { + for (HandlerAdapter handlerAdapter : this.handlerAdapters) { + if (handlerAdapter.isSupport(handler)) { + return handlerAdapter; + } + } + throw new IllegalArgumentException("해당 handler를 찾을 수 없습니다."); + } +} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerMapping.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerMapping.java new file mode 100644 index 0000000000..2fd9df144a --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerMapping.java @@ -0,0 +1,10 @@ +package webmvc.org.springframework.web.servlet.mvc.tobe; + +import jakarta.servlet.http.HttpServletRequest; + +public interface HandlerMapping { + + void initialize(); + + Object getHandler(HttpServletRequest request); +} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerMappingRegistry.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerMappingRegistry.java new file mode 100644 index 0000000000..e92966952c --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerMappingRegistry.java @@ -0,0 +1,34 @@ +package webmvc.org.springframework.web.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 HandlerMappingRegistry { + + private final List handlerMappings; + + public HandlerMappingRegistry() { + this.handlerMappings = new ArrayList<>(); + } + + public void init() { + for (HandlerMapping handlerMapping : this.handlerMappings) { + 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) + .findFirst(); + } +} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JspView.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JspView.java index 3f4cc906ff..81e7f338a4 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JspView.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JspView.java @@ -14,7 +14,10 @@ public class JspView implements View { public static final String REDIRECT_PREFIX = "redirect:"; - public JspView(final String viewName) { + private final String view; + + public JspView(String view) { + this.view = view; } @Override @@ -28,4 +31,8 @@ public void render(final Map model, final HttpServletRequest request, // todo } + + public String getView() { + return view; + } }