diff --git a/app/src/main/java/com/techcourse/DispatcherServlet.java b/app/src/main/java/com/techcourse/DispatcherServlet.java index 277d8eed9a..cbe188ccfe 100644 --- a/app/src/main/java/com/techcourse/DispatcherServlet.java +++ b/app/src/main/java/com/techcourse/DispatcherServlet.java @@ -6,46 +6,49 @@ import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import webmvc.org.springframework.web.servlet.view.JspView; +import webmvc.org.springframework.web.servlet.ModelAndView; +import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerAdapter; +import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerAdapters; +import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerMappings; + +import java.util.Optional; 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 transient HandlerMappings handlerMappings; + private final transient HandlerAdapters handlerAdapters; public DispatcherServlet() { + this.handlerMappings = new HandlerMappings(); + this.handlerAdapters = new HandlerAdapters(); } @Override public void init() { - manualHandlerMapping = new ManualHandlerMapping(); - manualHandlerMapping.initialize(); + handlerMappings.init(new ManualHandlerMapping()); + handlerAdapters.init(); } @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); - + log.info("Method : {}, Request URI : {}", request.getMethod(), request.getRequestURI()); try { - final var controller = manualHandlerMapping.getHandler(requestURI); - final var viewName = controller.execute(request, response); - move(viewName, request, response); - } catch (Throwable e) { + final Optional handlerOptional = handlerMappings.getHandler(request); + if (handlerOptional.isEmpty()) { + response.sendError(HttpServletResponse.SC_NOT_FOUND); + return; + } + final Object handler = handlerOptional.get(); + final HandlerAdapter handlerAdapter = handlerAdapters.getAdapter(handler) + .orElseThrow(() -> new IllegalStateException("핸들러 어댑터를 찾을 수 없습니다.")); + final ModelAndView modelAndView = handlerAdapter.handle(handler, request, response); + modelAndView.getView().render(modelAndView.getModel(), request, response); + } catch (final Exception 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; - } - - final var requestDispatcher = request.getRequestDispatcher(viewName); - requestDispatcher.forward(request, response); - } } diff --git a/app/src/main/java/com/techcourse/ManualHandlerMapping.java b/app/src/main/java/com/techcourse/ManualHandlerMapping.java index a54863caf8..f96717f597 100644 --- a/app/src/main/java/com/techcourse/ManualHandlerMapping.java +++ b/app/src/main/java/com/techcourse/ManualHandlerMapping.java @@ -1,20 +1,31 @@ package com.techcourse; -import com.techcourse.controller.*; +import com.techcourse.controller.LoginController; +import com.techcourse.controller.LoginViewController; +import com.techcourse.controller.LogoutController; +import com.techcourse.controller.RegisterController; +import com.techcourse.controller.RegisterViewController; +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<>(); + public ManualHandlerMapping() { + initialize(); + } + + @Override public void initialize() { controllers.put("/", new ForwardController("/index.jsp")); controllers.put("/login", new LoginController()); @@ -28,8 +39,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); - return controllers.get(requestURI); + @Override + public Object getHandler(final HttpServletRequest request) { + log.debug("Request Mapping Uri : {}", request.getRequestURI()); + return controllers.get(request.getRequestURI()); } } diff --git a/app/src/main/java/com/techcourse/controller/LoginController.java b/app/src/main/java/com/techcourse/controller/LoginController.java index 91d27b51d8..0428fe109e 100644 --- a/app/src/main/java/com/techcourse/controller/LoginController.java +++ b/app/src/main/java/com/techcourse/controller/LoginController.java @@ -4,9 +4,9 @@ import com.techcourse.repository.InMemoryUserRepository; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import webmvc.org.springframework.web.servlet.mvc.asis.Controller; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import webmvc.org.springframework.web.servlet.mvc.asis.Controller; public class LoginController implements Controller { diff --git a/app/src/main/java/com/techcourse/controller/RegisterAnnotationController.java b/app/src/main/java/com/techcourse/controller/RegisterAnnotationController.java new file mode 100644 index 0000000000..031e076663 --- /dev/null +++ b/app/src/main/java/com/techcourse/controller/RegisterAnnotationController.java @@ -0,0 +1,31 @@ +package com.techcourse.controller; + +import com.techcourse.domain.User; +import com.techcourse.repository.InMemoryUserRepository; +import context.org.springframework.stereotype.Controller; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import web.org.springframework.web.bind.annotation.RequestMapping; +import web.org.springframework.web.bind.annotation.RequestMethod; +import webmvc.org.springframework.web.servlet.ModelAndView; +import webmvc.org.springframework.web.servlet.view.JspView; + +@Controller +public class RegisterAnnotationController { + + @RequestMapping(value = "/register-annotated", method = RequestMethod.POST) + public ModelAndView save(final HttpServletRequest request, final HttpServletResponse response) { + final var user = new User(2, + request.getParameter("account"), + request.getParameter("password"), + request.getParameter("email")); + InMemoryUserRepository.save(user); + + return new ModelAndView(new JspView("redirect:/index.jsp")); + } + + @RequestMapping(value = "/register-annotated", method = RequestMethod.GET) + public ModelAndView show(final HttpServletRequest request, final HttpServletResponse response) { + return new ModelAndView(new JspView("register.jsp")); + } +} 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..b1c8055e9a --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerAdapter.java @@ -0,0 +1,18 @@ +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 supports(final Object handler) { + return handler instanceof HandlerExecution; + } + + @Override + public ModelAndView handle(final Object handler, final HttpServletRequest request, final HttpServletResponse response) throws Exception { + final 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 2fb153cac5..4db4211cf5 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 @@ -8,7 +8,6 @@ import web.org.springframework.web.bind.annotation.RequestMapping; import web.org.springframework.web.bind.annotation.RequestMethod; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashMap; @@ -16,7 +15,7 @@ import java.util.Map; import java.util.stream.Collectors; -public class AnnotationHandlerMapping { +public class AnnotationHandlerMapping implements HandlerMapping { private static final Logger log = LoggerFactory.getLogger(AnnotationHandlerMapping.class); @@ -26,9 +25,11 @@ public class AnnotationHandlerMapping { public AnnotationHandlerMapping(final Object... basePackage) { this.basePackage = basePackage; this.handlerExecutions = new HashMap<>(); + initialize(); } - public void initialize() { + @Override + public void initialize() { log.info("Initialized AnnotationHandlerMapping!"); final Reflections reflections = new Reflections(basePackage); reflections.getTypesAnnotatedWith(Controller.class) @@ -36,27 +37,21 @@ public void initialize() { } private void register(final Class clazz) { - try { - final Object instance = clazz.getDeclaredConstructor().newInstance(); - final List annotatedMethods = Arrays.stream(clazz.getDeclaredMethods()) - .filter(method -> method.isAnnotationPresent(RequestMapping.class)) - .collect(Collectors.toList()); - - for (final Method annotatedMethod : annotatedMethods) { - final RequestMapping annotation = annotatedMethod.getAnnotation(RequestMapping.class); - for (RequestMethod requestMethod : annotation.method()) { - final HandlerKey handlerKey = new HandlerKey(annotation.value(), requestMethod); - handlerExecutions.put(handlerKey, new HandlerExecution(instance, annotatedMethod)); - } + final List annotatedMethods = Arrays.stream(clazz.getDeclaredMethods()) + .filter(method -> method.isAnnotationPresent(RequestMapping.class)) + .collect(Collectors.toList()); + + for (final Method annotatedMethod : annotatedMethods) { + final RequestMapping annotation = annotatedMethod.getAnnotation(RequestMapping.class); + for (final RequestMethod requestMethod : annotation.method()) { + final HandlerKey handlerKey = new HandlerKey(annotation.value(), requestMethod); + log.info("handlerKey = {}", handlerKey); + handlerExecutions.put(handlerKey, new HandlerExecution(annotatedMethod)); } - } catch (NoSuchMethodException | - InvocationTargetException | - InstantiationException | - IllegalAccessException e) { - throw new RuntimeException(e); } } + @Override public Object getHandler(final HttpServletRequest request) { final HandlerKey handlerKey = new HandlerKey(request.getRequestURI(), RequestMethod.valueOf(request.getMethod())); return handlerExecutions.get(handlerKey); 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..a9dcc44563 --- /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 supports(final Object handler); + + ModelAndView handle(final Object handler, final HttpServletRequest request, final HttpServletResponse response) throws Exception; +} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerAdapters.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerAdapters.java new file mode 100644 index 0000000000..fb6e5caee3 --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerAdapters.java @@ -0,0 +1,27 @@ +package webmvc.org.springframework.web.servlet.mvc.tobe; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class HandlerAdapters { + private final List adapters; + + public HandlerAdapters() { + this.adapters = new ArrayList<>(); + } + + public void init() { + adapters.add(new AnnotationHandlerAdapter()); + adapters.add(new ManualHandlerAdapter()); + } + + public Optional getAdapter(final Object handler) { + for (final HandlerAdapter handlerAdapter : adapters) { + if (handlerAdapter.supports(handler)) { + return Optional.of(handlerAdapter); + } + } + return Optional.empty(); + } +} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecution.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecution.java index 0698e18e84..6d302b392a 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecution.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecution.java @@ -7,15 +7,14 @@ import java.lang.reflect.Method; public class HandlerExecution { - private final Object instance; private final Method method; - public HandlerExecution(final Object instance, final Method method) { - this.instance = instance; + public HandlerExecution( final Method method) { this.method = method; } public ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response) throws Exception { - return (ModelAndView) method.invoke(instance, request, response); + final Object handler = method.getDeclaringClass().getDeclaredConstructor().newInstance(); + return (ModelAndView) method.invoke(handler, request, response); } } 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..d900bb88f5 --- /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(final HttpServletRequest request); +} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerMappings.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerMappings.java new file mode 100644 index 0000000000..7cc38fed43 --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerMappings.java @@ -0,0 +1,31 @@ +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 HandlerMappings { + private final List mappings; + + public HandlerMappings() { + this.mappings = new ArrayList<>(); + } + + public void init(final HandlerMapping... mappings) { + this.mappings.add(new AnnotationHandlerMapping()); + this.mappings.addAll(List.of(mappings)); + } + + public Optional getHandler(final HttpServletRequest request) { + for (final HandlerMapping handlerMapping : mappings) { + final Object handler = handlerMapping.getHandler(request); + if (Objects.nonNull(handler)) { + return Optional.of(handler); + } + } + return Optional.empty(); + } +} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/ManualHandlerAdapter.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/ManualHandlerAdapter.java new file mode 100644 index 0000000000..15bb8db605 --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/ManualHandlerAdapter.java @@ -0,0 +1,21 @@ +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; +import webmvc.org.springframework.web.servlet.mvc.asis.Controller; +import webmvc.org.springframework.web.servlet.view.JspView; + +public class ManualHandlerAdapter implements HandlerAdapter { + @Override + public boolean supports(final Object handler) { + return handler instanceof Controller; + } + + @Override + public ModelAndView handle(final Object handler, final HttpServletRequest request, final HttpServletResponse response) throws Exception { + final Controller controller = (Controller) handler; + final String path = controller.execute(request, response); + return new ModelAndView(new JspView(path)); + } +} 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..01ac01b657 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 @@ -1,5 +1,6 @@ package webmvc.org.springframework.web.servlet.view; +import jakarta.servlet.RequestDispatcher; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; @@ -9,23 +10,26 @@ import java.util.Map; public class JspView implements View { - private static final Logger log = LoggerFactory.getLogger(JspView.class); - public static final String REDIRECT_PREFIX = "redirect:"; + private final String viewName; + public JspView(final String viewName) { + this.viewName = viewName; } @Override public void render(final Map model, final HttpServletRequest request, final HttpServletResponse response) throws Exception { - // todo - + if (viewName.startsWith(REDIRECT_PREFIX)) { + response.sendRedirect(viewName.substring(JspView.REDIRECT_PREFIX.length())); + return; + } model.keySet().forEach(key -> { log.debug("attribute name : {}, value : {}", key, model.get(key)); request.setAttribute(key, model.get(key)); }); - - // todo + final RequestDispatcher requestDispatcher = request.getRequestDispatcher(viewName); + requestDispatcher.forward(request, response); } }