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단계] 망고(고재철) 미션 제출합니다. #531

Merged
merged 7 commits into from
Sep 24, 2023
28 changes: 19 additions & 9 deletions app/src/main/java/com/techcourse/DispatcherServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,50 @@
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.asis.ManualHandlerAdapter;
import webmvc.org.springframework.web.servlet.mvc.tobe.AnnotationHandlerAdapter;
import webmvc.org.springframework.web.servlet.mvc.tobe.AnnotationHandlerMapping;
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 ManualHandlerMapping manualHandlerMapping;
private final transient HandlerMappings handlerMappings;
private final transient HandlerAdapters handlerAdapters;
Comment on lines +20 to +21

Choose a reason for hiding this comment

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

transient를 선택하신 이유는 무엇일까요~?
혹시 ServletSerializable한 것과 관계가 있을까요?

Copy link
Author

Choose a reason for hiding this comment

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

넵! 이 부분은 Serializable 클래스는 필드들도 Serializable or transient 해야한다는 SonarLint의 조언을 보고 적용했습니다.
HandlerMappingsHandlerAdapters는 직렬화 해줄 필요성을 못느껴서 transient로 두었습니다!


public DispatcherServlet() {
this.handlerMappings = new HandlerMappings();
this.handlerAdapters = new HandlerAdapters();
}

@Override
public void init() {
manualHandlerMapping = new ManualHandlerMapping();
manualHandlerMapping.initialize();
handlerMappings.addHandlerMapping(new ManualHandlerMapping());
handlerMappings.addHandlerMapping(new AnnotationHandlerMapping());
handlerAdapters.addHandlerAdapter(new ManualHandlerAdapter());
handlerAdapters.addHandlerAdapter(new AnnotationHandlerAdapter());
}

@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);
log.debug("Method : {}, Request URI : {}", request.getMethod(), request.getRequestURI());

try {
final var controller = manualHandlerMapping.getHandler(requestURI);
final var viewName = controller.execute(request, response);
move(viewName, request, response);
final var handler = handlerMappings.getHandler(request);
final var handlerAdapter = handlerAdapters.getHandlerAdapter(handler);
final ModelAndView modelAndView = handlerAdapter.handle(request, response, handler);
move(modelAndView, request, response);
} catch (Throwable e) {
log.error("Exception : {}", e.getMessage(), e);
throw new ServletException(e.getMessage());
}
}

private void move(final String viewName, final HttpServletRequest request, final HttpServletResponse response) throws Exception {
private void move(final ModelAndView modelAndView, final HttpServletRequest request, final HttpServletResponse response) throws Exception {
final String viewName = modelAndView.getView().getViewName();
if (viewName.startsWith(JspView.REDIRECT_PREFIX)) {
response.sendRedirect(viewName.substring(JspView.REDIRECT_PREFIX.length()));
return;
Expand Down
26 changes: 26 additions & 0 deletions app/src/main/java/com/techcourse/HandlerAdapters.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.techcourse;

import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerAdapter;

import java.util.ArrayList;
import java.util.List;

public class HandlerAdapters {

private final List<HandlerAdapter> adapters;

public HandlerAdapters() {
this.adapters = new ArrayList<>();
}

public void addHandlerAdapter(final HandlerAdapter handlerAdapter) {
adapters.add(handlerAdapter);
}

public HandlerAdapter getHandlerAdapter(final Object handler) {
return adapters.stream()
.filter(it -> it.supports(handler))
.findFirst()
.orElseThrow(IllegalArgumentException::new);
}
}
30 changes: 30 additions & 0 deletions app/src/main/java/com/techcourse/HandlerMappings.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.techcourse;

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

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public class HandlerMappings {

private final List<HandlerMapping> mappings;

public HandlerMappings() {
this.mappings = new ArrayList<>();
}

public void addHandlerMapping(final HandlerMapping handlerMapping) {
handlerMapping.initialize();
mappings.add(handlerMapping);
}

public Object getHandler(final HttpServletRequest request) {
return mappings.stream()
.map(it -> it.getHandler(request))
.filter(Objects::nonNull)
.findFirst()
.orElseThrow(IllegalStateException::new);
}
}
15 changes: 10 additions & 5 deletions app/src/main/java/com/techcourse/ManualHandlerMapping.java
Original file line number Diff line number Diff line change
@@ -1,34 +1,39 @@
package com.techcourse;

import com.techcourse.controller.*;
import com.techcourse.controller.LoginController;
import com.techcourse.controller.LoginViewController;
import com.techcourse.controller.LogoutController;
import jakarta.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import webmvc.org.springframework.web.servlet.mvc.asis.Controller;
import webmvc.org.springframework.web.servlet.mvc.asis.ForwardController;
import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerMapping;

import java.util.HashMap;
import java.util.Map;

public class ManualHandlerMapping {
public class ManualHandlerMapping implements HandlerMapping {

private static final Logger log = LoggerFactory.getLogger(ManualHandlerMapping.class);

private static final Map<String, Controller> controllers = new HashMap<>();

@Override
public void initialize() {
controllers.put("/", new ForwardController("/index.jsp"));
controllers.put("/login", new LoginController());
controllers.put("/login/view", new LoginViewController());
controllers.put("/logout", new LogoutController());
controllers.put("/register/view", new RegisterViewController());
controllers.put("/register", new RegisterController());

log.info("Initialized Handler Mapping!");
controllers.keySet()
.forEach(path -> log.info("Path : {}, Controller : {}", path, controllers.get(path).getClass()));
}

public Controller getHandler(final String requestURI) {
@Override
public Controller getHandler(final HttpServletRequest request) {
final String requestURI = request.getRequestURI();
log.debug("Request Mapping Uri : {}", requestURI);
return controllers.get(requestURI);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,30 @@

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 save(final HttpServletRequest request, final HttpServletResponse response) {
final var user = new User(2,
req.getParameter("account"),
req.getParameter("password"),
req.getParameter("email"));
request.getParameter("account"),
request.getParameter("password"),
request.getParameter("email"));
InMemoryUserRepository.save(user);

return "redirect:/index.jsp";
return new ModelAndView(new JspView("redirect:/index.jsp"));
}

@RequestMapping(value = "/register/view", method = RequestMethod.GET)
public ModelAndView show(final HttpServletRequest request, final HttpServletResponse response) {
return new ModelAndView(new JspView("/register.jsp"));
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@

public interface View {
void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;

String getViewName();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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.tobe.HandlerAdapter;
import webmvc.org.springframework.web.servlet.view.JspView;

public class ManualHandlerAdapter implements HandlerAdapter {

@Override
public boolean supports(final Object handler) {
return handler instanceof Controller;
}

@Override
public ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response, final Object handler)
throws Exception {
final var controller = (Controller) handler;
final String viewName = controller.execute(request, response);
return new ModelAndView(new JspView(viewName));
}
}
Original file line number Diff line number Diff line change
@@ -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;

public class AnnotationHandlerAdapter implements HandlerAdapter {

@Override
public boolean supports(final Object handler) {
return handler instanceof HandlerExecution;
}

@Override
public ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response, final Object handler)
throws Exception {
final var handlerExecution = (HandlerExecution) handler;
return handlerExecution.handle(request, response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import java.util.*;
import java.util.stream.Collectors;

public class AnnotationHandlerMapping {
public class AnnotationHandlerMapping implements HandlerMapping {

private static final Logger log = LoggerFactory.getLogger(AnnotationHandlerMapping.class);

Expand All @@ -24,6 +24,7 @@ public AnnotationHandlerMapping(final Object... basePackage) {
this.handlerExecutions = new HashMap<>();
}

@Override
public void initialize() {
final var reflections = new Reflections(basePackage);
final Set<Class<?>> controllerClasses = reflections.getTypesAnnotatedWith(Controller.class);
Expand Down Expand Up @@ -57,6 +58,7 @@ private void addHandlerExecution(final Method method, final RequestMapping reque
}
}

@Override
public Object getHandler(final HttpServletRequest request) {
final String requestURI = request.getRequestURI();
final var requestMethod = RequestMethod.valueOf(request.getMethod());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
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;

public interface HandlerAdapter {

boolean supports(final Object handler);

ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) throws Exception;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package webmvc.org.springframework.web.servlet.mvc.tobe;

import jakarta.servlet.http.HttpServletRequest;

public interface HandlerMapping {

void initialize();

Object getHandler(final HttpServletRequest request);
Comment on lines +7 to +9

Choose a reason for hiding this comment

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

아마 Spring의 HandlerMapping에는 getHandler만이 존재하지만
원래 존재하던 ManualHandlerMapping에는 initialize()가 존재해 이것을 맞춰주고자 initialize()를 인터페이스에도 추가하신게 아닐까 싶어요~

이것을 없애기 위해서는 어댑터 패턴을 활용하면 해결하실 수 있을거에요~!
기존의 ManualHandlerMapping의 handle()메서드 시그니처를 바꿀 필요도 없구요!

Copy link
Author

Choose a reason for hiding this comment

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

ManualHandlerMapping과 기존 AnnotationHandlerMapping의 메서드 구성이 같아서 해당 클래스들을 추상화 한 HandlerMapping 인터페이스를 만들고 두 메서드를 다 가지도록 했습니다!

이 과정에서 오는 ManualHandlerMappinghandle() 메서드 시그니쳐 변경은 오히려 이렇게 변경하는 편이 더 자연스럽다고 판단해서 변경했습니다..!

Choose a reason for hiding this comment

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

스프링과 유사하게 initialize()를 생성자에서 호출하면 어떨까 하여 여쭌 것인데
외부에서 초기화를 수행하는 것도 괜찮으시다면 그대로 가셔도 될 듯 합니다 😁

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,19 @@

public class JsonView implements View {

private final String viewName;

public JsonView(final String viewName) {
this.viewName = viewName;
}

@Override
public void render(final Map<String, ?> model, final HttpServletRequest request, HttpServletResponse response) throws Exception {
throw new UnsupportedOperationException();
}

@Override
public String getViewName() {
return viewName;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ public class JspView implements View {

public static final String REDIRECT_PREFIX = "redirect:";

private final String viewName;

public JspView(final String viewName) {
this.viewName = viewName;
}

@Override
Expand All @@ -28,4 +31,9 @@ public void render(final Map<String, ?> model, final HttpServletRequest request,

// todo
}

@Override
public String getViewName() {
return viewName;
}
}