diff --git a/app/src/main/java/com/techcourse/DispatcherServletInitializer.java b/app/src/main/java/com/techcourse/DispatcherServletInitializer.java index 11a18e45ef..d8bf6cab8a 100644 --- a/app/src/main/java/com/techcourse/DispatcherServletInitializer.java +++ b/app/src/main/java/com/techcourse/DispatcherServletInitializer.java @@ -7,7 +7,6 @@ import webmvc.org.springframework.web.servlet.mvc.DispatcherServlet; import webmvc.org.springframework.web.servlet.mvc.HandlerAdapters; import webmvc.org.springframework.web.servlet.mvc.HandlerMappings; -import webmvc.org.springframework.web.servlet.mvc.asis.ControllerHandlerAdapter; import webmvc.org.springframework.web.servlet.mvc.tobe.AnnotationHandlerMapping; import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerExecutionHandlerAdapter; @@ -24,11 +23,9 @@ public class DispatcherServletInitializer implements WebApplicationInitializer { @Override public void onStartup(final ServletContext servletContext) { HandlerMappings handlerMappings = new HandlerMappings() - .addHandlerMapping(new ManualHandlerMapping()) .addHandlerMapping(new AnnotationHandlerMapping("com.techcourse.controller")); HandlerAdapters handlerAdapters = new HandlerAdapters() - .addHandlerAdapter(new ControllerHandlerAdapter()) .addHandlerAdapter(new HandlerExecutionHandlerAdapter()); final var dispatcherServlet = new DispatcherServlet(handlerMappings, handlerAdapters); diff --git a/app/src/main/java/com/techcourse/LegacyHandlerMapping.java b/app/src/main/java/com/techcourse/LegacyHandlerMapping.java deleted file mode 100644 index a11c629751..0000000000 --- a/app/src/main/java/com/techcourse/LegacyHandlerMapping.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.techcourse; - -import jakarta.servlet.http.HttpServletRequest; -import webmvc.org.springframework.web.servlet.mvc.HandlerMapping; - -public abstract class LegacyHandlerMapping implements HandlerMapping { - - @Override - public Object getHandler(HttpServletRequest httpServletRequest) { - return getHandler(httpServletRequest.getRequestURI()); - } - - public abstract Object getHandler(String requestURI); -} 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 beedc210fb..0000000000 --- a/app/src/main/java/com/techcourse/ManualHandlerMapping.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.techcourse; - -import com.techcourse.controller.*; -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 java.util.HashMap; -import java.util.Map; - -public class ManualHandlerMapping extends LegacyHandlerMapping { - - 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()); - - log.info("Initialized Handler Mapping!"); - controllers.keySet() - .forEach(path -> log.info("Path : {}, Controller : {}", path, controllers.get(path).getClass())); - } - - @Override - public Controller getHandler(final String requestURI) { - log.debug("Request Mapping Uri : {}", requestURI); - return controllers.get(requestURI); - } -} diff --git a/app/src/main/java/com/techcourse/controller/HomeController.java b/app/src/main/java/com/techcourse/controller/HomeController.java new file mode 100644 index 0000000000..38c0e13011 --- /dev/null +++ b/app/src/main/java/com/techcourse/controller/HomeController.java @@ -0,0 +1,18 @@ +package com.techcourse.controller; + +import context.org.springframework.stereotype.Controller; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import web.org.springframework.web.bind.annotation.RequestMapping; +import web.org.springframework.web.bind.annotation.RequestMethod; +import webmvc.org.springframework.web.servlet.ModelAndView; +import webmvc.org.springframework.web.servlet.view.JspView; + +@Controller +public class HomeController { + + @RequestMapping(value = "/", method = RequestMethod.GET) + public ModelAndView execute(HttpServletRequest req, HttpServletResponse res) { + 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..52d55a5105 100644 --- a/app/src/main/java/com/techcourse/controller/LoginController.java +++ b/app/src/main/java/com/techcourse/controller/LoginController.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 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.JspView; -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 new ModelAndView(new JspView("redirect:/index.jsp")); } return InMemoryUserRepository.findByAccount(req.getParameter("account")) @@ -23,15 +28,15 @@ public String execute(final HttpServletRequest req, final HttpServletResponse re log.info("User : {}", user); return login(req, user); }) - .orElse("redirect:/401.jsp"); + .orElse(new ModelAndView(new JspView("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 new ModelAndView(new JspView("redirect:/index.jsp")); } - return "redirect:/401.jsp"; + return new ModelAndView(new JspView("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..48d0c56dfd 100644 --- a/app/src/main/java/com/techcourse/controller/LoginViewController.java +++ b/app/src/main/java/com/techcourse/controller/LoginViewController.java @@ -1,22 +1,27 @@ 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; +import webmvc.org.springframework.web.servlet.view.JspView; -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/view", 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 new ModelAndView(new JspView("redirect:/index.jsp")); }) - .orElse("/login.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..531d12c13a 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.ModelAndView; +import webmvc.org.springframework.web.servlet.view.JspView; -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 new ModelAndView(new JspView("redirect:/")); } } diff --git a/app/src/main/java/com/techcourse/controller/RegisterController.java b/app/src/main/java/com/techcourse/controller/RegisterController.java index 3474a7669d..c92c4be567 100644 --- a/app/src/main/java/com/techcourse/controller/RegisterController.java +++ b/app/src/main/java/com/techcourse/controller/RegisterController.java @@ -23,7 +23,7 @@ public ModelAndView save(HttpServletRequest req, HttpServletResponse res) { return new ModelAndView(new JspView("redirect:/")); } - @RequestMapping(value = "/register", method = RequestMethod.GET) + @RequestMapping(value = "/register/view", method = RequestMethod.GET) public ModelAndView show(HttpServletRequest req, HttpServletResponse res) { return new ModelAndView(new JspView("/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..c1361f487b --- /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; + } +} 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/ControllerHandlerAdapter.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/asis/ControllerHandlerAdapter.java deleted file mode 100644 index f3ac39622c..0000000000 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/asis/ControllerHandlerAdapter.java +++ /dev/null @@ -1,23 +0,0 @@ -package webmvc.org.springframework.web.servlet.mvc.asis; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import webmvc.org.springframework.web.servlet.ModelAndView; -import webmvc.org.springframework.web.servlet.mvc.HandlerAdapter; -import webmvc.org.springframework.web.servlet.view.JspView; - -public class ControllerHandlerAdapter implements HandlerAdapter { - - @Override - public boolean supports(Object object) { - return object instanceof Controller; - } - - @Override - public ModelAndView handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception { - Controller controller = (Controller) handler; - String viewName = controller.execute(httpServletRequest, httpServletResponse); - ModelAndView mav = new ModelAndView(new JspView(viewName)); - return mav; - } -} 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/view/JsonView.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JsonView.java index b42c3466f0..796bc11a4e 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,14 +1,33 @@ 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; import java.util.Map; +import java.util.Set; + +import static web.org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE; 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 { + String body = objectMapper.writeValueAsString(parseModel(model)); + + response.getWriter().write(body); + response.setContentType(APPLICATION_JSON_UTF8_VALUE); + } + + private Object parseModel(Map model) { + Set keys = model.keySet(); + if (keys.size() == 1) { + String key = keys.iterator().next(); + return model.get(key); + } + return model; } } diff --git a/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/DispatcherServletTest.java b/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/DispatcherServletTest.java index 7494a9dc6f..281fb0d621 100644 --- a/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/DispatcherServletTest.java +++ b/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/DispatcherServletTest.java @@ -12,7 +12,9 @@ import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.willDoNothing; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; class DispatcherServletTest { diff --git a/study/src/test/java/di/stage3/context/DIContainer.java b/study/src/test/java/di/stage3/context/DIContainer.java index b62feb1ed3..ff4942b335 100644 --- a/study/src/test/java/di/stage3/context/DIContainer.java +++ b/study/src/test/java/di/stage3/context/DIContainer.java @@ -1,6 +1,9 @@ package di.stage3.context; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; import java.util.Set; +import java.util.stream.Collectors; /** * 스프링의 BeanFactory, ApplicationContext에 해당되는 클래스 @@ -10,11 +13,48 @@ class DIContainer { private final Set beans; public DIContainer(final Set> classes) { - this.beans = Set.of(); + this.beans = createBeans(classes); + this.beans.forEach(this::setFields); + } + + private Set createBeans(Set> classes) { + return classes.stream() + .map(clazz -> { + try { + Constructor constructor = clazz.getDeclaredConstructor(); + constructor.setAccessible(true); + return constructor.newInstance(); + } catch (Exception e) { + throw new IllegalArgumentException("Bean Constructor Exception !!"); + } + }) + .collect(Collectors.toSet()); + } + + private void setFields(Object bean) { + Field[] fields = bean.getClass().getDeclaredFields(); + for (Field field : fields) { + Class type = field.getType(); + field.setAccessible(true); + try { + Object injectField = beans.stream() + .filter(type::isInstance) + .findAny() + .orElse(null); + if (injectField != null) { + field.set(bean, injectField); + } + } catch (Exception e) { + throw new IllegalArgumentException("Field Initialize Exception !!"); + } + } } @SuppressWarnings("unchecked") public T getBean(final Class aClass) { - return null; + return (T) beans.stream() + .filter(aClass::isInstance) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("no such bean")); } } diff --git a/study/src/test/java/di/stage4/annotations/ClassPathScanner.java b/study/src/test/java/di/stage4/annotations/ClassPathScanner.java index 9dab1fd9c4..afcc638613 100644 --- a/study/src/test/java/di/stage4/annotations/ClassPathScanner.java +++ b/study/src/test/java/di/stage4/annotations/ClassPathScanner.java @@ -1,10 +1,26 @@ package di.stage4.annotations; +import org.reflections.Reflections; + +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.List; import java.util.Set; +import java.util.stream.Collectors; public class ClassPathScanner { + private static final List> targets = List.of(Service.class, Repository.class); + public static Set> getAllClassesInPackage(final String packageName) { - return null; + Reflections reflections = new Reflections(packageName); + return getAnnotatedClasses(reflections); + } + + private static Set> getAnnotatedClasses(Reflections reflections) { + return targets.stream() + .map(reflections::getTypesAnnotatedWith) + .flatMap(Collection::stream) + .collect(Collectors.toSet()); } } diff --git a/study/src/test/java/di/stage4/annotations/DIContainer.java b/study/src/test/java/di/stage4/annotations/DIContainer.java index 9248ecad7e..0024fd0caa 100644 --- a/study/src/test/java/di/stage4/annotations/DIContainer.java +++ b/study/src/test/java/di/stage4/annotations/DIContainer.java @@ -1,6 +1,11 @@ package di.stage4.annotations; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.List; import java.util.Set; +import java.util.stream.Collectors; /** * 스프링의 BeanFactory, ApplicationContext에 해당되는 클래스 @@ -10,15 +15,60 @@ class DIContainer { private final Set beans; public DIContainer(final Set> classes) { - this.beans = Set.of(); + this.beans = createBeans(classes); + this.beans.forEach(this::setFields); } public static DIContainer createContainerForPackage(final String rootPackageName) { - return null; + Set> classes = ClassPathScanner.getAllClassesInPackage(rootPackageName); + return new DIContainer(classes); + } + + private Set createBeans(Set> classes) { + return classes.stream() + .map(clazz -> { + try { + Constructor constructor = clazz.getDeclaredConstructor(); + constructor.setAccessible(true); + return constructor.newInstance(); + } catch (Exception e) { + throw new IllegalArgumentException("Bean Constructor Exception !!"); + } + }) + .collect(Collectors.toSet()); + } + + private void setFields(Object bean) { + List fields = getToInjectFields(bean); + fields.forEach(field -> field.setAccessible(true)); + + for (Field field : fields) { + Class type = field.getType(); + try { + Object injectField = beans.stream() + .filter(type::isInstance) + .findAny() + .orElse(null); + if (injectField != null) { + field.set(bean, injectField); + } + } catch (Exception e) { + throw new IllegalArgumentException("Field Initialize Exception !!"); + } + } + } + + private List getToInjectFields(Object bean) { + return Arrays.stream(bean.getClass().getDeclaredFields()) + .filter(field -> field.isAnnotationPresent(Inject.class)) + .collect(Collectors.toList()); } @SuppressWarnings("unchecked") public T getBean(final Class aClass) { - return null; + return (T) beans.stream() + .filter(aClass::isInstance) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("no such bean")); } }