From d4f1d98f5da4a8ed476fd7423a5de9707c7d1f77 Mon Sep 17 00:00:00 2001 From: echo724 Date: Mon, 25 Sep 2023 11:55:31 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20Legacy=20MVC=20=EC=A0=9C=EA=B1=B0?= =?UTF-8?q?=20=EB=B0=8F=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/techcourse/DispatcherServlet.java | 98 ------------------- .../DispatcherServletInitializer.java | 1 + .../com/techcourse/ManualHandlerMapping.java | 41 -------- .../controller/ForwardController.java | 16 +++ .../controller/LoginController.java | 22 +++-- .../controller/LoginViewController.java | 16 +-- .../controller/LogoutController.java | 14 ++- .../controller/RegisterController.java | 14 ++- .../controller/RegisterViewController.java | 3 +- .../springframework/util/ReflectionUtils.java | 21 +++- .../web/servlet/ModelAndView.java | 6 ++ .../web/servlet/mvc/DispatcherServlet.java | 66 +++++++++++++ .../web/servlet/mvc/HandlerAdaptor.java | 11 +++ .../servlet/mvc/HandlerAdaptorRegistry.java | 19 ++++ .../mvc/{tobe => }/HandlerMapping.java | 2 +- .../servlet/mvc/HandlerMappingRegistry.java | 27 +++++ .../web/servlet/mvc/asis/Controller.java | 8 -- .../servlet/mvc/asis/ForwardController.java | 20 ---- .../mvc/tobe/AnnotationHandlerMapping.java | 50 ++++++---- .../servlet/mvc/tobe/ControllerScanner.java | 34 +++++++ .../servlet/mvc/tobe/HandlerExecution.java | 12 ++- .../tobe/HandlerExecutionHandlerAdaptor.java | 18 ++++ 22 files changed, 303 insertions(+), 216 deletions(-) delete mode 100644 app/src/main/java/com/techcourse/DispatcherServlet.java delete mode 100644 app/src/main/java/com/techcourse/ManualHandlerMapping.java create mode 100644 app/src/main/java/com/techcourse/controller/ForwardController.java create mode 100644 mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/DispatcherServlet.java create mode 100644 mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerAdaptor.java create mode 100644 mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerAdaptorRegistry.java rename mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/{tobe => }/HandlerMapping.java (74%) create mode 100644 mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerMappingRegistry.java delete mode 100644 mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/asis/Controller.java delete mode 100644 mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/asis/ForwardController.java create mode 100644 mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/ControllerScanner.java create mode 100644 mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecutionHandlerAdaptor.java diff --git a/app/src/main/java/com/techcourse/DispatcherServlet.java b/app/src/main/java/com/techcourse/DispatcherServlet.java deleted file mode 100644 index 76f0fb8b0d..0000000000 --- a/app/src/main/java/com/techcourse/DispatcherServlet.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.techcourse; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServlet; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import webmvc.org.springframework.web.servlet.mvc.asis.Controller; -import webmvc.org.springframework.web.servlet.mvc.tobe.AnnotationHandlerMapping; -import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerExecution; -import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerMapping; -import webmvc.org.springframework.web.servlet.view.JspView; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; - -public class DispatcherServlet extends HttpServlet { - - public static final String BASE_PACKAGE = "com.techcourse.controller"; - private static final long serialVersionUID = 1L; - private static final Logger log = LoggerFactory.getLogger(DispatcherServlet.class); - - - private final List handlerMappings = new ArrayList<>(); - - public DispatcherServlet() { - } - - @Override - public void init() { - handlerMappings.add(new ManualHandlerMapping()); - handlerMappings.add(new AnnotationHandlerMapping(BASE_PACKAGE)); - handlerMappings.forEach(HandlerMapping::initialize); - } - - @Override - protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException { - final String requestURI = request.getRequestURI(); - log.info("Method : {}, Request URI : {}", request.getMethod(), requestURI); - - try { - final Optional optionalHandler = handlerMappings.stream() - .map(handlerMapping -> handlerMapping.getHandler(request)) - .filter(Objects::nonNull) - .findFirst(); - - optionalHandler.ifPresentOrElse(handler -> handle(request, response, handler), () -> { - throw new IllegalArgumentException("Not found handler for request URI : " + requestURI); - }); - } catch (Throwable e) { - log.error("Exception : {}", e.getMessage(), e); - throw new ServletException(e.getMessage()); - } - } - - private void handle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) { - if (handler instanceof Controller) { - handleController(request, response, (Controller) handler); - return; - } - if (handler instanceof HandlerExecution) { - handleException(request, response, (HandlerExecution) handler); - return; - } - throw new IllegalArgumentException("Not supported handler type"); - } - - private void handleException(final HttpServletRequest request, final HttpServletResponse response, final HandlerExecution handler) { - try { - final var modelAndView = handler.handle(request, response); - move(modelAndView.getViewName(), request, response); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private void handleController(final HttpServletRequest request, final HttpServletResponse response, final Controller handler) { - try { - final String viewName = handler.execute(request, response); - move(viewName, request, response); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - 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/DispatcherServletInitializer.java b/app/src/main/java/com/techcourse/DispatcherServletInitializer.java index 6e814cdd25..53fb9af5c8 100644 --- a/app/src/main/java/com/techcourse/DispatcherServletInitializer.java +++ b/app/src/main/java/com/techcourse/DispatcherServletInitializer.java @@ -4,6 +4,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import web.org.springframework.web.WebApplicationInitializer; +import webmvc.org.springframework.web.servlet.mvc.DispatcherServlet; /** * Base class for {@link WebApplicationInitializer} diff --git a/app/src/main/java/com/techcourse/ManualHandlerMapping.java b/app/src/main/java/com/techcourse/ManualHandlerMapping.java deleted file mode 100644 index bb8b43531f..0000000000 --- a/app/src/main/java/com/techcourse/ManualHandlerMapping.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.techcourse; - -import com.techcourse.controller.LoginController; -import com.techcourse.controller.LoginViewController; -import com.techcourse.controller.LogoutController; -import com.techcourse.controller.RegisterController; -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 implements HandlerMapping { - - private static final Logger log = LoggerFactory.getLogger(ManualHandlerMapping.class); - - private static final Map controllers = new HashMap<>(); - - @Override - public void initialize() { - controllers.put("/", new ForwardController("/index.jsp")); - controllers.put("/login", new LoginController()); - controllers.put("/login/view", new LoginViewController()); - controllers.put("/logout", new LogoutController()); - controllers.put("/register", new RegisterController()); - - log.info("Initialized Handler Mapping!"); - controllers.keySet() - .forEach(path -> log.info("Path : {}, Controller : {}", path, controllers.get(path).getClass())); - } - - @Override - public Object getHandler(final HttpServletRequest request) { - final String requestURI = request.getRequestURI(); - return controllers.get(requestURI); - } -} diff --git a/app/src/main/java/com/techcourse/controller/ForwardController.java b/app/src/main/java/com/techcourse/controller/ForwardController.java new file mode 100644 index 0000000000..05481f5590 --- /dev/null +++ b/app/src/main/java/com/techcourse/controller/ForwardController.java @@ -0,0 +1,16 @@ +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; + +@Controller +public class ForwardController { + @RequestMapping(value = "/", method = RequestMethod.GET) + public ModelAndView execute(HttpServletRequest request, HttpServletResponse response) throws Exception { + return ModelAndView.fromJspViewName("redirect:/index.jsp"); + } +} diff --git a/app/src/main/java/com/techcourse/controller/LoginController.java b/app/src/main/java/com/techcourse/controller/LoginController.java index 0428fe109e..8a822c4bcf 100644 --- a/app/src/main/java/com/techcourse/controller/LoginController.java +++ b/app/src/main/java/com/techcourse/controller/LoginController.java @@ -2,20 +2,24 @@ 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 webmvc.org.springframework.web.servlet.mvc.asis.Controller; import org.slf4j.Logger; 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.ModelAndView; -public class LoginController implements Controller { +@Controller +public class LoginController { private static final Logger log = LoggerFactory.getLogger(LoginController.class); - @Override - public String execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception { + @RequestMapping(value = "/login", method = RequestMethod.POST) + public ModelAndView execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception { if (UserSession.isLoggedIn(req.getSession())) { - return "redirect:/index.jsp"; + return ModelAndView.fromJspViewName("redirect:/index.jsp"); } return InMemoryUserRepository.findByAccount(req.getParameter("account")) @@ -23,15 +27,15 @@ public String execute(final HttpServletRequest req, final HttpServletResponse re log.info("User : {}", user); return login(req, user); }) - .orElse("redirect:/401.jsp"); + .orElse(ModelAndView.fromJspViewName("redirect:/401.jsp")); } - private String login(final HttpServletRequest request, final User user) { + private ModelAndView login(final HttpServletRequest request, final User user) { if (user.checkPassword(request.getParameter("password"))) { final var session = request.getSession(); session.setAttribute(UserSession.SESSION_KEY, user); - return "redirect:/index.jsp"; + return ModelAndView.fromJspViewName("redirect:/index.jsp"); } - return "redirect:/401.jsp"; + return ModelAndView.fromJspViewName("redirect:/401.jsp"); } } diff --git a/app/src/main/java/com/techcourse/controller/LoginViewController.java b/app/src/main/java/com/techcourse/controller/LoginViewController.java index 86ec26cdce..6ad762abad 100644 --- a/app/src/main/java/com/techcourse/controller/LoginViewController.java +++ b/app/src/main/java/com/techcourse/controller/LoginViewController.java @@ -1,22 +1,26 @@ package com.techcourse.controller; +import context.org.springframework.stereotype.Controller; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import webmvc.org.springframework.web.servlet.mvc.asis.Controller; +import web.org.springframework.web.bind.annotation.RequestMapping; +import web.org.springframework.web.bind.annotation.RequestMethod; +import webmvc.org.springframework.web.servlet.ModelAndView; -public class LoginViewController implements Controller { +@Controller +public class LoginViewController { private static final Logger log = LoggerFactory.getLogger(LoginViewController.class); - @Override - public String execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception { + @RequestMapping(value = "/login", method = RequestMethod.GET) + public ModelAndView execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception { return UserSession.getUserFrom(req.getSession()) .map(user -> { log.info("logged in {}", user.getAccount()); - return "redirect:/index.jsp"; + return ModelAndView.fromJspViewName("redirect:/index.jsp"); }) - .orElse("/login.jsp"); + .orElse(ModelAndView.fromJspViewName("/login.jsp")); } } diff --git a/app/src/main/java/com/techcourse/controller/LogoutController.java b/app/src/main/java/com/techcourse/controller/LogoutController.java index 4642fd9450..207a322548 100644 --- a/app/src/main/java/com/techcourse/controller/LogoutController.java +++ b/app/src/main/java/com/techcourse/controller/LogoutController.java @@ -1,15 +1,19 @@ package com.techcourse.controller; +import context.org.springframework.stereotype.Controller; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import webmvc.org.springframework.web.servlet.mvc.asis.Controller; +import web.org.springframework.web.bind.annotation.RequestMapping; +import web.org.springframework.web.bind.annotation.RequestMethod; +import webmvc.org.springframework.web.servlet.ModelAndView; -public class LogoutController implements Controller { +@Controller +public class LogoutController { - @Override - public String execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception { + @RequestMapping(value = "/logout", method = RequestMethod.GET) + public ModelAndView execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception { final var session = req.getSession(); session.removeAttribute(UserSession.SESSION_KEY); - return "redirect:/"; + return ModelAndView.fromJspViewName("redirect:/"); } } diff --git a/app/src/main/java/com/techcourse/controller/RegisterController.java b/app/src/main/java/com/techcourse/controller/RegisterController.java index da62e5e8e9..5d77cc298a 100644 --- a/app/src/main/java/com/techcourse/controller/RegisterController.java +++ b/app/src/main/java/com/techcourse/controller/RegisterController.java @@ -2,20 +2,24 @@ 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 webmvc.org.springframework.web.servlet.mvc.asis.Controller; +import web.org.springframework.web.bind.annotation.RequestMapping; +import web.org.springframework.web.bind.annotation.RequestMethod; +import webmvc.org.springframework.web.servlet.ModelAndView; -public class RegisterController implements Controller { +@Controller +public class RegisterController { - @Override - public String execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception { + @RequestMapping(value = "/register", method = RequestMethod.POST) + public ModelAndView execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception { final var user = new User(2, req.getParameter("account"), req.getParameter("password"), req.getParameter("email")); InMemoryUserRepository.save(user); - return "redirect:/index.jsp"; + return ModelAndView.fromJspViewName("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 index dd571ba10f..9b6eb461f2 100644 --- a/app/src/main/java/com/techcourse/controller/RegisterViewController.java +++ b/app/src/main/java/com/techcourse/controller/RegisterViewController.java @@ -6,13 +6,12 @@ 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 RegisterViewController { @RequestMapping(value = "/register/view", method = RequestMethod.GET) public ModelAndView execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception { - return new ModelAndView(new JspView("/register.jsp")); + return ModelAndView.fromJspViewName("/register.jsp"); } } diff --git a/mvc/src/main/java/core/org/springframework/util/ReflectionUtils.java b/mvc/src/main/java/core/org/springframework/util/ReflectionUtils.java index e89c1743f3..6f8240c49d 100644 --- a/mvc/src/main/java/core/org/springframework/util/ReflectionUtils.java +++ b/mvc/src/main/java/core/org/springframework/util/ReflectionUtils.java @@ -1,13 +1,25 @@ package core.org.springframework.util; +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Constructor; +import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; public abstract class ReflectionUtils { + public static Predicate withAnnotation(final Class annotation) { + return input -> input != null && input.isAnnotationPresent(annotation); + } + /** * Obtain an accessible constructor for the given class and parameters. - * @param clazz the clazz to check + * + * @param clazz the clazz to check * @param parameterTypes the parameter types of the desired constructor * @return the constructor reference * @throws NoSuchMethodException if no such constructor exists @@ -25,6 +37,7 @@ public static Constructor accessibleConstructor(Class clazz, Class. * Make the given constructor accessible, explicitly setting it accessible * if necessary. The {@code setAccessible(true)} method is only called * when actually necessary, to avoid unnecessary conflicts. + * * @param ctor the constructor to make accessible * @see Constructor#setAccessible */ @@ -35,4 +48,10 @@ public static void makeAccessible(Constructor ctor) { ctor.setAccessible(true); } } + + public static List getAllMethods(final Class clazz, final Predicate annotatedElementPredicate) { + return Arrays.stream(clazz.getMethods()) + .filter(annotatedElementPredicate) + .collect(Collectors.toList()); + } } diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/ModelAndView.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/ModelAndView.java index fdf4e585d3..50f855d4ee 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/ModelAndView.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/ModelAndView.java @@ -1,5 +1,7 @@ package webmvc.org.springframework.web.servlet; +import webmvc.org.springframework.web.servlet.view.JspView; + import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -14,6 +16,10 @@ public ModelAndView(final View view) { this.model = new HashMap<>(); } + public static ModelAndView fromJspViewName(final String viewName) { + return new ModelAndView(new JspView(viewName)); + } + public ModelAndView addObject(final String attributeName, final Object attributeValue) { model.put(attributeName, attributeValue); return this; diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/DispatcherServlet.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/DispatcherServlet.java new file mode 100644 index 0000000000..3ab5e2a18c --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/DispatcherServlet.java @@ -0,0 +1,66 @@ +package webmvc.org.springframework.web.servlet.mvc; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +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.AnnotationHandlerMapping; +import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerExecutionHandlerAdaptor; +import webmvc.org.springframework.web.servlet.view.JspView; + +public class DispatcherServlet extends HttpServlet { + private static final long serialVersionUID = 1L; + private static final Logger log = LoggerFactory.getLogger(DispatcherServlet.class); + + + HandlerMappingRegistry handlerMappingRegistry = new HandlerMappingRegistry(); + HandlerAdaptorRegistry handlerAdaptorRegistry = new HandlerAdaptorRegistry(); + + public DispatcherServlet() { + } + + public void addHandlerAdaptor(final HandlerAdaptor handlerAdaptor) { + handlerAdaptorRegistry.addHandlerAdaptor(handlerAdaptor); + } + + public void addHandlerMapping(final HandlerMapping handlerMapping) { + handlerMappingRegistry.addHandlerMapping(handlerMapping); + } + + @Override + public void init() { + handlerMappingRegistry.addHandlerMapping(new AnnotationHandlerMapping("com.techcourse.controller")); + handlerAdaptorRegistry.addHandlerAdaptor(new HandlerExecutionHandlerAdaptor()); + handlerMappingRegistry.initialize(); + } + + @Override + protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException { + final String requestURI = request.getRequestURI(); + log.info("Method : {}, Request URI : {}", request.getMethod(), requestURI); + + try { + final Object handler = handlerMappingRegistry.getHandler(request) + .orElseThrow(() -> new IllegalArgumentException("요청에 맞는 핸들러를 찾을 수 없습니다.")); + final HandlerAdaptor handlerAdaptor = handlerAdaptorRegistry.getHandlerAdaptor(handler); + final ModelAndView modelAndView = handlerAdaptor.handle(request, response, handler); + move(modelAndView.getViewName(), request, response); + } 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; + } + + final var requestDispatcher = request.getRequestDispatcher(viewName); + requestDispatcher.forward(request, response); + } +} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerAdaptor.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerAdaptor.java new file mode 100644 index 0000000000..77cfb8b4d4 --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerAdaptor.java @@ -0,0 +1,11 @@ +package webmvc.org.springframework.web.servlet.mvc; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import webmvc.org.springframework.web.servlet.ModelAndView; + +public interface HandlerAdaptor { + 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/mvc/HandlerAdaptorRegistry.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerAdaptorRegistry.java new file mode 100644 index 0000000000..949eb247a2 --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerAdaptorRegistry.java @@ -0,0 +1,19 @@ +package webmvc.org.springframework.web.servlet.mvc; + +import java.util.ArrayList; +import java.util.List; + +public class HandlerAdaptorRegistry { + private final List handlerAdaptors = new ArrayList<>(); + + public void addHandlerAdaptor(final HandlerAdaptor handlerAdaptor) { + handlerAdaptors.add(handlerAdaptor); + } + + public HandlerAdaptor getHandlerAdaptor(final Object handler) { + return handlerAdaptors.stream() + .filter(handlerAdaptor -> handlerAdaptor.supports(handler)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("해당 핸들러를 처리할 수 있는 핸들러 어댑터가 없습니다.")); + } +} 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/HandlerMapping.java similarity index 74% rename from mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerMapping.java rename to mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerMapping.java index d26a232f55..126604eb52 100644 --- 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/HandlerMapping.java @@ -1,4 +1,4 @@ -package webmvc.org.springframework.web.servlet.mvc.tobe; +package webmvc.org.springframework.web.servlet.mvc; import jakarta.servlet.http.HttpServletRequest; diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerMappingRegistry.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerMappingRegistry.java new file mode 100644 index 0000000000..277c46dd96 --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerMappingRegistry.java @@ -0,0 +1,27 @@ +package webmvc.org.springframework.web.servlet.mvc; + +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 = new ArrayList<>(); + + public void addHandlerMapping(final HandlerMapping handlerMapping) { + handlerMappings.add(handlerMapping); + } + + public Optional getHandler(final HttpServletRequest request) { + return handlerMappings.stream() + .map(handlerMapping -> handlerMapping.getHandler(request)) + .filter(Objects::nonNull) + .findFirst(); + } + + public void initialize() { + handlerMappings.forEach(HandlerMapping::initialize); + } +} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/asis/Controller.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/asis/Controller.java deleted file mode 100644 index bdd1fde780..0000000000 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/asis/Controller.java +++ /dev/null @@ -1,8 +0,0 @@ -package webmvc.org.springframework.web.servlet.mvc.asis; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -public interface Controller { - String execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception; -} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/asis/ForwardController.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/asis/ForwardController.java deleted file mode 100644 index cd8f1ef371..0000000000 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/asis/ForwardController.java +++ /dev/null @@ -1,20 +0,0 @@ -package webmvc.org.springframework.web.servlet.mvc.asis; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import java.util.Objects; - -public class ForwardController implements Controller { - - private final String path; - - public ForwardController(final String path) { - this.path = Objects.requireNonNull(path); - } - - @Override - public String execute(final HttpServletRequest request, final HttpServletResponse response) { - return path; - } -} 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 b23f6a0fa2..ae5b85145b 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 @@ -1,18 +1,19 @@ package webmvc.org.springframework.web.servlet.mvc.tobe; -import context.org.springframework.stereotype.Controller; import jakarta.servlet.http.HttpServletRequest; -import org.reflections.Reflections; import org.slf4j.Logger; 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.mvc.HandlerMapping; import java.lang.reflect.Method; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; public class AnnotationHandlerMapping implements HandlerMapping { @@ -28,25 +29,40 @@ public AnnotationHandlerMapping(final Object... basePackage) { @Override public void initialize() { - final Reflections reflections = new Reflections(basePackage); - final Set> typesAnnotatedWith = reflections.getTypesAnnotatedWith(Controller.class); - typesAnnotatedWith.stream() - .map(Class::getMethods) - .flatMap(methods -> List.of(methods).stream()) - .filter(method -> method.isAnnotationPresent(RequestMapping.class)) - .forEach(this::generateHandlerMapping); - log.info("Initialized AnnotationHandlerMapping!"); + final ControllerScanner controllerScanner = new ControllerScanner(basePackage); + final Map, Object> controllers = controllerScanner.getControllers(); + final Set> controllerClasses = controllers.keySet(); + Set methods = getRequestMappingMethods(controllerClasses); + methods.forEach(method -> { + final RequestMapping requestMapping = method.getAnnotation(RequestMapping.class); + addHandlerExecutions(controllers, method, requestMapping); + }); + } + + private void addHandlerExecutions(Map, Object> controllerMapper, final Method method, final RequestMapping requestMapping) { + final String requestURI = requestMapping.value(); + List handlerKeys = mapHandlerKeys(requestURI, requestMapping.method()); + handlerKeys.stream() + .forEach(handlerKey -> { + final Object instance = controllerMapper.get(method.getDeclaringClass()); + handlerExecutions.put(handlerKey, new HandlerExecution(instance, method)); + }); } - private void generateHandlerMapping(final Method method) { - final RequestMapping mapping = method.getAnnotation(RequestMapping.class); - for (final RequestMethod requestMethod : mapping.method()) { - final HandlerKey key = new HandlerKey(mapping.value(), requestMethod); - final HandlerExecution handlerExecution = new HandlerExecution(method); - handlerExecutions.put(key, handlerExecution); - } + private List mapHandlerKeys(final String requestURI, final RequestMethod[] methods) { + return Arrays.stream(methods) + .map(method -> new HandlerKey(requestURI, method)) + .collect(Collectors.toList()); } + private Set getRequestMappingMethods(final Set> controllerClasses) { + return controllerClasses.stream() + .flatMap(controllerClass -> Arrays.stream(controllerClass.getMethods())) + .filter(method -> method.isAnnotationPresent(RequestMapping.class)) + .collect(Collectors.toSet()); + } + + @Override public Object getHandler(final HttpServletRequest request) { final String requestURI = request.getRequestURI(); diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/ControllerScanner.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/ControllerScanner.java new file mode 100644 index 0000000000..31c4441838 --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/ControllerScanner.java @@ -0,0 +1,34 @@ +package webmvc.org.springframework.web.servlet.mvc.tobe; + +import context.org.springframework.stereotype.Controller; +import org.reflections.Reflections; + +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +public class ControllerScanner { + private final Reflections reflections; + + public ControllerScanner(final Object... basePackage) { + this.reflections = new Reflections(basePackage); + } + + public Map, Object> getControllers() { + final Set> typesAnnotatedWith = this.reflections.getTypesAnnotatedWith(Controller.class); + return instantiateControllers(typesAnnotatedWith); + } + + private Map, Object> instantiateControllers(Set> controllers) { + return controllers.stream() + .collect(Collectors.toMap(controller -> controller, this::instantiateController)); + } + + private Object instantiateController(final Class controller) { + try { + return controller.getConstructor().newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} 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 a32cb5c4b9..4705fb49f6 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 @@ -8,17 +8,23 @@ public class HandlerExecution { private final Method method; - private final Object controller; + private final Object declaredObject; + public HandlerExecution(Method method) { this.method = method; try { - this.controller = method.getDeclaringClass().getConstructor().newInstance(); + this.declaredObject = method.getDeclaringClass().getConstructor().newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } + public HandlerExecution(Object declaredObject, Method method) { + this.declaredObject = declaredObject; + this.method = method; + } + public ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response) throws Exception { - return (ModelAndView) method.invoke(controller, request, response); + return (ModelAndView) method.invoke(declaredObject, request, response); } } diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecutionHandlerAdaptor.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecutionHandlerAdaptor.java new file mode 100644 index 0000000000..622dd4db5b --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecutionHandlerAdaptor.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; +import webmvc.org.springframework.web.servlet.mvc.HandlerAdaptor; + +public class HandlerExecutionHandlerAdaptor implements HandlerAdaptor { + @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 { + return ((HandlerExecution) handler).handle(request, response); + } +} From 00c83b16313686312151e7e193022c9b80205ddc Mon Sep 17 00:00:00 2001 From: echo724 Date: Mon, 25 Sep 2023 12:00:15 +0900 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20DispatcherServlet=EC=97=90=EC=84=9C?= =?UTF-8?q?=20JSP=20Render=20=EC=98=81=EC=97=AD=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../springframework/web/servlet/ModelAndView.java | 10 ++++++---- .../web/servlet/mvc/DispatcherServlet.java | 12 +----------- .../springframework/web/servlet/view/JspView.java | 8 ++++++-- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/ModelAndView.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/ModelAndView.java index 50f855d4ee..02a9d3b08f 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/ModelAndView.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/ModelAndView.java @@ -1,5 +1,7 @@ package webmvc.org.springframework.web.servlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import webmvc.org.springframework.web.servlet.view.JspView; import java.util.Collections; @@ -29,6 +31,10 @@ public Object getObject(final String attributeName) { return model.get(attributeName); } + public void render(final HttpServletRequest request, final HttpServletResponse response) throws Exception { + view.render(model, request, response); + } + public Map getModel() { return Collections.unmodifiableMap(model); } @@ -36,8 +42,4 @@ public Map getModel() { public View getView() { return view; } - - public String getViewName() { - return view.getName(); - } } diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/DispatcherServlet.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/DispatcherServlet.java index 3ab5e2a18c..71d3036da7 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/DispatcherServlet.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/DispatcherServlet.java @@ -9,7 +9,6 @@ import webmvc.org.springframework.web.servlet.ModelAndView; import webmvc.org.springframework.web.servlet.mvc.tobe.AnnotationHandlerMapping; import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerExecutionHandlerAdaptor; -import webmvc.org.springframework.web.servlet.view.JspView; public class DispatcherServlet extends HttpServlet { private static final long serialVersionUID = 1L; @@ -47,20 +46,11 @@ protected void service(final HttpServletRequest request, final HttpServletRespon .orElseThrow(() -> new IllegalArgumentException("요청에 맞는 핸들러를 찾을 수 없습니다.")); final HandlerAdaptor handlerAdaptor = handlerAdaptorRegistry.getHandlerAdaptor(handler); final ModelAndView modelAndView = handlerAdaptor.handle(request, response, handler); - move(modelAndView.getViewName(), request, response); + modelAndView.render(request, response); } 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; - } - - final var requestDispatcher = request.getRequestDispatcher(viewName); - requestDispatcher.forward(request, response); - } } 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 41161a581e..a201d9ae85 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 @@ -21,14 +21,18 @@ public JspView(final String viewName) { @Override public void render(final Map model, final HttpServletRequest request, final HttpServletResponse response) throws Exception { - // todo + if (viewName.startsWith(JspView.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 var requestDispatcher = request.getRequestDispatcher(viewName); + requestDispatcher.forward(request, response); } @Override From 0c7d549c45d966d77d21b919e79e9256b967593d Mon Sep 17 00:00:00 2001 From: echo724 Date: Mon, 25 Sep 2023 16:01:26 +0900 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20JSON=20View=20render=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/RegisterViewController.java | 2 +- .../techcourse/controller/UserController.java | 32 +++++++++++++++++++ .../mvc/tobe/AnnotationHandlerMapping.java | 9 +++--- .../web/servlet/view/JsonView.java | 11 +++++++ 4 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/com/techcourse/controller/UserController.java diff --git a/app/src/main/java/com/techcourse/controller/RegisterViewController.java b/app/src/main/java/com/techcourse/controller/RegisterViewController.java index 9b6eb461f2..4d1c0a4daa 100644 --- a/app/src/main/java/com/techcourse/controller/RegisterViewController.java +++ b/app/src/main/java/com/techcourse/controller/RegisterViewController.java @@ -10,7 +10,7 @@ @Controller public class RegisterViewController { - @RequestMapping(value = "/register/view", method = RequestMethod.GET) + @RequestMapping(value = "/register", method = RequestMethod.GET) public ModelAndView execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception { return ModelAndView.fromJspViewName("/register.jsp"); } diff --git a/app/src/main/java/com/techcourse/controller/UserController.java b/app/src/main/java/com/techcourse/controller/UserController.java new file mode 100644 index 0000000000..a3cc63cf06 --- /dev/null +++ b/app/src/main/java/com/techcourse/controller/UserController.java @@ -0,0 +1,32 @@ +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 org.slf4j.Logger; +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.ModelAndView; +import webmvc.org.springframework.web.servlet.view.JsonView; + +@Controller +public class UserController { + + private static final Logger log = LoggerFactory.getLogger(UserController.class); + + @RequestMapping(value = "/api/user", method = RequestMethod.GET) + public ModelAndView show(HttpServletRequest request, HttpServletResponse response) { + final String account = request.getParameter("account"); + log.debug("user id : {}", account); + + final ModelAndView modelAndView = new ModelAndView(new JsonView()); + final User user = InMemoryUserRepository.findByAccount(account) + .orElseThrow(); + + modelAndView.addObject("user", user); + return modelAndView; + } +} \ No newline at end of file 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 ae5b85145b..f246c52504 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 @@ -42,11 +42,10 @@ public void initialize() { private void addHandlerExecutions(Map, Object> controllerMapper, final Method method, final RequestMapping requestMapping) { final String requestURI = requestMapping.value(); List handlerKeys = mapHandlerKeys(requestURI, requestMapping.method()); - handlerKeys.stream() - .forEach(handlerKey -> { - final Object instance = controllerMapper.get(method.getDeclaringClass()); - handlerExecutions.put(handlerKey, new HandlerExecution(instance, method)); - }); + handlerKeys.forEach(handlerKey -> { + final Object instance = controllerMapper.get(method.getDeclaringClass()); + handlerExecutions.put(handlerKey, new HandlerExecution(instance, method)); + }); } private List mapHandlerKeys(final String requestURI, final RequestMethod[] methods) { diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JsonView.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JsonView.java index 2c0ba3fc3b..dc1ef9bf87 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JsonView.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JsonView.java @@ -1,5 +1,6 @@ package webmvc.org.springframework.web.servlet.view; +import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import webmvc.org.springframework.web.servlet.View; @@ -10,6 +11,16 @@ public class JsonView implements View { @Override public void render(final Map model, final HttpServletRequest request, HttpServletResponse response) throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + String json = ""; + if (model.size() == 1) { + json = objectMapper.writeValueAsString(model.get(model.keySet().iterator().next())); + } else { + json = objectMapper.writeValueAsString(model); + } + response.setContentType("application/json"); + response.setCharacterEncoding("utf-8"); + response.getWriter().write(json); } @Override From 74aa413993d74d28d3fc58faabec1d37feb15ca3 Mon Sep 17 00:00:00 2001 From: echo724 Date: Mon, 25 Sep 2023 17:12:24 +0900 Subject: [PATCH 4/8] =?UTF-8?q?refactor:=20NotFound=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/techcourse/controller/UserController.java | 4 ++-- .../techcourse/exception/UserNotFoundException.java | 9 +++++++++ .../web/servlet/mvc/DispatcherServlet.java | 11 +++++++++-- .../mvc/exception/HandlerNotFoundException.java | 7 +++++++ .../web/servlet/mvc/exception/NotFoundException.java | 7 +++++++ 5 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/com/techcourse/exception/UserNotFoundException.java create mode 100644 mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/exception/HandlerNotFoundException.java create mode 100644 mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/exception/NotFoundException.java diff --git a/app/src/main/java/com/techcourse/controller/UserController.java b/app/src/main/java/com/techcourse/controller/UserController.java index a3cc63cf06..8edea242f0 100644 --- a/app/src/main/java/com/techcourse/controller/UserController.java +++ b/app/src/main/java/com/techcourse/controller/UserController.java @@ -1,6 +1,7 @@ package com.techcourse.controller; import com.techcourse.domain.User; +import com.techcourse.exception.UserNotFoundException; import com.techcourse.repository.InMemoryUserRepository; import context.org.springframework.stereotype.Controller; import jakarta.servlet.http.HttpServletRequest; @@ -24,8 +25,7 @@ public ModelAndView show(HttpServletRequest request, HttpServletResponse respons final ModelAndView modelAndView = new ModelAndView(new JsonView()); final User user = InMemoryUserRepository.findByAccount(account) - .orElseThrow(); - + .orElseThrow(() -> new UserNotFoundException("존재하지 않는 사용자입니다.")); modelAndView.addObject("user", user); return modelAndView; } diff --git a/app/src/main/java/com/techcourse/exception/UserNotFoundException.java b/app/src/main/java/com/techcourse/exception/UserNotFoundException.java new file mode 100644 index 0000000000..0f748251eb --- /dev/null +++ b/app/src/main/java/com/techcourse/exception/UserNotFoundException.java @@ -0,0 +1,9 @@ +package com.techcourse.exception; + +import webmvc.org.springframework.web.servlet.mvc.exception.NotFoundException; + +public class UserNotFoundException extends NotFoundException { + public UserNotFoundException(final String message) { + super(message); + } +} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/DispatcherServlet.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/DispatcherServlet.java index 71d3036da7..7e6d616d3e 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/DispatcherServlet.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/DispatcherServlet.java @@ -7,9 +7,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import webmvc.org.springframework.web.servlet.ModelAndView; +import webmvc.org.springframework.web.servlet.mvc.exception.HandlerNotFoundException; +import webmvc.org.springframework.web.servlet.mvc.exception.NotFoundException; import webmvc.org.springframework.web.servlet.mvc.tobe.AnnotationHandlerMapping; import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerExecutionHandlerAdaptor; +import java.io.IOException; + public class DispatcherServlet extends HttpServlet { private static final long serialVersionUID = 1L; private static final Logger log = LoggerFactory.getLogger(DispatcherServlet.class); @@ -37,16 +41,19 @@ public void init() { } @Override - protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException { + protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { final String requestURI = request.getRequestURI(); log.info("Method : {}, Request URI : {}", request.getMethod(), requestURI); try { final Object handler = handlerMappingRegistry.getHandler(request) - .orElseThrow(() -> new IllegalArgumentException("요청에 맞는 핸들러를 찾을 수 없습니다.")); + .orElseThrow(() -> new HandlerNotFoundException("Handler Not Found")); final HandlerAdaptor handlerAdaptor = handlerAdaptorRegistry.getHandlerAdaptor(handler); final ModelAndView modelAndView = handlerAdaptor.handle(request, response, handler); modelAndView.render(request, response); + } catch (NotFoundException e) { + log.error("Exception : {}", e.getMessage(), e); + response.sendError(HttpServletResponse.SC_NOT_FOUND); } catch (Throwable e) { log.error("Exception : {}", e.getMessage(), e); throw new ServletException(e.getMessage()); diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/exception/HandlerNotFoundException.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/exception/HandlerNotFoundException.java new file mode 100644 index 0000000000..16f23de378 --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/exception/HandlerNotFoundException.java @@ -0,0 +1,7 @@ +package webmvc.org.springframework.web.servlet.mvc.exception; + +public class HandlerNotFoundException extends NotFoundException { + public HandlerNotFoundException(final String message) { + super(message); + } +} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/exception/NotFoundException.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/exception/NotFoundException.java new file mode 100644 index 0000000000..c3d7eadaa8 --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/exception/NotFoundException.java @@ -0,0 +1,7 @@ +package webmvc.org.springframework.web.servlet.mvc.exception; + +public class NotFoundException extends RuntimeException { + public NotFoundException(final String message) { + super(message); + } +} From a2ee4d25904a644b062af653d762bc9edbf1e471 Mon Sep 17 00:00:00 2001 From: echo724 Date: Wed, 27 Sep 2023 12:49:09 +0900 Subject: [PATCH 5/8] =?UTF-8?q?study:=20DI=20=EC=BB=A8=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EB=84=88=20=EC=8A=A4=ED=84=B0=EB=94=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/di/stage3/context/DIContainer.java | 69 +++++++++++++++++- .../stage4/annotations/ClassPathScanner.java | 10 ++- .../di/stage4/annotations/DIContainer.java | 72 ++++++++++++++++++- 3 files changed, 145 insertions(+), 6 deletions(-) diff --git a/study/src/test/java/di/stage3/context/DIContainer.java b/study/src/test/java/di/stage3/context/DIContainer.java index b62feb1ed3..3084900fcc 100644 --- a/study/src/test/java/di/stage3/context/DIContainer.java +++ b/study/src/test/java/di/stage3/context/DIContainer.java @@ -1,6 +1,14 @@ package di.stage3.context; +import java.lang.reflect.Constructor; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; /** * 스프링의 BeanFactory, ApplicationContext에 해당되는 클래스 @@ -10,11 +18,68 @@ class DIContainer { private final Set beans; public DIContainer(final Set> classes) { - this.beans = Set.of(); + beans = instantiateBeans(classes); + } + + private Set instantiateBeans(final Set> classes) { + final Map, Object> classObjectMap = new HashMap<>(); + final List> sortedClasses = classes.stream() + .sorted(Comparator.comparing(a -> a.getConstructors()[0].getParameterCount())) + .collect(Collectors.toList()); + System.out.println("sortedClasses = " + sortedClasses); + sortedClasses.forEach(aClass -> { + final Constructor[] constructors = aClass.getConstructors(); + Arrays.stream(constructors).forEachOrdered(constructor -> instantiateBean(aClass, constructor, classObjectMap)); + }); + return new HashSet<>(classObjectMap.values()); + } + + private static void instantiateBean(final Class aClass, final Constructor constructor, final Map, Object> classObjectMap) { + if (constructor.getParameterCount() == 0) { + try { + putInterfacesToInstanceEntry(aClass, constructor, classObjectMap); + } catch (Exception e) { + throw new RuntimeException(e); + } + } else { + findDependencyAndInstantiateBean(aClass, constructor, classObjectMap); + } + } + + private static void findDependencyAndInstantiateBean(final Class aClass, final Constructor constructor, final Map, Object> classObjectMap) { + final Class[] parameterTypes = constructor.getParameterTypes(); + final Object[] parameters = new Object[parameterTypes.length]; + for (int i = 0; i < parameterTypes.length; i++) { + final Class parameterType = parameterTypes[i]; + final Object parameter = classObjectMap.get(parameterType); + parameters[i] = parameter; + } + try { + putInterfacesToInstanceEntry(aClass, constructor, classObjectMap, parameters); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void putInterfacesToInstanceEntry(final Class aClass, final Constructor constructor, final Map, Object> classObjectMap, final Object... parameters) throws Exception { + final Class[] interfaces = aClass.getInterfaces(); + final Object instance = constructor.newInstance(parameters); + if (interfaces.length == 0) { + classObjectMap.put(aClass, instance); + } else { + for (final Class anInterface : interfaces) { + classObjectMap.put(anInterface, instance); + } + } } @SuppressWarnings("unchecked") public T getBean(final Class aClass) { - return null; + System.out.println("beans = " + beans); + return (T) beans.stream() + .peek(System.out::println) + .filter(bean -> aClass.isAssignableFrom(bean.getClass())) + .findFirst() + .orElseThrow(() -> new RuntimeException("해당하는 클래스가 존재하지 않습니다.")); } } diff --git a/study/src/test/java/di/stage4/annotations/ClassPathScanner.java b/study/src/test/java/di/stage4/annotations/ClassPathScanner.java index 9dab1fd9c4..0d08e6c290 100644 --- a/study/src/test/java/di/stage4/annotations/ClassPathScanner.java +++ b/study/src/test/java/di/stage4/annotations/ClassPathScanner.java @@ -1,10 +1,18 @@ package di.stage4.annotations; +import org.reflections.Reflections; + +import java.util.HashSet; import java.util.Set; public class ClassPathScanner { public static Set> getAllClassesInPackage(final String packageName) { - return null; + final HashSet> classes = new HashSet<>(); + final Reflections reflections = new Reflections(packageName); + classes.addAll(reflections.getTypesAnnotatedWith(Repository.class)); + classes.addAll(reflections.getTypesAnnotatedWith(Service.class)); + classes.addAll(reflections.getTypesAnnotatedWith(Inject.class)); + return classes; } } diff --git a/study/src/test/java/di/stage4/annotations/DIContainer.java b/study/src/test/java/di/stage4/annotations/DIContainer.java index 9248ecad7e..f4c60a35ee 100644 --- a/study/src/test/java/di/stage4/annotations/DIContainer.java +++ b/study/src/test/java/di/stage4/annotations/DIContainer.java @@ -1,6 +1,14 @@ package di.stage4.annotations; +import java.lang.reflect.Constructor; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; /** * 스프링의 BeanFactory, ApplicationContext에 해당되는 클래스 @@ -10,15 +18,73 @@ class DIContainer { private final Set beans; public DIContainer(final Set> classes) { - this.beans = Set.of(); + beans = instantiateBeans(classes); + } + + private Set instantiateBeans(final Set> classes) { + final Map, Object> classObjectMap = new HashMap<>(); + final List> sortedClasses = classes.stream() + .sorted(Comparator.comparing(a -> a.getConstructors()[0].getParameterCount())) + .collect(Collectors.toList()); + System.out.println("sortedClasses = " + sortedClasses); + sortedClasses.forEach(aClass -> { + final Constructor[] constructors = aClass.getConstructors(); + Arrays.stream(constructors).forEachOrdered(constructor -> instantiateBean(aClass, constructor, classObjectMap)); + }); + return new HashSet<>(classObjectMap.values()); + } + + private static void instantiateBean(final Class aClass, final Constructor constructor, final Map, Object> classObjectMap) { + if (constructor.getParameterCount() == 0) { + try { + putInterfacesToInstanceEntry(aClass, constructor, classObjectMap); + } catch (Exception e) { + throw new RuntimeException(e); + } + } else { + findDependencyAndInstantiateBean(aClass, constructor, classObjectMap); + } + } + + private static void findDependencyAndInstantiateBean(final Class aClass, final Constructor constructor, final Map, Object> classObjectMap) { + final Class[] parameterTypes = constructor.getParameterTypes(); + final Object[] parameters = new Object[parameterTypes.length]; + for (int i = 0; i < parameterTypes.length; i++) { + final Class parameterType = parameterTypes[i]; + final Object parameter = classObjectMap.get(parameterType); + parameters[i] = parameter; + } + try { + putInterfacesToInstanceEntry(aClass, constructor, classObjectMap, parameters); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void putInterfacesToInstanceEntry(final Class aClass, final Constructor constructor, final Map, Object> classObjectMap, final Object... parameters) throws Exception { + final Class[] interfaces = aClass.getInterfaces(); + final Object instance = constructor.newInstance(parameters); + if (interfaces.length == 0) { + classObjectMap.put(aClass, instance); + } else { + for (final Class anInterface : interfaces) { + classObjectMap.put(anInterface, instance); + } + } } public static DIContainer createContainerForPackage(final String rootPackageName) { - return null; + final Set> allClassesInPackage = ClassPathScanner.getAllClassesInPackage(rootPackageName); + return new DIContainer(allClassesInPackage); } @SuppressWarnings("unchecked") public T getBean(final Class aClass) { - return null; + System.out.println("beans = " + beans); + return (T) beans.stream() + .peek(System.out::println) + .filter(bean -> aClass.isAssignableFrom(bean.getClass())) + .findFirst() + .orElseThrow(() -> new RuntimeException("해당하는 클래스가 존재하지 않습니다.")); } } From 7bd858e5479480b03aec6de7bc11da55e788c753 Mon Sep 17 00:00:00 2001 From: echo724 Date: Thu, 28 Sep 2023 21:43:52 +0900 Subject: [PATCH 6/8] =?UTF-8?q?study:=20DI=20=EC=BB=A8=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EB=84=88=20=EC=8A=A4=ED=84=B0=EB=94=94=20step4=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stage4/annotations/ClassPathScanner.java | 1 - .../di/stage4/annotations/DIContainer.java | 93 ++++++++++--------- 2 files changed, 50 insertions(+), 44 deletions(-) diff --git a/study/src/test/java/di/stage4/annotations/ClassPathScanner.java b/study/src/test/java/di/stage4/annotations/ClassPathScanner.java index 0d08e6c290..68b9dc94f6 100644 --- a/study/src/test/java/di/stage4/annotations/ClassPathScanner.java +++ b/study/src/test/java/di/stage4/annotations/ClassPathScanner.java @@ -12,7 +12,6 @@ public static Set> getAllClassesInPackage(final String packageName) { final Reflections reflections = new Reflections(packageName); classes.addAll(reflections.getTypesAnnotatedWith(Repository.class)); classes.addAll(reflections.getTypesAnnotatedWith(Service.class)); - classes.addAll(reflections.getTypesAnnotatedWith(Inject.class)); return classes; } } diff --git a/study/src/test/java/di/stage4/annotations/DIContainer.java b/study/src/test/java/di/stage4/annotations/DIContainer.java index f4c60a35ee..248e359fc7 100644 --- a/study/src/test/java/di/stage4/annotations/DIContainer.java +++ b/study/src/test/java/di/stage4/annotations/DIContainer.java @@ -1,6 +1,7 @@ package di.stage4.annotations; import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; @@ -24,58 +25,64 @@ public DIContainer(final Set> classes) { private Set instantiateBeans(final Set> classes) { final Map, Object> classObjectMap = new HashMap<>(); final List> sortedClasses = classes.stream() - .sorted(Comparator.comparing(a -> a.getConstructors()[0].getParameterCount())) + .sorted(Comparator + .comparing(a -> ((Class) a).getDeclaredFields().length) + .reversed() + ) .collect(Collectors.toList()); System.out.println("sortedClasses = " + sortedClasses); - sortedClasses.forEach(aClass -> { - final Constructor[] constructors = aClass.getConstructors(); - Arrays.stream(constructors).forEachOrdered(constructor -> instantiateBean(aClass, constructor, classObjectMap)); - }); - return new HashSet<>(classObjectMap.values()); - } - - private static void instantiateBean(final Class aClass, final Constructor constructor, final Map, Object> classObjectMap) { - if (constructor.getParameterCount() == 0) { - try { - putInterfacesToInstanceEntry(aClass, constructor, classObjectMap); - } catch (Exception e) { - throw new RuntimeException(e); + for (final Class sortedClass : sortedClasses) { + if (sortedClass.getConstructors().length != 0) { + final Constructor defaultConstructor = sortedClass.getConstructors()[0]; + if (defaultConstructor.getParameterCount() != 0) { + final Class[] parameterTypes = defaultConstructor.getParameterTypes(); + final Object[] parameters = new Object[parameterTypes.length]; + for (int i = 0; i < parameterTypes.length; i++) { + parameters[i] = classObjectMap.get(parameterTypes[i]); + } + try { + putInterfaceOrClass(sortedClass, classObjectMap, defaultConstructor, parameters); + } catch (Exception e) { + throw new RuntimeException(e); + } + } else { + final Constructor constructor = sortedClass.getConstructors()[0]; + try { + putInterfaceOrClass(sortedClass, classObjectMap, constructor); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } else if (sortedClass.getDeclaredFields().length != 0) { + Arrays.stream(sortedClass.getDeclaredFields()) + .filter(field -> field.isAnnotationPresent(Inject.class)) + .forEach(field -> { + try { + field.setAccessible(true); + final Constructor declaredConstructor = sortedClass.getDeclaredConstructors()[0]; + declaredConstructor.setAccessible(true); + final Object instance = declaredConstructor.newInstance(); + field.set(instance, classObjectMap.get(field.getType())); + classObjectMap.put(sortedClass, instance); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); } - } else { - findDependencyAndInstantiateBean(aClass, constructor, classObjectMap); - } - } - - private static void findDependencyAndInstantiateBean(final Class aClass, final Constructor constructor, final Map, Object> classObjectMap) { - final Class[] parameterTypes = constructor.getParameterTypes(); - final Object[] parameters = new Object[parameterTypes.length]; - for (int i = 0; i < parameterTypes.length; i++) { - final Class parameterType = parameterTypes[i]; - final Object parameter = classObjectMap.get(parameterType); - parameters[i] = parameter; - } - try { - putInterfacesToInstanceEntry(aClass, constructor, classObjectMap, parameters); - } catch (Exception e) { - throw new RuntimeException(e); } + return new HashSet<>(classObjectMap.values()); } - private static void putInterfacesToInstanceEntry(final Class aClass, final Constructor constructor, final Map, Object> classObjectMap, final Object... parameters) throws Exception { - final Class[] interfaces = aClass.getInterfaces(); - final Object instance = constructor.newInstance(parameters); - if (interfaces.length == 0) { - classObjectMap.put(aClass, instance); - } else { - for (final Class anInterface : interfaces) { - classObjectMap.put(anInterface, instance); - } + private static void putInterfaceOrClass(final Class sortedClass, final Map, Object> classObjectMap, final Constructor constructor, final Object... parameters) throws InstantiationException, IllegalAccessException, InvocationTargetException { + final Class[] interfaces = sortedClass.getInterfaces(); + for (final Class anInterface : interfaces) { + classObjectMap.put(anInterface, constructor.newInstance(parameters)); } + classObjectMap.put(sortedClass, constructor.newInstance(parameters)); } - public static DIContainer createContainerForPackage(final String rootPackageName) { - final Set> allClassesInPackage = ClassPathScanner.getAllClassesInPackage(rootPackageName); - return new DIContainer(allClassesInPackage); + public static DIContainer createContainerForPackage(final String packageName) { + return new DIContainer(ClassPathScanner.getAllClassesInPackage(packageName)); } @SuppressWarnings("unchecked") From a423a954fbfa0163753cfa28d5ab9b08cca2e742 Mon Sep 17 00:00:00 2001 From: echo724 Date: Thu, 28 Sep 2023 21:50:51 +0900 Subject: [PATCH 7/8] =?UTF-8?q?refactor:=20Json=20Body=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=B6=80=EB=B6=84=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/techcourse/controller/UserController.java | 2 +- .../springframework/web/servlet/view/JsonView.java | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/techcourse/controller/UserController.java b/app/src/main/java/com/techcourse/controller/UserController.java index 8edea242f0..0c57470009 100644 --- a/app/src/main/java/com/techcourse/controller/UserController.java +++ b/app/src/main/java/com/techcourse/controller/UserController.java @@ -29,4 +29,4 @@ public ModelAndView show(HttpServletRequest request, HttpServletResponse respons modelAndView.addObject("user", user); return modelAndView; } -} \ No newline at end of file +} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JsonView.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JsonView.java index dc1ef9bf87..b946c0a1bf 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JsonView.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JsonView.java @@ -1,5 +1,6 @@ package webmvc.org.springframework.web.servlet.view; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -11,16 +12,21 @@ public class JsonView implements View { @Override public void render(final Map model, final HttpServletRequest request, HttpServletResponse response) throws Exception { - ObjectMapper objectMapper = new ObjectMapper(); + String body = createJsonBody(model); + response.setContentType("application/json"); + response.setCharacterEncoding("utf-8"); + response.getWriter().write(body); + } + + private String createJsonBody(final Map model) throws JsonProcessingException { + final ObjectMapper objectMapper = new ObjectMapper(); String json = ""; if (model.size() == 1) { json = objectMapper.writeValueAsString(model.get(model.keySet().iterator().next())); } else { json = objectMapper.writeValueAsString(model); } - response.setContentType("application/json"); - response.setCharacterEncoding("utf-8"); - response.getWriter().write(json); + return json; } @Override From 68233c45da61c7ae1c5d44c1a7e5fc84ada03e10 Mon Sep 17 00:00:00 2001 From: echo724 Date: Thu, 28 Sep 2023 22:02:18 +0900 Subject: [PATCH 8/8] =?UTF-8?q?study:=20DIContainer=20=EB=A6=AC=ED=8E=99?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../di/stage4/annotations/DIContainer.java | 97 +++++++++++-------- 1 file changed, 59 insertions(+), 38 deletions(-) diff --git a/study/src/test/java/di/stage4/annotations/DIContainer.java b/study/src/test/java/di/stage4/annotations/DIContainer.java index 248e359fc7..cd383aa6c2 100644 --- a/study/src/test/java/di/stage4/annotations/DIContainer.java +++ b/study/src/test/java/di/stage4/annotations/DIContainer.java @@ -24,56 +24,77 @@ public DIContainer(final Set> classes) { private Set instantiateBeans(final Set> classes) { final Map, Object> classObjectMap = new HashMap<>(); - final List> sortedClasses = classes.stream() + final List> sortedClasses = sortClassByLessDependnecies(classes); + for (final Class sortedClass : sortedClasses) { + if (sortedClass.getConstructors().length != 0) { + injectConstructorDependency(sortedClass, classObjectMap); + } + injectFieldDependency(sortedClass, classObjectMap); + } + return new HashSet<>(classObjectMap.values()); + } + + private List> sortClassByLessDependnecies(final Set> classes) { + return classes.stream() .sorted(Comparator .comparing(a -> ((Class) a).getDeclaredFields().length) .reversed() ) .collect(Collectors.toList()); - System.out.println("sortedClasses = " + sortedClasses); - for (final Class sortedClass : sortedClasses) { - if (sortedClass.getConstructors().length != 0) { - final Constructor defaultConstructor = sortedClass.getConstructors()[0]; - if (defaultConstructor.getParameterCount() != 0) { - final Class[] parameterTypes = defaultConstructor.getParameterTypes(); - final Object[] parameters = new Object[parameterTypes.length]; - for (int i = 0; i < parameterTypes.length; i++) { - parameters[i] = classObjectMap.get(parameterTypes[i]); - } - try { - putInterfaceOrClass(sortedClass, classObjectMap, defaultConstructor, parameters); - } catch (Exception e) { - throw new RuntimeException(e); - } - } else { - final Constructor constructor = sortedClass.getConstructors()[0]; + } + + private void injectFieldDependency(final Class sortedClass, final Map, Object> classObjectMap) { + Arrays.stream(sortedClass.getDeclaredFields()) + .filter(field -> field.isAnnotationPresent(Inject.class)) + .forEach(field -> { try { - putInterfaceOrClass(sortedClass, classObjectMap, constructor); + field.setAccessible(true); + final Object instance = classObjectMap.getOrDefault(sortedClass, instantiatePrivateConstructor(sortedClass)); + field.set(instance, classObjectMap.get(field.getType())); + classObjectMap.put(sortedClass, instance); } catch (Exception e) { throw new RuntimeException(e); } - } - } else if (sortedClass.getDeclaredFields().length != 0) { - Arrays.stream(sortedClass.getDeclaredFields()) - .filter(field -> field.isAnnotationPresent(Inject.class)) - .forEach(field -> { - try { - field.setAccessible(true); - final Constructor declaredConstructor = sortedClass.getDeclaredConstructors()[0]; - declaredConstructor.setAccessible(true); - final Object instance = declaredConstructor.newInstance(); - field.set(instance, classObjectMap.get(field.getType())); - classObjectMap.put(sortedClass, instance); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - } + }); + } + + private Object instantiatePrivateConstructor(final Class sortedClass) throws InstantiationException, IllegalAccessException, InvocationTargetException { + final Constructor declaredConstructor = sortedClass.getDeclaredConstructors()[0]; + declaredConstructor.setAccessible(true); + return declaredConstructor.newInstance(); + } + + private void injectConstructorDependency(final Class sortedClass, final Map, Object> classObjectMap) { + final Constructor defaultConstructor = sortedClass.getConstructors()[0]; + if (defaultConstructor.getParameterCount() != 0) { + instantiateBeanWithParameters(sortedClass, classObjectMap, defaultConstructor); + } else { + instantiateBean(sortedClass, classObjectMap, defaultConstructor); + } + } + + private void instantiateBean(final Class sortedClass, final Map, Object> classObjectMap, final Constructor defaultConstructor) { + try { + putInterfaceOrClass(sortedClass, classObjectMap, defaultConstructor); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private void instantiateBeanWithParameters(final Class sortedClass, final Map, Object> classObjectMap, final Constructor defaultConstructor) { + final Class[] parameterTypes = defaultConstructor.getParameterTypes(); + final Object[] parameters = new Object[parameterTypes.length]; + for (int i = 0; i < parameterTypes.length; i++) { + parameters[i] = classObjectMap.get(parameterTypes[i]); + } + try { + putInterfaceOrClass(sortedClass, classObjectMap, defaultConstructor, parameters); + } catch (Exception e) { + throw new RuntimeException(e); } - return new HashSet<>(classObjectMap.values()); } - private static void putInterfaceOrClass(final Class sortedClass, final Map, Object> classObjectMap, final Constructor constructor, final Object... parameters) throws InstantiationException, IllegalAccessException, InvocationTargetException { + private void putInterfaceOrClass(final Class sortedClass, final Map, Object> classObjectMap, final Constructor constructor, final Object... parameters) throws InstantiationException, IllegalAccessException, InvocationTargetException { final Class[] interfaces = sortedClass.getInterfaces(); for (final Class anInterface : interfaces) { classObjectMap.put(anInterface, constructor.newInstance(parameters));