diff --git a/app/src/main/java/com/techcourse/DispatcherServlet.java b/app/src/main/java/com/techcourse/DispatcherServlet.java index 277d8eed9a..23ae3d20fc 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.HandlerAdapter; +import webmvc.org.springframework.web.servlet.ModelAndView; +import webmvc.org.springframework.web.servlet.View; +import webmvc.org.springframework.web.servlet.mvc.tobe.AnnotationHandlerAdapter; +import webmvc.org.springframework.web.servlet.mvc.tobe.AnnotationHandlerMapping; import webmvc.org.springframework.web.servlet.view.JspView; public class DispatcherServlet extends HttpServlet { @@ -13,26 +18,45 @@ public class DispatcherServlet extends HttpServlet { private static final long serialVersionUID = 1L; private static final Logger log = LoggerFactory.getLogger(DispatcherServlet.class); - private ManualHandlerMapping manualHandlerMapping; + private HandlerMappings handlerMappings; + private HandlerAdapters handlerAdapters; public DispatcherServlet() { } @Override public void init() { - manualHandlerMapping = new ManualHandlerMapping(); - manualHandlerMapping.initialize(); + initHandlerMappings(); + initHandlerAdapter(); + } + + private void initHandlerMappings() { + final HandlerMappings handlerMappings = new HandlerMappings(); + handlerMappings.addHandlerMapping(new ManualHandlerMapping()); + handlerMappings.addHandlerMapping(new AnnotationHandlerMapping()); + this.handlerMappings = handlerMappings; + } + + private void initHandlerAdapter() { + final HandlerAdapters handlerAdapters = new HandlerAdapters(); + handlerAdapters.addHandlerAdapter(new ManualHandlerAdapter()); + handlerAdapters.addHandlerAdapter(new AnnotationHandlerAdapter()); + this.handlerAdapters = handlerAdapters; } @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); + protected void service(final HttpServletRequest req, final HttpServletResponse res) throws ServletException { + final String requestURI = req.getRequestURI(); + log.debug("Method : {}, Request URI : {}", req.getMethod(), requestURI); try { - final var controller = manualHandlerMapping.getHandler(requestURI); - final var viewName = controller.execute(request, response); - move(viewName, request, response); + final Object handler = handlerMappings.getHandler(req); + final HandlerAdapter handlerAdapter = handlerAdapters.getHandlerAdapter(handler); + final ModelAndView modelAndView = handlerAdapter.handle(req, res, handler); + final View view = modelAndView.getView(); + if (view instanceof JspView) { + move(((JspView) view).getViewName(), req, res); + } } 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..ae8c57ae53 100644 --- a/app/src/main/java/com/techcourse/DispatcherServletInitializer.java +++ b/app/src/main/java/com/techcourse/DispatcherServletInitializer.java @@ -1,6 +1,7 @@ package com.techcourse; import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletRegistration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import web.org.springframework.web.WebApplicationInitializer; @@ -17,9 +18,9 @@ public class DispatcherServletInitializer implements WebApplicationInitializer { @Override public void onStartup(final ServletContext servletContext) { - final var dispatcherServlet = new DispatcherServlet(); + final DispatcherServlet dispatcherServlet = new DispatcherServlet(); - final var registration = servletContext.addServlet(DEFAULT_SERVLET_NAME, dispatcherServlet); + final ServletRegistration.Dynamic 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/HandlerAdapters.java b/app/src/main/java/com/techcourse/HandlerAdapters.java new file mode 100644 index 0000000000..572375bef6 --- /dev/null +++ b/app/src/main/java/com/techcourse/HandlerAdapters.java @@ -0,0 +1,26 @@ +package com.techcourse; + +import webmvc.org.springframework.web.servlet.HandlerAdapter; + +import java.util.HashSet; +import java.util.Set; + +public class HandlerAdapters { + + private final Set handlerAdapters; + + public HandlerAdapters() { + this.handlerAdapters = new HashSet<>(); + } + + public void addHandlerAdapter(final HandlerAdapter handlerAdapter) { + handlerAdapters.add(handlerAdapter); + } + + public HandlerAdapter getHandlerAdapter(final Object handler) { + return handlerAdapters.stream() + .filter(handlerAdapter -> handlerAdapter.supports(handler)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException(String.format("%s를 실행시킬 어댑터를 찾지 못했습니다.", handler))); + } +} diff --git a/app/src/main/java/com/techcourse/HandlerMappings.java b/app/src/main/java/com/techcourse/HandlerMappings.java new file mode 100644 index 0000000000..0db40e8a89 --- /dev/null +++ b/app/src/main/java/com/techcourse/HandlerMappings.java @@ -0,0 +1,30 @@ +package com.techcourse; + +import jakarta.servlet.http.HttpServletRequest; +import webmvc.org.springframework.web.servlet.HandlerMapping; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +public class HandlerMappings { + + private final Set handlerMappings; + + public HandlerMappings() { + this.handlerMappings = new HashSet<>(); + } + + public void addHandlerMapping(final HandlerMapping handlerMapping) { + handlerMapping.initialize(); + handlerMappings.add(handlerMapping); + } + + public Object getHandler(final HttpServletRequest req) { + return handlerMappings.stream() + .map(handlerMapping -> handlerMapping.getHandler(req)) + .filter(Objects::nonNull) + .findAny() + .orElseThrow(() -> new IllegalArgumentException(String.format("%s %s를 실행시킬 핸들러를 찾을 수 없습니다.", req.getMethod(), req.getRequestURI()))); + } +} 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..fa86b16cb5 --- /dev/null +++ b/app/src/main/java/com/techcourse/ManualHandlerAdapter.java @@ -0,0 +1,25 @@ +package com.techcourse; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import webmvc.org.springframework.web.servlet.HandlerAdapter; +import webmvc.org.springframework.web.servlet.ModelAndView; +import webmvc.org.springframework.web.servlet.View; +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 HttpServletRequest req, final HttpServletResponse res, final Object handler) throws Exception { + final Controller controller = (Controller) handler; + final String viewName = controller.execute(req, res); + final View view = new JspView(viewName); + return new ModelAndView(view); + } +} diff --git a/app/src/main/java/com/techcourse/ManualHandlerMapping.java b/app/src/main/java/com/techcourse/ManualHandlerMapping.java index a54863caf8..b1d5ed265d 100644 --- a/app/src/main/java/com/techcourse/ManualHandlerMapping.java +++ b/app/src/main/java/com/techcourse/ManualHandlerMapping.java @@ -1,20 +1,27 @@ 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.HandlerMapping; import webmvc.org.springframework.web.servlet.mvc.asis.Controller; import webmvc.org.springframework.web.servlet.mvc.asis.ForwardController; 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 +35,10 @@ 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 Controller getHandler(final HttpServletRequest req) { + final String uri = req.getRequestURI(); + log.debug("Request Mapping Uri : {}", uri); + return controllers.get(uri); } } diff --git a/app/src/main/java/com/techcourse/controller/DazzleController.java b/app/src/main/java/com/techcourse/controller/DazzleController.java new file mode 100644 index 0000000000..a51628ba26 --- /dev/null +++ b/app/src/main/java/com/techcourse/controller/DazzleController.java @@ -0,0 +1,18 @@ +package com.techcourse.controller; + +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 DazzleController { + + @RequestMapping(value = "/dazzle", method = RequestMethod.GET) + public ModelAndView showMyDazzle(final HttpServletRequest req, final HttpServletResponse res) { + return new ModelAndView(new JspView("404.jsp")); + } +} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/HandlerAdapter.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/HandlerAdapter.java new file mode 100644 index 0000000000..60f3684507 --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/HandlerAdapter.java @@ -0,0 +1,11 @@ +package webmvc.org.springframework.web.servlet; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public interface HandlerAdapter { + + boolean supports(final Object handler); + + ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) throws Exception; +} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/HandlerMapping.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/HandlerMapping.java new file mode 100644 index 0000000000..eaee258dcd --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/HandlerMapping.java @@ -0,0 +1,10 @@ +package webmvc.org.springframework.web.servlet; + +import jakarta.servlet.http.HttpServletRequest; + +public interface HandlerMapping { + + void initialize(); + + Object getHandler(final HttpServletRequest req); +} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/View.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/View.java index 4499f36866..bcb9313556 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/View.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/View.java @@ -6,5 +6,6 @@ import java.util.Map; public interface View { + void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception; } 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..ee17a3bd67 --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerAdapter.java @@ -0,0 +1,20 @@ +package webmvc.org.springframework.web.servlet.mvc.tobe; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import webmvc.org.springframework.web.servlet.HandlerAdapter; +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 HttpServletRequest request, final HttpServletResponse response, final Object handler) 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 8a838ae4b7..7dcd3b4fea 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 @@ -7,6 +7,7 @@ import org.slf4j.LoggerFactory; import web.org.springframework.web.bind.annotation.RequestMapping; import web.org.springframework.web.bind.annotation.RequestMethod; +import webmvc.org.springframework.web.servlet.HandlerMapping; import java.lang.reflect.Method; import java.util.Arrays; @@ -16,7 +17,7 @@ import java.util.Set; import java.util.stream.Collectors; -public class AnnotationHandlerMapping { +public class AnnotationHandlerMapping implements HandlerMapping { private static final Logger log = LoggerFactory.getLogger(AnnotationHandlerMapping.class); @@ -28,6 +29,7 @@ public AnnotationHandlerMapping(final Object... basePackage) { this.handlerExecutions = new HashMap<>(); } + @Override public void initialize() { Reflections reflections = new Reflections(basePackage); final Set> classes = reflections.getTypesAnnotatedWith(Controller.class); @@ -56,6 +58,9 @@ private Object makeInstance(final Class clazz) { } private void initializeHandlerExecutions(final Method method, final Object controller) { + if (!isValidHandlerExecution(method, controller)) { + throw new IllegalArgumentException("옳지 않은 메소드이거나 없는 핸들러 입니다!"); + } final String uri = method.getAnnotation(RequestMapping.class).value(); final RequestMethod[] requestMethods = method.getAnnotation(RequestMapping.class).method(); for (final RequestMethod requestMethod : requestMethods) { @@ -64,9 +69,14 @@ private void initializeHandlerExecutions(final Method method, final Object contr } } - public Object getHandler(final HttpServletRequest request) { - final String uri = request.getRequestURI(); - final RequestMethod requestMethod = RequestMethod.valueOf(request.getMethod()); + private boolean isValidHandlerExecution(final Method method, final Object controller) { + return method != null && controller != null; + } + + @Override + public Object getHandler(final HttpServletRequest req) { + final String uri = req.getRequestURI(); + final RequestMethod requestMethod = RequestMethod.valueOf(req.getMethod()); final HandlerKey handlerKey = new HandlerKey(uri, requestMethod); return handlerExecutions.get(handlerKey); } 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..a86514497b 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:"; + private final String viewName; + public JspView(final String viewName) { + this.viewName = viewName; } @Override @@ -28,4 +31,8 @@ public void render(final Map model, final HttpServletRequest request, // todo } + + public String getViewName() { + return viewName; + } } diff --git a/study/src/test/java/reflection/ReflectionsTest.java b/study/src/test/java/reflection/ReflectionsTest.java index fb0de3e27a..ace97e8db8 100644 --- a/study/src/test/java/reflection/ReflectionsTest.java +++ b/study/src/test/java/reflection/ReflectionsTest.java @@ -10,6 +10,8 @@ import java.util.Set; +import static org.reflections.scanners.Scanners.TypesAnnotated; + class ReflectionsTest { private static final Logger log = LoggerFactory.getLogger(ReflectionsTest.class); @@ -33,5 +35,9 @@ void showAnnotationClass() throws Exception { for (final Class repository : repositories) { log.info("Repository: {}", repository.getName()); } + + // 전체 조회 + reflections.get(TypesAnnotated.of(Controller.class, Service.class, Repository.class)) + .forEach(object -> log.info("Object: {}", object)); } } diff --git a/study/src/test/java/servlet/com/example/ServletTest.java b/study/src/test/java/servlet/com/example/ServletTest.java index 47c01d2c6b..e335f8190a 100644 --- a/study/src/test/java/servlet/com/example/ServletTest.java +++ b/study/src/test/java/servlet/com/example/ServletTest.java @@ -20,7 +20,6 @@ void testSharedCounter() { HttpUtils.send(PATH); HttpUtils.send(PATH); final var response = HttpUtils.send(PATH); - System.out.println(response.body()); // 톰캣 서버 종료 tomcatStarter.stop();