Skip to content

Commit

Permalink
[MVC 구현하기 2단계] 레오(차영호) 미션 제출합니다. (#468)
Browse files Browse the repository at this point in the history
* feat: annotation 기반 핸들러 어댑터 작성

* feat: 핸들러 매핑 구현 및 DispatcherServlet 수정

* refactor: 메서드 추출

* feat: HandlerExecution 에서 instance 의존하도록 수정

* refactor: css 최신화

* refactor: 저장 타입 Object -> Controller 로 변경

* refactor: try-catch 예외 명시

* refactor: 사용하지 않는 클래스 제거

* refactor: 메서드 순서 변경
  • Loading branch information
youngh0 authored Sep 24, 2023
1 parent 3a3e4b0 commit 88af1aa
Show file tree
Hide file tree
Showing 16 changed files with 205 additions and 43 deletions.
54 changes: 48 additions & 6 deletions app/src/main/java/com/techcourse/DispatcherServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,81 @@
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.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.HandlerMapping;
import webmvc.org.springframework.web.servlet.view.JspView;

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

public class DispatcherServlet extends HttpServlet {

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

private ManualHandlerMapping manualHandlerMapping;
private List<HandlerAdapter> handlerAdapters;
private List<HandlerMapping> handlerMappings;

public DispatcherServlet() {
}

@Override
public void init() {
manualHandlerMapping = new ManualHandlerMapping();
initHandlerMappings();
initHandlerAdapters();
}

private void initHandlerMappings() {
this.handlerMappings = new ArrayList<>();
ManualHandlerMapping manualHandlerMapping = new ManualHandlerMapping();
manualHandlerMapping.initialize();
handlerMappings.add(manualHandlerMapping);

AnnotationHandlerMapping annotationHandlerMapping = new AnnotationHandlerMapping();
annotationHandlerMapping.initialize();
handlerMappings.add(annotationHandlerMapping);
}

private void initHandlerAdapters() {
this.handlerAdapters = new ArrayList<>();
handlerAdapters.add(new ManualHandlerAdapter());
handlerAdapters.add(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);

try {
final var controller = manualHandlerMapping.getHandler(requestURI);
final var viewName = controller.execute(request, response);
move(viewName, request, response);
Object handler = findHandler(request);
HandlerAdapter handlerAdapter = findHandlerAdapter(handler);

ModelAndView modelAndView = handlerAdapter.handle(request, response, handler);
move(modelAndView.getViewName(), request, response);
} catch (Throwable e) {
log.error("Exception : {}", e.getMessage(), e);
throw new ServletException(e.getMessage());
}
}

private Object findHandler(HttpServletRequest request) {
return handlerMappings.stream()
.filter(handlerMapping -> handlerMapping.getHandler(request) != null)
.findAny()
.orElseThrow(() -> new IllegalArgumentException("처리할 수 없는 요청입니다."))
.getHandler(request);
}

private HandlerAdapter findHandlerAdapter(Object handler) {
return handlerAdapters.stream()
.filter(adapter -> adapter.supports(handler))
.findAny()
.orElseThrow(() -> new IllegalArgumentException("요청을 처리할 수 있는 핸들러 어댑터가 없습니다."));
}

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()));
Expand Down
22 changes: 22 additions & 0 deletions app/src/main/java/com/techcourse/ManualHandlerAdapter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.techcourse;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import webmvc.org.springframework.web.servlet.ModelAndView;
import webmvc.org.springframework.web.servlet.mvc.asis.Controller;
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(Object handler) {
return handler instanceof Controller;
}

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

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

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

@Override
public String execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception {
private static final Logger log = LoggerFactory.getLogger(RegisterController.class);

@RequestMapping(value = "/register", method = RequestMethod.POST)
public ModelAndView save(HttpServletRequest req, HttpServletResponse res) {
final var user = new User(2,
req.getParameter("account"),
req.getParameter("password"),
req.getParameter("email"));
InMemoryUserRepository.save(user);
return new ModelAndView(new JspView("/index.jsp"));
}

return "redirect:/index.jsp";
@RequestMapping(value = "/register", method = RequestMethod.GET)
public ModelAndView show(HttpServletRequest req, HttpServletResponse res) {
return new ModelAndView(new JspView("/register.jsp"));
}
}

This file was deleted.

2 changes: 1 addition & 1 deletion app/src/main/webapp/css/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -11262,4 +11262,4 @@ body {
width: 20px !important;
font-size: 0.75rem;
border-radius: 0.25rem !important;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,8 @@ public Map<String, Object> getModel() {
public View getView() {
return view;
}

public String getViewName() {
return view.getName();
}
}
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,18 @@
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(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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import web.org.springframework.web.bind.annotation.RequestMethod;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
Expand All @@ -17,7 +18,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);

Expand All @@ -29,14 +30,16 @@ public AnnotationHandlerMapping(final Object... basePackage) {
this.handlerExecutions = new HashMap<>();
}

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

Set<Class<?>> controllers = reflections.getTypesAnnotatedWith(Controller.class);
for (Class<?> controller : controllers) {
List<Method> requestMappingMethods = getRequestMappingMethods(controller);
Constructor<?> constructor = getConstructor(controller);
addHandlerExecutions(requestMappingMethods, constructor);
Object instance = makeInstance(constructor);
addHandlerExecutions(requestMappingMethods, instance);
}
}

Expand All @@ -55,18 +58,27 @@ private Constructor<?> getConstructor(Class<?> controller) {
}
}

private void addHandlerExecutions(List<Method> requestMappingMethods, Constructor<?> constructor) {
private void addHandlerExecutions(List<Method> requestMappingMethods, Object instance) {
for (Method requestMappingMethod : requestMappingMethods) {
RequestMapping requestMapping = requestMappingMethod.getAnnotation(RequestMapping.class);
RequestMethod[] controllergMethods = requestMapping.method();
List<HandlerKey> handlerKeys = makeHandlerKeys(requestMapping, controllergMethods);
putHandlerExecutions(constructor, requestMappingMethod, handlerKeys);
RequestMethod[] controllerMethods = requestMapping.method();
List<HandlerKey> handlerKeys = makeHandlerKeys(requestMapping, controllerMethods);
putHandlerExecutions(instance, requestMappingMethod, handlerKeys);
}
}

private void putHandlerExecutions(Constructor<?> constructor, Method requestMappingMethod, List<HandlerKey> handlerKeys) {
private Object makeInstance(Constructor<?> constructor) {
try {
return constructor.newInstance();
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
log.debug("fail make to instance");
throw new RuntimeException(e);
}
}

private void putHandlerExecutions(Object instance, Method requestMappingMethod, List<HandlerKey> handlerKeys) {
for (HandlerKey handlerKey : handlerKeys) {
handlerExecutions.put(handlerKey, new HandlerExecution(requestMappingMethod, constructor));
handlerExecutions.put(handlerKey, new HandlerExecution(requestMappingMethod, instance));
}
}

Expand All @@ -76,6 +88,7 @@ private List<HandlerKey> makeHandlerKeys(RequestMapping requestMapping, RequestM
.collect(Collectors.toList());
}

@Override
public Object getHandler(final HttpServletRequest request) {
RequestMethod requestMethod = RequestMethod.from(request.getMethod());
String requestURI = request.getRequestURI();
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(Object handler);

ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,19 @@
import jakarta.servlet.http.HttpServletResponse;
import webmvc.org.springframework.web.servlet.ModelAndView;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class HandlerExecution {

private final Method method;
private final Constructor<?> constructor;
private final Object instance;

public HandlerExecution(Method method, Constructor<?> constructor) {
public HandlerExecution(Method method, Object instance) {
this.method = method;
this.constructor = constructor;
this.instance = instance;
}

public ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response) throws Exception {
return (ModelAndView) method.invoke(constructor.newInstance(), request, response);
return (ModelAndView) method.invoke(instance, request, response);
}
}
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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@ public class JsonView implements View {
@Override
public void render(final Map<String, ?> model, final HttpServletRequest request, HttpServletResponse response) throws Exception {
}

@Override
public String getName() {
return "";
}
}
Loading

0 comments on commit 88af1aa

Please sign in to comment.