Skip to content

Commit

Permalink
[MVC 구현하기 2단계] 디투(박정훈) 미션 제출합니다. (#456)
Browse files Browse the repository at this point in the history
* test: Reflection 새로운 방식으로 출력 테스트

* test: 불필요한 출력 제거

* refactor: 잘못된 핸들러는 무시하도록 설정

* feat: HandlerMapping 인터페이스 추가

* feat: 어노테이션 기반, 메뉴얼 기반 핸들러에 implements HandlerMapping

* feat: HandlerExecutionHandlerAdapter 생성

* feat: HandlerMappings 일급 컬렉션 추가

* feat: HandlerAdapter 구현

* feat: Dazzle 컨트롤러 생성

* refactor: 예외 처리 못한 것 예외 처리
  • Loading branch information
shb03323 authored Sep 23, 2023
1 parent 2f82af3 commit 3bbba19
Show file tree
Hide file tree
Showing 15 changed files with 218 additions and 21 deletions.
42 changes: 33 additions & 9 deletions app/src/main/java/com/techcourse/DispatcherServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,57 @@
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import webmvc.org.springframework.web.servlet.HandlerAdapter;
import webmvc.org.springframework.web.servlet.ModelAndView;
import webmvc.org.springframework.web.servlet.View;
import webmvc.org.springframework.web.servlet.mvc.tobe.AnnotationHandlerAdapter;
import webmvc.org.springframework.web.servlet.mvc.tobe.AnnotationHandlerMapping;
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;
private HandlerAdapters handlerAdapters;

public DispatcherServlet() {
}

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

private void initHandlerMappings() {
final HandlerMappings handlerMappings = new HandlerMappings();
handlerMappings.addHandlerMapping(new ManualHandlerMapping());
handlerMappings.addHandlerMapping(new AnnotationHandlerMapping());
this.handlerMappings = handlerMappings;
}

private void initHandlerAdapter() {
final HandlerAdapters handlerAdapters = new HandlerAdapters();
handlerAdapters.addHandlerAdapter(new ManualHandlerAdapter());
handlerAdapters.addHandlerAdapter(new AnnotationHandlerAdapter());
this.handlerAdapters = handlerAdapters;
}

@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);
protected void service(final HttpServletRequest req, final HttpServletResponse res) throws ServletException {
final String requestURI = req.getRequestURI();
log.debug("Method : {}, Request URI : {}", req.getMethod(), requestURI);

try {
final var controller = manualHandlerMapping.getHandler(requestURI);
final var viewName = controller.execute(request, response);
move(viewName, request, response);
final Object handler = handlerMappings.getHandler(req);
final HandlerAdapter handlerAdapter = handlerAdapters.getHandlerAdapter(handler);
final ModelAndView modelAndView = handlerAdapter.handle(req, res, handler);
final View view = modelAndView.getView();
if (view instanceof JspView) {
move(((JspView) view).getViewName(), req, res);
}
} catch (Throwable e) {
log.error("Exception : {}", e.getMessage(), e);
throw new ServletException(e.getMessage());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.techcourse;

import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletRegistration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import web.org.springframework.web.WebApplicationInitializer;
Expand All @@ -17,9 +18,9 @@ public class DispatcherServletInitializer implements WebApplicationInitializer {

@Override
public void onStartup(final ServletContext servletContext) {
final var dispatcherServlet = new DispatcherServlet();
final DispatcherServlet dispatcherServlet = new DispatcherServlet();

final var registration = servletContext.addServlet(DEFAULT_SERVLET_NAME, dispatcherServlet);
final ServletRegistration.Dynamic registration = servletContext.addServlet(DEFAULT_SERVLET_NAME, dispatcherServlet);
if (registration == null) {
throw new IllegalStateException("Failed to register servlet with name '" + DEFAULT_SERVLET_NAME + "'. " +
"Check if there is another servlet registered under the same name.");
Expand Down
26 changes: 26 additions & 0 deletions app/src/main/java/com/techcourse/HandlerAdapters.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.techcourse;

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

import java.util.HashSet;
import java.util.Set;

public class HandlerAdapters {

private final Set<HandlerAdapter> handlerAdapters;

public HandlerAdapters() {
this.handlerAdapters = new HashSet<>();
}

public void addHandlerAdapter(final HandlerAdapter handlerAdapter) {
handlerAdapters.add(handlerAdapter);
}

public HandlerAdapter getHandlerAdapter(final Object handler) {
return handlerAdapters.stream()
.filter(handlerAdapter -> handlerAdapter.supports(handler))
.findAny()
.orElseThrow(() -> new IllegalArgumentException(String.format("%s를 실행시킬 어댑터를 찾지 못했습니다.", handler)));
}
}
30 changes: 30 additions & 0 deletions app/src/main/java/com/techcourse/HandlerMappings.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.techcourse;

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

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

public class HandlerMappings {

private final Set<HandlerMapping> handlerMappings;

public HandlerMappings() {
this.handlerMappings = new HashSet<>();
}

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

public Object getHandler(final HttpServletRequest req) {
return handlerMappings.stream()
.map(handlerMapping -> handlerMapping.getHandler(req))
.filter(Objects::nonNull)
.findAny()
.orElseThrow(() -> new IllegalArgumentException(String.format("%s %s를 실행시킬 핸들러를 찾을 수 없습니다.", req.getMethod(), req.getRequestURI())));
}
}
25 changes: 25 additions & 0 deletions app/src/main/java/com/techcourse/ManualHandlerAdapter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.techcourse;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import webmvc.org.springframework.web.servlet.HandlerAdapter;
import webmvc.org.springframework.web.servlet.ModelAndView;
import webmvc.org.springframework.web.servlet.View;
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 HttpServletRequest req, final HttpServletResponse res, final Object handler) throws Exception {
final Controller controller = (Controller) handler;
final String viewName = controller.execute(req, res);
final View view = new JspView(viewName);
return new ModelAndView(view);
}
}
19 changes: 14 additions & 5 deletions app/src/main/java/com/techcourse/ManualHandlerMapping.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
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.HandlerMapping;
import webmvc.org.springframework.web.servlet.mvc.asis.Controller;
import webmvc.org.springframework.web.servlet.mvc.asis.ForwardController;

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 @@ -28,8 +35,10 @@ 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 Controller getHandler(final HttpServletRequest req) {
final String uri = req.getRequestURI();
log.debug("Request Mapping Uri : {}", uri);
return controllers.get(uri);
}
}
18 changes: 18 additions & 0 deletions app/src/main/java/com/techcourse/controller/DazzleController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.techcourse.controller;

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

@RequestMapping(value = "/dazzle", method = RequestMethod.GET)
public ModelAndView showMyDazzle(final HttpServletRequest req, final HttpServletResponse res) {
return new ModelAndView(new JspView("404.jsp"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package webmvc.org.springframework.web.servlet;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

public interface HandlerAdapter {

boolean supports(final Object handler);

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

import jakarta.servlet.http.HttpServletRequest;

public interface HandlerMapping {

void initialize();

Object getHandler(final HttpServletRequest req);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
import java.util.Map;

public interface View {

void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}
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.HandlerAdapter;
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 @@ -7,6 +7,7 @@
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.HandlerMapping;

import java.lang.reflect.Method;
import java.util.Arrays;
Expand All @@ -16,7 +17,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 @@ -28,6 +29,7 @@ public AnnotationHandlerMapping(final Object... basePackage) {
this.handlerExecutions = new HashMap<>();
}

@Override
public void initialize() {
Reflections reflections = new Reflections(basePackage);
final Set<Class<?>> classes = reflections.getTypesAnnotatedWith(Controller.class);
Expand Down Expand Up @@ -56,6 +58,9 @@ private Object makeInstance(final Class<?> clazz) {
}

private void initializeHandlerExecutions(final Method method, final Object controller) {
if (!isValidHandlerExecution(method, controller)) {
throw new IllegalArgumentException("옳지 않은 메소드이거나 없는 핸들러 입니다!");
}
final String uri = method.getAnnotation(RequestMapping.class).value();
final RequestMethod[] requestMethods = method.getAnnotation(RequestMapping.class).method();
for (final RequestMethod requestMethod : requestMethods) {
Expand All @@ -64,9 +69,14 @@ private void initializeHandlerExecutions(final Method method, final Object contr
}
}

public Object getHandler(final HttpServletRequest request) {
final String uri = request.getRequestURI();
final RequestMethod requestMethod = RequestMethod.valueOf(request.getMethod());
private boolean isValidHandlerExecution(final Method method, final Object controller) {
return method != null && controller != null;
}

@Override
public Object getHandler(final HttpServletRequest req) {
final String uri = req.getRequestURI();
final RequestMethod requestMethod = RequestMethod.valueOf(req.getMethod());
final HandlerKey handlerKey = new HandlerKey(uri, requestMethod);
return handlerExecutions.get(handlerKey);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ public class JspView implements View {

public static final String REDIRECT_PREFIX = "redirect:";

private final String viewName;

public JspView(final String viewName) {
this.viewName = viewName;
}

@Override
Expand All @@ -28,4 +31,8 @@ public void render(final Map<String, ?> model, final HttpServletRequest request,

// todo
}

public String getViewName() {
return viewName;
}
}
6 changes: 6 additions & 0 deletions study/src/test/java/reflection/ReflectionsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

import java.util.Set;

import static org.reflections.scanners.Scanners.TypesAnnotated;

class ReflectionsTest {

private static final Logger log = LoggerFactory.getLogger(ReflectionsTest.class);
Expand All @@ -33,5 +35,9 @@ void showAnnotationClass() throws Exception {
for (final Class<?> repository : repositories) {
log.info("Repository: {}", repository.getName());
}

// 전체 조회
reflections.get(TypesAnnotated.of(Controller.class, Service.class, Repository.class))
.forEach(object -> log.info("Object: {}", object));
}
}
1 change: 0 additions & 1 deletion study/src/test/java/servlet/com/example/ServletTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ void testSharedCounter() {
HttpUtils.send(PATH);
HttpUtils.send(PATH);
final var response = HttpUtils.send(PATH);
System.out.println(response.body());

// 톰캣 서버 종료
tomcatStarter.stop();
Expand Down

0 comments on commit 3bbba19

Please sign in to comment.