From 52ee7781beea99fa939f155e3948ebd2531730fd Mon Sep 17 00:00:00 2001 From: kang-hyungu Date: Mon, 11 Sep 2023 15:29:28 +0900 Subject: [PATCH 01/14] =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build.yml | 2 +- .../AppWebApplicationInitializer.java | 24 ------ .../main/java/com/techcourse/Application.java | 33 ++++---- .../com/techcourse}/DispatcherServlet.java | 18 ++-- .../DispatcherServletInitializer.java | 33 ++++++++ .../com/techcourse/ManualHandlerMapping.java | 13 +-- .../java/com/techcourse/TomcatStarter.java | 83 +++++++++++++++++++ .../techcourse/UncheckedServletException.java | 8 ++ .../controller/LoginController.java | 5 +- .../controller/LoginViewController.java | 2 +- .../controller/LogoutController.java | 2 +- .../controller/RegisterController.java | 2 +- .../controller/RegisterViewController.java | 2 +- build.gradle | 6 ++ .../stereotype}/Controller.java | 2 +- .../springframework/util/ReflectionUtils.java | 38 +++++++++ .../java/nextstep/mvc/HandlerAdapter.java | 12 --- .../java/nextstep/mvc/HandlerMapping.java | 10 --- .../org/springframework/http}/MediaType.java | 2 +- .../SpringServletContainerInitializer.java} | 16 ++-- .../web/WebApplicationInitializer.java | 2 +- .../web/bind}/annotation/PathVariable.java | 2 +- .../web/bind}/annotation/RequestMapping.java | 4 +- .../web/bind/annotation}/RequestMethod.java | 2 +- .../web/bind}/annotation/RequestParam.java | 2 +- .../web/servlet}/ModelAndView.java | 2 +- .../springframework/web/servlet}/View.java | 2 +- .../web/servlet/mvc}/asis/Controller.java | 2 +- .../servlet/mvc}/asis/ForwardController.java | 2 +- .../mvc}/tobe/AnnotationHandlerMapping.java | 5 +- .../servlet/mvc}/tobe/HandlerExecution.java | 4 +- .../web/servlet/mvc}/tobe/HandlerKey.java | 4 +- .../web/servlet}/view/JsonView.java | 3 +- .../web/servlet}/view/JspView.java | 3 +- ...akarta.servlet.ServletContainerInitializer | 2 +- mvc/src/test/java/samples/TestController.java | 10 +-- .../tobe/AnnotationHandlerMappingTest.java | 2 +- 37 files changed, 241 insertions(+), 125 deletions(-) delete mode 100644 app/src/main/java/com/techcourse/AppWebApplicationInitializer.java rename {mvc/src/main/java/nextstep/mvc => app/src/main/java/com/techcourse}/DispatcherServlet.java (76%) create mode 100644 app/src/main/java/com/techcourse/DispatcherServletInitializer.java create mode 100644 app/src/main/java/com/techcourse/TomcatStarter.java create mode 100644 app/src/main/java/com/techcourse/UncheckedServletException.java rename mvc/src/main/java/{nextstep/web/annotation => context/org/springframework/stereotype}/Controller.java (87%) create mode 100644 mvc/src/main/java/core/org/springframework/util/ReflectionUtils.java delete mode 100644 mvc/src/main/java/nextstep/mvc/HandlerAdapter.java delete mode 100644 mvc/src/main/java/nextstep/mvc/HandlerMapping.java rename mvc/src/main/java/{nextstep/web/support => web/org/springframework/http}/MediaType.java (76%) rename mvc/src/main/java/{nextstep/web/NextstepServletContainerInitializer.java => web/org/springframework/web/SpringServletContainerInitializer.java} (73%) rename mvc/src/main/java/{nextstep => web/org/springframework}/web/WebApplicationInitializer.java (84%) rename mvc/src/main/java/{nextstep/web => web/org/springframework/web/bind}/annotation/PathVariable.java (82%) rename mvc/src/main/java/{nextstep/web => web/org/springframework/web/bind}/annotation/RequestMapping.java (82%) rename mvc/src/main/java/{nextstep/web/support => web/org/springframework/web/bind/annotation}/RequestMethod.java (62%) rename mvc/src/main/java/{nextstep/web => web/org/springframework/web/bind}/annotation/RequestParam.java (82%) rename mvc/src/main/java/{nextstep/mvc/view => webmvc/org/springframework/web/servlet}/ModelAndView.java (93%) rename mvc/src/main/java/{nextstep/mvc/view => webmvc/org/springframework/web/servlet}/View.java (84%) rename mvc/src/main/java/{nextstep/mvc/controller => webmvc/org/springframework/web/servlet/mvc}/asis/Controller.java (80%) rename mvc/src/main/java/{nextstep/mvc/controller => webmvc/org/springframework/web/servlet/mvc}/asis/ForwardController.java (89%) rename mvc/src/main/java/{nextstep/mvc/controller => webmvc/org/springframework/web/servlet/mvc}/tobe/AnnotationHandlerMapping.java (83%) rename mvc/src/main/java/{nextstep/mvc/controller => webmvc/org/springframework/web/servlet/mvc}/tobe/HandlerExecution.java (70%) rename mvc/src/main/java/{nextstep/mvc/controller => webmvc/org/springframework/web/servlet/mvc}/tobe/HandlerKey.java (87%) rename mvc/src/main/java/{nextstep/mvc => webmvc/org/springframework/web/servlet}/view/JsonView.java (75%) rename mvc/src/main/java/{nextstep/mvc => webmvc/org/springframework/web/servlet}/view/JspView.java (88%) rename mvc/src/test/java/{nextstep/mvc/controller => webmvc/org/springframework/web/servlet/mvc}/tobe/AnnotationHandlerMappingTest.java (96%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d672c0be0a..6f63e4a797 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,4 +34,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: ./gradlew build sonar --info -x :study:build + run: ./gradlew clean build codeCoverageReport --info -x :study:build diff --git a/app/src/main/java/com/techcourse/AppWebApplicationInitializer.java b/app/src/main/java/com/techcourse/AppWebApplicationInitializer.java deleted file mode 100644 index 53e45857fd..0000000000 --- a/app/src/main/java/com/techcourse/AppWebApplicationInitializer.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.techcourse; - -import jakarta.servlet.ServletContext; -import nextstep.mvc.DispatcherServlet; -import nextstep.web.WebApplicationInitializer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class AppWebApplicationInitializer implements WebApplicationInitializer { - - private static final Logger log = LoggerFactory.getLogger(AppWebApplicationInitializer.class); - - @Override - public void onStartup(final ServletContext servletContext) { - final var dispatcherServlet = new DispatcherServlet(); - dispatcherServlet.addHandlerMapping(new ManualHandlerMapping()); - - final var dispatcher = servletContext.addServlet("dispatcher", dispatcherServlet); - dispatcher.setLoadOnStartup(1); - dispatcher.addMapping("/"); - - log.info("Start AppWebApplication Initializer"); - } -} diff --git a/app/src/main/java/com/techcourse/Application.java b/app/src/main/java/com/techcourse/Application.java index f5c8248a3e..500d3f2d8b 100644 --- a/app/src/main/java/com/techcourse/Application.java +++ b/app/src/main/java/com/techcourse/Application.java @@ -1,11 +1,9 @@ package com.techcourse; -import org.apache.catalina.connector.Connector; -import org.apache.catalina.startup.Tomcat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; +import java.io.IOException; import java.util.stream.Stream; public class Application { @@ -16,22 +14,11 @@ public class Application { public static void main(final String[] args) throws Exception { final int port = defaultPortIfNull(args); - - final var tomcat = new Tomcat(); - tomcat.setConnector(createConnector(port)); - final var docBase = new File("app/src/main/webapp/").getAbsolutePath(); - tomcat.addWebapp("", docBase); - log.info("configuring app with basedir: {}", docBase); + final var tomcat = new TomcatStarter(port); + log.info("configuring app with basedir: {}", TomcatStarter.WEBAPP_DIR_LOCATION); tomcat.start(); - tomcat.getServer().await(); - } - - private static Connector createConnector(final int port) { - final var connector = new Connector(); - connector.setPort(port); - connector.setProperty("bindOnInit", "false"); - return connector; + stop(tomcat); } private static int defaultPortIfNull(final String[] args) { @@ -40,4 +27,16 @@ private static int defaultPortIfNull(final String[] args) { .map(Integer::parseInt) .orElse(DEFAULT_PORT); } + + private static void stop(final TomcatStarter tomcat) { + try { + // make the application wait until we press any key. + System.in.read(); + } catch (IOException e) { + log.error(e.getMessage(), e); + } finally { + log.info("web server stop."); + tomcat.stop(); + } + } } diff --git a/mvc/src/main/java/nextstep/mvc/DispatcherServlet.java b/app/src/main/java/com/techcourse/DispatcherServlet.java similarity index 76% rename from mvc/src/main/java/nextstep/mvc/DispatcherServlet.java rename to app/src/main/java/com/techcourse/DispatcherServlet.java index 9e6951a259..277d8eed9a 100644 --- a/mvc/src/main/java/nextstep/mvc/DispatcherServlet.java +++ b/app/src/main/java/com/techcourse/DispatcherServlet.java @@ -1,36 +1,36 @@ -package nextstep.mvc; +package com.techcourse; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import nextstep.mvc.controller.asis.Controller; -import nextstep.mvc.view.JspView; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +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); - private HandlerMapping handlerMappings; + private ManualHandlerMapping manualHandlerMapping; - public void addHandlerMapping(final HandlerMapping handlerMapping) { - this.handlerMappings = handlerMapping; + public DispatcherServlet() { } @Override public void init() { - this.handlerMappings.initialize(); + manualHandlerMapping = new ManualHandlerMapping(); + manualHandlerMapping.initialize(); } @Override protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException { - log.debug("Method : {}, Request URI : {}", request.getMethod(), request.getRequestURI()); + final String requestURI = request.getRequestURI(); + log.debug("Method : {}, Request URI : {}", request.getMethod(), requestURI); try { - final var controller = (Controller) handlerMappings.getHandler(request); + final var controller = manualHandlerMapping.getHandler(requestURI); final var viewName = controller.execute(request, response); move(viewName, request, response); } catch (Throwable e) { diff --git a/app/src/main/java/com/techcourse/DispatcherServletInitializer.java b/app/src/main/java/com/techcourse/DispatcherServletInitializer.java new file mode 100644 index 0000000000..6e814cdd25 --- /dev/null +++ b/app/src/main/java/com/techcourse/DispatcherServletInitializer.java @@ -0,0 +1,33 @@ +package com.techcourse; + +import jakarta.servlet.ServletContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import web.org.springframework.web.WebApplicationInitializer; + +/** + * 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 DEFAULT_SERVLET_NAME = "dispatcher"; + + @Override + public void onStartup(final ServletContext servletContext) { + final var dispatcherServlet = new DispatcherServlet(); + + 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."); + } + + registration.setLoadOnStartup(1); + registration.addMapping("/"); + + log.info("Start AppWebApplication Initializer"); + } +} diff --git a/app/src/main/java/com/techcourse/ManualHandlerMapping.java b/app/src/main/java/com/techcourse/ManualHandlerMapping.java index 8a6fa9080f..a54863caf8 100644 --- a/app/src/main/java/com/techcourse/ManualHandlerMapping.java +++ b/app/src/main/java/com/techcourse/ManualHandlerMapping.java @@ -1,23 +1,20 @@ package com.techcourse; import com.techcourse.controller.*; -import jakarta.servlet.http.HttpServletRequest; -import nextstep.mvc.HandlerMapping; -import nextstep.mvc.controller.asis.Controller; -import nextstep.mvc.controller.asis.ForwardController; 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 implements HandlerMapping { +public class ManualHandlerMapping { 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()); @@ -31,9 +28,7 @@ public void initialize() { .forEach(path -> log.info("Path : {}, Controller : {}", path, controllers.get(path).getClass())); } - @Override - public Controller getHandler(HttpServletRequest request) { - final String requestURI = request.getRequestURI(); + 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/TomcatStarter.java b/app/src/main/java/com/techcourse/TomcatStarter.java new file mode 100644 index 0000000000..4f26f228e3 --- /dev/null +++ b/app/src/main/java/com/techcourse/TomcatStarter.java @@ -0,0 +1,83 @@ +package com.techcourse; + +import org.apache.catalina.Context; +import org.apache.catalina.LifecycleException; +import org.apache.catalina.connector.Connector; +import org.apache.catalina.core.StandardContext; +import org.apache.catalina.startup.Tomcat; +import org.apache.tomcat.util.scan.StandardJarScanner; + +import java.io.File; + +public class TomcatStarter { + + public static final String WEBAPP_DIR_LOCATION = "app/src/main/webapp/"; + + private final Tomcat tomcat; + + public TomcatStarter(final int port) { + this(WEBAPP_DIR_LOCATION, port); + } + + public TomcatStarter(final String webappDirLocation, final int port) { + this.tomcat = new Tomcat(); + tomcat.setConnector(createConnector(port)); + + final var docBase = new File(webappDirLocation).getAbsolutePath(); + final var context = (StandardContext) tomcat.addWebapp("", docBase); + skipJarScan(context); + skipClearReferences(context); + } + + public void start() { + try { + tomcat.start(); + } catch (LifecycleException e) { + throw new UncheckedServletException(e); + } + } + + public void stop() { + try { + tomcat.stop(); + tomcat.destroy(); + } catch (LifecycleException e) { + throw new UncheckedServletException(e); + } + } + + private Connector createConnector(final int port) { + final var connector = new Connector(); + connector.setPort(port); + return connector; + } + + private void skipJarScan(final Context context) { + final var jarScanner = (StandardJarScanner) context.getJarScanner(); + jarScanner.setScanClassPath(false); + } + + private void skipClearReferences(final StandardContext context) { + /** + * https://tomcat.apache.org/tomcat-10.1-doc/config/context.html + * + * setClearReferencesObjectStreamClassCaches 번역 + * true인 경우 웹 응용 프로그램이 중지되면 Tomcat은 직렬화에 사용되는 + * ObjectStreamClass 클래스에서 웹 응용 프로그램에 의해 로드된 + * 클래스에 대한 SoftReference를 찾고 찾은 모든 SoftReference를 지웁니다. + * 이 기능은 리플렉션을 사용하여 SoftReference를 식별하므로 Java 9 이상에서 + * 실행할 때 명령줄 옵션 -XaddExports:java.base/java.io=ALL-UNNAMED를 설정해야 합니다. + * 지정하지 않으면 기본값인 true가 사용됩니다. + * + * ObjectStreamClass와 관련된 메모리 누수는 Java 19 이상, Java 17.0.4 이상 및 + * Java 11.0.16 이상에서 수정되었습니다. + * 수정 사항이 포함된 Java 버전에서 실행할 때 확인이 비활성화됩니다. + * + * Amazon Corretto-17.0.6은 경고 메시지가 나옴. + * 학습과 관련 없는 메시지가 나오지 않도록 관련 설정을 끈다. + */ + context.setClearReferencesObjectStreamClassCaches(false); + context.setClearReferencesRmiTargets(false); + context.setClearReferencesThreadLocals(false); + } +} diff --git a/app/src/main/java/com/techcourse/UncheckedServletException.java b/app/src/main/java/com/techcourse/UncheckedServletException.java new file mode 100644 index 0000000000..26acea7605 --- /dev/null +++ b/app/src/main/java/com/techcourse/UncheckedServletException.java @@ -0,0 +1,8 @@ +package com.techcourse; + +public class UncheckedServletException extends RuntimeException { + + public UncheckedServletException(Exception e) { + super(e); + } +} diff --git a/app/src/main/java/com/techcourse/controller/LoginController.java b/app/src/main/java/com/techcourse/controller/LoginController.java index a07292e25b..0428fe109e 100644 --- a/app/src/main/java/com/techcourse/controller/LoginController.java +++ b/app/src/main/java/com/techcourse/controller/LoginController.java @@ -4,7 +4,7 @@ import com.techcourse.repository.InMemoryUserRepository; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import nextstep.mvc.controller.asis.Controller; +import webmvc.org.springframework.web.servlet.mvc.asis.Controller; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,8 +31,7 @@ private String login(final HttpServletRequest request, final User user) { final var session = request.getSession(); session.setAttribute(UserSession.SESSION_KEY, user); return "redirect:/index.jsp"; - } else { - return "redirect:/401.jsp"; } + return "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 85d64a73f8..86ec26cdce 100644 --- a/app/src/main/java/com/techcourse/controller/LoginViewController.java +++ b/app/src/main/java/com/techcourse/controller/LoginViewController.java @@ -2,9 +2,9 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import nextstep.mvc.controller.asis.Controller; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import webmvc.org.springframework.web.servlet.mvc.asis.Controller; public class LoginViewController implements Controller { diff --git a/app/src/main/java/com/techcourse/controller/LogoutController.java b/app/src/main/java/com/techcourse/controller/LogoutController.java index 9d1f099a98..4642fd9450 100644 --- a/app/src/main/java/com/techcourse/controller/LogoutController.java +++ b/app/src/main/java/com/techcourse/controller/LogoutController.java @@ -2,7 +2,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import nextstep.mvc.controller.asis.Controller; +import webmvc.org.springframework.web.servlet.mvc.asis.Controller; public class LogoutController implements Controller { diff --git a/app/src/main/java/com/techcourse/controller/RegisterController.java b/app/src/main/java/com/techcourse/controller/RegisterController.java index 56bb436f6e..da62e5e8e9 100644 --- a/app/src/main/java/com/techcourse/controller/RegisterController.java +++ b/app/src/main/java/com/techcourse/controller/RegisterController.java @@ -4,7 +4,7 @@ import com.techcourse.repository.InMemoryUserRepository; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import nextstep.mvc.controller.asis.Controller; +import webmvc.org.springframework.web.servlet.mvc.asis.Controller; public class RegisterController implements Controller { diff --git a/app/src/main/java/com/techcourse/controller/RegisterViewController.java b/app/src/main/java/com/techcourse/controller/RegisterViewController.java index 052639134b..136962136d 100644 --- a/app/src/main/java/com/techcourse/controller/RegisterViewController.java +++ b/app/src/main/java/com/techcourse/controller/RegisterViewController.java @@ -2,7 +2,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import nextstep.mvc.controller.asis.Controller; +import webmvc.org.springframework.web.servlet.mvc.asis.Controller; public class RegisterViewController implements Controller { diff --git a/build.gradle b/build.gradle index fd90bc151f..79cba97a1b 100644 --- a/build.gradle +++ b/build.gradle @@ -4,6 +4,12 @@ plugins { id "org.sonarqube" version "4.2.1.3168" } +allprojects { + repositories { + mavenCentral() + } +} + subprojects { apply plugin: 'org.sonarqube' sonar { diff --git a/mvc/src/main/java/nextstep/web/annotation/Controller.java b/mvc/src/main/java/context/org/springframework/stereotype/Controller.java similarity index 87% rename from mvc/src/main/java/nextstep/web/annotation/Controller.java rename to mvc/src/main/java/context/org/springframework/stereotype/Controller.java index cb264235b4..ef7379b54a 100644 --- a/mvc/src/main/java/nextstep/web/annotation/Controller.java +++ b/mvc/src/main/java/context/org/springframework/stereotype/Controller.java @@ -1,4 +1,4 @@ -package nextstep.web.annotation; +package context.org.springframework.stereotype; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/mvc/src/main/java/core/org/springframework/util/ReflectionUtils.java b/mvc/src/main/java/core/org/springframework/util/ReflectionUtils.java new file mode 100644 index 0000000000..e89c1743f3 --- /dev/null +++ b/mvc/src/main/java/core/org/springframework/util/ReflectionUtils.java @@ -0,0 +1,38 @@ +package core.org.springframework.util; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Modifier; + +public abstract class ReflectionUtils { + + /** + * Obtain an accessible constructor for the given class and parameters. + * @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 + * @since 5.0 + */ + public static Constructor accessibleConstructor(Class clazz, Class... parameterTypes) + throws NoSuchMethodException { + + Constructor ctor = clazz.getDeclaredConstructor(parameterTypes); + makeAccessible(ctor); + return ctor; + } + + /** + * 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 + */ + @SuppressWarnings("deprecation") + public static void makeAccessible(Constructor ctor) { + if ((!Modifier.isPublic(ctor.getModifiers()) || + !Modifier.isPublic(ctor.getDeclaringClass().getModifiers())) && !ctor.isAccessible()) { + ctor.setAccessible(true); + } + } +} diff --git a/mvc/src/main/java/nextstep/mvc/HandlerAdapter.java b/mvc/src/main/java/nextstep/mvc/HandlerAdapter.java deleted file mode 100644 index b1666cb54e..0000000000 --- a/mvc/src/main/java/nextstep/mvc/HandlerAdapter.java +++ /dev/null @@ -1,12 +0,0 @@ -package nextstep.mvc; - -import nextstep.mvc.view.ModelAndView; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -public interface HandlerAdapter { - boolean supports(Object handler); - - ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; -} diff --git a/mvc/src/main/java/nextstep/mvc/HandlerMapping.java b/mvc/src/main/java/nextstep/mvc/HandlerMapping.java deleted file mode 100644 index 7da7184209..0000000000 --- a/mvc/src/main/java/nextstep/mvc/HandlerMapping.java +++ /dev/null @@ -1,10 +0,0 @@ -package nextstep.mvc; - -import jakarta.servlet.http.HttpServletRequest; - -public interface HandlerMapping { - - void initialize(); - - Object getHandler(HttpServletRequest request); -} diff --git a/mvc/src/main/java/nextstep/web/support/MediaType.java b/mvc/src/main/java/web/org/springframework/http/MediaType.java similarity index 76% rename from mvc/src/main/java/nextstep/web/support/MediaType.java rename to mvc/src/main/java/web/org/springframework/http/MediaType.java index f881e02174..3a31f51d33 100644 --- a/mvc/src/main/java/nextstep/web/support/MediaType.java +++ b/mvc/src/main/java/web/org/springframework/http/MediaType.java @@ -1,4 +1,4 @@ -package nextstep.web.support; +package web.org.springframework.http; public class MediaType { public static final String APPLICATION_JSON_UTF8_VALUE = "application/json;charset=UTF-8"; diff --git a/mvc/src/main/java/nextstep/web/NextstepServletContainerInitializer.java b/mvc/src/main/java/web/org/springframework/web/SpringServletContainerInitializer.java similarity index 73% rename from mvc/src/main/java/nextstep/web/NextstepServletContainerInitializer.java rename to mvc/src/main/java/web/org/springframework/web/SpringServletContainerInitializer.java index 3e79b34b6a..f1c13fcd53 100644 --- a/mvc/src/main/java/nextstep/web/NextstepServletContainerInitializer.java +++ b/mvc/src/main/java/web/org/springframework/web/SpringServletContainerInitializer.java @@ -1,28 +1,30 @@ -package nextstep.web; +package web.org.springframework.web; +import core.org.springframework.util.ReflectionUtils; import jakarta.servlet.ServletContainerInitializer; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.HandlesTypes; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; import java.util.Set; @HandlesTypes(WebApplicationInitializer.class) -public class NextstepServletContainerInitializer implements ServletContainerInitializer { +public class SpringServletContainerInitializer implements ServletContainerInitializer { @Override public void onStartup(Set> webAppInitializerClasses, ServletContext servletContext) throws ServletException { - final List initializers = new LinkedList<>(); + final List initializers = new ArrayList<>(); if (webAppInitializerClasses != null) { for (Class waiClass : webAppInitializerClasses) { try { - initializers.add((WebApplicationInitializer) waiClass.getDeclaredConstructor().newInstance()); - } catch (Throwable e) { - throw new ServletException("Failed to instantiate WebApplicationInitializer class", e); + initializers.add((WebApplicationInitializer) + ReflectionUtils.accessibleConstructor(waiClass).newInstance()); + } catch (Throwable ex) { + throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex); } } } diff --git a/mvc/src/main/java/nextstep/web/WebApplicationInitializer.java b/mvc/src/main/java/web/org/springframework/web/WebApplicationInitializer.java similarity index 84% rename from mvc/src/main/java/nextstep/web/WebApplicationInitializer.java rename to mvc/src/main/java/web/org/springframework/web/WebApplicationInitializer.java index 60e6a89543..4b9b7d893d 100644 --- a/mvc/src/main/java/nextstep/web/WebApplicationInitializer.java +++ b/mvc/src/main/java/web/org/springframework/web/WebApplicationInitializer.java @@ -1,4 +1,4 @@ -package nextstep.web; +package web.org.springframework.web; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletException; diff --git a/mvc/src/main/java/nextstep/web/annotation/PathVariable.java b/mvc/src/main/java/web/org/springframework/web/bind/annotation/PathVariable.java similarity index 82% rename from mvc/src/main/java/nextstep/web/annotation/PathVariable.java rename to mvc/src/main/java/web/org/springframework/web/bind/annotation/PathVariable.java index 4f2a9b50a5..ceefd548e1 100644 --- a/mvc/src/main/java/nextstep/web/annotation/PathVariable.java +++ b/mvc/src/main/java/web/org/springframework/web/bind/annotation/PathVariable.java @@ -1,4 +1,4 @@ -package nextstep.web.annotation; +package web.org.springframework.web.bind.annotation; import java.lang.annotation.*; diff --git a/mvc/src/main/java/nextstep/web/annotation/RequestMapping.java b/mvc/src/main/java/web/org/springframework/web/bind/annotation/RequestMapping.java similarity index 82% rename from mvc/src/main/java/nextstep/web/annotation/RequestMapping.java rename to mvc/src/main/java/web/org/springframework/web/bind/annotation/RequestMapping.java index bb8c9e6e7b..6a09dfa0c4 100644 --- a/mvc/src/main/java/nextstep/web/annotation/RequestMapping.java +++ b/mvc/src/main/java/web/org/springframework/web/bind/annotation/RequestMapping.java @@ -1,6 +1,4 @@ -package nextstep.web.annotation; - -import nextstep.web.support.RequestMethod; +package web.org.springframework.web.bind.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/mvc/src/main/java/nextstep/web/support/RequestMethod.java b/mvc/src/main/java/web/org/springframework/web/bind/annotation/RequestMethod.java similarity index 62% rename from mvc/src/main/java/nextstep/web/support/RequestMethod.java rename to mvc/src/main/java/web/org/springframework/web/bind/annotation/RequestMethod.java index 1f37f21d5a..1dd958bd23 100644 --- a/mvc/src/main/java/nextstep/web/support/RequestMethod.java +++ b/mvc/src/main/java/web/org/springframework/web/bind/annotation/RequestMethod.java @@ -1,4 +1,4 @@ -package nextstep.web.support; +package web.org.springframework.web.bind.annotation; public enum RequestMethod { GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE diff --git a/mvc/src/main/java/nextstep/web/annotation/RequestParam.java b/mvc/src/main/java/web/org/springframework/web/bind/annotation/RequestParam.java similarity index 82% rename from mvc/src/main/java/nextstep/web/annotation/RequestParam.java rename to mvc/src/main/java/web/org/springframework/web/bind/annotation/RequestParam.java index 2813247c4a..5b3be4cedc 100644 --- a/mvc/src/main/java/nextstep/web/annotation/RequestParam.java +++ b/mvc/src/main/java/web/org/springframework/web/bind/annotation/RequestParam.java @@ -1,4 +1,4 @@ -package nextstep.web.annotation; +package web.org.springframework.web.bind.annotation; import java.lang.annotation.*; diff --git a/mvc/src/main/java/nextstep/mvc/view/ModelAndView.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/ModelAndView.java similarity index 93% rename from mvc/src/main/java/nextstep/mvc/view/ModelAndView.java rename to mvc/src/main/java/webmvc/org/springframework/web/servlet/ModelAndView.java index cb172084b3..ff8e24553f 100644 --- a/mvc/src/main/java/nextstep/mvc/view/ModelAndView.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/ModelAndView.java @@ -1,4 +1,4 @@ -package nextstep.mvc.view; +package webmvc.org.springframework.web.servlet; import java.util.Collections; import java.util.HashMap; diff --git a/mvc/src/main/java/nextstep/mvc/view/View.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/View.java similarity index 84% rename from mvc/src/main/java/nextstep/mvc/view/View.java rename to mvc/src/main/java/webmvc/org/springframework/web/servlet/View.java index 493ed75c64..4499f36866 100644 --- a/mvc/src/main/java/nextstep/mvc/view/View.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/View.java @@ -1,4 +1,4 @@ -package nextstep.mvc.view; +package webmvc.org.springframework.web.servlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; diff --git a/mvc/src/main/java/nextstep/mvc/controller/asis/Controller.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/asis/Controller.java similarity index 80% rename from mvc/src/main/java/nextstep/mvc/controller/asis/Controller.java rename to mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/asis/Controller.java index b0edabf3fb..bdd1fde780 100644 --- a/mvc/src/main/java/nextstep/mvc/controller/asis/Controller.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/asis/Controller.java @@ -1,4 +1,4 @@ -package nextstep.mvc.controller.asis; +package webmvc.org.springframework.web.servlet.mvc.asis; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; diff --git a/mvc/src/main/java/nextstep/mvc/controller/asis/ForwardController.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/asis/ForwardController.java similarity index 89% rename from mvc/src/main/java/nextstep/mvc/controller/asis/ForwardController.java rename to mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/asis/ForwardController.java index ed0f08d940..cd8f1ef371 100644 --- a/mvc/src/main/java/nextstep/mvc/controller/asis/ForwardController.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/asis/ForwardController.java @@ -1,4 +1,4 @@ -package nextstep.mvc.controller.asis; +package webmvc.org.springframework.web.servlet.mvc.asis; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; diff --git a/mvc/src/main/java/nextstep/mvc/controller/tobe/AnnotationHandlerMapping.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMapping.java similarity index 83% rename from mvc/src/main/java/nextstep/mvc/controller/tobe/AnnotationHandlerMapping.java rename to mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMapping.java index cc1331de49..a355218efa 100644 --- a/mvc/src/main/java/nextstep/mvc/controller/tobe/AnnotationHandlerMapping.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMapping.java @@ -1,14 +1,13 @@ -package nextstep.mvc.controller.tobe; +package webmvc.org.springframework.web.servlet.mvc.tobe; import jakarta.servlet.http.HttpServletRequest; -import nextstep.mvc.HandlerMapping; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashMap; import java.util.Map; -public class AnnotationHandlerMapping implements HandlerMapping { +public class AnnotationHandlerMapping { private static final Logger log = LoggerFactory.getLogger(AnnotationHandlerMapping.class); diff --git a/mvc/src/main/java/nextstep/mvc/controller/tobe/HandlerExecution.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecution.java similarity index 70% rename from mvc/src/main/java/nextstep/mvc/controller/tobe/HandlerExecution.java rename to mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecution.java index ebdc3d9b10..37c583fbdf 100644 --- a/mvc/src/main/java/nextstep/mvc/controller/tobe/HandlerExecution.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecution.java @@ -1,8 +1,8 @@ -package nextstep.mvc.controller.tobe; +package webmvc.org.springframework.web.servlet.mvc.tobe; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import nextstep.mvc.view.ModelAndView; +import webmvc.org.springframework.web.servlet.ModelAndView; public class HandlerExecution { diff --git a/mvc/src/main/java/nextstep/mvc/controller/tobe/HandlerKey.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerKey.java similarity index 87% rename from mvc/src/main/java/nextstep/mvc/controller/tobe/HandlerKey.java rename to mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerKey.java index b6934407a1..30d3c780ff 100644 --- a/mvc/src/main/java/nextstep/mvc/controller/tobe/HandlerKey.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerKey.java @@ -1,6 +1,6 @@ -package nextstep.mvc.controller.tobe; +package webmvc.org.springframework.web.servlet.mvc.tobe; -import nextstep.web.support.RequestMethod; +import web.org.springframework.web.bind.annotation.RequestMethod; import java.util.Objects; diff --git a/mvc/src/main/java/nextstep/mvc/view/JsonView.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JsonView.java similarity index 75% rename from mvc/src/main/java/nextstep/mvc/view/JsonView.java rename to mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JsonView.java index b7c4c06a71..b42c3466f0 100644 --- a/mvc/src/main/java/nextstep/mvc/view/JsonView.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JsonView.java @@ -1,7 +1,8 @@ -package nextstep.mvc.view; +package webmvc.org.springframework.web.servlet.view; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import webmvc.org.springframework.web.servlet.View; import java.util.Map; diff --git a/mvc/src/main/java/nextstep/mvc/view/JspView.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JspView.java similarity index 88% rename from mvc/src/main/java/nextstep/mvc/view/JspView.java rename to mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JspView.java index 858c3ccece..3f4cc906ff 100644 --- a/mvc/src/main/java/nextstep/mvc/view/JspView.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JspView.java @@ -1,9 +1,10 @@ -package nextstep.mvc.view; +package webmvc.org.springframework.web.servlet.view; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import webmvc.org.springframework.web.servlet.View; import java.util.Map; diff --git a/mvc/src/main/resources/META-INF/services/jakarta.servlet.ServletContainerInitializer b/mvc/src/main/resources/META-INF/services/jakarta.servlet.ServletContainerInitializer index b9002355ee..d98fc63525 100644 --- a/mvc/src/main/resources/META-INF/services/jakarta.servlet.ServletContainerInitializer +++ b/mvc/src/main/resources/META-INF/services/jakarta.servlet.ServletContainerInitializer @@ -1 +1 @@ -nextstep.web.NextstepServletContainerInitializer \ No newline at end of file +web.org.springframework.web.SpringServletContainerInitializer diff --git a/mvc/src/test/java/samples/TestController.java b/mvc/src/test/java/samples/TestController.java index 49d81be351..1f0e4acfb3 100644 --- a/mvc/src/test/java/samples/TestController.java +++ b/mvc/src/test/java/samples/TestController.java @@ -1,14 +1,14 @@ package samples; +import context.org.springframework.stereotype.Controller; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import nextstep.mvc.view.JspView; -import nextstep.mvc.view.ModelAndView; -import nextstep.web.annotation.Controller; -import nextstep.web.annotation.RequestMapping; -import nextstep.web.support.RequestMethod; 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; @Controller public class TestController { diff --git a/mvc/src/test/java/nextstep/mvc/controller/tobe/AnnotationHandlerMappingTest.java b/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMappingTest.java similarity index 96% rename from mvc/src/test/java/nextstep/mvc/controller/tobe/AnnotationHandlerMappingTest.java rename to mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMappingTest.java index 3236c5c433..dcec215a3f 100644 --- a/mvc/src/test/java/nextstep/mvc/controller/tobe/AnnotationHandlerMappingTest.java +++ b/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMappingTest.java @@ -1,4 +1,4 @@ -package nextstep.mvc.controller.tobe; +package webmvc.org.springframework.web.servlet.mvc.tobe; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; From e7c21ab067b56fde67900cb4088c2a8ca3bf4016 Mon Sep 17 00:00:00 2001 From: kang-hyungu Date: Mon, 11 Sep 2023 17:09:04 +0900 Subject: [PATCH 02/14] =?UTF-8?q?=EC=84=9C=EB=B8=94=EB=A6=BF=20=ED=95=99?= =?UTF-8?q?=EC=8A=B5=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 ++ .../servlet/com/example/TomcatStarter.java | 1 + .../java/servlet/com/example/FilterTest.java | 7 +++-- .../java/servlet/com/example/ServletTest.java | 23 ++++++++------- .../servlet/com/example/TestHttpUtils.java | 28 ------------------- 5 files changed, 20 insertions(+), 41 deletions(-) delete mode 100644 study/src/test/java/servlet/com/example/TestHttpUtils.java diff --git a/.gitignore b/.gitignore index 672d1adecf..1bcba66f9d 100644 --- a/.gitignore +++ b/.gitignore @@ -171,3 +171,5 @@ Temporary Items tomcat.* tomcat.*/** + +**/WEB-INF/classes/** diff --git a/study/src/main/java/servlet/com/example/TomcatStarter.java b/study/src/main/java/servlet/com/example/TomcatStarter.java index 109b9d5297..b91214e9c3 100644 --- a/study/src/main/java/servlet/com/example/TomcatStarter.java +++ b/study/src/main/java/servlet/com/example/TomcatStarter.java @@ -44,6 +44,7 @@ public void await() { public void stop() { try { tomcat.stop(); + tomcat.destroy(); } catch (LifecycleException e) { throw new RuntimeException(e); } diff --git a/study/src/test/java/servlet/com/example/FilterTest.java b/study/src/test/java/servlet/com/example/FilterTest.java index 7c958af764..53d6236dff 100644 --- a/study/src/test/java/servlet/com/example/FilterTest.java +++ b/study/src/test/java/servlet/com/example/FilterTest.java @@ -1,6 +1,7 @@ package servlet.com.example; import org.junit.jupiter.api.Test; +import support.HttpUtils; import static org.assertj.core.api.Assertions.assertThat; import static servlet.com.example.KoreanServlet.인코딩; @@ -8,12 +9,12 @@ class FilterTest { @Test - void testFilter() throws Exception { + void testFilter() { // 톰캣 서버 시작 - final var tomcatStarter = TestHttpUtils.createTomcatStarter(); + final var tomcatStarter = new TomcatStarter("src/main/webapp/"); tomcatStarter.start(); - final var response = TestHttpUtils.send("/korean"); + final var response = HttpUtils.send("/korean"); // 톰캣 서버 종료 tomcatStarter.stop(); diff --git a/study/src/test/java/servlet/com/example/ServletTest.java b/study/src/test/java/servlet/com/example/ServletTest.java index 0cb371f4d0..75fbb10dd5 100644 --- a/study/src/test/java/servlet/com/example/ServletTest.java +++ b/study/src/test/java/servlet/com/example/ServletTest.java @@ -1,22 +1,25 @@ package servlet.com.example; import org.junit.jupiter.api.Test; +import support.HttpUtils; import static org.assertj.core.api.Assertions.assertThat; class ServletTest { + private final String WEBAPP_DIR_LOCATION = "src/main/webapp/"; + @Test - void testSharedCounter() throws Exception { + void testSharedCounter() { // 톰캣 서버 시작 - final var tomcatStarter = TestHttpUtils.createTomcatStarter(); + final var tomcatStarter = new TomcatStarter(WEBAPP_DIR_LOCATION); tomcatStarter.start(); // shared-counter 페이지를 3번 호출한다. final var PATH = "/shared-counter"; - TestHttpUtils.send(PATH); - TestHttpUtils.send(PATH); - final var response = TestHttpUtils.send(PATH); + HttpUtils.send(PATH); + HttpUtils.send(PATH); + final var response = HttpUtils.send(PATH); // 톰캣 서버 종료 tomcatStarter.stop(); @@ -29,16 +32,16 @@ void testSharedCounter() throws Exception { } @Test - void testLocalCounter() throws Exception { + void testLocalCounter() { // 톰캣 서버 시작 - final var tomcatStarter = TestHttpUtils.createTomcatStarter(); + final var tomcatStarter = new TomcatStarter(WEBAPP_DIR_LOCATION); tomcatStarter.start(); // local-counter 페이지를 3번 호출한다. final var PATH = "/local-counter"; - TestHttpUtils.send(PATH); - TestHttpUtils.send(PATH); - final var response = TestHttpUtils.send(PATH); + HttpUtils.send(PATH); + HttpUtils.send(PATH); + final var response = HttpUtils.send(PATH); // 톰캣 서버 종료 tomcatStarter.stop(); diff --git a/study/src/test/java/servlet/com/example/TestHttpUtils.java b/study/src/test/java/servlet/com/example/TestHttpUtils.java deleted file mode 100644 index dde039228f..0000000000 --- a/study/src/test/java/servlet/com/example/TestHttpUtils.java +++ /dev/null @@ -1,28 +0,0 @@ -package servlet.com.example; - -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.time.Duration; - -public class TestHttpUtils { - - private static final HttpClient httpClient = HttpClient.newBuilder() - .version(HttpClient.Version.HTTP_1_1) - .connectTimeout(Duration.ofSeconds(3)) - .build(); - - public static TomcatStarter createTomcatStarter() { - return new TomcatStarter("../servlet/src/main/webapp/"); - } - - public static HttpResponse send(final String path) throws Exception { - final var request = HttpRequest.newBuilder() - .uri(URI.create("http://localhost:8080" + path)) - .timeout(Duration.ofSeconds(3)) - .build(); - - return httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - } -} From a0269457fe6030228289c5ef3162557bacd43105 Mon Sep 17 00:00:00 2001 From: drunkenhw Date: Tue, 12 Sep 2023 13:46:00 +0900 Subject: [PATCH 03/14] =?UTF-8?q?test:=20=ED=95=99=EC=8A=B5=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/reflection/Junit3TestRunner.java | 8 +++ .../java/reflection/Junit4TestRunner.java | 11 ++++ .../test/java/reflection/ReflectionTest.java | 56 +++++++++++-------- study/src/test/java/reflection/Student.java | 2 +- 4 files changed, 52 insertions(+), 25 deletions(-) diff --git a/study/src/test/java/reflection/Junit3TestRunner.java b/study/src/test/java/reflection/Junit3TestRunner.java index b4e465240c..e71ece4f32 100644 --- a/study/src/test/java/reflection/Junit3TestRunner.java +++ b/study/src/test/java/reflection/Junit3TestRunner.java @@ -2,6 +2,8 @@ import org.junit.jupiter.api.Test; +import java.lang.reflect.Method; + class Junit3TestRunner { @Test @@ -9,5 +11,11 @@ void run() throws Exception { Class clazz = Junit3Test.class; // TODO Junit3Test에서 test로 시작하는 메소드 실행 + Method[] methods = clazz.getMethods(); + for (Method method : methods) { + if (method.getName().startsWith("test")) { + method.invoke(clazz.getDeclaredConstructor().newInstance()); + } + } } } diff --git a/study/src/test/java/reflection/Junit4TestRunner.java b/study/src/test/java/reflection/Junit4TestRunner.java index 8a6916bc24..b18601da37 100644 --- a/study/src/test/java/reflection/Junit4TestRunner.java +++ b/study/src/test/java/reflection/Junit4TestRunner.java @@ -2,6 +2,9 @@ import org.junit.jupiter.api.Test; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + class Junit4TestRunner { @Test @@ -9,5 +12,13 @@ void run() throws Exception { Class clazz = Junit4Test.class; // TODO Junit4Test에서 @MyTest 애노테이션이 있는 메소드 실행 + Method[] methods = clazz.getMethods(); + for (Method method : methods) { + for (Annotation annotation : method.getAnnotations()) { + if (annotation.annotationType().equals(MyTest.class)) { + method.invoke(clazz.getDeclaredConstructor().newInstance()); + } + } + } } } diff --git a/study/src/test/java/reflection/ReflectionTest.java b/study/src/test/java/reflection/ReflectionTest.java index 370f0932b9..d8c10dd330 100644 --- a/study/src/test/java/reflection/ReflectionTest.java +++ b/study/src/test/java/reflection/ReflectionTest.java @@ -7,7 +7,10 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Date; import java.util.List; +import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; @@ -19,34 +22,38 @@ class ReflectionTest { void givenObject_whenGetsClassName_thenCorrect() { final Class clazz = Question.class; - assertThat(clazz.getSimpleName()).isEqualTo(""); - assertThat(clazz.getName()).isEqualTo(""); - assertThat(clazz.getCanonicalName()).isEqualTo(""); + assertThat(clazz.getSimpleName()).isEqualTo("Question"); + assertThat(clazz.getName()).isEqualTo("reflection.Question"); + assertThat(clazz.getCanonicalName()).isEqualTo("reflection.Question"); } @Test void givenClassName_whenCreatesObject_thenCorrect() throws ClassNotFoundException { final Class clazz = Class.forName("reflection.Question"); - assertThat(clazz.getSimpleName()).isEqualTo(""); - assertThat(clazz.getName()).isEqualTo(""); - assertThat(clazz.getCanonicalName()).isEqualTo(""); + assertThat(clazz.getSimpleName()).isEqualTo("Question"); + assertThat(clazz.getName()).isEqualTo("reflection.Question"); + assertThat(clazz.getCanonicalName()).isEqualTo("reflection.Question"); } @Test void givenObject_whenGetsFieldNamesAtRuntime_thenCorrect() { final Object student = new Student(); - final Field[] fields = null; - final List actualFieldNames = null; + final Field[] fields = student.getClass().getDeclaredFields(); + final List actualFieldNames = Arrays.stream(fields) + .map(Field::getName) + .collect(Collectors.toList()); assertThat(actualFieldNames).contains("name", "age"); } @Test void givenClass_whenGetsMethods_thenCorrect() { - final Class animalClass = Student.class; - final Method[] methods = null; - final List actualMethods = null; + final Class studentClass = Student.class; + final Method[] methods = studentClass.getDeclaredMethods(); + final List actualMethods = Arrays.stream(methods) + .map(Method::getName) + .collect(Collectors.toList()); assertThat(actualMethods) .hasSize(3) @@ -56,7 +63,7 @@ void givenClass_whenGetsMethods_thenCorrect() { @Test void givenClass_whenGetsAllConstructors_thenCorrect() { final Class questionClass = Question.class; - final Constructor[] constructors = null; + final Constructor[] constructors = questionClass.getConstructors(); assertThat(constructors).hasSize(2); } @@ -65,11 +72,11 @@ void givenClass_whenGetsAllConstructors_thenCorrect() { void givenClass_whenInstantiatesObjectsAtRuntime_thenCorrect() throws Exception { final Class questionClass = Question.class; - final Constructor firstConstructor = null; - final Constructor secondConstructor = null; + final Constructor firstConstructor = questionClass.getConstructor(String.class, String.class, String.class); + final Constructor secondConstructor = questionClass.getConstructor(long.class, String.class, String.class, String.class, Date.class, int.class); - final Question firstQuestion = null; - final Question secondQuestion = null; + final Question firstQuestion = (Question) firstConstructor.newInstance("gugu", "제목1", "내용1"); + final Question secondQuestion = (Question) secondConstructor.newInstance(1L, "gugu", "제목2", "내용2", new Date(), 1); assertThat(firstQuestion.getWriter()).isEqualTo("gugu"); assertThat(firstQuestion.getTitle()).isEqualTo("제목1"); @@ -82,15 +89,15 @@ void givenClass_whenInstantiatesObjectsAtRuntime_thenCorrect() throws Exception @Test void givenClass_whenGetsPublicFields_thenCorrect() { final Class questionClass = Question.class; - final Field[] fields = null; + final Field[] fields = questionClass.getFields(); - assertThat(fields).hasSize(0); + assertThat(fields).isEmpty(); } @Test void givenClass_whenGetsDeclaredFields_thenCorrect() { final Class questionClass = Question.class; - final Field[] fields = null; + final Field[] fields = questionClass.getDeclaredFields(); assertThat(fields).hasSize(6); assertThat(fields[0].getName()).isEqualTo("questionId"); @@ -99,7 +106,7 @@ void givenClass_whenGetsDeclaredFields_thenCorrect() { @Test void givenClass_whenGetsFieldsByName_thenCorrect() throws Exception { final Class questionClass = Question.class; - final Field field = null; + final Field field = questionClass.getDeclaredField("questionId"); assertThat(field.getName()).isEqualTo("questionId"); } @@ -107,7 +114,7 @@ void givenClass_whenGetsFieldsByName_thenCorrect() throws Exception { @Test void givenClassField_whenGetsType_thenCorrect() throws Exception { final Field field = Question.class.getDeclaredField("questionId"); - final Class fieldClass = null; + final Class fieldClass = field.getType(); assertThat(fieldClass.getSimpleName()).isEqualTo("long"); } @@ -115,15 +122,16 @@ void givenClassField_whenGetsType_thenCorrect() throws Exception { @Test void givenClassField_whenSetsAndGetsValue_thenCorrect() throws Exception { final Class studentClass = Student.class; - final Student student = null; - final Field field = null; + final Student student = (Student) studentClass.getConstructor().newInstance(); + final Field field = studentClass.getDeclaredField("age"); + field.setAccessible(true); // todo field에 접근 할 수 있도록 만든다. assertThat(field.getInt(student)).isZero(); assertThat(student.getAge()).isZero(); - field.set(null, null); + field.set(student, 99); assertThat(field.getInt(student)).isEqualTo(99); assertThat(student.getAge()).isEqualTo(99); diff --git a/study/src/test/java/reflection/Student.java b/study/src/test/java/reflection/Student.java index 85526b7425..1cd4a2d823 100644 --- a/study/src/test/java/reflection/Student.java +++ b/study/src/test/java/reflection/Student.java @@ -20,4 +20,4 @@ public String toString() { ", age=" + age + '}'; } -} \ No newline at end of file +} From 40f27f97c9fbbd98675f1fb8cf18e4cf8fc9e716 Mon Sep 17 00:00:00 2001 From: drunkenhw Date: Tue, 12 Sep 2023 15:29:44 +0900 Subject: [PATCH 04/14] =?UTF-8?q?test:=20=ED=95=99=EC=8A=B5=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/servlet/com/example/CharacterEncodingFilter.java | 7 ++++++- study/src/test/java/reflection/ReflectionsTest.java | 8 ++++++++ study/src/test/java/servlet/com/example/ServletTest.java | 4 ++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/study/src/main/java/servlet/com/example/CharacterEncodingFilter.java b/study/src/main/java/servlet/com/example/CharacterEncodingFilter.java index cf4d886974..2dc9c42290 100644 --- a/study/src/main/java/servlet/com/example/CharacterEncodingFilter.java +++ b/study/src/main/java/servlet/com/example/CharacterEncodingFilter.java @@ -1,6 +1,10 @@ package servlet.com.example; -import jakarta.servlet.*; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; import jakarta.servlet.annotation.WebFilter; import java.io.IOException; @@ -11,6 +15,7 @@ public class CharacterEncodingFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.getServletContext().log("doFilter() 호출"); + response.setCharacterEncoding("utf-8"); chain.doFilter(request, response); } } diff --git a/study/src/test/java/reflection/ReflectionsTest.java b/study/src/test/java/reflection/ReflectionsTest.java index 5040c2ffa2..e139a43ff9 100644 --- a/study/src/test/java/reflection/ReflectionsTest.java +++ b/study/src/test/java/reflection/ReflectionsTest.java @@ -1,9 +1,13 @@ package reflection; +import org.apache.commons.lang3.stream.Streams; import org.junit.jupiter.api.Test; import org.reflections.Reflections; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import reflection.annotation.Controller; +import reflection.annotation.Repository; +import reflection.annotation.Service; class ReflectionsTest { @@ -14,5 +18,9 @@ void showAnnotationClass() throws Exception { Reflections reflections = new Reflections("reflection.examples"); // TODO 클래스 레벨에 @Controller, @Service, @Repository 애노테이션이 설정되어 모든 클래스 찾아 로그로 출력한다. + Streams.of(Controller.class, Service.class, Repository.class) + .flatMap(it -> reflections.getTypesAnnotatedWith(it).stream()) + .map(Class::getSimpleName) + .forEach(log::info); } } diff --git a/study/src/test/java/servlet/com/example/ServletTest.java b/study/src/test/java/servlet/com/example/ServletTest.java index 75fbb10dd5..e335f8190a 100644 --- a/study/src/test/java/servlet/com/example/ServletTest.java +++ b/study/src/test/java/servlet/com/example/ServletTest.java @@ -28,7 +28,7 @@ void testSharedCounter() { // expected를 0이 아닌 올바른 값으로 바꿔보자. // 예상한 결과가 나왔는가? 왜 이런 결과가 나왔을까? - assertThat(Integer.parseInt(response.body())).isEqualTo(0); + assertThat(Integer.parseInt(response.body())).isEqualTo(3); } @Test @@ -50,6 +50,6 @@ void testLocalCounter() { // expected를 0이 아닌 올바른 값으로 바꿔보자. // 예상한 결과가 나왔는가? 왜 이런 결과가 나왔을까? - assertThat(Integer.parseInt(response.body())).isEqualTo(0); + assertThat(Integer.parseInt(response.body())).isEqualTo(1); } } From a3d165eda7c00fcb95324e18a849d515e76a6772 Mon Sep 17 00:00:00 2001 From: drunkenhw Date: Tue, 12 Sep 2023 17:27:55 +0900 Subject: [PATCH 05/14] =?UTF-8?q?feat:=20annotation=EC=9C=BC=EB=A1=9C=20sc?= =?UTF-8?q?an=20=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mvc/tobe/AnnotationHandlerMapping.java | 60 +++++++++++++++++-- .../servlet/mvc/tobe/HandlerExecution.java | 15 ++++- .../tobe/AnnotationHandlerMappingTest.java | 2 +- .../test/java/reflection/ReflectionTest.java | 3 +- 4 files changed, 71 insertions(+), 9 deletions(-) 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 a355218efa..772bc05a69 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,29 +1,79 @@ 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 java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; +import java.util.Set; public class AnnotationHandlerMapping { private static final Logger log = LoggerFactory.getLogger(AnnotationHandlerMapping.class); - private final Object[] basePackage; + private final Object[] basePackages; private final Map handlerExecutions; - public AnnotationHandlerMapping(final Object... basePackage) { - this.basePackage = basePackage; + public AnnotationHandlerMapping(final Object... basePackages) { + this.basePackages = basePackages; this.handlerExecutions = new HashMap<>(); } - public void initialize() { + public void initialize() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { log.info("Initialized AnnotationHandlerMapping!"); + Reflections reflections = new Reflections(basePackages); + Set> clazz = reflections.getTypesAnnotatedWith(Controller.class); + for (Class aClass : clazz) { + initializeControllerMappings(aClass); + } + } + + private void initializeControllerMappings(Class clazz) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + Method[] methods = clazz.getMethods(); + Object object = clazz.getConstructor().newInstance(); + processControllerMethods(methods, object); + } + + private void processControllerMethods(Method[] methods, Object object) { + for (Method method : methods) { + processMethodAnnotations(object, method, method.getAnnotations()); + + } + } + + private void processMethodAnnotations(Object object, Method method, Annotation[] annotations) { + for (Annotation annotation : annotations) { + handleRequestMappingAnnotation(object, method, annotation); + } + } + + private void handleRequestMappingAnnotation(Object object, Method method, Annotation annotation) { + if (annotation.annotationType().equals(RequestMapping.class)) { + RequestMapping requestMapping = method.getAnnotation(RequestMapping.class); + processControllerMethods(object, method, requestMapping); + } + } + + private void processControllerMethods(Object object, Method method, RequestMapping requestMapping) { + for (RequestMethod requestMethod : requestMapping.method()) { + HandlerKey handlerKey = new HandlerKey(requestMapping.value(), requestMethod); + HandlerExecution handlerExecution = new HandlerExecution(object, method); + handlerExecutions.put(handlerKey, handlerExecution); + } } public Object getHandler(final HttpServletRequest request) { - return null; + String requestURI = request.getRequestURI(); + RequestMethod requestMethod = RequestMethod.valueOf(request.getMethod()); + + return handlerExecutions.get(new HandlerKey(requestURI, requestMethod)); } } 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 37c583fbdf..e78a82a6d0 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 @@ -4,9 +4,20 @@ import jakarta.servlet.http.HttpServletResponse; import webmvc.org.springframework.web.servlet.ModelAndView; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + public class HandlerExecution { - public ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response) throws Exception { - return null; + private final Object clazz; + private final Method method; + + public HandlerExecution(Object clazz, Method method) { + this.clazz = clazz; + this.method = method; + } + + public ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response) throws InvocationTargetException, IllegalAccessException { + return (ModelAndView) method.invoke(clazz, request, response); } } 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 dcec215a3f..0bd82253c3 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 @@ -14,7 +14,7 @@ class AnnotationHandlerMappingTest { private AnnotationHandlerMapping handlerMapping; @BeforeEach - void setUp() { + void setUp() throws Exception { handlerMapping = new AnnotationHandlerMapping("samples"); handlerMapping.initialize(); } diff --git a/study/src/test/java/reflection/ReflectionTest.java b/study/src/test/java/reflection/ReflectionTest.java index d8c10dd330..ab113b4296 100644 --- a/study/src/test/java/reflection/ReflectionTest.java +++ b/study/src/test/java/reflection/ReflectionTest.java @@ -6,6 +6,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Date; @@ -120,7 +121,7 @@ void givenClassField_whenGetsType_thenCorrect() throws Exception { } @Test - void givenClassField_whenSetsAndGetsValue_thenCorrect() throws Exception { + void givenClassField_whenSetsAndGetsValue_thenCorrect() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException { final Class studentClass = Student.class; final Student student = (Student) studentClass.getConstructor().newInstance(); final Field field = studentClass.getDeclaredField("age"); From 8629e540abe05e10e3ff32f6e97fe79f4aa8ede9 Mon Sep 17 00:00:00 2001 From: drunkenhw Date: Tue, 12 Sep 2023 19:23:43 +0900 Subject: [PATCH 06/14] =?UTF-8?q?feat:=20controller=20scanner=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 --- .../mvc/tobe/AnnotationHandlerMapping.java | 57 ++++++++----------- .../servlet/mvc/tobe/ControllerScanner.java | 32 +++++++++++ .../mvc/tobe/ControllerScannerTest.java | 30 ++++++++++ 3 files changed, 85 insertions(+), 34 deletions(-) create mode 100644 mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/ControllerScanner.java create mode 100644 mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/ControllerScannerTest.java 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 772bc05a69..17cfc26bde 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,19 +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 java.lang.annotation.Annotation; 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; public class AnnotationHandlerMapping { @@ -27,47 +27,36 @@ public AnnotationHandlerMapping(final Object... basePackages) { this.handlerExecutions = new HashMap<>(); } - public void initialize() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + public void initialize() throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { log.info("Initialized AnnotationHandlerMapping!"); - Reflections reflections = new Reflections(basePackages); - Set> clazz = reflections.getTypesAnnotatedWith(Controller.class); - for (Class aClass : clazz) { - initializeControllerMappings(aClass); - } - } - - private void initializeControllerMappings(Class clazz) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { - Method[] methods = clazz.getMethods(); - Object object = clazz.getConstructor().newInstance(); - processControllerMethods(methods, object); - } - - private void processControllerMethods(Method[] methods, Object object) { + ControllerScanner controllerScanner = new ControllerScanner(basePackages); + Map, Object> controllers = controllerScanner.getControllers(); + Set methods = getRequestMappingMethods(controllers.keySet()); for (Method method : methods) { - processMethodAnnotations(object, method, method.getAnnotations()); - + RequestMapping requestMapping = method.getAnnotation(RequestMapping.class); + addHandleExecutions(controllers, method, requestMapping); } } - private void processMethodAnnotations(Object object, Method method, Annotation[] annotations) { - for (Annotation annotation : annotations) { - handleRequestMappingAnnotation(object, method, annotation); + private void addHandleExecutions(Map, Object> controllers, Method method, RequestMapping requestMapping) { + List handlerKeys = mapHandlerKey(requestMapping.value(), requestMapping.method()); + for (HandlerKey handlerKey : handlerKeys) { + HandlerExecution handlerExecution = new HandlerExecution(controllers.get(method.getDeclaringClass()), method); + handlerExecutions.put(handlerKey, handlerExecution); } } - private void handleRequestMappingAnnotation(Object object, Method method, Annotation annotation) { - if (annotation.annotationType().equals(RequestMapping.class)) { - RequestMapping requestMapping = method.getAnnotation(RequestMapping.class); - processControllerMethods(object, method, requestMapping); - } + private Set getRequestMappingMethods(Set> classes) { + return classes.stream() + .flatMap(clazz -> Arrays.stream(clazz.getMethods())) + .filter(method -> method.isAnnotationPresent(RequestMapping.class)) + .collect(Collectors.toSet()); } - private void processControllerMethods(Object object, Method method, RequestMapping requestMapping) { - for (RequestMethod requestMethod : requestMapping.method()) { - HandlerKey handlerKey = new HandlerKey(requestMapping.value(), requestMethod); - HandlerExecution handlerExecution = new HandlerExecution(object, method); - handlerExecutions.put(handlerKey, handlerExecution); - } + private List mapHandlerKey(String path, RequestMethod[] methods) { + return Arrays.stream(methods) + .map(method -> new HandlerKey(path, method)) + .collect(Collectors.toList()); } public Object getHandler(final HttpServletRequest request) { 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..e5c3ba0950 --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/ControllerScanner.java @@ -0,0 +1,32 @@ +package webmvc.org.springframework.web.servlet.mvc.tobe; + +import context.org.springframework.stereotype.Controller; +import org.reflections.Reflections; + +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class ControllerScanner { + + private final Reflections reflections; + + public ControllerScanner(Object[] basePacages) { + this.reflections = new Reflections(basePacages); + } + + public Map, Object> getControllers() throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { + Set> controllers = reflections.getTypesAnnotatedWith(Controller.class); + return instantiateControllers(controllers); + } + + private Map, Object> instantiateControllers(Set> classes) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + Map, Object> classByInstance = new HashMap<>(); + for (Class clazz : classes) { + Object instance = clazz.getConstructor().newInstance(); + classByInstance.put(clazz, instance); + } + return classByInstance; + } +} diff --git a/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/ControllerScannerTest.java b/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/ControllerScannerTest.java new file mode 100644 index 0000000000..fbe9e56f55 --- /dev/null +++ b/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/ControllerScannerTest.java @@ -0,0 +1,30 @@ +package webmvc.org.springframework.web.servlet.mvc.tobe; + +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class ControllerScannerTest { + + @Test + void Controller_어노테이션이_붙은_클래스를_반환한다() throws Exception { + final var controllerScanner = new ControllerScanner(new Object[]{"samples"}); + + var controllers = controllerScanner.getControllers(); + + assertThat(controllers).hasSize(1); + } + + @Test + void Controller_어노테이션이_없는_클래스를_반환하지_않는다() throws Exception { + final var controllerScanner = new ControllerScanner(new Object[]{"webmvc.org.springframework.web.servlet.mvc.tobe"}); + + var controllers = controllerScanner.getControllers(); + + assertThat(controllers).isEmpty(); + } +} From f2e40acc3f25f01161b5378bfa9d8a50c48beed9 Mon Sep 17 00:00:00 2001 From: drunkenhw Date: Tue, 12 Sep 2023 19:43:21 +0900 Subject: [PATCH 07/14] =?UTF-8?q?refactor:=20controller=20scanner=20?= =?UTF-8?q?=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/servlet/mvc/tobe/ControllerScanner.java | 4 ++-- .../web/servlet/mvc/tobe/ControllerScannerTest.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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 index e5c3ba0950..c02307e57a 100644 --- 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 @@ -12,8 +12,8 @@ public class ControllerScanner { private final Reflections reflections; - public ControllerScanner(Object[] basePacages) { - this.reflections = new Reflections(basePacages); + public ControllerScanner(Object... basePackages) { + this.reflections = new Reflections(basePackages); } public Map, Object> getControllers() throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { diff --git a/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/ControllerScannerTest.java b/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/ControllerScannerTest.java index fbe9e56f55..7ab021770f 100644 --- a/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/ControllerScannerTest.java +++ b/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/ControllerScannerTest.java @@ -12,7 +12,7 @@ class ControllerScannerTest { @Test void Controller_어노테이션이_붙은_클래스를_반환한다() throws Exception { - final var controllerScanner = new ControllerScanner(new Object[]{"samples"}); + final var controllerScanner = new ControllerScanner("samples"); var controllers = controllerScanner.getControllers(); @@ -21,7 +21,7 @@ class ControllerScannerTest { @Test void Controller_어노테이션이_없는_클래스를_반환하지_않는다() throws Exception { - final var controllerScanner = new ControllerScanner(new Object[]{"webmvc.org.springframework.web.servlet.mvc.tobe"}); + final var controllerScanner = new ControllerScanner("webmvc.org.springframework.web.servlet.mvc.tobe"); var controllers = controllerScanner.getControllers(); From 0023c7ecc50615811a505123ea581adcff00e797 Mon Sep 17 00:00:00 2001 From: drunkenhw Date: Tue, 12 Sep 2023 19:47:44 +0900 Subject: [PATCH 08/14] =?UTF-8?q?test:=20HandlerExecution=20test=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mvc/tobe/HandlerExecutionTest.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecutionTest.java diff --git a/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecutionTest.java b/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecutionTest.java new file mode 100644 index 0000000000..477862f404 --- /dev/null +++ b/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecutionTest.java @@ -0,0 +1,29 @@ +package webmvc.org.springframework.web.servlet.mvc.tobe; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import samples.TestController; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class HandlerExecutionTest { + + @Test + void 클래스의_ModelAndView를_반환한다() throws Exception { + var handlerExecution = new HandlerExecution(new TestController(), TestController.class.getMethod("findUserId", HttpServletRequest.class, HttpServletResponse.class)); + var request = mock(HttpServletRequest.class); + var response = mock(HttpServletResponse.class); + when(request.getAttribute("id")).thenReturn("gugu"); + + var modelAndView = handlerExecution.handle(request, response); + + assertThat(modelAndView.getObject("id")).isEqualTo("gugu"); + } +} From 1462c6921fc092a162bc45572b0e554c22af366f Mon Sep 17 00:00:00 2001 From: drunkenhw Date: Tue, 12 Sep 2023 19:59:03 +0900 Subject: [PATCH 09/14] =?UTF-8?q?feat:=20handler=20mapping=20=EC=B6=94?= =?UTF-8?q?=EC=83=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/techcourse/DispatcherServlet.java | 10 ++++++---- .../com/techcourse/ManualHandlerMapping.java | 17 ++++++++++++----- .../web/servlet/mvc/HandlerMapping.java | 8 ++++++++ .../mvc/tobe/AnnotationHandlerMapping.java | 4 +++- 4 files changed, 29 insertions(+), 10 deletions(-) create mode 100644 mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerMapping.java diff --git a/app/src/main/java/com/techcourse/DispatcherServlet.java b/app/src/main/java/com/techcourse/DispatcherServlet.java index 277d8eed9a..3c7ef435dd 100644 --- a/app/src/main/java/com/techcourse/DispatcherServlet.java +++ b/app/src/main/java/com/techcourse/DispatcherServlet.java @@ -6,8 +6,11 @@ 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.view.JspView; +import java.io.IOException; + public class DispatcherServlet extends HttpServlet { private static final long serialVersionUID = 1L; @@ -30,16 +33,15 @@ protected void service(final HttpServletRequest request, final HttpServletRespon log.debug("Method : {}, Request URI : {}", request.getMethod(), requestURI); try { - final var controller = manualHandlerMapping.getHandler(requestURI); + final var controller = (Controller) manualHandlerMapping.getHandler(request); final var viewName = controller.execute(request, response); move(viewName, request, response); - } catch (Throwable e) { - log.error("Exception : {}", e.getMessage(), e); + } catch (Exception e) { throw new ServletException(e.getMessage()); } } - private void move(final String viewName, final HttpServletRequest request, final HttpServletResponse response) throws Exception { + private void move(final String viewName, final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException { if (viewName.startsWith(JspView.REDIRECT_PREFIX)) { response.sendRedirect(viewName.substring(JspView.REDIRECT_PREFIX.length())); return; diff --git a/app/src/main/java/com/techcourse/ManualHandlerMapping.java b/app/src/main/java/com/techcourse/ManualHandlerMapping.java index a54863caf8..8745e65537 100644 --- a/app/src/main/java/com/techcourse/ManualHandlerMapping.java +++ b/app/src/main/java/com/techcourse/ManualHandlerMapping.java @@ -1,15 +1,21 @@ package com.techcourse; -import com.techcourse.controller.*; +import com.techcourse.controller.LoginController; +import com.techcourse.controller.LoginViewController; +import com.techcourse.controller.LogoutController; +import com.techcourse.controller.RegisterController; +import com.techcourse.controller.RegisterViewController; +import jakarta.servlet.http.HttpServletRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import webmvc.org.springframework.web.servlet.mvc.HandlerMapping; import webmvc.org.springframework.web.servlet.mvc.asis.Controller; import webmvc.org.springframework.web.servlet.mvc.asis.ForwardController; import java.util.HashMap; import java.util.Map; -public class ManualHandlerMapping { +public class ManualHandlerMapping implements HandlerMapping { private static final Logger log = LoggerFactory.getLogger(ManualHandlerMapping.class); @@ -28,8 +34,9 @@ public void initialize() { .forEach(path -> log.info("Path : {}, Controller : {}", path, controllers.get(path).getClass())); } - public Controller getHandler(final String requestURI) { - log.debug("Request Mapping Uri : {}", requestURI); - return controllers.get(requestURI); + @Override + public Object getHandler(HttpServletRequest request) { + log.debug("Request Mapping Uri : {}", request); + return controllers.get(request.getRequestURI()); } } diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerMapping.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerMapping.java new file mode 100644 index 0000000000..0662025f59 --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerMapping.java @@ -0,0 +1,8 @@ +package webmvc.org.springframework.web.servlet.mvc; + +import jakarta.servlet.http.HttpServletRequest; + +public interface HandlerMapping { + + Object getHandler(HttpServletRequest request); +} 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 17cfc26bde..4a35aa8e7c 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 @@ -5,6 +5,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.mvc.HandlerMapping; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -15,7 +16,7 @@ import java.util.Set; import java.util.stream.Collectors; -public class AnnotationHandlerMapping { +public class AnnotationHandlerMapping implements HandlerMapping { private static final Logger log = LoggerFactory.getLogger(AnnotationHandlerMapping.class); @@ -59,6 +60,7 @@ private List mapHandlerKey(String path, RequestMethod[] methods) { .collect(Collectors.toList()); } + @Override public Object getHandler(final HttpServletRequest request) { String requestURI = request.getRequestURI(); RequestMethod requestMethod = RequestMethod.valueOf(request.getMethod()); From 75b06c3e9c7ecb3bb5b7d688ceed6e703373160b Mon Sep 17 00:00:00 2001 From: drunkenhw Date: Tue, 12 Sep 2023 21:03:02 +0900 Subject: [PATCH 10/14] =?UTF-8?q?feat:=20handler=20adapter=20=EC=B6=94?= =?UTF-8?q?=EC=83=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/servlet/mvc/HandlerAdapter.java | 12 +++++ .../mvc/asis/ControllerHandlerAdapter.java | 22 ++++++++ .../tobe/HandlerExecutionHandlerAdapter.java | 20 +++++++ .../asis/ControllerHandlerAdapterTest.java | 53 +++++++++++++++++++ .../HandlerExecutionHandlerAdapterTest.java | 33 ++++++++++++ 5 files changed, 140 insertions(+) create mode 100644 mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerAdapter.java create mode 100644 mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/asis/ControllerHandlerAdapter.java create mode 100644 mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecutionHandlerAdapter.java create mode 100644 mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/asis/ControllerHandlerAdapterTest.java create mode 100644 mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecutionHandlerAdapterTest.java diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerAdapter.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerAdapter.java new file mode 100644 index 0000000000..bb6fe8e046 --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerAdapter.java @@ -0,0 +1,12 @@ +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 HandlerAdapter { + + boolean supports(Object handler); + + ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) 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 new file mode 100644 index 0000000000..53e2705961 --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/asis/ControllerHandlerAdapter.java @@ -0,0 +1,22 @@ +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 handler) { + return handler instanceof Controller; + } + + @Override + public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + Controller controller = (Controller) handler; + String path = controller.execute(request, response); + return new ModelAndView(new JspView(path)); + } +} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecutionHandlerAdapter.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecutionHandlerAdapter.java new file mode 100644 index 0000000000..304fb05172 --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecutionHandlerAdapter.java @@ -0,0 +1,20 @@ +package webmvc.org.springframework.web.servlet.mvc.tobe; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import webmvc.org.springframework.web.servlet.ModelAndView; +import webmvc.org.springframework.web.servlet.mvc.HandlerAdapter; + +public class HandlerExecutionHandlerAdapter implements HandlerAdapter { + + @Override + public boolean supports(Object handler) { + return handler instanceof HandlerExecution; + } + + @Override + public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + HandlerExecution handlerExecution = (HandlerExecution) handler; + return handlerExecution.handle(request, response); + } +} diff --git a/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/asis/ControllerHandlerAdapterTest.java b/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/asis/ControllerHandlerAdapterTest.java new file mode 100644 index 0000000000..45d75f86a3 --- /dev/null +++ b/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/asis/ControllerHandlerAdapterTest.java @@ -0,0 +1,53 @@ +package webmvc.org.springframework.web.servlet.mvc.asis; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import webmvc.org.springframework.web.servlet.mvc.HandlerAdapter; +import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerExecution; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class ControllerHandlerAdapterTest { + + @Test + void Controller_구현체는_true를_반환한다() { + HandlerAdapter controllerHandlerAdapter = new ControllerHandlerAdapter(); + + boolean expect = controllerHandlerAdapter.supports(new ForwardController("")); + + assertThat(expect).isTrue(); + } + + @Test + void HandlerExecution_구현체는_false를_반환한다() throws Exception { + HandlerAdapter controllerHandlerAdapter = new ControllerHandlerAdapter(); + var handlerExecution = new HandlerExecution(null, null); + + boolean expect = controllerHandlerAdapter.supports(handlerExecution); + + assertThat(expect).isFalse(); + } + + @Test + void Controller_구현체의_execute_메서드를_호출한다() throws Exception { + HandlerAdapter controllerHandlerAdapter = new ControllerHandlerAdapter(); + var controller = new Controller() { + @Override + public String execute(HttpServletRequest req, HttpServletResponse res) throws Exception { + return "/index.jsp"; + } + }; + var request = mock(HttpServletRequest.class); + var response = mock(HttpServletResponse.class); + + var modelAndView = controllerHandlerAdapter.handle(request, response, controller); + + assertThat(modelAndView).isNotNull(); + } +} diff --git a/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecutionHandlerAdapterTest.java b/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecutionHandlerAdapterTest.java new file mode 100644 index 0000000000..71bdba0090 --- /dev/null +++ b/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecutionHandlerAdapterTest.java @@ -0,0 +1,33 @@ +package webmvc.org.springframework.web.servlet.mvc.tobe; + +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import webmvc.org.springframework.web.servlet.mvc.HandlerAdapter; +import webmvc.org.springframework.web.servlet.mvc.asis.ForwardController; + +import static org.assertj.core.api.Assertions.assertThat; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class HandlerExecutionHandlerAdapterTest { + + @Test + void HandlerExecution_구현체는_true를_반환한다() { + HandlerAdapter controllerHandlerAdapter = new HandlerExecutionHandlerAdapter(); + + boolean expect = controllerHandlerAdapter.supports(new HandlerExecution(null, null)); + + assertThat(expect).isTrue(); + } + + @Test + void Controller_구현체는_false를_반환한다() { + HandlerAdapter controllerHandlerAdapter = new HandlerExecutionHandlerAdapter(); + var controller = new ForwardController(""); + + boolean expect = controllerHandlerAdapter.supports(controller); + + assertThat(expect).isFalse(); + } +} From 00c8016a2e9d25209c3a6463e2d3a53fd0a16543 Mon Sep 17 00:00:00 2001 From: drunkenhw Date: Tue, 12 Sep 2023 21:14:28 +0900 Subject: [PATCH 11/14] =?UTF-8?q?feat:=20handler=20=EB=93=B1=EB=A1=9D?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../servlet/mvc/HandlerMappingRegistry.java | 30 +++++++++++++++++++ .../mvc/HandlerMappingRegistryTest.java | 30 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerMappingRegistry.java create mode 100644 mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/HandlerMappingRegistryTest.java 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..bc7c7d188d --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerMappingRegistry.java @@ -0,0 +1,30 @@ +package webmvc.org.springframework.web.servlet.mvc; + +import jakarta.servlet.http.HttpServletRequest; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class HandlerMappingRegistry { + + private final List handlerMappings; + + public HandlerMappingRegistry(List handlerMappings) { + this.handlerMappings = handlerMappings; + } + + public HandlerMappingRegistry() { + this.handlerMappings = new ArrayList<>(); + } + + public void addHandlerMapping(HandlerMapping handlerMapping) { + handlerMappings.add(handlerMapping); + } + + public Optional getHandler(HttpServletRequest request) { + return handlerMappings.stream() + .map(handlerMapping -> handlerMapping.getHandler(request)) + .findAny(); + } +} diff --git a/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/HandlerMappingRegistryTest.java b/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/HandlerMappingRegistryTest.java new file mode 100644 index 0000000000..defb2c8643 --- /dev/null +++ b/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/HandlerMappingRegistryTest.java @@ -0,0 +1,30 @@ +package webmvc.org.springframework.web.servlet.mvc; + +import jakarta.servlet.http.HttpServletRequest; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import webmvc.org.springframework.web.servlet.mvc.tobe.AnnotationHandlerMapping; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class HandlerMappingRegistryTest { + + @Test + void HandlerMapping을_등록하고_가져온다() throws Exception { + var handlerMappingRegistry = new HandlerMappingRegistry(); + var handlerMapping = new AnnotationHandlerMapping("samples"); + handlerMapping.initialize(); + var request = mock(HttpServletRequest.class); + when(request.getRequestURI()).thenReturn("/get-test"); + when(request.getMethod()).thenReturn("GET"); + + handlerMappingRegistry.addHandlerMapping(handlerMapping); + + assertThat(handlerMappingRegistry.getHandler(request)).isPresent(); + } +} From e18900d37b1e92da4d1e8a54d3f23e9d95cedf73 Mon Sep 17 00:00:00 2001 From: drunkenhw Date: Tue, 12 Sep 2023 21:22:17 +0900 Subject: [PATCH 12/14] =?UTF-8?q?feat:=20adapter=20=EB=93=B1=EB=A1=9D?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EA=B0=9D=EC=B2=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../webmvc/UnsupportedHandlerException.java | 7 +++ .../servlet/mvc/HandlerAdapterRegistry.java | 30 +++++++++++++ .../mvc/HandlerAdapterRegistryTest.java | 43 +++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 mvc/src/main/java/webmvc/UnsupportedHandlerException.java create mode 100644 mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerAdapterRegistry.java create mode 100644 mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/HandlerAdapterRegistryTest.java diff --git a/mvc/src/main/java/webmvc/UnsupportedHandlerException.java b/mvc/src/main/java/webmvc/UnsupportedHandlerException.java new file mode 100644 index 0000000000..59d06c46b6 --- /dev/null +++ b/mvc/src/main/java/webmvc/UnsupportedHandlerException.java @@ -0,0 +1,7 @@ +package webmvc; + +public class UnsupportedHandlerException extends RuntimeException { + public UnsupportedHandlerException() { + super("unsupported handler"); + } +} diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerAdapterRegistry.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerAdapterRegistry.java new file mode 100644 index 0000000000..594d0b03a7 --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerAdapterRegistry.java @@ -0,0 +1,30 @@ +package webmvc.org.springframework.web.servlet.mvc; + +import webmvc.UnsupportedHandlerException; + +import java.util.ArrayList; +import java.util.List; + +public class HandlerAdapterRegistry { + + private final List handlerAdapters; + + public HandlerAdapterRegistry(List handlerAdapters) { + this.handlerAdapters = handlerAdapters; + } + + public HandlerAdapterRegistry() { + this.handlerAdapters = new ArrayList<>(); + } + + public void addHandlerAdapter(HandlerAdapter handlerAdapter) { + handlerAdapters.add(handlerAdapter); + } + + public HandlerAdapter getHandlerAdapter(Object handler) { + return handlerAdapters.stream() + .filter(handlerAdapter -> handlerAdapter.supports(handler)) + .findAny() + .orElseThrow(UnsupportedHandlerException::new); + } +} diff --git a/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/HandlerAdapterRegistryTest.java b/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/HandlerAdapterRegistryTest.java new file mode 100644 index 0000000000..00ceb97b81 --- /dev/null +++ b/mvc/src/test/java/webmvc/org/springframework/web/servlet/mvc/HandlerAdapterRegistryTest.java @@ -0,0 +1,43 @@ +package webmvc.org.springframework.web.servlet.mvc; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import webmvc.UnsupportedHandlerException; +import webmvc.org.springframework.web.servlet.mvc.asis.Controller; +import webmvc.org.springframework.web.servlet.mvc.asis.ControllerHandlerAdapter; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class HandlerAdapterRegistryTest { + + @Test + void HandlerAdapter에_등록한_타입에_맞는_Adapter를_가져온다() throws Exception { + var handlerAdapterRegistry = new HandlerAdapterRegistry(); + + ControllerHandlerAdapter controllerHandlerAdapter = new ControllerHandlerAdapter(); + handlerAdapterRegistry.addHandlerAdapter(controllerHandlerAdapter); + + HandlerAdapter handlerAdapter = handlerAdapterRegistry.getHandlerAdapter(new Controller() { + @Override + public String execute(HttpServletRequest req, HttpServletResponse res) throws Exception { + return null; + } + }); + + assertThat(handlerAdapter).isEqualTo(controllerHandlerAdapter); + } + + @Test + void HandlerAdapter에_등록되어_있지_않는_객체면_예외가_발생한다() { + var handlerAdapterRegistry = new HandlerAdapterRegistry(); + + assertThatThrownBy(() -> handlerAdapterRegistry.getHandlerAdapter("")) + .isInstanceOf(UnsupportedHandlerException.class); + } +} From b52db748e9c4d4647262410885ff7e4e5409d16e Mon Sep 17 00:00:00 2001 From: drunkenhw Date: Wed, 13 Sep 2023 07:39:07 +0900 Subject: [PATCH 13/14] =?UTF-8?q?feat:=20dispatcher=20servlet=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EB=8F=99=EC=9E=91=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/techcourse/DispatcherServlet.java | 33 ++++++++++++++----- .../DispatcherServletInitializer.java | 8 ++++- .../com/techcourse/ManualHandlerMapping.java | 1 + .../techcourse/controller/TestController.java | 24 ++++++++++++++ .../web/servlet/ModelAndView.java | 11 +++++++ .../org/springframework/web/servlet/View.java | 2 ++ .../web/servlet/mvc/HandlerMapping.java | 2 ++ .../servlet/mvc/HandlerMappingRegistry.java | 9 ++++- .../mvc/tobe/AnnotationHandlerMapping.java | 8 +++-- .../servlet/mvc/tobe/ControllerScanner.java | 13 +++++--- .../web/servlet/view/JsonView.java | 5 +++ .../web/servlet/view/JspView.java | 10 ++++-- 12 files changed, 106 insertions(+), 20 deletions(-) create mode 100644 app/src/main/java/com/techcourse/controller/TestController.java diff --git a/app/src/main/java/com/techcourse/DispatcherServlet.java b/app/src/main/java/com/techcourse/DispatcherServlet.java index 3c7ef435dd..3de241fa28 100644 --- a/app/src/main/java/com/techcourse/DispatcherServlet.java +++ b/app/src/main/java/com/techcourse/DispatcherServlet.java @@ -6,7 +6,11 @@ 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.ModelAndView; +import webmvc.org.springframework.web.servlet.mvc.HandlerAdapter; +import webmvc.org.springframework.web.servlet.mvc.HandlerAdapterRegistry; +import webmvc.org.springframework.web.servlet.mvc.HandlerMapping; +import webmvc.org.springframework.web.servlet.mvc.HandlerMappingRegistry; import webmvc.org.springframework.web.servlet.view.JspView; import java.io.IOException; @@ -16,15 +20,17 @@ public class DispatcherServlet extends HttpServlet { private static final long serialVersionUID = 1L; private static final Logger log = LoggerFactory.getLogger(DispatcherServlet.class); - private ManualHandlerMapping manualHandlerMapping; + private final HandlerMappingRegistry handlerMappingRegistry; + private final HandlerAdapterRegistry handlerAdapterRegistry; public DispatcherServlet() { + this.handlerAdapterRegistry = new HandlerAdapterRegistry(); + this.handlerMappingRegistry = new HandlerMappingRegistry(); } @Override public void init() { - manualHandlerMapping = new ManualHandlerMapping(); - manualHandlerMapping.initialize(); + handlerMappingRegistry.initialize(); } @Override @@ -33,15 +39,18 @@ protected void service(final HttpServletRequest request, final HttpServletRespon log.debug("Method : {}, Request URI : {}", request.getMethod(), requestURI); try { - final var controller = (Controller) manualHandlerMapping.getHandler(request); - final var viewName = controller.execute(request, response); - move(viewName, request, response); + Object handler = handlerMappingRegistry.getHandler(request) + .orElseThrow(); + HandlerAdapter handlerAdapter = handlerAdapterRegistry.getHandlerAdapter(handler); + ModelAndView modelAndView = handlerAdapter.handle(request, response, handler); + move(modelAndView, request, response); } catch (Exception e) { throw new ServletException(e.getMessage()); } } - private void move(final String viewName, final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException { + private void move(final ModelAndView modelAndView, final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException { + String viewName = modelAndView.getViewName(); if (viewName.startsWith(JspView.REDIRECT_PREFIX)) { response.sendRedirect(viewName.substring(JspView.REDIRECT_PREFIX.length())); return; @@ -50,4 +59,12 @@ private void move(final String viewName, final HttpServletRequest request, final final var requestDispatcher = request.getRequestDispatcher(viewName); requestDispatcher.forward(request, response); } + + public void addHandlerAdapter(HandlerAdapter handlerAdapter) { + handlerAdapterRegistry.addHandlerAdapter(handlerAdapter); + } + + public void addHandlerMapping(HandlerMapping handlerMapping) { + handlerMappingRegistry.addHandlerMapping(handlerMapping); + } } diff --git a/app/src/main/java/com/techcourse/DispatcherServletInitializer.java b/app/src/main/java/com/techcourse/DispatcherServletInitializer.java index 6e814cdd25..c77b4396a5 100644 --- a/app/src/main/java/com/techcourse/DispatcherServletInitializer.java +++ b/app/src/main/java/com/techcourse/DispatcherServletInitializer.java @@ -4,6 +4,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import web.org.springframework.web.WebApplicationInitializer; +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; /** * Base class for {@link WebApplicationInitializer} @@ -18,7 +21,10 @@ public class DispatcherServletInitializer implements WebApplicationInitializer { @Override public void onStartup(final ServletContext servletContext) { final var dispatcherServlet = new DispatcherServlet(); - + dispatcherServlet.addHandlerAdapter(new ControllerHandlerAdapter()); + dispatcherServlet.addHandlerAdapter(new HandlerExecutionHandlerAdapter()); + dispatcherServlet.addHandlerMapping(new AnnotationHandlerMapping("com.techcourse")); + dispatcherServlet.addHandlerMapping(new ManualHandlerMapping()); final var registration = servletContext.addServlet(DEFAULT_SERVLET_NAME, dispatcherServlet); if (registration == null) { throw new IllegalStateException("Failed to register servlet with name '" + DEFAULT_SERVLET_NAME + "'. " + diff --git a/app/src/main/java/com/techcourse/ManualHandlerMapping.java b/app/src/main/java/com/techcourse/ManualHandlerMapping.java index 8745e65537..d4ffd8abfb 100644 --- a/app/src/main/java/com/techcourse/ManualHandlerMapping.java +++ b/app/src/main/java/com/techcourse/ManualHandlerMapping.java @@ -21,6 +21,7 @@ public class ManualHandlerMapping implements HandlerMapping { private static final Map controllers = new HashMap<>(); + @Override public void initialize() { controllers.put("/", new ForwardController("/index.jsp")); controllers.put("/login", new LoginController()); diff --git a/app/src/main/java/com/techcourse/controller/TestController.java b/app/src/main/java/com/techcourse/controller/TestController.java new file mode 100644 index 0000000000..d13a8037a2 --- /dev/null +++ b/app/src/main/java/com/techcourse/controller/TestController.java @@ -0,0 +1,24 @@ +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 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 TestController { + private static final Logger log = LoggerFactory.getLogger(TestController.class); + + @RequestMapping(value = "/test", method = RequestMethod.GET) + public ModelAndView test(HttpServletRequest request, HttpServletResponse response) { + log.info("test controller get method"); + final var modelAndView = new ModelAndView(new JspView("")); + modelAndView.addObject("id", request.getAttribute("id")); + return modelAndView; + } +} 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 ff8e24553f..c70e69a4c0 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,8 @@ package webmvc.org.springframework.web.servlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -30,4 +33,12 @@ public Map getModel() { public View getView() { return view; } + + public String getViewName() { + return view.getName(); + } + + public void render(HttpServletRequest request, HttpServletResponse response) throws Exception { + view.render(model, request, response); + } } diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/View.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/View.java index 4499f36866..27833b3cd1 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/View.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/View.java @@ -7,4 +7,6 @@ public interface View { void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception; + + String getName(); } diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerMapping.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerMapping.java index 0662025f59..5826d763a8 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerMapping.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerMapping.java @@ -4,5 +4,7 @@ public interface HandlerMapping { + void initialize(); + Object getHandler(HttpServletRequest request); } 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 index bc7c7d188d..96eef943dd 100644 --- 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 @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Optional; public class HandlerMappingRegistry { @@ -25,6 +26,12 @@ public void addHandlerMapping(HandlerMapping handlerMapping) { public Optional getHandler(HttpServletRequest request) { return handlerMappings.stream() .map(handlerMapping -> handlerMapping.getHandler(request)) - .findAny(); + .filter(Objects::nonNull) + .findFirst(); + } + + public void initialize() { + handlerMappings.stream() + .forEach(HandlerMapping::initialize); } } 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 4a35aa8e7c..27585cb494 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMapping.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMapping.java @@ -7,7 +7,6 @@ import web.org.springframework.web.bind.annotation.RequestMethod; import webmvc.org.springframework.web.servlet.mvc.HandlerMapping; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashMap; @@ -28,7 +27,8 @@ public AnnotationHandlerMapping(final Object... basePackages) { this.handlerExecutions = new HashMap<>(); } - public void initialize() throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { + @Override + public void initialize() { log.info("Initialized AnnotationHandlerMapping!"); ControllerScanner controllerScanner = new ControllerScanner(basePackages); Map, Object> controllers = controllerScanner.getControllers(); @@ -42,7 +42,9 @@ public void initialize() throws InvocationTargetException, NoSuchMethodException private void addHandleExecutions(Map, Object> controllers, Method method, RequestMapping requestMapping) { List handlerKeys = mapHandlerKey(requestMapping.value(), requestMapping.method()); for (HandlerKey handlerKey : handlerKeys) { - HandlerExecution handlerExecution = new HandlerExecution(controllers.get(method.getDeclaringClass()), method); + Object controller = controllers.get(method.getDeclaringClass()); + log.info("Path : {}, Controller : {}", handlerKey, controller); + HandlerExecution handlerExecution = new HandlerExecution(controller, method); handlerExecutions.put(handlerKey, handlerExecution); } } 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 index c02307e57a..7680f6d082 100644 --- 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 @@ -3,7 +3,6 @@ import context.org.springframework.stereotype.Controller; import org.reflections.Reflections; -import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -16,16 +15,20 @@ public ControllerScanner(Object... basePackages) { this.reflections = new Reflections(basePackages); } - public Map, Object> getControllers() throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { + public Map, Object> getControllers() { Set> controllers = reflections.getTypesAnnotatedWith(Controller.class); return instantiateControllers(controllers); } - private Map, Object> instantiateControllers(Set> classes) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + private Map, Object> instantiateControllers(Set> classes) { Map, Object> classByInstance = new HashMap<>(); for (Class clazz : classes) { - Object instance = clazz.getConstructor().newInstance(); - classByInstance.put(clazz, instance); + try { + Object instance = clazz.getConstructor().newInstance(); + classByInstance.put(clazz, instance); + } catch (Exception e) { + throw new RuntimeException(e); + } } return classByInstance; } 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..2c0ba3fc3b 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 @@ -11,4 +11,9 @@ public class JsonView implements View { @Override public void render(final Map model, final HttpServletRequest request, HttpServletResponse response) throws Exception { } + + @Override + public String getName() { + return null; + } } diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JspView.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/view/JspView.java index 3f4cc906ff..d83b0eefdf 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 @@ -10,11 +10,12 @@ public class JspView implements View { - private static final Logger log = LoggerFactory.getLogger(JspView.class); - public static final String REDIRECT_PREFIX = "redirect:"; + private static final Logger log = LoggerFactory.getLogger(JspView.class); + private final String viewName; public JspView(final String viewName) { + this.viewName = viewName; } @Override @@ -28,4 +29,9 @@ public void render(final Map model, final HttpServletRequest request, // todo } + + @Override + public String getName() { + return viewName; + } } From b562365850fc1a6f151cf3022fabab96d8ba9f43 Mon Sep 17 00:00:00 2001 From: drunkenhw Date: Wed, 13 Sep 2023 07:46:33 +0900 Subject: [PATCH 14/14] =?UTF-8?q?feat:=20handler=20=EC=8B=A4=ED=96=89?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EA=B0=9D=EC=B2=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/techcourse/DispatcherServlet.java | 6 ++++-- .../web/servlet/mvc/HandlerExecutor.java | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerExecutor.java diff --git a/app/src/main/java/com/techcourse/DispatcherServlet.java b/app/src/main/java/com/techcourse/DispatcherServlet.java index 3de241fa28..b7ec4d124d 100644 --- a/app/src/main/java/com/techcourse/DispatcherServlet.java +++ b/app/src/main/java/com/techcourse/DispatcherServlet.java @@ -9,6 +9,7 @@ import webmvc.org.springframework.web.servlet.ModelAndView; import webmvc.org.springframework.web.servlet.mvc.HandlerAdapter; import webmvc.org.springframework.web.servlet.mvc.HandlerAdapterRegistry; +import webmvc.org.springframework.web.servlet.mvc.HandlerExecutor; import webmvc.org.springframework.web.servlet.mvc.HandlerMapping; import webmvc.org.springframework.web.servlet.mvc.HandlerMappingRegistry; import webmvc.org.springframework.web.servlet.view.JspView; @@ -22,10 +23,12 @@ public class DispatcherServlet extends HttpServlet { private final HandlerMappingRegistry handlerMappingRegistry; private final HandlerAdapterRegistry handlerAdapterRegistry; + private final HandlerExecutor handlerExecutor; public DispatcherServlet() { this.handlerAdapterRegistry = new HandlerAdapterRegistry(); this.handlerMappingRegistry = new HandlerMappingRegistry(); + this.handlerExecutor = new HandlerExecutor(handlerAdapterRegistry); } @Override @@ -41,8 +44,7 @@ protected void service(final HttpServletRequest request, final HttpServletRespon try { Object handler = handlerMappingRegistry.getHandler(request) .orElseThrow(); - HandlerAdapter handlerAdapter = handlerAdapterRegistry.getHandlerAdapter(handler); - ModelAndView modelAndView = handlerAdapter.handle(request, response, handler); + ModelAndView modelAndView = handlerExecutor.handle(request, response, handler); move(modelAndView, request, response); } catch (Exception e) { throw new ServletException(e.getMessage()); diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerExecutor.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerExecutor.java new file mode 100644 index 0000000000..a733510619 --- /dev/null +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/HandlerExecutor.java @@ -0,0 +1,19 @@ +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 class HandlerExecutor { + + private final HandlerAdapterRegistry handlerAdapterRegistry; + + public HandlerExecutor(HandlerAdapterRegistry handlerAdapterRegistry) { + this.handlerAdapterRegistry = handlerAdapterRegistry; + } + + public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + HandlerAdapter handlerAdapter = handlerAdapterRegistry.getHandlerAdapter(handler); + return handlerAdapter.handle(request, response, handler); + } +}