Skip to content

Commit

Permalink
[MVC 구현하기 2단계] 포이(김보준) 미션 제출합니다. (#446)
Browse files Browse the repository at this point in the history
* feat: 어노테이션 기반의 HandlerMapping을 기존의 app 패키지에서 사용할 수 있도록 추가

* feat: Reflection을 통해 핸들러 매핑을 추가하도록 변경
  • Loading branch information
poi1649 authored Sep 20, 2023
1 parent 949fac1 commit c828142
Show file tree
Hide file tree
Showing 19 changed files with 280 additions and 86 deletions.
11 changes: 5 additions & 6 deletions app/src/main/java/com/techcourse/Application.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package com.techcourse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Application {

Expand All @@ -23,9 +22,9 @@ public static void main(final String[] args) throws Exception {

private static int defaultPortIfNull(final String[] args) {
return Stream.of(args)
.findFirst()
.map(Integer::parseInt)
.orElse(DEFAULT_PORT);
.findFirst()
.map(Integer::parseInt)
.orElse(DEFAULT_PORT);
}

private static void stop(final TomcatStarter tomcat) {
Expand Down
51 changes: 0 additions & 51 deletions app/src/main/java/com/techcourse/DispatcherServlet.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import web.org.springframework.web.WebApplicationInitializer;
import webmvc.org.springframework.web.servlet.mvc.DispatcherServlet;

/**
* Base class for {@link WebApplicationInitializer}
* implementations that register a {@link DispatcherServlet} in the servlet context.
* Base class for {@link WebApplicationInitializer} implementations that register a {@link DispatcherServlet} in the servlet context.
*/
public class DispatcherServletInitializer implements WebApplicationInitializer {

Expand All @@ -17,7 +17,7 @@ public class DispatcherServletInitializer implements WebApplicationInitializer {

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

final var registration = servletContext.addServlet(DEFAULT_SERVLET_NAME, dispatcherServlet);
if (registration == null) {
Expand Down
23 changes: 15 additions & 8 deletions app/src/main/java/com/techcourse/ManualHandlerMapping.java
Original file line number Diff line number Diff line change
@@ -1,31 +1,38 @@
package com.techcourse;

import com.techcourse.controller.*;
import com.techcourse.controller.LoginController;
import com.techcourse.controller.LoginViewController;
import com.techcourse.controller.LogoutController;
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());
controllers.put("/login/view", new LoginViewController());
controllers.put("/logout", new LogoutController());
controllers.put("/register/view", new RegisterViewController());
controllers.put("/register", new RegisterController());

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()));
}

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

public Controller getHandler(final String requestURI) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,30 @@

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 webmvc.org.springframework.web.servlet.mvc.asis.Controller;
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;

public class RegisterController implements Controller {
@Controller(path = "/register")
public class RegisterController {

@Override
public String execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception {
@RequestMapping(method = RequestMethod.GET)
public ModelAndView show(final HttpServletRequest req, final HttpServletResponse res) {
return new ModelAndView(new JspView("/register.jsp"));
}

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

return "redirect:/index.jsp";
return new ModelAndView(new JspView("/index.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
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
package core.org.springframework.util;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;

public abstract class ReflectionUtils {
public class ReflectionUtils {

/**
* Create a new instance of the specified {@code clazz} by invoking its default constructor.
* @param clazz the clazz to instantiate
* @return the new instance
* @param <T> the type of the class
*/
public static <T> T instantiate(final Class<T> clazz) {
try {
return ReflectionUtils.accessibleConstructor(clazz).newInstance();
} catch (InvocationTargetException | InstantiationException | IllegalAccessException |
NoSuchMethodException e) {
throw new RuntimeException(e);
}
}

/**
* Obtain an accessible constructor for the given class and parameters.
Expand All @@ -13,7 +29,7 @@ public abstract class ReflectionUtils {
* @throws NoSuchMethodException if no such constructor exists
* @since 5.0
*/
public static <T> Constructor<T> accessibleConstructor(Class<T> clazz, Class<?>... parameterTypes)
public static <T> Constructor<T> accessibleConstructor(final Class<T> clazz, Class<?>... parameterTypes)
throws NoSuchMethodException {

Constructor<T> ctor = clazz.getDeclaredConstructor(parameterTypes);
Expand All @@ -29,7 +45,7 @@ public static <T> Constructor<T> accessibleConstructor(Class<T> clazz, Class<?>.
* @see Constructor#setAccessible
*/
@SuppressWarnings("deprecation")
public static void makeAccessible(Constructor<?> ctor) {
public static void makeAccessible(final Constructor<?> ctor) {
if ((!Modifier.isPublic(ctor.getModifiers()) ||
!Modifier.isPublic(ctor.getDeclaringClass().getModifiers())) && !ctor.isAccessible()) {
ctor.setAccessible(true);
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 @@ -7,4 +7,6 @@

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
@@ -0,0 +1,116 @@
package webmvc.org.springframework.web.servlet.mvc;

import core.org.springframework.util.ReflectionUtils;
import jakarta.servlet.ServletException;
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.Objects;
import java.util.Set;
import org.reflections.Reflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import webmvc.org.springframework.web.servlet.mvc.tobe.AnnotationHandlerMapping;
import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerAdapter;
import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerMapping;
import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerNotFoundException;
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 INTERNAL_BASE_PACKAGE = "webmvc.org.springframework.web.servlet.mvc";

private final Object[] externalBasePackages;
private final List<HandlerMapping> handlerMappings;
private final List<HandlerAdapter> handlerAdapters;

public DispatcherServlet(Object... externalBasePackages) {
this.externalBasePackages = externalBasePackages;
this.handlerMappings = new ArrayList<>();
this.handlerAdapters = new ArrayList<>();
}

@Override
public void init() {
final var reflections = new Reflections(externalBasePackages, INTERNAL_BASE_PACKAGE);
initHandlerMappings(reflections);
initHandlerAdapters(reflections);
}

private void initHandlerMappings(final Reflections reflections) {
final var handlerMappingSubTypes = reflections.getSubTypesOf(HandlerMapping.class);
addHandlerMappings(handlerMappingSubTypes);
handlerMappings.forEach(HandlerMapping::initialize);
}

private void addHandlerMappings(final Set<Class<? extends HandlerMapping>> handlerMappingSubTypes) {
for (final var handlerMappingSubType : handlerMappingSubTypes) {
if (handlerMappingSubType.isInterface()) {
continue;
}
// exclude pre-defined handler mapping
if (handlerMappingSubType.equals(AnnotationHandlerMapping.class)) {
handlerMappings.add(new AnnotationHandlerMapping(externalBasePackages));
continue;
}
handlerMappings.add(ReflectionUtils.instantiate(handlerMappingSubType));
}
}

private void initHandlerAdapters(final Reflections reflections) {
final var handlerAdapterSubTypes = reflections.getSubTypesOf(HandlerAdapter.class);
addHandlerAdapters(handlerAdapterSubTypes);
}

private void addHandlerAdapters(final Set<Class<? extends HandlerAdapter>> handlerAdapterSubTypes) {
for (final var reflectionsSubTypeOf : handlerAdapterSubTypes) {
if (reflectionsSubTypeOf.isInterface()) {
continue;
}
handlerAdapters.add(ReflectionUtils.instantiate(reflectionsSubTypeOf));
}
}

@Override
protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException {
log.debug("Method : {}, Request URI : {}", request.getMethod(), request.getRequestURI());
final var handler = getHandler(request);
final var handlerAdapter = getHandlerAdapter(handler);
try {
final var modelAndView = handlerAdapter.handle(request, response, handler);
move(modelAndView.getViewName(), request, response);
} catch (Throwable e) {
log.error("Exception : {}", e.getMessage(), e);
throw new ServletException(e.getMessage());
}
}

private Object getHandler(final HttpServletRequest request) {
return handlerMappings.stream()
.map(mapping -> mapping.getHandler(request))
.filter(Objects::nonNull)
.findAny()
.orElseThrow(() -> new HandlerNotFoundException("Not found handler for request URI : " + request.getRequestURI()));
}

private HandlerAdapter getHandlerAdapter(final Object handler) {
return handlerAdapters.stream()
.filter(adapter -> adapter.supports(handler))
.findAny()
.orElseThrow(() -> new HandlerNotFoundException("Not found handler adapter for handler : " + handler));
}

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);
}
}
Loading

0 comments on commit c828142

Please sign in to comment.