Skip to content

Commit 5037359

Browse files
authored
3단계 - @MVC 구현(힌트) - 리뷰요청 (#245)
* feat: AnnotationHandlerMapping 구현 (요구사항 1) * feat: forward, redirect View 구현 * refactor: Controller가 ModelAndView를 반환하도록 수정 * feat: HandlerAdapter 구현 * refactor: 점진적 리팩토링으로 RequestMapping, AnnotationHandlerMapping 공존 (요구사항 2) * refactor: RequestMapping을 AnnotationHandlerMapping으로 변경 (요구사항 2) * docs: Step2 요구사항 정리 * refactor: HandlerMapping을 AnnotationHandlerMapping이 바로 구현하도록 수정 * refactor: redirectView에서 ForwardView로 수정 * refactor: 클래스명 수정 * feat: form, loginForm 컨트롤러 메서드 추가 * feat: 매개변수 예외처리 추가 * refactor: redirect render 메서드 수정 * fix: loginForm URI 수정 * refactor: render 메서드 수정 * refactor: Adapter 패턴 삭제 * feat: ControllerScanner 구현 (요구사항 1) * refactor: ControllerScanner 적용 및 HandlerKey 생성 메서드 추출 (요구사항 1) * refactor: initMapping 메서드 인터페이스 레벨로 수정 * refactor: 변수명 수정 * feat: 어뎁터 패턴 적용 * docs: 요구사항 정리
1 parent 03e2dac commit 5037359

11 files changed

+195
-52
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,19 @@
5959
* 점진적 리팩토링 도입
6060
* 기존 MVC와 새로운 MVC가 공존.
6161
* 모든 Controller가 새로운 MVC로 전환되면 기존 레거시 MVC 삭제.
62+
63+
## 3단계 - @MVC 구현(힌트)
64+
65+
### 요구사항 - ControllerScanner 클래스 추가
66+
67+
* **ControllerScanner**
68+
* reflections 사용해서 `@Controller`어노테이션 설정된 클래스를 **찾는다**.
69+
* 찾은 각 클래스에 대한 **인스턴스 생성**을 담당한다.
70+
71+
* **AnnotationHandlerMapping**
72+
* 어노테이션 기반 매핑을 담당.
73+
* @Controller 클래스의 메서드 중 `@RequestMapping` 어노테이션 설정된 모든 메서드를 **찾는다**
74+
* 찾은 메서드를 바탕으로 `Map<HandlerKey, HandlerExecution>`에 값을 저장.
75+
* `HandlerKey` : `@RequestMapping` 어노테이션이 갖고 있는 URL과 HTTP 메서드를 저장.
76+
* `HandlerExecution` : 자바 리플랙션에서 메서드를 실행하기 위해 필요한 정보 저장.
77+
* 실행할 메서드가 존재하는 클래스의 인스턴스 정보, 실행할 메서드 정보 저장.

src/main/java/core/mvc/asis/DispatcherServlet.java

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
package core.mvc.asis;
22

33
import core.mvc.ModelAndView;
4-
import core.mvc.tobe.AnnotationHandlerMapping;
5-
import core.mvc.tobe.HandlerExecution;
6-
import core.mvc.tobe.HandlerMapping;
4+
import core.mvc.tobe.*;
75
import core.mvc.view.View;
86
import exception.NotFoundException;
97
import org.slf4j.Logger;
@@ -24,18 +22,22 @@ public class DispatcherServlet extends HttpServlet {
2422
private static final long serialVersionUID = 1L;
2523
private static final Logger logger = LoggerFactory.getLogger(DispatcherServlet.class);
2624

25+
private HandlerAdapterStorage handlerAdapterStorage;
2726
private RequestMapping requestMapping;
28-
27+
2928
private AnnotationHandlerMapping AnnotationHandlerMapping;
3029
private final List<HandlerMapping> mappings = new ArrayList<>();
3130

3231
@Override
3332
public void init() {
33+
handlerAdapterStorage = new HandlerAdapterStorage();
34+
handlerAdapterStorage.init();
35+
3436
requestMapping = new RequestMapping();
3537
requestMapping.initMapping();
3638

3739
AnnotationHandlerMapping = new AnnotationHandlerMapping();
38-
AnnotationHandlerMapping.initialize();
40+
AnnotationHandlerMapping.initMapping();
3941

4042
mappings.add(requestMapping);
4143
mappings.add(AnnotationHandlerMapping);
@@ -47,22 +49,25 @@ protected void service(HttpServletRequest request, HttpServletResponse response)
4749
if (handler == null) {
4850
throw new NotFoundException(HttpStatus.NOT_FOUND);
4951
}
52+
HandlerAdapter adapter = handlerAdapterStorage.getHandlerAdapter(handler);
53+
handleAdapter(request, response, handler, adapter);
54+
}
5055

56+
private void handleAdapter(HttpServletRequest request, HttpServletResponse response,
57+
Object handler, HandlerAdapter adapter) throws ServletException {
5158
try {
52-
ModelAndView modelAndView = execute(request, response, handler);
53-
View view = modelAndView.getView();
54-
view.render(modelAndView.getModel(), request, response);
59+
handle(request, response, handler, adapter);
5560
} catch (Exception e) {
5661
logger.error("Exception: {}", e.getMessage());
5762
throw new ServletException(e.getMessage());
5863
}
5964
}
6065

61-
private ModelAndView execute(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
62-
if (handler instanceof Controller) {
63-
return ((Controller) handler).execute(request, response);
64-
}
65-
return ((HandlerExecution) handler).handle(request, response);
66+
private void handle(HttpServletRequest request, HttpServletResponse response,
67+
Object handler, HandlerAdapter adapter) throws Exception {
68+
ModelAndView modelAndView = adapter.handle(request, response, handler);
69+
View view = modelAndView.getView();
70+
view.render(modelAndView.getModel(), request, response);
6671
}
6772

6873
private Object getHandler(HttpServletRequest request) {

src/main/java/core/mvc/asis/RequestMapping.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ public class RequestMapping implements HandlerMapping {
1212
private static final Logger logger = LoggerFactory.getLogger(DispatcherServlet.class);
1313
private final Map<String, Controller> mappings = new HashMap<>();
1414

15-
void initMapping() {
15+
@Override
16+
public void initMapping() {
1617
logger.info("Initialized Request Mapping!");
1718
mappings.keySet().forEach(path -> {
1819
logger.info("Path : {}, Controller : {}", path, mappings.get(path).getClass());

src/main/java/core/mvc/tobe/AnnotationHandlerMapping.java

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
import core.annotation.web.Controller;
55
import core.annotation.web.RequestMapping;
66
import core.annotation.web.RequestMethod;
7-
import org.reflections.Reflections;
7+
import exception.NotFoundException;
8+
import org.reflections.ReflectionUtils;
89
import org.slf4j.Logger;
910
import org.slf4j.LoggerFactory;
11+
import org.springframework.http.HttpStatus;
1012

1113
import javax.servlet.http.HttpServletRequest;
1214
import java.lang.reflect.Method;
13-
import java.util.List;
15+
import java.util.HashSet;
1416
import java.util.Map;
1517
import java.util.Set;
1618

@@ -24,44 +26,50 @@ public AnnotationHandlerMapping(Object... basePackage) {
2426
this.basePackage = basePackage;
2527
}
2628

27-
public void initialize() {
28-
Reflections reflections = new Reflections(basePackage);
29-
Set<Class<?>> controllers = reflections.getTypesAnnotatedWith(Controller.class);
30-
for (Class<?> controller : controllers) {
31-
String path = controller.getAnnotation(Controller.class).value();
32-
detectHandlerExecution(path, controller);
29+
@Override
30+
public void initMapping() {
31+
ControllerScanner controllerScanner = new ControllerScanner(basePackage);
32+
Map<Class<?>, Object> controllers = controllerScanner.getControllers();
33+
if (controllers.isEmpty()) {
34+
throw new NotFoundException(HttpStatus.NOT_FOUND);
3335
}
36+
initHandlerExecution(controllers);
3437
}
3538

36-
@Override
37-
public HandlerExecution getHandler(HttpServletRequest request) {
38-
String requestURI = request.getRequestURI();
39-
RequestMethod requestMethod = RequestMethod.valueOf(request.getMethod().toUpperCase());
40-
return handlerExecutions.get(new HandlerKey(requestURI, requestMethod));
39+
private void initHandlerExecution(Map<Class<?>, Object> controllers) {
40+
Set<Method> methods = getMethods(controllers);
41+
for (Method method : methods) {
42+
Class<?> declaringClass = method.getDeclaringClass();
43+
Controller annotation = declaringClass.getAnnotation(Controller.class);
44+
addHandlerExecution(controllers.get(declaringClass), annotation.value(), method);
45+
}
4146
}
4247

43-
private void detectHandlerExecution(String path, Class<?> controller) {
44-
Object controllerInstance = null;
45-
try {
46-
controllerInstance = controller.getConstructor().newInstance();
47-
} catch (Exception e) {
48-
e.printStackTrace();
49-
logger.error("detectHandlerExecution Exception : {}", e.getMessage());
50-
}
51-
List<Method> methods = List.of(controller.getDeclaredMethods());
52-
for (Method method : methods) {
53-
addHandlerExecution(controllerInstance, path, method);
48+
private Set<Method> getMethods(Map<Class<?>, Object> controllers) {
49+
Set<Method> methods = new HashSet<>();
50+
for (Class<?> clazz : controllers.keySet()) {
51+
methods.addAll(ReflectionUtils.getAllMethods(clazz, ReflectionUtils.withAnnotation(RequestMapping.class)));
5452
}
53+
return methods;
5554
}
5655

5756
private void addHandlerExecution(Object controllerInstance, String path, Method method) {
58-
if (method.isAnnotationPresent(RequestMapping.class)) {
59-
String uriPath = method.getAnnotation(RequestMapping.class).value();
60-
RequestMethod requestMethod = method.getAnnotation(RequestMapping.class).method();
61-
HandlerKey handlerKey = new HandlerKey(path + uriPath, requestMethod);
57+
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
58+
HandlerKey handlerKey = createHandlerKey(requestMapping, path);
59+
logger.debug("Add RequestMapping. URI: {}, requestMethod: {}", handlerKey, method);
60+
handlerExecutions.put(handlerKey, new HandlerExecution(controllerInstance, method));
61+
}
6262

63-
logger.debug("Add RequestMapping. URI: {}, requestMethod: {}", handlerKey, method);
64-
handlerExecutions.put(handlerKey, new HandlerExecution(controllerInstance, method));
65-
}
63+
private HandlerKey createHandlerKey(RequestMapping requestMapping, String path) {
64+
String uriPath = requestMapping.value();
65+
RequestMethod requestMethod = requestMapping.method();
66+
return new HandlerKey(path + uriPath, requestMethod);
67+
}
68+
69+
@Override
70+
public HandlerExecution getHandler(HttpServletRequest request) {
71+
String requestURI = request.getRequestURI();
72+
RequestMethod requestMethod = RequestMethod.valueOf(request.getMethod().toUpperCase());
73+
return handlerExecutions.get(new HandlerKey(requestURI, requestMethod));
6674
}
6775
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package core.mvc.tobe;
2+
3+
import core.annotation.web.Controller;
4+
import org.reflections.Reflections;
5+
import org.slf4j.Logger;
6+
import org.slf4j.LoggerFactory;
7+
8+
import java.lang.reflect.InvocationTargetException;
9+
import java.util.HashMap;
10+
import java.util.Map;
11+
import java.util.Set;
12+
13+
public class ControllerScanner {
14+
private static final Logger logger = LoggerFactory.getLogger(ControllerScanner.class);
15+
16+
private final Reflections reflections;
17+
18+
public ControllerScanner(Object... basePackage) {
19+
this.reflections = new Reflections(basePackage);
20+
}
21+
22+
public Map<Class<?>, Object> getControllers() {
23+
Set<Class<?>> controllerClasses = reflections.getTypesAnnotatedWith(Controller.class);
24+
try {
25+
return instantiateControllers(controllerClasses);
26+
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
27+
logger.error("Fail to instantiate controllers : {}", e.getMessage());
28+
}
29+
return null;
30+
}
31+
32+
private Map<Class<?>, Object> instantiateControllers(Set<Class<?>> controllerClasses)
33+
throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
34+
Map<Class<?>, Object> controllerInstances = new HashMap<>();
35+
for (Class<?> controllerClass : controllerClasses) {
36+
Object instance = controllerClass.getConstructor().newInstance();
37+
controllerInstances.put(controllerClass, instance);
38+
}
39+
return controllerInstances;
40+
}
41+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package core.mvc.tobe;
2+
3+
import core.mvc.ModelAndView;
4+
5+
import javax.servlet.http.HttpServletRequest;
6+
import javax.servlet.http.HttpServletResponse;
7+
8+
public interface HandlerAdapter {
9+
10+
boolean support(Object handler);
11+
12+
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
13+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package core.mvc.tobe;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
public class HandlerAdapterStorage {
7+
8+
private final List<HandlerAdapter> handlerAdapters = new ArrayList<>();
9+
10+
public HandlerAdapterStorage() {
11+
}
12+
13+
public void init() {
14+
handlerAdapters.add(new HandlerExecution());
15+
handlerAdapters.add(new SimpleControllerHandlerAdapter());
16+
}
17+
18+
public HandlerAdapter getHandlerAdapter(Object handler) {
19+
for (HandlerAdapter adapter : handlerAdapters) {
20+
if (adapter.support(handler)) {
21+
return adapter;
22+
}
23+
}
24+
throw new IllegalArgumentException("매칭되는 HandlerAdapter를 찾지 못했습니다. handler = " + handler);
25+
}
26+
}

src/main/java/core/mvc/tobe/HandlerExecution.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@
77
import javax.servlet.http.HttpServletResponse;
88
import java.lang.reflect.Method;
99

10-
public class HandlerExecution {
10+
public class HandlerExecution implements HandlerAdapter {
1111

12-
private final Object bean;
13-
private final Method method;
12+
private Object bean;
13+
private Method method;
14+
15+
public HandlerExecution() {
16+
}
1417

1518
public HandlerExecution(Object bean, Method method) {
1619
Assert.notNull(bean, "Bean이 null이어선 안됩니다.");
@@ -19,7 +22,17 @@ public HandlerExecution(Object bean, Method method) {
1922
this.method = method;
2023
}
2124

22-
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response) throws Exception {
25+
@Override
26+
public boolean support(Object handler) {
27+
return (handler instanceof HandlerExecution);
28+
}
29+
30+
@Override
31+
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
32+
return ((HandlerExecution) handler).execute(request, response);
33+
}
34+
35+
public ModelAndView execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
2336
return (ModelAndView) method.invoke(bean, request, response);
2437
}
2538
}

src/main/java/core/mvc/tobe/HandlerMapping.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44

55
public interface HandlerMapping {
66

7+
void initMapping();
78
Object getHandler(HttpServletRequest request);
89
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package core.mvc.tobe;
2+
3+
import core.mvc.ModelAndView;
4+
import core.mvc.asis.Controller;
5+
6+
import javax.servlet.http.HttpServletRequest;
7+
import javax.servlet.http.HttpServletResponse;
8+
9+
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
10+
@Override
11+
public boolean support(Object handler) {
12+
return (handler instanceof Controller);
13+
}
14+
15+
@Override
16+
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
17+
return ((Controller) handler).execute(request, response);
18+
}
19+
}

0 commit comments

Comments
 (0)