Skip to content

Commit

Permalink
[MVC 구현하기 - 2단계] 매튜(김재연) 미션 제출합니다. (#508)
Browse files Browse the repository at this point in the history
* refactor : Register(View)Controller 통합

* refactor : interface HandlerExecution 을 추가하고, Manual, Annotation HandlerExecution 들이 이를 구현

* refactor : DispatcherServlet Service 에서 ModelAndView 를 처리하도록 구현

* refactor : HandlerMapping 추가

* refactor : DispatcherServle이 HandlerMapping 을 가질 수 있도록 구현

* refactor : JsonView getViewName() 이 의미 없는 값을 반환하도록 수정

* fix : basePackages 수정

* refactor : Exception 처리

* refactor : 기존 Register(View)Controller 복구

* refactor : 로그인 화면에서 회원가입시 이동하는 URL `/register/view` -> `/register` 로 변경

* refactor : 필요없는 형변환 삭제

* refactor : Annotation 기반 Controller 가 더 우선적으로 선택될 수 있도록 수정

* refactor : HandlerExecution 추상화 제거

* refactor : HandlerMapping 추상화
  • Loading branch information
kpeel5839 authored Sep 23, 2023
1 parent 3a07351 commit 9fa1bd5
Show file tree
Hide file tree
Showing 19 changed files with 220 additions and 40 deletions.
6 changes: 3 additions & 3 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ dependencies {

implementation "org.apache.tomcat.embed:tomcat-embed-core:10.1.13"
implementation "org.apache.tomcat.embed:tomcat-embed-jasper:10.1.13"
implementation "ch.qos.logback:logback-classic:1.2.12"
implementation 'ch.qos.logback:logback-classic:1.4.7'
implementation "org.apache.commons:commons-lang3:3.13.0"

testImplementation "org.assertj:assertj-core:3.24.2"
testImplementation "org.junit.jupiter:junit-jupiter-api:5.7.2"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.2'
testImplementation "org.mockito:mockito-core:5.4.0"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.7.2"
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.2'
}

idea {
Expand Down
34 changes: 25 additions & 9 deletions app/src/main/java/com/techcourse/DispatcherServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,57 @@
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import web.org.springframework.http.HttpStatus;
import webmvc.org.springframework.web.servlet.ModelAndView;
import webmvc.org.springframework.web.servlet.mvc.tobe.AnnotationHandlerMapping;
import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerExecution;
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 HandlerMappings handlerMappings;

public DispatcherServlet() {
}

@Override
public void init() {
manualHandlerMapping = new ManualHandlerMapping();
manualHandlerMapping.initialize();
handlerMappings = new HandlerMappings();
handlerMappings.addHandlerMapping(new AnnotationHandlerMapping("com.techcourse.controller"));
handlerMappings.addHandlerMapping(new ManualHandlerMapping());
}

@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);
Optional<HandlerExecution> execution = handlerMappings.getHandler(request);

if (execution.isEmpty()) {
response.setStatus(HttpStatus.NOT_FOUND.getValue());
return;
}

ModelAndView view = execution.get().handle(request, response);
move(view.getViewName(), 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(
String viewName,
HttpServletRequest request,
HttpServletResponse response
) throws Exception {
if (viewName.startsWith(JspView.REDIRECT_PREFIX)) {
response.sendRedirect(viewName.substring(JspView.REDIRECT_PREFIX.length()));
return;
Expand All @@ -48,4 +63,5 @@ private void move(final String viewName, final HttpServletRequest request, final
final var requestDispatcher = request.getRequestDispatcher(viewName);
requestDispatcher.forward(request, response);
}

}
36 changes: 36 additions & 0 deletions app/src/main/java/com/techcourse/HandlerMappings.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.techcourse;

import jakarta.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerExecution;
import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerMapping;

public class HandlerMappings {

private final List<HandlerMapping> handlerMappings;

public HandlerMappings(List<HandlerMapping> handlerMappings) {
this.handlerMappings = new ArrayList<>(handlerMappings);
}

public HandlerMappings() {
this(Collections.emptyList());
}

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

public Optional<HandlerExecution> getHandler(HttpServletRequest request) {
return handlerMappings.stream()
.map(handlerMapping -> handlerMapping.getHandler(request))
.filter(Objects::nonNull)
.findFirst();
}

}
45 changes: 35 additions & 10 deletions app/src/main/java/com/techcourse/ManualHandlerMapping.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
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 java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
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.HandlerExecution;
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 String IMPLEMENT_METHOD_NAME = "execute";

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

@Override
public void initialize() {
controllers.put("/", new ForwardController("/index.jsp"));
controllers.put("/login", new LoginController());
Expand All @@ -23,13 +34,27 @@ public void initialize() {
controllers.put("/register/view", new RegisterViewController());
controllers.put("/register", new RegisterController());

log.info("Initialized Handler Mapping!");
log.info("Initialized Manual Handler Mapping!");
controllers.keySet()
.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 HandlerExecution getHandler(HttpServletRequest request) {
log.debug("Request Mapping Uri : {}", request.getRequestURI());
Controller controller = controllers.getOrDefault(request.getRequestURI(), null);

if (Objects.isNull(controller)) {
return null;
}

Method execution = Arrays.stream(controller.getClass()
.getMethods())
.filter(method -> method.getName().equals(IMPLEMENT_METHOD_NAME))
.findFirst()
.orElseThrow(() -> new IllegalStateException("Execute 메서드가 구현되어 있지 않습니다."));

return new HandlerExecution(controller, execution);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.techcourse.controller;

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 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 RegisterAnnotationController {

@RequestMapping(value = "/register", method = RequestMethod.POST)
public ModelAndView save(HttpServletRequest req, HttpServletResponse res) {
User user = new User(
2,
req.getParameter("account"),
req.getParameter("password"),
req.getParameter("email")
);

InMemoryUserRepository.save(user);

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

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@ public class RegisterController implements Controller {

@Override
public String execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception {
final var user = new User(2,
User user = new User(
2,
req.getParameter("account"),
req.getParameter("password"),
req.getParameter("email"));
InMemoryUserRepository.save(user);
req.getParameter("email")
);

InMemoryUserRepository.save(user);
return "redirect:/index.jsp";
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ public class RegisterViewController implements Controller {
public String execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception {
return "/register.jsp";
}

}
2 changes: 1 addition & 1 deletion app/src/main/webapp/login.jsp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
</form>
</div>
<div class="card-footer text-center py-3">
<div class="small"><a href="/register/view">아이디가 없나요? 회원가입 하러가기</a></div>
<div class="small"><a href="/register">아이디가 없나요? 회원가입 하러가기</a></div>
</div>
</div>
</div>
Expand Down
17 changes: 17 additions & 0 deletions mvc/src/main/java/web/org/springframework/http/HttpStatus.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package web.org.springframework.http;

public enum HttpStatus {

NOT_FOUND(404);

private final int value;

HttpStatus(int value) {
this.value = value;
}

public int getValue() {
return value;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public enum RequestMethod {
public static RequestMethod from(String input) {
try {
return valueOf(input);
} catch (IllegalArgumentException illegalArgumentException) {
} catch (Exception exception) {
throw new IllegalArgumentException("잘못된 Method 입니다.");
}
}
Expand Down
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.getViewName();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,9 @@
import java.util.Map;

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
Expand Up @@ -4,5 +4,7 @@
import jakarta.servlet.http.HttpServletResponse;

public interface Controller {

String execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception;

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,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 @@ -27,6 +27,7 @@ public AnnotationHandlerMapping(final Object... basePackages) {
this.handlerExecutions = new HashMap<>();
}

@Override
public void initialize() {
log.info("Initialized AnnotationHandlerMapping!");

Expand All @@ -47,6 +48,7 @@ private void putHandlerExecutionsByController(Set<Class<?>> classes) {
}
} catch (Exception exception) {
log.error("예외 발생 {0}", exception);
throw new RuntimeException("AnnotationHandlerMapping Initialized 중에 예외가 발생하였습니다.");
}
}

Expand All @@ -66,18 +68,15 @@ private void putHandlerExecutionByAnnotation(Object controller, Method method) {
}
}

public Object getHandler(final HttpServletRequest request) {

@Override
public HandlerExecution getHandler(final HttpServletRequest request) {
HandlerKey key = new HandlerKey(
request.getRequestURI(),
RequestMethod.from(request.getMethod())
);

return handlerExecutions.computeIfAbsent(
key,
ignored -> {
throw new IllegalArgumentException("올바르지 않은 요청입니다.");
}
);
return handlerExecutions.getOrDefault(key, null);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import jakarta.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import webmvc.org.springframework.web.servlet.ModelAndView;
import webmvc.org.springframework.web.servlet.view.JspView;

public class HandlerExecution {

Expand All @@ -16,7 +17,15 @@ public HandlerExecution(Object instance, Method method) {
}

public ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response) throws Exception {
return (ModelAndView) method.invoke(instance, request, response);
Object resultOfHandle = method.invoke(instance, request, response);

if (resultOfHandle instanceof ModelAndView) {
return (ModelAndView) resultOfHandle;
}

return new ModelAndView(
new JspView((String) resultOfHandle)
);
}

}
Loading

0 comments on commit 9fa1bd5

Please sign in to comment.