diff --git a/app/src/main/java/com/techcourse/DispatcherServletInitializer.java b/app/src/main/java/com/techcourse/DispatcherServletInitializer.java index 6e814cdd25..51d9ca26cb 100644 --- a/app/src/main/java/com/techcourse/DispatcherServletInitializer.java +++ b/app/src/main/java/com/techcourse/DispatcherServletInitializer.java @@ -1,28 +1,33 @@ package com.techcourse; +import context.org.springframework.context.ApplicationContext; import jakarta.servlet.ServletContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import web.org.springframework.web.WebApplicationInitializer; +import webmvc.org.springframework.web.servlet.DispatcherServlet; /** - * Base class for {@link WebApplicationInitializer} - * implementations that register a {@link DispatcherServlet} in the servlet context. + * Base class for {@link WebApplicationInitializer} implementations that register a {@link DispatcherServlet} in the + * servlet context. */ public class DispatcherServletInitializer implements WebApplicationInitializer { private static final Logger log = LoggerFactory.getLogger(DispatcherServletInitializer.class); - + private static final String APPLICATION_BASE_PACKAGE = "com.techcourse"; + private static final String INTERNAL_BASE_PACKAGE = "webmvc.org.springframework.web.servlet"; private static final String DEFAULT_SERVLET_NAME = "dispatcher"; @Override public void onStartup(final ServletContext servletContext) { - final var dispatcherServlet = new DispatcherServlet(); + final ApplicationContext applicationContext = new ApplicationContext(APPLICATION_BASE_PACKAGE, + INTERNAL_BASE_PACKAGE); + final var dispatcherServlet = new DispatcherServlet(applicationContext); final var registration = servletContext.addServlet(DEFAULT_SERVLET_NAME, dispatcherServlet); if (registration == null) { throw new IllegalStateException("Failed to register servlet with name '" + DEFAULT_SERVLET_NAME + "'. " + - "Check if there is another servlet registered under the same name."); + "Check if there is another servlet registered under the same name."); } registration.setLoadOnStartup(1); diff --git a/app/src/main/java/com/techcourse/HandlerAdapterFactory.java b/app/src/main/java/com/techcourse/HandlerAdapterFactory.java deleted file mode 100644 index 542ef51a32..0000000000 --- a/app/src/main/java/com/techcourse/HandlerAdapterFactory.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.techcourse; - -import java.util.List; -import webmvc.org.springframework.web.servlet.mvc.tobe.AnnotationHandlerAdapter; -import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerAdapter; -import webmvc.org.springframework.web.servlet.mvc.tobe.ManualHandlerAdapter; - -public class HandlerAdapterFactory { - - public static List getHandlerAdapters() { - return List.of( - new AnnotationHandlerAdapter(), - new ManualHandlerAdapter() - ); - } -} diff --git a/app/src/main/java/com/techcourse/HandlerMappingFactory.java b/app/src/main/java/com/techcourse/HandlerMappingFactory.java deleted file mode 100644 index 2d2b2219e5..0000000000 --- a/app/src/main/java/com/techcourse/HandlerMappingFactory.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.techcourse; - -import java.util.List; -import webmvc.org.springframework.web.servlet.mvc.tobe.AnnotationHandlerMapping; -import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerMapping; - -public class HandlerMappingFactory { - - public static List getHandlerMappings(final Object... basePackagePath) { - return List.of( - new AnnotationHandlerMapping(basePackagePath), - new ManualHandlerMapping() - ); - } -} 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 2da0caf5ce..0000000000 --- a/app/src/main/java/com/techcourse/ManualHandlerMapping.java +++ /dev/null @@ -1,42 +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 com.techcourse.controller.RegisterViewController; -import jakarta.servlet.http.HttpServletRequest; -import java.util.HashMap; -import java.util.Map; -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; - -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/view", new RegisterViewController()); - controllers.put("/register", new RegisterController()); - - log.info("Initialized Handler Mapping!"); - controllers.keySet() - .forEach(path -> log.info("Path : {}, Controller : {}", path, controllers.get(path).getClass())); - } - - @Override - public Object getHandler(final HttpServletRequest request) { - final String requestURI = request.getRequestURI(); - log.debug("Request Mapping Uri : {}", requestURI); - 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..1459813e4f --- /dev/null +++ b/app/src/main/java/com/techcourse/controller/ForwardController.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.view.ModelAndView; +import webmvc.org.springframework.web.servlet.view.JspView; + +@Controller +public class ForwardController { + + @RequestMapping(value = "/", method = RequestMethod.GET) + public ModelAndView showIndex(final HttpServletRequest request, final HttpServletResponse response) { + return new ModelAndView(new JspView("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..cafea258f4 100644 --- a/app/src/main/java/com/techcourse/controller/LoginController.java +++ b/app/src/main/java/com/techcourse/controller/LoginController.java @@ -2,36 +2,41 @@ 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.view.ModelAndView; +import webmvc.org.springframework.web.servlet.view.RedirectView; -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 { - if (UserSession.isLoggedIn(req.getSession())) { - return "redirect:/index.jsp"; + @RequestMapping(value = "/login", method = RequestMethod.POST) + public ModelAndView login(final HttpServletRequest request, final HttpServletResponse response) { + if (UserSession.isLoggedIn(request.getSession())) { + return new ModelAndView(new RedirectView("/index.jsp")); } - return InMemoryUserRepository.findByAccount(req.getParameter("account")) - .map(user -> { - log.info("User : {}", user); - return login(req, user); - }) - .orElse("redirect:/401.jsp"); + return InMemoryUserRepository.findByAccount(request.getParameter("account")) + .map(user -> { + log.info("User : {}", user); + return checkPassword(request, user); + }) + .orElse(new ModelAndView(new RedirectView("/401.jsp"))); } - private String login(final HttpServletRequest request, final User user) { + private ModelAndView checkPassword(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 new ModelAndView(new RedirectView("/index.jsp")); } - return "redirect:/401.jsp"; + return new ModelAndView(new RedirectView("/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..3f2167723a 100644 --- a/app/src/main/java/com/techcourse/controller/LoginViewController.java +++ b/app/src/main/java/com/techcourse/controller/LoginViewController.java @@ -1,22 +1,28 @@ 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.view.ModelAndView; +import webmvc.org.springframework.web.servlet.view.JspView; +import webmvc.org.springframework.web.servlet.view.RedirectView; -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 { - return UserSession.getUserFrom(req.getSession()) - .map(user -> { - log.info("logged in {}", user.getAccount()); - return "redirect:/index.jsp"; - }) - .orElse("/login.jsp"); + @RequestMapping(value = "/login/view", method = RequestMethod.GET) + public ModelAndView show(final HttpServletRequest request, final HttpServletResponse response) { + return UserSession.getUserFrom(request.getSession()) + .map(user -> { + log.info("logged in {}", user.getAccount()); + return new ModelAndView(new RedirectView("/index.jsp")); + }) + .orElse(new ModelAndView(new JspView("/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..806c4e3393 100644 --- a/app/src/main/java/com/techcourse/controller/LogoutController.java +++ b/app/src/main/java/com/techcourse/controller/LogoutController.java @@ -1,15 +1,20 @@ 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.view.ModelAndView; +import webmvc.org.springframework.web.servlet.view.RedirectView; -public class LogoutController implements Controller { +@Controller +public class LogoutController { - @Override - public String execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception { - final var session = req.getSession(); + @RequestMapping(value = "/logout", method = RequestMethod.GET) + public ModelAndView logout(final HttpServletRequest request, final HttpServletResponse response) { + final var session = request.getSession(); session.removeAttribute(UserSession.SESSION_KEY); - return "redirect:/"; + return new ModelAndView(new RedirectView("/index.jsp")); } } diff --git a/app/src/main/java/com/techcourse/controller/RegisterController.java b/app/src/main/java/com/techcourse/controller/RegisterController.java index da62e5e8e9..a74bf28dc1 100644 --- a/app/src/main/java/com/techcourse/controller/RegisterController.java +++ b/app/src/main/java/com/techcourse/controller/RegisterController.java @@ -2,20 +2,25 @@ 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.view.ModelAndView; +import webmvc.org.springframework.web.servlet.view.RedirectView; -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 register(final HttpServletRequest request, final HttpServletResponse response) { final var user = new User(2, - req.getParameter("account"), - req.getParameter("password"), - req.getParameter("email")); + request.getParameter("account"), + request.getParameter("password"), + request.getParameter("email")); InMemoryUserRepository.save(user); - return "redirect:/index.jsp"; + return new ModelAndView(new RedirectView("/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 136962136d..d9a29a5358 100644 --- a/app/src/main/java/com/techcourse/controller/RegisterViewController.java +++ b/app/src/main/java/com/techcourse/controller/RegisterViewController.java @@ -1,13 +1,18 @@ 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.view.ModelAndView; +import webmvc.org.springframework.web.servlet.view.JspView; -public class RegisterViewController implements Controller { +@Controller +public class RegisterViewController { - @Override - public String execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception { - return "/register.jsp"; + @RequestMapping(value = "/register/view", method = RequestMethod.GET) + public ModelAndView show(final HttpServletRequest request, final HttpServletResponse response) { + return new ModelAndView(new JspView("/register.jsp")); } } diff --git a/mvc/src/main/java/context/org/springframework/context/ApplicationContext.java b/mvc/src/main/java/context/org/springframework/context/ApplicationContext.java new file mode 100644 index 0000000000..0458c37653 --- /dev/null +++ b/mvc/src/main/java/context/org/springframework/context/ApplicationContext.java @@ -0,0 +1,122 @@ +package context.org.springframework.context; + +import context.org.springframework.stereotype.Controller; +import core.org.springframework.util.ReflectionUtils; +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +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; +import org.reflections.Reflections; +import webmvc.org.springframework.web.servlet.mvc.handler.adapter.HandlerAdapter; +import webmvc.org.springframework.web.servlet.mvc.handler.mapping.HandlerMapping; + +public class ApplicationContext { + + private static final List> beanClasses = List.of( + HandlerMapping.class, + HandlerAdapter.class, + ApplicationContextAware.class + ); + private static final List> beanAnnotations = List.of( + Controller.class + ); + + private final Map, Object> beans; + + public ApplicationContext(final String... basePackages) { + this.beans = new HashMap<>(); + Arrays.stream(basePackages) + .forEach(this::registerBeans); + initialize(); + } + + public void registerBeans(final String basePackage) { + final Reflections reflections = new Reflections(basePackage); + beanClasses.stream() + .map(reflections::getSubTypesOf) + .forEach(this::registerBeans); + beanAnnotations.stream() + .map(reflections::getTypesAnnotatedWith) + .forEach(this::registerBeans); + } + + private void registerBeans(final Set> objectClasses) { + objectClasses.stream() + .filter(clazz -> !clazz.isInterface()) + .forEach(this::registerBean); + } + + private void registerBean(final Class clazz) { + try { + final Constructor constructor = ReflectionUtils.accessibleConstructor(clazz); + final Object bean = constructor.newInstance(); + beans.put(clazz, bean); + } catch (NoSuchMethodException e) { + throw new RuntimeException(clazz.getName() + " 기본 생성자가 존재하지 않습니다."); + } catch (InvocationTargetException e) { + throw new RuntimeException("기본 생성자를 호출할 수 없습니다."); + } catch (InstantiationException e) { + throw new RuntimeException("인스턴스를 생성할 수 없습니다."); + } catch (IllegalAccessException e) { + throw new RuntimeException("기본 생성자에 접근할 수 없습니다."); + } + } + + public void initialize() { + setFields(); + setApplicationContext(); + } + + private void setFields() { + for (final Class clazz : beans.keySet()) { + final Field[] fields = clazz.getDeclaredFields(); + for (final Field field : fields) { + setField(clazz, field); + } + } + } + + private void setField(final Class clazz, final Field field) { + final Class type = field.getType(); + final Object bean = getBean(type); + if (bean != null) { + try { + field.setAccessible(true); + field.set(beans.get(clazz), bean); + } catch (IllegalAccessException e) { + throw new RuntimeException("필드에 접근할 수 없습니다."); + } + } + } + + public void setApplicationContext() { + beans.values().stream() + .filter(bean -> bean instanceof ApplicationContextAware) + .forEach(bean -> ((ApplicationContextAware) bean).setApplicationContext(this)); + } + + public Object getBean(final Class classType) { + return beans.get(classType); + } + + public List getBeansOfAnnotationType(final Class annotationType) { + return beans.keySet().stream() + .filter(clazz -> clazz.isAnnotationPresent(annotationType)) + .map(beans::get) + .collect(Collectors.toList()); + } + + public List getBeansOfType(final Class classType) { + return beans.keySet().stream() + .filter(classType::isAssignableFrom) + .map(beans::get) + .map(classType::cast) + .collect(Collectors.toList()); + } +} diff --git a/mvc/src/main/java/context/org/springframework/context/ApplicationContextAware.java b/mvc/src/main/java/context/org/springframework/context/ApplicationContextAware.java new file mode 100644 index 0000000000..d8561400fd --- /dev/null +++ b/mvc/src/main/java/context/org/springframework/context/ApplicationContextAware.java @@ -0,0 +1,14 @@ +package context.org.springframework.context; + +public class ApplicationContextAware { + + private ApplicationContext applicationContext; + + public void setApplicationContext(final ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + public ApplicationContext getApplicationContext() { + return applicationContext; + } +} diff --git a/app/src/main/java/com/techcourse/DispatcherServlet.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/DispatcherServlet.java similarity index 67% rename from app/src/main/java/com/techcourse/DispatcherServlet.java rename to mvc/src/main/java/webmvc/org/springframework/web/servlet/DispatcherServlet.java index 2036cfb3e8..9acedea719 100644 --- a/app/src/main/java/com/techcourse/DispatcherServlet.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/DispatcherServlet.java @@ -1,4 +1,4 @@ -package com.techcourse; +package webmvc.org.springframework.web.servlet; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; @@ -10,23 +10,25 @@ import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import webmvc.org.springframework.web.servlet.ModelAndView; -import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerAdapter; -import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerMapping; -import webmvc.org.springframework.web.servlet.view.JspView; +import context.org.springframework.context.ApplicationContext; +import webmvc.org.springframework.web.servlet.view.ModelAndView; +import webmvc.org.springframework.web.servlet.mvc.handler.adapter.HandlerAdapter; +import webmvc.org.springframework.web.servlet.mvc.handler.mapping.HandlerMapping; public class DispatcherServlet extends HttpServlet { private static final long serialVersionUID = 1L; + private static final Logger log = LoggerFactory.getLogger(DispatcherServlet.class); - private static final String BASE_PACKAGE_PATH = "com.techcourse"; private final List handlerMappings; private final List handlerAdapters; + private final ApplicationContext applicationContext; - public DispatcherServlet() { - handlerMappings = new ArrayList<>(); - handlerAdapters = new ArrayList<>(); + public DispatcherServlet(final ApplicationContext applicationContext) { + this.handlerMappings = new ArrayList<>(); + this.handlerAdapters = new ArrayList<>(); + this.applicationContext = applicationContext; } @Override @@ -36,19 +38,22 @@ public void init() { } private void initHandlerMappings() { - final List handlerMappingInstances = HandlerMappingFactory.getHandlerMappings(BASE_PACKAGE_PATH) + final List handlerMappingInstances = applicationContext.getBeansOfType(HandlerMapping.class) .stream() + .map(HandlerMapping.class::cast) .peek(HandlerMapping::initialize) .collect(Collectors.toList()); handlerMappings.addAll(handlerMappingInstances); } private void initHandlerAdapters() { - final List handlerAdapterInstances = HandlerAdapterFactory.getHandlerAdapters(); + final List handlerAdapterInstances = applicationContext.getBeansOfType(HandlerAdapter.class) + .stream() + .map(HandlerAdapter.class::cast) + .collect(Collectors.toList()); handlerAdapters.addAll(handlerAdapterInstances); } - @Override protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException { @@ -66,7 +71,7 @@ private void process(final HttpServletRequest request, final HttpServletResponse final Object handler = getHandler(request); final HandlerAdapter handlerAdapter = getHandlerAdapter(handler); final ModelAndView modelAndView = handlerAdapter.handle(request, response, handler); - move(modelAndView, request, response); + modelAndView.getView().render(modelAndView.getModel(), request, response); } private Object getHandler(final HttpServletRequest request) { @@ -83,19 +88,4 @@ private HandlerAdapter getHandlerAdapter(final Object handler) { .findFirst() .orElseThrow(() -> new NoSuchElementException("해당하는 HandlerAdapter가 없습니다.")); } - - private void move( - final ModelAndView modelAndView, - final HttpServletRequest request, - final HttpServletResponse response - ) throws Exception { - final String viewName = modelAndView.getViewName(); - 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/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/AnnotationHandlerAdapter.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/handler/adapter/AnnotationHandlerAdapter.java similarity index 73% rename from mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerAdapter.java rename to mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/handler/adapter/AnnotationHandlerAdapter.java index 3a1fb33c8a..19526e657f 100644 --- 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/handler/adapter/AnnotationHandlerAdapter.java @@ -1,8 +1,9 @@ -package webmvc.org.springframework.web.servlet.mvc.tobe; +package webmvc.org.springframework.web.servlet.mvc.handler.adapter; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import webmvc.org.springframework.web.servlet.ModelAndView; +import webmvc.org.springframework.web.servlet.view.ModelAndView; +import webmvc.org.springframework.web.servlet.mvc.handler.mapping.HandlerExecution; public class AnnotationHandlerAdapter implements HandlerAdapter { diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerAdapter.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/handler/adapter/HandlerAdapter.java similarity index 68% rename from mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerAdapter.java rename to mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/handler/adapter/HandlerAdapter.java index 13351a1575..f7331b9060 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerAdapter.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/handler/adapter/HandlerAdapter.java @@ -1,8 +1,8 @@ -package webmvc.org.springframework.web.servlet.mvc.tobe; +package webmvc.org.springframework.web.servlet.mvc.handler.adapter; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import webmvc.org.springframework.web.servlet.ModelAndView; +import webmvc.org.springframework.web.servlet.view.ModelAndView; public interface HandlerAdapter { 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/handler/mapping/AnnotationHandlerMapping.java similarity index 60% rename from mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMapping.java rename to mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/handler/mapping/AnnotationHandlerMapping.java index 0e977cc7a6..6eaa9558ab 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/handler/mapping/AnnotationHandlerMapping.java @@ -1,66 +1,40 @@ -package webmvc.org.springframework.web.servlet.mvc.tobe; +package webmvc.org.springframework.web.servlet.mvc.handler.mapping; import context.org.springframework.stereotype.Controller; import jakarta.servlet.http.HttpServletRequest; -import java.lang.reflect.InvocationTargetException; 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; -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 context.org.springframework.context.ApplicationContextAware; -public class AnnotationHandlerMapping implements HandlerMapping { +public class AnnotationHandlerMapping extends ApplicationContextAware implements HandlerMapping { private static final Logger log = LoggerFactory.getLogger(AnnotationHandlerMapping.class); - private final List basePackages; private final Map handlerExecutions; - public AnnotationHandlerMapping(final Object... basePackages) { - this.basePackages = List.of(basePackages); + public AnnotationHandlerMapping() { this.handlerExecutions = new HashMap<>(); } @Override public void initialize() { log.info("Initialized AnnotationHandlerMapping!"); - makeHandlerExecutions(basePackages); + createHandlerExecution(); } - private void makeHandlerExecutions(final List basePackages) { - for (final Object basePackage : basePackages) { - final Reflections reflections = new Reflections(basePackage); - final var classes = reflections.getTypesAnnotatedWith(Controller.class); - putHandlerExecutionsByControllers(classes); - } - } - - private void putHandlerExecutionsByControllers(final Set> classes) { - for (final Class clazz : classes) { - final Object instance = getNewInstance(clazz); - final List methods = getMethodsWithAnnotation(clazz); - putHandlerExecutionsByMethods(instance, methods); - } - } - - private Object getNewInstance(final Class clazz) { - try { - return clazz.getConstructor().newInstance(); - } catch (InstantiationException e) { - throw new RuntimeException("클래스 객체를 인스턴스화 할 수 없습니다."); - } catch (IllegalAccessException e) { - throw new RuntimeException("클래스 객체의 생성자에 접근할 수 없습니다."); - } catch (InvocationTargetException e) { - throw new RuntimeException("클래스 객체의 생성자를 호출할 수 없습니다."); - } catch (NoSuchMethodException e) { - throw new RuntimeException("클래스 객체의 생성자를 찾을 수 없습니다."); + private void createHandlerExecution() { + final List handlerExecutions = getApplicationContext().getBeansOfAnnotationType(Controller.class); + for (final Object handlerExecution : handlerExecutions) { + final List methods = getMethodsWithAnnotation(handlerExecution.getClass()); + putHandlerExecutionsByMethods(handlerExecution, methods); } } 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/handler/mapping/HandlerExecution.java similarity index 81% rename from mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecution.java rename to mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/handler/mapping/HandlerExecution.java index 41a9df7710..e1b1527101 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/handler/mapping/HandlerExecution.java @@ -1,9 +1,9 @@ -package webmvc.org.springframework.web.servlet.mvc.tobe; +package webmvc.org.springframework.web.servlet.mvc.handler.mapping; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.lang.reflect.Method; -import webmvc.org.springframework.web.servlet.ModelAndView; +import webmvc.org.springframework.web.servlet.view.ModelAndView; public class HandlerExecution { diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerKey.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/handler/mapping/HandlerKey.java similarity index 93% rename from mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerKey.java rename to mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/handler/mapping/HandlerKey.java index 30d3c780ff..1937f9c231 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerKey.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/handler/mapping/HandlerKey.java @@ -1,4 +1,4 @@ -package webmvc.org.springframework.web.servlet.mvc.tobe; +package webmvc.org.springframework.web.servlet.mvc.handler.mapping; import web.org.springframework.web.bind.annotation.RequestMethod; 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/handler/mapping/HandlerMapping.java similarity index 71% 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/handler/mapping/HandlerMapping.java index ba0798f4da..a9cf389211 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/handler/mapping/HandlerMapping.java @@ -1,4 +1,4 @@ -package webmvc.org.springframework.web.servlet.mvc.tobe; +package webmvc.org.springframework.web.servlet.mvc.handler.mapping; import jakarta.servlet.http.HttpServletRequest; diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/ManualHandlerAdapter.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/ManualHandlerAdapter.java deleted file mode 100644 index 88f73ded3e..0000000000 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/ManualHandlerAdapter.java +++ /dev/null @@ -1,27 +0,0 @@ -package webmvc.org.springframework.web.servlet.mvc.tobe; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import webmvc.org.springframework.web.servlet.ModelAndView; -import webmvc.org.springframework.web.servlet.mvc.asis.Controller; -import webmvc.org.springframework.web.servlet.view.JspView; - -public class ManualHandlerAdapter implements HandlerAdapter { - - @Override - public boolean supports(final Object handler) { - return handler instanceof Controller; - } - - @Override - public ModelAndView handle( - final HttpServletRequest request, - final HttpServletResponse response, - final Object handler - ) - throws Exception { - final Controller controller = (Controller) handler; - final String path = controller.execute(request, response); - return new ModelAndView(new JspView(path)); - } -} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JsonView.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JsonView.java index 2c0ba3fc3b..66cdf00729 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,19 +1,29 @@ 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; -import webmvc.org.springframework.web.servlet.View; - import java.util.Map; +import web.org.springframework.http.MediaType; public class JsonView implements View { + private static final ObjectMapper objectMapper = new ObjectMapper(); + @Override - public void render(final Map model, final HttpServletRequest request, HttpServletResponse response) throws Exception { + public void render(final Map model, final HttpServletRequest request, HttpServletResponse response) + throws Exception { + final String body = writeJson(model); + response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); + response.getWriter().write(body); } - @Override - public String getName() { - return null; + private String writeJson(final Map objects) { + try { + return objectMapper.writeValueAsString(objects); + } catch (JsonProcessingException e) { + throw new RuntimeException("Json 객체 변환 과정에서 예외가 발생했습니다."); + } } } 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 aaa69da116..a00f905c46 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 @@ -2,17 +2,13 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import webmvc.org.springframework.web.servlet.View; - -import java.util.Map; public class JspView implements View { private static final Logger log = LoggerFactory.getLogger(JspView.class); - - public static final String REDIRECT_PREFIX = "redirect:"; private final String viewName; public JspView(final String viewName) { @@ -20,19 +16,18 @@ public JspView(final String viewName) { } @Override - public void render(final Map model, final HttpServletRequest request, final HttpServletResponse response) throws Exception { - // todo + public void render(final Map model, final HttpServletRequest request, final HttpServletResponse response) + throws Exception { + setAttributes(model, request); + + final var requestDispatcher = request.getRequestDispatcher(viewName); + requestDispatcher.forward(request, response); + } + private void setAttributes(final Map model, final HttpServletRequest request) { model.keySet().forEach(key -> { log.debug("attribute name : {}, value : {}", key, model.get(key)); request.setAttribute(key, model.get(key)); }); - - // todo - } - - @Override - public String getName() { - return viewName; } } diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/ModelAndView.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/ModelAndView.java similarity index 85% rename from mvc/src/main/java/webmvc/org/springframework/web/servlet/ModelAndView.java rename to mvc/src/main/java/webmvc/org/springframework/web/servlet/view/ModelAndView.java index fdf4e585d3..c60572596a 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/ModelAndView.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/ModelAndView.java @@ -1,4 +1,4 @@ -package webmvc.org.springframework.web.servlet; +package webmvc.org.springframework.web.servlet.view; import java.util.Collections; import java.util.HashMap; @@ -30,8 +30,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/view/RedirectView.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/RedirectView.java new file mode 100644 index 0000000000..4c04757347 --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/RedirectView.java @@ -0,0 +1,25 @@ +package webmvc.org.springframework.web.servlet.view; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RedirectView implements View { + + private static final Logger log = LoggerFactory.getLogger(RedirectView.class); + + private final String viewName; + + public RedirectView(final String viewName) { + this.viewName = viewName; + } + + @Override + public void render(final Map model, final HttpServletRequest request, final HttpServletResponse response) + throws Exception { + log.debug("redirect to {}", viewName); + response.sendRedirect(viewName); + } +} 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/View.java similarity index 78% rename from mvc/src/main/java/webmvc/org/springframework/web/servlet/View.java rename to mvc/src/main/java/webmvc/org/springframework/web/servlet/view/View.java index ca5e002b7e..02ed82ed83 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/View.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/View.java @@ -1,4 +1,4 @@ -package webmvc.org.springframework.web.servlet; +package webmvc.org.springframework.web.servlet.view; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -7,6 +7,4 @@ public interface View { void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception; - - String getName(); } diff --git a/mvc/src/test/java/samples/TestController.java b/mvc/src/test/java/samples/TestController.java index 574d22934d..c9d710f782 100644 --- a/mvc/src/test/java/samples/TestController.java +++ b/mvc/src/test/java/samples/TestController.java @@ -7,7 +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.ModelAndView; +import webmvc.org.springframework.web.servlet.view.ModelAndView; import webmvc.org.springframework.web.servlet.view.JspView; @Controller diff --git a/app/src/test/java/com/techcourse/DispatcherServletTest.java b/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/DispatcherServletTest.java similarity index 82% rename from app/src/test/java/com/techcourse/DispatcherServletTest.java rename to mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/DispatcherServletTest.java index 890dbcc960..a6e89934e6 100644 --- a/app/src/test/java/com/techcourse/DispatcherServletTest.java +++ b/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/DispatcherServletTest.java @@ -1,4 +1,4 @@ -package com.techcourse; +package webmvc.org.springframework.web.servlet.mvc; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.mock; @@ -9,6 +9,8 @@ import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import context.org.springframework.context.ApplicationContext; +import webmvc.org.springframework.web.servlet.DispatcherServlet; class DispatcherServletTest { @@ -16,7 +18,8 @@ class DispatcherServletTest { @Test void notExistHandlerMappingThrowsException() { // given - final DispatcherServlet dispatcherServlet = new DispatcherServlet(); + final DispatcherServlet dispatcherServlet = new DispatcherServlet( + new ApplicationContext("com.techcourse")); dispatcherServlet.init(); final HttpServletRequest request = mock(HttpServletRequest.class); when(request.getRequestURI()).thenReturn("/not-exist"); diff --git a/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerAdapterTest.java b/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerAdapterTest.java index 8c988bc25b..a379e4939b 100644 --- a/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerAdapterTest.java +++ b/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerAdapterTest.java @@ -4,21 +4,32 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import context.org.springframework.context.ApplicationContext; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import samples.TestController; -import webmvc.org.springframework.web.servlet.ModelAndView; +import webmvc.org.springframework.web.servlet.mvc.handler.adapter.AnnotationHandlerAdapter; +import webmvc.org.springframework.web.servlet.mvc.handler.mapping.HandlerExecution; +import webmvc.org.springframework.web.servlet.view.ModelAndView; class AnnotationHandlerAdapterTest { + private AnnotationHandlerAdapter handlerAdapter; + + @BeforeEach + void setUp() { + ApplicationContext applicationContext = new ApplicationContext("samples"); + applicationContext.initialize(); + handlerAdapter = applicationContext.getBeansOfType(AnnotationHandlerAdapter.class).get(0); + } + @DisplayName("처리할 수 있는 핸들러인지 확인한다.") @Test void supports() { // given - final var handlerAdapter = new AnnotationHandlerAdapter(); - // when // then assertThat(handlerAdapter.supports(new HandlerExecution(null, null))).isTrue(); @@ -28,8 +39,6 @@ void supports() { @Test void supportsReturnFalse() { // given - final var handlerAdapter = new AnnotationHandlerAdapter(); - // when // then assertThat(handlerAdapter.supports(new Object())).isFalse(); @@ -39,13 +48,12 @@ void supportsReturnFalse() { @Test void handle() throws Exception { // given - final var handlerAdapter = new AnnotationHandlerAdapter(); - // when final HttpServletRequest request = mock(HttpServletRequest.class); when(request.getAttribute("id")).thenReturn("1"); - final ModelAndView modelAndView = handlerAdapter.handle(request, null, new HandlerExecution(new TestController(), - TestController.class.getMethod("findUserId", HttpServletRequest.class, HttpServletResponse.class))); + final ModelAndView modelAndView = handlerAdapter.handle(request, null, + new HandlerExecution(new TestController(), + TestController.class.getMethod("findUserId", HttpServletRequest.class, HttpServletResponse.class))); // then assertThat(modelAndView.getModel().get("id")).isEqualTo("1"); diff --git a/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMappingTest.java b/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMappingTest.java index c6ae5b490f..bb4c24b9ef 100644 --- a/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMappingTest.java +++ b/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMappingTest.java @@ -4,10 +4,13 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import context.org.springframework.context.ApplicationContext; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import webmvc.org.springframework.web.servlet.mvc.handler.mapping.AnnotationHandlerMapping; +import webmvc.org.springframework.web.servlet.mvc.handler.mapping.HandlerExecution; class AnnotationHandlerMappingTest { @@ -15,7 +18,9 @@ class AnnotationHandlerMappingTest { @BeforeEach void setUp() { - handlerMapping = new AnnotationHandlerMapping("samples"); + ApplicationContext applicationContext = new ApplicationContext("samples"); + applicationContext.initialize(); + handlerMapping = applicationContext.getBeansOfType(AnnotationHandlerMapping.class).get(0); handlerMapping.initialize(); }