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 구현하기 1, 2단계] 하디(전동혁) 미션 제출합니다. #340

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
be2c663
test: Junit4Test 작성
jundonghyuk Sep 12, 2023
ca01f32
test: Junit4Test 작성
jundonghyuk Sep 12, 2023
3662225
test: ReflectionTest 작성
jundonghyuk Sep 12, 2023
cbb7e7d
test: ReflectionsTest 작성
jundonghyuk Sep 12, 2023
de091a5
test: ServletTest 작성
jundonghyuk Sep 12, 2023
31b2626
test: CharacterEncodingFilter 변경
jundonghyuk Sep 12, 2023
d585ffa
feat: HandlerExecution 구현
jundonghyuk Sep 12, 2023
a452ee6
feat: AnnotationHandlerMapping 기능 구현
jundonghyuk Sep 12, 2023
104e66f
test: AnnotationHandlerMappingTest 테스트 추가
jundonghyuk Sep 12, 2023
1d92d92
feat: ControllerScanner 기능 구현
jundonghyuk Sep 12, 2023
ab974ba
refactor: ControllerScanner 이용하여 개선
jundonghyuk Sep 12, 2023
cf8b11f
refactor: 미션 힌트에 따른 메서드 분리
jundonghyuk Sep 12, 2023
81a4b0e
refactor: HandlerExecution 을 반환하는 메서드 분리
jundonghyuk Sep 12, 2023
8451fd1
feat: HandlerMapping 인터페이스 설계
jundonghyuk Sep 12, 2023
e00d415
feat: Annotation 기반 HandlerMapping 설계 및 구현
jundonghyuk Sep 12, 2023
835f05e
feat: LegacyHandlerMapping 구현 및 상속 (본래의 메서드를 건들지 않기 위해 설계)
jundonghyuk Sep 12, 2023
056d55c
feat: HandlerAdapter 설계 및 구현
jundonghyuk Sep 12, 2023
0318db2
feat: HandlerExecution 전용 HandlerAdapter 설계 및 구현
jundonghyuk Sep 12, 2023
e5ed2db
feat: Controller 전용 HandlerAdapter 설계 및 구현
jundonghyuk Sep 12, 2023
0b39528
feat: HandlerMapping 리스트를 가지는 클래스 설계 및 구현
jundonghyuk Sep 12, 2023
650508e
feat: HandlerAdapter 리스트를 가지는 클래스 설계 및 구현
jundonghyuk Sep 12, 2023
6e633a7
refactor: DispatcherServlet 에 어노테이션 기반 핸들러 적용
jundonghyuk Sep 12, 2023
b96ad61
fix: 필드에서 초기화
jundonghyuk Sep 12, 2023
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
37 changes: 22 additions & 15 deletions app/src/main/java/com/techcourse/DispatcherServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,32 @@
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.HandlerAdapter;
import webmvc.org.springframework.web.servlet.mvc.asis.ControllerHandlerAdapter;
import webmvc.org.springframework.web.servlet.mvc.tobe.AnnotationHandlerMapping;
import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerExecutionHandlerAdapter;

public class DispatcherServlet extends HttpServlet {

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

private ManualHandlerMapping manualHandlerMapping;
private HandlerMappingRegistry handlerMappingRegistry = new HandlerMappingRegistry();
private HandlerAdapterRegistry handlerAdapterRegistry = new HandlerAdapterRegistry();

public DispatcherServlet() {
}

@Override
public void init() {
manualHandlerMapping = new ManualHandlerMapping();
manualHandlerMapping.initialize();
handlerMappingRegistry
.addHandlerMapping(new ManualHandlerMapping())
.addHandlerMapping(new AnnotationHandlerMapping());
handlerAdapterRegistry
.addHandlerAdapter(new ControllerHandlerAdapter())
.addHandlerAdapter(new HandlerExecutionHandlerAdapter());
handlerMappingRegistry.initialize();
}

@Override
Expand All @@ -30,22 +40,19 @@ protected void service(final HttpServletRequest request, final HttpServletRespon
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 = handlerMappingRegistry.getHandler(request)
.orElseThrow(() -> new IllegalArgumentException("There is not matched handler"));
HandlerAdapter handlerAdapter = handlerAdapterRegistry.getHandlerAdapter(handler)
.orElseThrow(() -> new IllegalArgumentException("Not Supported Handler"));
ModelAndView modelAndView = handlerAdapter.handle(request, response, handler);
render(modelAndView, 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 {
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);
private void render(ModelAndView modelAndView, HttpServletRequest request, HttpServletResponse response) {
// TODO: 3단계?
}
}
26 changes: 26 additions & 0 deletions app/src/main/java/com/techcourse/HandlerAdapterRegistry.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.techcourse;

import webmvc.org.springframework.web.servlet.mvc.HandlerAdapter;

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

public class HandlerAdapterRegistry {

private final List<HandlerAdapter> handlerAdapters = new ArrayList<>();

public HandlerAdapterRegistry addHandlerAdapter(HandlerAdapter handlerAdapter) {
handlerAdapters.add(handlerAdapter);
return this;
}

public Optional<HandlerAdapter> getHandlerAdapter(Object handler) {
for (HandlerAdapter handlerAdapter : handlerAdapters) {
if (handlerAdapter.supports(handler)) {
return Optional.of(handlerAdapter);
}
}
return Optional.empty();
}
}
32 changes: 32 additions & 0 deletions app/src/main/java/com/techcourse/HandlerMappingRegistry.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.techcourse;

import jakarta.servlet.http.HttpServletRequest;
import webmvc.org.springframework.web.servlet.mvc.HandlerMapping;

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

public class HandlerMappingRegistry {

private final List<HandlerMapping> handlerMappings = new ArrayList<>();

public HandlerMappingRegistry addHandlerMapping(HandlerMapping handlerMapping) {
handlerMappings.add(handlerMapping);
return this;
}

public void initialize() {
handlerMappings.forEach(HandlerMapping::initialize);
}

public Optional<Object> getHandler(HttpServletRequest httpServletRequest) {
for (HandlerMapping handlerMapping : handlerMappings) {
Object handler = handlerMapping.getHandler(httpServletRequest);
if (handler != null) {
return Optional.of(handler);
}
}
return Optional.empty();
}
}
14 changes: 14 additions & 0 deletions app/src/main/java/com/techcourse/LegacyHandlerMapping.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.techcourse;

import jakarta.servlet.http.HttpServletRequest;
import webmvc.org.springframework.web.servlet.mvc.HandlerMapping;

public abstract class LegacyHandlerMapping implements HandlerMapping {

@Override
public Object getHandler(HttpServletRequest httpServletRequest) {
return getHandler(httpServletRequest.getRequestURI());
}

public abstract Object getHandler(String requestURI);
}
4 changes: 3 additions & 1 deletion app/src/main/java/com/techcourse/ManualHandlerMapping.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@
import java.util.HashMap;
import java.util.Map;

public class ManualHandlerMapping {
public class ManualHandlerMapping extends LegacyHandlerMapping {

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 @@ -28,6 +29,7 @@ public void initialize() {
.forEach(path -> log.info("Path : {}, Controller : {}", path, controllers.get(path).getClass()));
}

@Override
public Controller getHandler(final String requestURI) {
log.debug("Request Mapping Uri : {}", requestURI);
return controllers.get(requestURI);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package webmvc.org.springframework.web.servlet.mvc;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import webmvc.org.springframework.web.servlet.ModelAndView;

public interface HandlerAdapter {

boolean supports(Object object);

ModelAndView handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package webmvc.org.springframework.web.servlet.mvc;

import jakarta.servlet.http.HttpServletRequest;

public interface HandlerMapping {

void initialize();

Object getHandler(HttpServletRequest httpServletRequest);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package webmvc.org.springframework.web.servlet.mvc.asis;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import webmvc.org.springframework.web.servlet.ModelAndView;
import webmvc.org.springframework.web.servlet.mvc.HandlerAdapter;
import webmvc.org.springframework.web.servlet.view.JspView;

public class ControllerHandlerAdapter implements HandlerAdapter {

@Override
public boolean supports(Object object) {
return object instanceof Controller;
}

@Override
public ModelAndView handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception {
Controller controller = (Controller) handler;
String viewName = controller.execute(httpServletRequest, httpServletResponse);
ModelAndView mav = new ModelAndView(new JspView(viewName));
return mav;
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package webmvc.org.springframework.web.servlet.mvc.tobe;

import jakarta.servlet.http.HttpServletRequest;
import org.reflections.Reflections;
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.mvc.HandlerMapping;

import java.util.HashMap;
import java.util.Map;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

public class AnnotationHandlerMapping {
public class AnnotationHandlerMapping implements HandlerMapping {

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

Expand All @@ -19,11 +24,60 @@ public AnnotationHandlerMapping(final Object... basePackage) {
this.handlerExecutions = new HashMap<>();
}

@Override
public void initialize() {
log.info("Initialized AnnotationHandlerMapping!");
log.info("Initializing AnnotationHandlerMapping starts!");
for (Object base : basePackage) {
ControllerScanner controllerScanner = new ControllerScanner(new Reflections(base));
Map<Class<?>, Object> controllers = controllerScanner.getControllers();
Set<Method> methods = getRequestMappingMethods(controllers.keySet());
for (Method method : methods) {
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
addHandlerExecutions(controllers, method, requestMapping);
}
}
log.info("Initializing AnnotationHandlerMapping succeeds!");
}

@Override
public Object getHandler(final HttpServletRequest request) {
return null;
String uri = request.getRequestURI();
try {
RequestMethod requestMethod = RequestMethod.valueOf(request.getMethod());
HandlerKey handlerKey = new HandlerKey(uri, requestMethod);
return handlerExecutions.get(handlerKey);
} catch (IllegalArgumentException e) {
log.error("Unsupported request method.");
return null;
}
}

private void addHandlerExecutions(Map<Class<?>, Object> controllers, Method method, RequestMapping requestMapping) {
HandlerExecution handlerExecution = mapHandlerExecution(controllers, method);
RequestMethod[] requestMethods = requestMapping.method();
List<HandlerKey> handlerKeys = mapHandlerKeys(requestMapping.value(), requestMethods);
for (HandlerKey handlerKey : handlerKeys) {
handlerExecutions.put(handlerKey, handlerExecution);
}
}

private HandlerExecution mapHandlerExecution(Map<Class<?>, Object> controllers, Method method) {
Object instance = controllers.get(method.getDeclaringClass());
return new HandlerExecution(instance, method);
}

private List<HandlerKey> mapHandlerKeys(String url, RequestMethod[] requestMethods) {
return Arrays.stream(requestMethods)
.map(requestMethod -> new HandlerKey(url, requestMethod))
.collect(Collectors.toList());
}

private Set<Method> getRequestMappingMethods(Set<Class<?>> controllers) {
return controllers
.stream()
.map(Class::getMethods)
.flatMap(Arrays::stream)
.filter(method -> method.isAnnotationPresent(RequestMapping.class))
.collect(Collectors.toSet());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package webmvc.org.springframework.web.servlet.mvc.tobe;

import context.org.springframework.stereotype.Controller;
import org.reflections.Reflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class ControllerScanner {

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

private final Reflections reflections;

public ControllerScanner(Reflections reflections) {
this.reflections = reflections;
}

public Map<Class<?>, Object> getControllers() {
Set<Class<?>> controllers = reflections.getTypesAnnotatedWith(Controller.class);
return instantiateControllers(controllers);
}

private Map<Class<?>, Object> instantiateControllers(Set<Class<?>> controllers) {
Map<Class<?>, Object> controllerWithInstances = new HashMap<>();
try {
for (Class<?> controller : controllers) {
Object instance = controller.getDeclaredConstructor().newInstance();
controllerWithInstances.put(controller, instance);
}
return controllerWithInstances;
} catch (NoSuchMethodException e) {
log.error("NoArgConstructor doesn't exist.");
} catch (SecurityException e) {
log.error("Check permission");
} catch (IllegalAccessException e) {
log.error("Constructor is not accessible.");
} catch (IllegalArgumentException e) {
log.error("Type of Arguments doesn't matched.");
} catch (InstantiationException e) {
log.error("The instance is abstract class.");
} catch (InvocationTargetException e) {
log.error("Exception occurs during constructing.");
} catch (ExceptionInInitializerError error) {
log.error("Initializing fails.");
}
throw new IllegalArgumentException("Getting instance using constructor fails.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,19 @@
import jakarta.servlet.http.HttpServletResponse;
import webmvc.org.springframework.web.servlet.ModelAndView;

import java.lang.reflect.Method;

public class HandlerExecution {

private final Object instance;
private final Method method;

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

public ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response) throws Exception {
return null;
return (ModelAndView) method.invoke(instance, request, response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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.HandlerAdapter;

public class HandlerExecutionHandlerAdapter implements HandlerAdapter {

@Override
public boolean supports(Object object) {
return object instanceof HandlerExecution;
}

@Override
public ModelAndView handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception {
return ((HandlerExecution) handler).handle(httpServletRequest, httpServletResponse);
}
}
Loading
Loading