Skip to content

Commit

Permalink
[MVC 2단계] 헙크 미션 제출합니다 (#449)
Browse files Browse the repository at this point in the history
* 패키지 위치 변경 및 코드 정리

* 서블릿 학습 테스트 코드 개선

* [MVC 구현하기 - 1단계] 헙크(정현승) 미션 제출합니다. (#384)

* 패키지 위치 변경 및 코드 정리

* 서블릿 학습 테스트 코드 개선

* feat: 학습 테스트 구현

* test: Reflections 테스트 작성

* feat: AnnotationHandlerMapping 초안

* refactor: AnnotationHandlerMapping 수정

* test: Servlet 테스트 작성

---------

Co-authored-by: kang-hyungu <[email protected]>

* feat: HandlerMapping 구현

* feat: HandlerMappings와 Adapter 구현

* feat: DispatcherServlet에 HandlerMappings, HandlerAdapters 적용

* feat: JspView 구현

* fix: HandlerExecution 수정

* refactor: 패키지 구조 수정

* refactor: 패키지 구조 수정

* fix: DispatcherServlet 클래스의 ModelAndView의 model 반환 로직 수정

* refactor: HandlerMappings 패키지 위치 수정

---------

Co-authored-by: kang-hyungu <[email protected]>
  • Loading branch information
HubCreator and kang-hyungu authored Sep 21, 2023
1 parent 1f3fc3a commit e3ae1b7
Show file tree
Hide file tree
Showing 13 changed files with 220 additions and 57 deletions.
45 changes: 24 additions & 21 deletions app/src/main/java/com/techcourse/DispatcherServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,46 +6,49 @@
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import webmvc.org.springframework.web.servlet.view.JspView;
import webmvc.org.springframework.web.servlet.ModelAndView;
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.HandlerMappings;

import java.util.Optional;

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;

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

@Override
public void init() {
manualHandlerMapping = new ManualHandlerMapping();
manualHandlerMapping.initialize();
handlerMappings.init(new ManualHandlerMapping());
handlerAdapters.init();
}

@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.info("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);
} catch (Throwable e) {
final Optional<Object> handlerOptional = handlerMappings.getHandler(request);
if (handlerOptional.isEmpty()) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
final Object handler = handlerOptional.get();
final HandlerAdapter handlerAdapter = handlerAdapters.getAdapter(handler)
.orElseThrow(() -> new IllegalStateException("핸들러 어댑터를 찾을 수 없습니다."));
final ModelAndView modelAndView = handlerAdapter.handle(handler, request, response);
modelAndView.getView().render(modelAndView.getModel(), request, response);
} catch (final Exception 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 {
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);
}
}
22 changes: 17 additions & 5 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 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<>();

public ManualHandlerMapping() {
initialize();
}

@Override
public void initialize() {
controllers.put("/", new ForwardController("/index.jsp"));
controllers.put("/login", new LoginController());
Expand All @@ -28,8 +39,9 @@ public void initialize() {
.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 Object getHandler(final HttpServletRequest request) {
log.debug("Request Mapping Uri : {}", request.getRequestURI());
return controllers.get(request.getRequestURI());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
import com.techcourse.repository.InMemoryUserRepository;
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 webmvc.org.springframework.web.servlet.mvc.asis.Controller;

public class LoginController implements Controller {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
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-annotated", method = RequestMethod.POST)
public ModelAndView save(final HttpServletRequest request, final HttpServletResponse response) {
final var user = new User(2,
request.getParameter("account"),
request.getParameter("password"),
request.getParameter("email"));
InMemoryUserRepository.save(user);

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

@RequestMapping(value = "/register-annotated", method = RequestMethod.GET)
public ModelAndView show(final HttpServletRequest request, final HttpServletResponse response) {
return new ModelAndView(new JspView("register.jsp"));
}
}
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(final Object handler) {
return handler instanceof HandlerExecution;
}

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

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class AnnotationHandlerMapping {
public class AnnotationHandlerMapping implements HandlerMapping {

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

Expand All @@ -26,37 +25,33 @@ public class AnnotationHandlerMapping {
public AnnotationHandlerMapping(final Object... basePackage) {
this.basePackage = basePackage;
this.handlerExecutions = new HashMap<>();
initialize();
}

public void initialize() {
@Override
public void initialize() {
log.info("Initialized AnnotationHandlerMapping!");
final Reflections reflections = new Reflections(basePackage);
reflections.getTypesAnnotatedWith(Controller.class)
.forEach(this::register);
}

private void register(final Class<?> clazz) {
try {
final Object instance = clazz.getDeclaredConstructor().newInstance();
final List<Method> annotatedMethods = Arrays.stream(clazz.getDeclaredMethods())
.filter(method -> method.isAnnotationPresent(RequestMapping.class))
.collect(Collectors.toList());

for (final Method annotatedMethod : annotatedMethods) {
final RequestMapping annotation = annotatedMethod.getAnnotation(RequestMapping.class);
for (RequestMethod requestMethod : annotation.method()) {
final HandlerKey handlerKey = new HandlerKey(annotation.value(), requestMethod);
handlerExecutions.put(handlerKey, new HandlerExecution(instance, annotatedMethod));
}
final List<Method> annotatedMethods = Arrays.stream(clazz.getDeclaredMethods())
.filter(method -> method.isAnnotationPresent(RequestMapping.class))
.collect(Collectors.toList());

for (final Method annotatedMethod : annotatedMethods) {
final RequestMapping annotation = annotatedMethod.getAnnotation(RequestMapping.class);
for (final RequestMethod requestMethod : annotation.method()) {
final HandlerKey handlerKey = new HandlerKey(annotation.value(), requestMethod);
log.info("handlerKey = {}", handlerKey);
handlerExecutions.put(handlerKey, new HandlerExecution(annotatedMethod));
}
} catch (NoSuchMethodException |
InvocationTargetException |
InstantiationException |
IllegalAccessException e) {
throw new RuntimeException(e);
}
}

@Override
public Object getHandler(final HttpServletRequest request) {
final HandlerKey handlerKey = new HandlerKey(request.getRequestURI(), RequestMethod.valueOf(request.getMethod()));
return handlerExecutions.get(handlerKey);
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 Object handler, final HttpServletRequest request, final HttpServletResponse response) throws Exception;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package webmvc.org.springframework.web.servlet.mvc.tobe;

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

public class HandlerAdapters {
private final List<HandlerAdapter> adapters;

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

public void init() {
adapters.add(new AnnotationHandlerAdapter());
adapters.add(new ManualHandlerAdapter());
}

public Optional<HandlerAdapter> getAdapter(final Object handler) {
for (final HandlerAdapter handlerAdapter : adapters) {
if (handlerAdapter.supports(handler)) {
return Optional.of(handlerAdapter);
}
}
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@
import java.lang.reflect.Method;

public class HandlerExecution {
private final Object instance;
private final Method method;

public HandlerExecution(final Object instance, final Method method) {
this.instance = instance;
public HandlerExecution( final Method method) {
this.method = method;
}

public ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response) throws Exception {
return (ModelAndView) method.invoke(instance, request, response);
final Object handler = method.getDeclaringClass().getDeclaredConstructor().newInstance();
return (ModelAndView) method.invoke(handler, 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
@@ -0,0 +1,31 @@
package webmvc.org.springframework.web.servlet.mvc.tobe;

import jakarta.servlet.http.HttpServletRequest;

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

public class HandlerMappings {
private final List<HandlerMapping> mappings;

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

public void init(final HandlerMapping... mappings) {
this.mappings.add(new AnnotationHandlerMapping());
this.mappings.addAll(List.of(mappings));
}

public Optional<Object> getHandler(final HttpServletRequest request) {
for (final HandlerMapping handlerMapping : mappings) {
final Object handler = handlerMapping.getHandler(request);
if (Objects.nonNull(handler)) {
return Optional.of(handler);
}
}
return Optional.empty();
}
}
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;
import webmvc.org.springframework.web.servlet.ModelAndView;
import webmvc.org.springframework.web.servlet.mvc.asis.Controller;
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 Object handler, final HttpServletRequest request, final HttpServletResponse response) throws Exception {
final Controller controller = (Controller) handler;
final String path = controller.execute(request, response);
return new ModelAndView(new JspView(path));
}
}
Loading

0 comments on commit e3ae1b7

Please sign in to comment.