Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MVC 구현하기 2단계] 마코(이규성) 미션 제출합니다. #458

Merged
merged 12 commits into from
Sep 20, 2023
51 changes: 0 additions & 51 deletions app/src/main/java/com/techcourse/DispatcherServlet.java

This file was deleted.

1 change: 1 addition & 0 deletions app/src/main/java/com/techcourse/TomcatStarter.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.techcourse;

import com.techcourse.mvc.exception.UncheckedServletException;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Connector;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,25 @@

import com.techcourse.domain.User;
import com.techcourse.repository.InMemoryUserRepository;
import context.org.springframework.stereotype.Controller;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import webmvc.org.springframework.web.servlet.mvc.asis.Controller;
import web.org.springframework.web.bind.annotation.RequestMapping;
import web.org.springframework.web.bind.annotation.RequestMethod;
import webmvc.org.springframework.web.servlet.ModelAndView;
import webmvc.org.springframework.web.servlet.view.JspView;

public class RegisterController implements Controller {
@Controller
public class RegisterController {

@Override
public String execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception {
@RequestMapping(value = "/register", method = RequestMethod.POST)
public ModelAndView execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception {
final var user = new User(2,
req.getParameter("account"),
req.getParameter("password"),
req.getParameter("email"));
InMemoryUserRepository.save(user);

return "redirect:/index.jsp";
return new ModelAndView(new JspView("/index.jsp"));
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package com.techcourse.controller;

import context.org.springframework.stereotype.Controller;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import webmvc.org.springframework.web.servlet.mvc.asis.Controller;
import web.org.springframework.web.bind.annotation.RequestMapping;
import web.org.springframework.web.bind.annotation.RequestMethod;
import webmvc.org.springframework.web.servlet.ModelAndView;
import webmvc.org.springframework.web.servlet.view.JspView;

public class RegisterViewController implements Controller {
@Controller
public class RegisterViewController {

@Override
public String execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception {
return "/register.jsp";
@RequestMapping(value = "/register", method = RequestMethod.GET)
public ModelAndView execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception {
return new ModelAndView(new JspView("/register.jsp"));
}
}
46 changes: 46 additions & 0 deletions app/src/main/java/com/techcourse/mvc/DispatcherServlet.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.techcourse.mvc;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import webmvc.org.springframework.web.servlet.ModelAndView;
import webmvc.org.springframework.web.servlet.mvc.HandlerAdapter;
import webmvc.org.springframework.web.servlet.mvc.HandlerMapping;

public class DispatcherServlet extends HttpServlet {

private static final long serialVersionUID = 1L;
private static final Logger log = LoggerFactory.getLogger(DispatcherServlet.class);

private HandlerMapping handlerMapping;
private HandlerAdapter handlerAdapter;

public DispatcherServlet(final HandlerMapping handlerMapping, final HandlerAdapter handlerAdapter) {
this.handlerMapping = handlerMapping;
this.handlerAdapter = handlerAdapter;
}

@Override
public void init() {
handlerMapping.initialize();
}

@Override
protected void service(final HttpServletRequest request, final HttpServletResponse response)
throws ServletException {
final String requestURI = request.getRequestURI();
log.debug("Method : {}, Request URI : {}", request.getMethod(), requestURI);

try {
final Object handler = handlerMapping.getHandler(request);
final ModelAndView modelAndView = handlerAdapter.handle(request, response, handler);
modelAndView.render(request, response);
} catch (Throwable e) {
log.error("Exception : {}", e.getMessage(), e);
throw new ServletException(e.getMessage());
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package com.techcourse;
package com.techcourse.mvc;

import jakarta.servlet.ServletContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import web.org.springframework.web.WebApplicationInitializer;
import webmvc.org.springframework.web.servlet.mvc.CompositeHandlerAdapter;
import webmvc.org.springframework.web.servlet.mvc.CompositeHandlerMapping;
import webmvc.org.springframework.web.servlet.mvc.tobe.AnnotationHandlerAdapter;
import webmvc.org.springframework.web.servlet.mvc.tobe.AnnotationHandlerMapping;

/**
* Base class for {@link WebApplicationInitializer}
Expand All @@ -17,7 +21,7 @@ public class DispatcherServletInitializer implements WebApplicationInitializer {

@Override
public void onStartup(final ServletContext servletContext) {
final var dispatcherServlet = new DispatcherServlet();
final var dispatcherServlet = new DispatcherServlet(initHandlerMapping(), initHandlerAdapter());

final var registration = servletContext.addServlet(DEFAULT_SERVLET_NAME, dispatcherServlet);
if (registration == null) {
Expand All @@ -30,4 +34,16 @@ public void onStartup(final ServletContext servletContext) {

log.info("Start AppWebApplication Initializer");
}

private CompositeHandlerAdapter initHandlerAdapter() {
final var annotationHandlerAdapter = new AnnotationHandlerAdapter();
final var manualHandlerAdapter = new ManualHandlerAdapter();
return new CompositeHandlerAdapter(manualHandlerAdapter, annotationHandlerAdapter);
}

private CompositeHandlerMapping initHandlerMapping() {
final var annotationHandlerMapping = new AnnotationHandlerMapping();
final var manualHandlerMappingAdapter = new ManualHandlerMappingAdapter();
return new CompositeHandlerMapping(manualHandlerMappingAdapter, annotationHandlerMapping);
}
}
30 changes: 30 additions & 0 deletions app/src/main/java/com/techcourse/mvc/ManualHandlerAdapter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.techcourse.mvc;

import com.techcourse.mvc.exception.CanNotInvokeMethodException;
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.mvc.asis.Controller;
import webmvc.org.springframework.web.servlet.view.JspView;

public class ManualHandlerAdapter implements HandlerAdapter {

@Override
public ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response,
final Object handler) {
try {
final var controller = (Controller) handler;
final var viewName = controller.execute(request, response);
return new ModelAndView(new JspView(viewName));
} catch (Exception e) {
e.printStackTrace();
throw new CanNotInvokeMethodException();
}
}

@Override
public boolean supports(Object handler) {
return handler instanceof Controller;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.techcourse;
package com.techcourse.mvc;

import com.techcourse.controller.*;
import com.techcourse.controller.LoginController;
import com.techcourse.controller.LoginViewController;
import com.techcourse.controller.LogoutController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import webmvc.org.springframework.web.servlet.mvc.asis.Controller;
Expand All @@ -20,8 +22,6 @@ public void initialize() {
controllers.put("/login", new LoginController());
controllers.put("/login/view", new LoginViewController());
controllers.put("/logout", new LogoutController());
controllers.put("/register/view", new RegisterViewController());
controllers.put("/register", new RegisterController());

log.info("Initialized Handler Mapping!");
controllers.keySet()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.techcourse.mvc;

import jakarta.servlet.http.HttpServletRequest;
import webmvc.org.springframework.web.servlet.mvc.HandlerMapping;

public class ManualHandlerMappingAdapter implements HandlerMapping {

private final ManualHandlerMapping manualHandlerMapping;

public ManualHandlerMappingAdapter() {
manualHandlerMapping = new ManualHandlerMapping();
}

Comment on lines +6 to +13

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

한번 더 추상화되어야하는 이유가 궁금해요
manualHandlerMapping이 바로 handlerMapping을 구현하는 형태면 생기는 문제가 뭐가 있을까요

Copy link
Author

@aak2075 aak2075 Sep 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

레거시 코드에 약간의 변경도 허용하고 싶지 않았습니다. 구현을 한다는 것도 변경이고 이로 인해 getHandler의 반환 타입이 Controller -> Object로 바뀌어야 하는데요, 작은 변경이기는 하지만 다른 코드에서 사용하고 있다면 문제가 생길 수도 있고 사실 다른 프레임워크를 적용했는데 시그니처도 같고 구현만 하면 호환이 된다는 것 자체가 좀 와닿지가 않았습니다.
정리하자면 구현으로 간단하게 해결할 수 있지만 실제로 프레임워크를 바꾼다는 생각으로 어댑터를 추가했습니다.
(미션에서 요구한건 컨트롤러만 변경하지 말라는 것인데 지금 봤네요)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋네요 이런 생각은 전혀 못햇어요

@Override
public void initialize() {
manualHandlerMapping.initialize();
}

@Override
public Object getHandler(final HttpServletRequest httpServletRequest) {
return manualHandlerMapping.getHandler(httpServletRequest.getRequestURI());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.techcourse.mvc.exception;

public class CanNotInvokeMethodException extends RuntimeException {
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.techcourse;
package com.techcourse.mvc.exception;

public class UncheckedServletException extends RuntimeException {

Expand Down
64 changes: 64 additions & 0 deletions app/src/test/java/com/techcourse/mvc/DispatcherServletTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.techcourse.mvc;

import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.Test;
import webmvc.org.springframework.web.servlet.mvc.tobe.AnnotationHandlerAdapter;
import webmvc.org.springframework.web.servlet.mvc.tobe.AnnotationHandlerMapping;

import static org.mockito.Mockito.*;

class DispatcherServletTest {

@Test
void service_RegisterViewController() throws Exception {
// given
final var handlerMapping = new AnnotationHandlerMapping("com.techcourse.controller");
handlerMapping.initialize();
final var handlerAdapter = new AnnotationHandlerAdapter();
final var dispatcherServlet = new DispatcherServlet(handlerMapping, handlerAdapter);

final var request = mock(HttpServletRequest.class);
final var response = mock(HttpServletResponse.class);
final var requestDispatcher = mock(RequestDispatcher.class);

when(request.getRequestURI()).thenReturn("/register");
when(request.getMethod()).thenReturn("GET");
when(request.getRequestDispatcher("/register.jsp")).thenReturn(requestDispatcher);
doNothing().when(requestDispatcher).forward(any(), any());

// when
dispatcherServlet.service(request, response);

// then
verify(request, times(1)).getRequestDispatcher("/register.jsp");
verify(requestDispatcher, times(1)).forward(request, response);
}

@Test
void service_RegisterController() throws Exception {
// given
final var handlerMapping = new AnnotationHandlerMapping("com.techcourse.controller");
handlerMapping.initialize();
final var handlerAdapter = new AnnotationHandlerAdapter();
final var dispatcherServlet = new DispatcherServlet(handlerMapping, handlerAdapter);

final var request = mock(HttpServletRequest.class);
final var response = mock(HttpServletResponse.class);
final var requestDispatcher = mock(RequestDispatcher.class);

when(request.getRequestURI()).thenReturn("/register");
when(request.getMethod()).thenReturn("POST");
when(request.getParameter(any())).thenReturn("anyParam");
when(request.getRequestDispatcher("/index.jsp")).thenReturn(requestDispatcher);
doNothing().when(requestDispatcher).forward(any(), any());

// when
dispatcherServlet.service(request, response);

// then
verify(request, times(1)).getRequestDispatcher("/index.jsp");
verify(requestDispatcher, times(1)).forward(request, response);
}
}
Loading