Skip to content

Commit

Permalink
feat: 어노테이션 기반 컨트롤러 지원
Browse files Browse the repository at this point in the history
  • Loading branch information
Cyma-s committed Sep 17, 2023
1 parent 4dfeda3 commit 5f86ecb
Show file tree
Hide file tree
Showing 15 changed files with 271 additions and 47 deletions.
74 changes: 62 additions & 12 deletions app/src/main/java/com/techcourse/DispatcherServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,92 @@
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import webmvc.org.springframework.web.servlet.ModelAndView;
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;

public class DispatcherServlet extends HttpServlet {

private static final long serialVersionUID = 1L;
private static final Logger log = LoggerFactory.getLogger(DispatcherServlet.class);
private static final String BASE_PACKAGE_PATH = "com.techcourse";

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

public DispatcherServlet() {
handlerMappings = new ArrayList<>();
handlerAdapters = new ArrayList<>();
}

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

private void initHandlerMappings() {
final List<HandlerMapping> handlerMappingInstances = HandlerMappingFactory.getHandlerMappings(BASE_PACKAGE_PATH)
.stream()
.peek(HandlerMapping::initialize)
.collect(Collectors.toList());
handlerMappings.addAll(handlerMappingInstances);
}

private void initHandlerAdapters() {
final List<HandlerAdapter> handlerAdapterInstances = HandlerAdapterFactory.getHandlerAdapters();
handlerAdapters.addAll(handlerAdapterInstances);
}

@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
protected void service(final HttpServletRequest request, final HttpServletResponse response)
throws ServletException {
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);
} catch (Throwable e) {
process(request, response);
} catch (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 {
private void process(final HttpServletRequest request, final HttpServletResponse response)
throws Exception {
final Object handler = getHandler(request);
final HandlerAdapter handlerAdapter = getHandlerAdapter(handler);
final ModelAndView modelAndView = handlerAdapter.handle(request, response, handler);
move(modelAndView, request, response);
}

private Object getHandler(final HttpServletRequest request) {
return handlerMappings.stream()
.filter(mapping -> mapping.getHandler(request) != null)
.findFirst()
.orElseThrow(() -> new NoSuchElementException("해당하는 HandlerMapping이 없습니다."))
.getHandler(request);
}

private HandlerAdapter getHandlerAdapter(final Object handler) {
return handlerAdapters.stream()
.filter(adapter -> adapter.supports(handler))
.findFirst()
.orElseThrow(() -> new NoSuchElementException("해당하는 HandlerAdapter가 없습니다."));
}

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

import java.util.List;
import webmvc.org.springframework.web.servlet.mvc.tobe.AnnotationHandlerAdapter;
import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerAdapter;
import webmvc.org.springframework.web.servlet.mvc.tobe.ManualHandlerAdapter;

public class HandlerAdapterFactory {

public static List<HandlerAdapter> getHandlerAdapters() {
return List.of(
new AnnotationHandlerAdapter(),
new ManualHandlerAdapter()
);
}
}
15 changes: 15 additions & 0 deletions app/src/main/java/com/techcourse/HandlerMappingFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.techcourse;

import java.util.List;
import webmvc.org.springframework.web.servlet.mvc.tobe.AnnotationHandlerMapping;
import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerMapping;

public class HandlerMappingFactory {

public static List<HandlerMapping> getHandlerMappings(final Object... basePackagePath) {
return List.of(
new AnnotationHandlerMapping(basePackagePath),
new ManualHandlerMapping()
);
}
}
22 changes: 15 additions & 7 deletions app/src/main/java/com/techcourse/ManualHandlerMapping.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
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.util.HashMap;
import java.util.Map;
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());
Expand All @@ -25,10 +31,12 @@ public void initialize() {

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

public Controller getHandler(final String requestURI) {
@Override
public Object getHandler(final HttpServletRequest request) {
final String requestURI = request.getRequestURI();
log.debug("Request Mapping Uri : {}", requestURI);
return controllers.get(requestURI);
}
Expand Down
31 changes: 31 additions & 0 deletions app/src/test/java/com/techcourse/DispatcherServletTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.techcourse;

import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

class DispatcherServletTest {

@DisplayName("해당하는 HandlerMapping 이 존재하지 않으면 예외를 반환한다.")
@Test
void notExistHandlerMappingThrowsException() {
// given
final DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.init();
final HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getRequestURI()).thenReturn("/not-exist");
when(request.getMethod()).thenReturn("GET");

// when
// then
assertThatThrownBy(() -> dispatcherServlet.service(request, mock(HttpServletResponse.class)))
.isInstanceOf(ServletException.class)
.hasMessage("해당하는 HandlerMapping이 없습니다.");
}
}
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
@@ -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;

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 HandlerExecution handlerExecution = (HandlerExecution) handler;
return handlerExecution.handle(request, response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.stream.Collectors;
import org.reflections.Reflections;
Expand All @@ -17,7 +16,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 @@ -29,6 +28,7 @@ public AnnotationHandlerMapping(final Object... basePackages) {
this.handlerExecutions = new HashMap<>();
}

@Override
public void initialize() {
log.info("Initialized AnnotationHandlerMapping!");
makeHandlerExecutions(basePackages);
Expand Down Expand Up @@ -96,14 +96,10 @@ private Map<HandlerKey, HandlerExecution> convertHandlerExecutions(final Object
));
}

@Override
public Object getHandler(final HttpServletRequest request) {
final HandlerKey handlerKey = makeHandlerKey(request);
final HandlerExecution handlerExecution = handlerExecutions.get(handlerKey);
if (handlerExecution == null) {
throw new NoSuchElementException("해당하는 핸들러가 없습니다.");
}

return handlerExecution;
return handlerExecutions.get(handlerKey);
}

private HandlerKey makeHandlerKey(final HttpServletRequest request) {
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
@@ -0,0 +1,9 @@
package webmvc.org.springframework.web.servlet.mvc.tobe;

import jakarta.servlet.http.HttpServletRequest;

public interface HandlerMapping {

Object getHandler(final HttpServletRequest request);
void initialize();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
app/src/main/java/com/techcourse
mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe
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 ManualHandlerAdapter implements HandlerAdapter{

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

@Override
public ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response,
final Object handler)
throws Exception {
return null;
}
}
4 changes: 2 additions & 2 deletions mvc/src/test/java/samples/TestController.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ public class TestController {
@RequestMapping(value = "/get-test", method = RequestMethod.GET)
public ModelAndView findUserId(final HttpServletRequest request, final HttpServletResponse response) {
log.info("test controller get method");
final var modelAndView = new ModelAndView(new JspView(""));
final var modelAndView = new ModelAndView(new JspView("test"));
modelAndView.addObject("id", request.getAttribute("id"));
return modelAndView;
}

@RequestMapping(value = "/post-test", method = RequestMethod.POST)
public ModelAndView save(final HttpServletRequest request, final HttpServletResponse response) {
log.info("test controller post method");
final var modelAndView = new ModelAndView(new JspView(""));
final var modelAndView = new ModelAndView(new JspView("test"));
modelAndView.addObject("id", request.getAttribute("id"));
return modelAndView;
}
Expand Down
Loading

0 comments on commit 5f86ecb

Please sign in to comment.