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단계] 우르(김현우) 미션 제출합니다. #476

Merged
merged 11 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 71 additions & 29 deletions app/src/main/java/com/techcourse/DispatcherServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,90 @@
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import webmvc.org.springframework.web.servlet.ModelAndView;
import webmvc.org.springframework.web.servlet.mvc.tobe.AnnotationHandlerAdapter;
import webmvc.org.springframework.web.servlet.mvc.tobe.AnnotationHandlerMapping;
import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerAdapter;
import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerAdapters;
import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerMapping;
import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerMappings;
import webmvc.org.springframework.web.servlet.mvc.tobe.ManualHandlerAdapter;
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 static final long serialVersionUID = 1L;
private static final Logger log = LoggerFactory.getLogger(DispatcherServlet.class);

private ManualHandlerMapping manualHandlerMapping;
private HandlerMapping handlerMappingComposite;
Copy link

Choose a reason for hiding this comment

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

일급컬렉션 사용 목적 질문과 비슷한 맥락의 질문입니다.
여기에서 직접 일급컬렉션을 갖지 않고 추상화해주신 의도가 궁금합니다!

Copy link
Author

Choose a reason for hiding this comment

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

인터페이스를 가지고 있는게 더 확장성 있다고 생각했습니다!!

private HandlerAdapter handlerAdapterComposite;

public DispatcherServlet() {
}
public DispatcherServlet() {
}

@Override
public void init() {
handlerMappingComposite = new HandlerMappings(
List.of(
new ManualHandlerMapping(),
new AnnotationHandlerMapping("com.techcourse")
)
);

handlerAdapterComposite = new HandlerAdapters(
List.of(
new ManualHandlerAdapter(),
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);

@Override
public void init() {
manualHandlerMapping = new ManualHandlerMapping();
manualHandlerMapping.initialize();
try {
final Object handler = handlerMappingComposite.getHandler(request);
final Object handleValue = handlerAdapterComposite.handle(request, response, handler);

// TODO : 3단계 리팩터링
renderByHandlerValueType(request, response, handleValue);

} catch (Throwable e) {
log.error("Exception : {}", e.getMessage(), e);
throw new ServletException(e.getMessage());
}
}

@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 var controller = manualHandlerMapping.getHandler(requestURI);
final var viewName = controller.execute(request, response);
move(viewName, request, response);
} catch (Throwable e) {
log.error("Exception : {}", e.getMessage(), e);
throw new ServletException(e.getMessage());
}
private void renderByHandlerValueType(
final HttpServletRequest request,
final HttpServletResponse response,
final Object handleValue
) throws Exception {
if (handleValue instanceof ModelAndView) {
final ModelAndView modelAndView = (ModelAndView) handleValue;
move(modelAndView.getView().getName(), request, response);
}

private void move(final String viewName, final HttpServletRequest request, final HttpServletResponse response) throws Exception {
if (viewName.startsWith(JspView.REDIRECT_PREFIX)) {
response.sendRedirect(viewName.substring(JspView.REDIRECT_PREFIX.length()));
return;
}
if (handleValue instanceof String) {
final String viewName = (String) handleValue;
move(viewName, request, response);
}
}

final var requestDispatcher = request.getRequestDispatcher(viewName);
requestDispatcher.forward(request, response);
private void move(final String viewName, final HttpServletRequest request,
final HttpServletResponse response) throws Exception {
if (viewName.startsWith(JspView.REDIRECT_PREFIX)) {
response.sendRedirect(viewName.substring(JspView.REDIRECT_PREFIX.length()));
return;
}

final var requestDispatcher = request.getRequestDispatcher(viewName);
requestDispatcher.forward(request, response);
}
}
11 changes: 7 additions & 4 deletions app/src/main/java/com/techcourse/ManualHandlerMapping.java
Original file line number Diff line number Diff line change
@@ -1,34 +1,37 @@
package com.techcourse;

import com.techcourse.controller.*;
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 java.util.HashMap;
import java.util.Map;
import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerMapping;

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 Object getHandler(final HttpServletRequest httpServletRequest) {
final String requestURI = httpServletRequest.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,26 @@

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";
final ModelAndView modelAndView = new ModelAndView(new JspView("redirect:/index.jsp"));
return modelAndView;
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import web.org.springframework.web.bind.annotation.RequestMapping;
import web.org.springframework.web.bind.annotation.RequestMethod;
import webmvc.org.springframework.web.servlet.ModelAndView;
import webmvc.org.springframework.web.servlet.view.JspView;

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

@Override
public String execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception {
return "/register.jsp";
private static final Logger log = LoggerFactory.getLogger(RegisterViewController.class);

@RequestMapping(value = "/register", method = RequestMethod.GET)
public ModelAndView execute(final HttpServletRequest req, final HttpServletResponse res) {
log.info("RegisterViewController GET method");

return new ModelAndView(new JspView("/register.jsp"));
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package web.org.springframework.web.bind.annotation;

import java.util.Arrays;

public enum RequestMethod {
GET,
HEAD,
Expand All @@ -12,11 +10,4 @@ public enum RequestMethod {
OPTIONS,
TRACE
;

public static RequestMethod find(final String method) {
return Arrays.stream(values())
.filter(it -> it.name().equals(method.toUpperCase()))
.findAny()
.orElseThrow(() -> new IllegalArgumentException("지원하지 않는 RequestMethod입니다."));
}
}
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 getName();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package webmvc.org.springframework.web.servlet.mvc.tobe;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

public class AnnotationHandlerAdapter implements HandlerAdapter {

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

@Override
public Object handle(
final HttpServletRequest request,
final HttpServletResponse response,
final Object handler
) {
return ((HandlerExecution) handler).handle(request, response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import web.org.springframework.web.bind.annotation.RequestMapping;
import web.org.springframework.web.bind.annotation.RequestMethod;

public class AnnotationHandlerMapping {
public class AnnotationHandlerMapping implements HandlerMapping {

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

Expand All @@ -26,23 +26,41 @@ public AnnotationHandlerMapping(final Object... basePackages) {
this.handlerExecutions = new HashMap<>();
}

public void initialize()
throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
log.info("Initialized AnnotationHandlerMapping!");

@Override
public void initialize() {
final Reflections reflections = new Reflections(basePackages);

final Set<Class<?>> controllerAnnotationClasses =
reflections.getTypesAnnotatedWith(Controller.class);

for (Class<?> controllerAnnotationClass : controllerAnnotationClasses) {
final Object instance = controllerAnnotationClass.getDeclaredConstructor().newInstance();
final Object instance = getControllerInstance(controllerAnnotationClass);
final Method[] methods = controllerAnnotationClass.getDeclaredMethods();

Arrays.stream(methods)
.filter(method -> method.isAnnotationPresent(RequestMapping.class))
.forEach(method -> putHandlerExecutions(method, instance));
}

log.info("Initialized AnnotationHandlerMapping!");

handlerExecutions.keySet()
.forEach(handlerKey -> log.info("handlerKey : {}, Controller : {}", handlerKey,
handlerExecutions.get(handlerKey)));
}

private Object getControllerInstance(final Class<?> controllerAnnotationClass) {
final Object instance;
try {
instance = controllerAnnotationClass.getDeclaredConstructor().newInstance();
} catch (InstantiationException
| IllegalAccessException
| InvocationTargetException
| NoSuchMethodException e
) {
throw new IllegalArgumentException("AnnotationHandlerMapping Reflection Exception");
}
return instance;
}

private void putHandlerExecutions(final Method method, final Object target) {
Expand All @@ -55,11 +73,12 @@ private void putHandlerExecutions(final Method method, final Object target) {
}
}

@Override
public Object getHandler(final HttpServletRequest request) {
final String requestURI = request.getRequestURI();
final String method = request.getMethod();

final HandlerKey handlerKey = new HandlerKey(requestURI, RequestMethod.find(method));
final HandlerKey handlerKey = new HandlerKey(requestURI, RequestMethod.valueOf(method));
Copy link

Choose a reason for hiding this comment

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

👍🏻👍🏻


return handlerExecutions.get(handlerKey);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package webmvc.org.springframework.web.servlet.mvc.tobe;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

public interface HandlerAdapter {

boolean isSupport(final Object handler);

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

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;

public class HandlerAdapters implements HandlerAdapter {

private final List<HandlerAdapter> handlerAdapters;

public HandlerAdapters(final List<HandlerAdapter> handlerAdapters) {
this.handlerAdapters = handlerAdapters;
}

@Override
public boolean isSupport(final Object handler) {
throw new UnsupportedOperationException();
Copy link

Choose a reason for hiding this comment

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

해당 메서드에서 바로 예외를 던지는 이유가 뭔가요?
boolean을 반환할 것을 기대하고 호출했는데 호출하자마자 예외가 던져지는 게 조금 어색하게 느껴져서 코멘트 남깁니다!

Copy link
Author

Choose a reason for hiding this comment

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

handle 메서드에서 모든 adapters를 탐색하면서 isSupport 를 실행하기 때문에 굳이 HandlerAdapters에서는 isSupport 를 사용하지 않았습니다.

총 두 번 adapters를 도는거라 불필요한 연산이라 생각이 들었어요.

그래서 해당 메서드는 지원하지 않는다고 개발자에게 명시적으로 보여주고자 작성했습니다

}

@Override
public Object handle(
final HttpServletRequest request,
final HttpServletResponse response,
final Object handler
) {
return handlerAdapters.stream()
.filter(handlerAdapter -> handlerAdapter.isSupport(handler))
.map(handlerAdapter -> handlerAdapter.handle(request, response, handler))
.findAny()
.orElseThrow(() -> new IllegalArgumentException("해당 요청은 처리할 수 없습니다."));
}
}
Loading
Loading