From cfb3e55be2f1eee7ce41869714b81c6df895ed93 Mon Sep 17 00:00:00 2001 From: DEOKWOO KIM Date: Fri, 15 Sep 2023 14:36:25 +0900 Subject: [PATCH] =?UTF-8?q?[MVC=20=EA=B5=AC=ED=98=84=201=EB=8B=A8=EA=B3=84?= =?UTF-8?q?]=20=EB=A1=9C=EC=9D=B4(=EA=B9=80=EB=8D=95=EC=9A=B0)=20=EB=AF=B8?= =?UTF-8?q?=EC=85=98=20=EC=A0=9C=EC=B6=9C=ED=95=A9=EB=8B=88=EB=8B=A4.=20(#?= =?UTF-8?q?416)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: 리플렉션 학습 테스트 * test: 서블릿 학습 테스트 * feat: 어노테이션 핸들러 맵핑 로직 구현 * feat: 핸들러 실행 로직 구현 * refactor: 어노테이션 부착 여부 확인 로직 수정 및 불필요한 로그 제거 * refactor: 핸들러 매핑 클래서 메서드 분리 * fix: 여러가지 RequestMethod에 대한 Handler 맵핑 처리 로직 추가 --- .../mvc/tobe/AnnotationHandlerMapping.java | 39 +++++++++++++- .../servlet/mvc/tobe/HandlerExecution.java | 12 ++++- .../java/reflection/Junit3TestRunner.java | 13 +++++ .../java/reflection/Junit4TestRunner.java | 12 +++++ .../test/java/reflection/ReflectionTest.java | 54 +++++++++++-------- .../test/java/reflection/ReflectionsTest.java | 20 +++++++ .../java/servlet/com/example/ServletTest.java | 4 +- 7 files changed, 127 insertions(+), 27 deletions(-) diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMapping.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMapping.java index a355218efa..a3e3f37fe6 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMapping.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/AnnotationHandlerMapping.java @@ -1,11 +1,17 @@ package webmvc.org.springframework.web.servlet.mvc.tobe; +import context.org.springframework.stereotype.Controller; 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 java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; +import java.util.Set; public class AnnotationHandlerMapping { @@ -21,9 +27,40 @@ public AnnotationHandlerMapping(final Object... basePackage) { public void initialize() { log.info("Initialized AnnotationHandlerMapping!"); + + Reflections reflections = new Reflections(basePackage); + Set> typesAnnotatedWithController = reflections.getTypesAnnotatedWith(Controller.class); + for (Class controller : typesAnnotatedWithController) { + mapHandlerForAnnotatedController(controller); + } + } + + private void mapHandlerForAnnotatedController(Class controller) { + Method[] declaredMethods = controller.getDeclaredMethods(); + for (Method declaredMethod : declaredMethods) { + mapHandlerForAnnotatedMethod(declaredMethod); + } + } + + private void mapHandlerForAnnotatedMethod(Method declaredMethod) { + if (declaredMethod.isAnnotationPresent(RequestMapping.class)) { + RequestMapping annotation = declaredMethod.getAnnotation(RequestMapping.class); + RequestMethod[] method = annotation.method(); + mapHandlerForRequestMethods(declaredMethod, annotation, method); + } + } + + private void mapHandlerForRequestMethods(Method declaredMethod, RequestMapping annotation, RequestMethod[] method) { + for (RequestMethod requestMethod : method) { + HandlerKey handlerKey = new HandlerKey(annotation.value(), requestMethod); + HandlerExecution handlerExecution = new HandlerExecution(declaredMethod); + handlerExecutions.put(handlerKey, handlerExecution); + } } public Object getHandler(final HttpServletRequest request) { - return null; + String requestURI = request.getRequestURI(); + String method = request.getMethod(); + return handlerExecutions.get(new HandlerKey(requestURI, RequestMethod.valueOf(method))); } } diff --git a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecution.java b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecution.java index 37c583fbdf..f517b2d923 100644 --- a/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecution.java +++ b/mvc/src/main/java/webmvc/org/springframework/web/servlet/mvc/tobe/HandlerExecution.java @@ -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 Method method; + + public HandlerExecution(Method method) { + this.method = method; + } + public ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response) throws Exception { - return null; + Object handler = method.getDeclaringClass().getDeclaredConstructor().newInstance(); + return (ModelAndView) method.invoke(handler, request, response); + } } diff --git a/study/src/test/java/reflection/Junit3TestRunner.java b/study/src/test/java/reflection/Junit3TestRunner.java index b4e465240c..6a50488f8a 100644 --- a/study/src/test/java/reflection/Junit3TestRunner.java +++ b/study/src/test/java/reflection/Junit3TestRunner.java @@ -2,12 +2,25 @@ import org.junit.jupiter.api.Test; +import java.lang.reflect.Method; + class Junit3TestRunner { @Test void run() throws Exception { Class clazz = Junit3Test.class; + Method[] methods = clazz.getMethods(); + + Junit3Test junit3Test = clazz.newInstance(); + + for (Method method : methods) { + if (method.getName().startsWith("test")) { + method.invoke(junit3Test); + } + } + + // TODO Junit3Test에서 test로 시작하는 메소드 실행 } } diff --git a/study/src/test/java/reflection/Junit4TestRunner.java b/study/src/test/java/reflection/Junit4TestRunner.java index 8a6916bc24..948d65775a 100644 --- a/study/src/test/java/reflection/Junit4TestRunner.java +++ b/study/src/test/java/reflection/Junit4TestRunner.java @@ -2,12 +2,24 @@ import org.junit.jupiter.api.Test; +import java.lang.reflect.Method; + class Junit4TestRunner { @Test void run() throws Exception { Class clazz = Junit4Test.class; + Method[] methods = clazz.getMethods(); + + Junit4Test junit4Test = clazz.newInstance(); + + for (Method method : methods) { + if (method.isAnnotationPresent(MyTest.class)) { + method.invoke(junit4Test); + } + } + // TODO Junit4Test에서 @MyTest 애노테이션이 있는 메소드 실행 } } diff --git a/study/src/test/java/reflection/ReflectionTest.java b/study/src/test/java/reflection/ReflectionTest.java index 370f0932b9..67dfc2aec8 100644 --- a/study/src/test/java/reflection/ReflectionTest.java +++ b/study/src/test/java/reflection/ReflectionTest.java @@ -7,7 +7,10 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Date; import java.util.List; +import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; @@ -19,25 +22,27 @@ class ReflectionTest { void givenObject_whenGetsClassName_thenCorrect() { final Class clazz = Question.class; - assertThat(clazz.getSimpleName()).isEqualTo(""); - assertThat(clazz.getName()).isEqualTo(""); - assertThat(clazz.getCanonicalName()).isEqualTo(""); + assertThat(clazz.getSimpleName()).isEqualTo("Question"); + assertThat(clazz.getName()).isEqualTo("reflection.Question"); + assertThat(clazz.getCanonicalName()).isEqualTo("reflection.Question"); } @Test void givenClassName_whenCreatesObject_thenCorrect() throws ClassNotFoundException { final Class clazz = Class.forName("reflection.Question"); - assertThat(clazz.getSimpleName()).isEqualTo(""); - assertThat(clazz.getName()).isEqualTo(""); - assertThat(clazz.getCanonicalName()).isEqualTo(""); + assertThat(clazz.getSimpleName()).isEqualTo("Question"); + assertThat(clazz.getName()).isEqualTo("reflection.Question"); + assertThat(clazz.getCanonicalName()).isEqualTo("reflection.Question"); } @Test void givenObject_whenGetsFieldNamesAtRuntime_thenCorrect() { final Object student = new Student(); - final Field[] fields = null; - final List actualFieldNames = null; + final Field[] fields = student.getClass().getDeclaredFields(); + final List actualFieldNames = Arrays.stream(fields) + .map(field -> field.getName()) + .collect(Collectors.toList()); assertThat(actualFieldNames).contains("name", "age"); } @@ -45,8 +50,10 @@ void givenObject_whenGetsFieldNamesAtRuntime_thenCorrect() { @Test void givenClass_whenGetsMethods_thenCorrect() { final Class animalClass = Student.class; - final Method[] methods = null; - final List actualMethods = null; + final Method[] methods = animalClass.getDeclaredMethods(); + final List actualMethods = Arrays.stream(methods) + .map(method -> method.getName()) + .collect(Collectors.toList()); assertThat(actualMethods) .hasSize(3) @@ -56,7 +63,7 @@ void givenClass_whenGetsMethods_thenCorrect() { @Test void givenClass_whenGetsAllConstructors_thenCorrect() { final Class questionClass = Question.class; - final Constructor[] constructors = null; + final Constructor[] constructors = questionClass.getConstructors(); assertThat(constructors).hasSize(2); } @@ -65,11 +72,11 @@ void givenClass_whenGetsAllConstructors_thenCorrect() { void givenClass_whenInstantiatesObjectsAtRuntime_thenCorrect() throws Exception { final Class questionClass = Question.class; - final Constructor firstConstructor = null; - final Constructor secondConstructor = null; + final Constructor firstConstructor = questionClass.getConstructors()[0]; + final Constructor secondConstructor = questionClass.getConstructors()[1]; - final Question firstQuestion = null; - final Question secondQuestion = null; + final Question firstQuestion = (Question) firstConstructor.newInstance("gugu", "제목1", "내용1"); + final Question secondQuestion = (Question) secondConstructor.newInstance(0l, "gugu", "제목2", "내용2", new Date(), 0); assertThat(firstQuestion.getWriter()).isEqualTo("gugu"); assertThat(firstQuestion.getTitle()).isEqualTo("제목1"); @@ -82,7 +89,7 @@ void givenClass_whenInstantiatesObjectsAtRuntime_thenCorrect() throws Exception @Test void givenClass_whenGetsPublicFields_thenCorrect() { final Class questionClass = Question.class; - final Field[] fields = null; + final Field[] fields = questionClass.getFields(); assertThat(fields).hasSize(0); } @@ -90,7 +97,7 @@ void givenClass_whenGetsPublicFields_thenCorrect() { @Test void givenClass_whenGetsDeclaredFields_thenCorrect() { final Class questionClass = Question.class; - final Field[] fields = null; + final Field[] fields = questionClass.getDeclaredFields(); assertThat(fields).hasSize(6); assertThat(fields[0].getName()).isEqualTo("questionId"); @@ -99,7 +106,7 @@ void givenClass_whenGetsDeclaredFields_thenCorrect() { @Test void givenClass_whenGetsFieldsByName_thenCorrect() throws Exception { final Class questionClass = Question.class; - final Field field = null; + final Field field = questionClass.getDeclaredField("questionId"); assertThat(field.getName()).isEqualTo("questionId"); } @@ -107,7 +114,7 @@ void givenClass_whenGetsFieldsByName_thenCorrect() throws Exception { @Test void givenClassField_whenGetsType_thenCorrect() throws Exception { final Field field = Question.class.getDeclaredField("questionId"); - final Class fieldClass = null; + final Class fieldClass = field.getType(); assertThat(fieldClass.getSimpleName()).isEqualTo("long"); } @@ -115,16 +122,17 @@ void givenClassField_whenGetsType_thenCorrect() throws Exception { @Test void givenClassField_whenSetsAndGetsValue_thenCorrect() throws Exception { final Class studentClass = Student.class; - final Student student = null; - final Field field = null; + final Student student = (Student) studentClass.getConstructor().newInstance(); + final Field field = student.getClass().getDeclaredField("age"); + field.setAccessible(true); + // todo field에 접근 할 수 있도록 만든다. assertThat(field.getInt(student)).isZero(); assertThat(student.getAge()).isZero(); - field.set(null, null); - + field.set(student, 99); assertThat(field.getInt(student)).isEqualTo(99); assertThat(student.getAge()).isEqualTo(99); } diff --git a/study/src/test/java/reflection/ReflectionsTest.java b/study/src/test/java/reflection/ReflectionsTest.java index 5040c2ffa2..56215dba10 100644 --- a/study/src/test/java/reflection/ReflectionsTest.java +++ b/study/src/test/java/reflection/ReflectionsTest.java @@ -4,6 +4,11 @@ import org.reflections.Reflections; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import reflection.annotation.Controller; +import reflection.annotation.Repository; +import reflection.annotation.Service; + +import java.util.Set; class ReflectionsTest { @@ -14,5 +19,20 @@ void showAnnotationClass() throws Exception { Reflections reflections = new Reflections("reflection.examples"); // TODO 클래스 레벨에 @Controller, @Service, @Repository 애노테이션이 설정되어 모든 클래스 찾아 로그로 출력한다. + + Set> typesAnnotatedWithController = reflections.getTypesAnnotatedWith(Controller.class); + for (Class aClass : typesAnnotatedWithController) { + log.info(aClass.getTypeName()); + } + + Set> typesAnnotatedWithService = reflections.getTypesAnnotatedWith(Service.class); + for (Class aClass : typesAnnotatedWithService) { + log.info(aClass.getTypeName()); + } + + Set> typesAnnotatedWithRepository = reflections.getTypesAnnotatedWith(Repository.class); + for (Class aClass : typesAnnotatedWithRepository) { + log.info(aClass.getTypeName()); + } } } diff --git a/study/src/test/java/servlet/com/example/ServletTest.java b/study/src/test/java/servlet/com/example/ServletTest.java index 75fbb10dd5..e335f8190a 100644 --- a/study/src/test/java/servlet/com/example/ServletTest.java +++ b/study/src/test/java/servlet/com/example/ServletTest.java @@ -28,7 +28,7 @@ void testSharedCounter() { // expected를 0이 아닌 올바른 값으로 바꿔보자. // 예상한 결과가 나왔는가? 왜 이런 결과가 나왔을까? - assertThat(Integer.parseInt(response.body())).isEqualTo(0); + assertThat(Integer.parseInt(response.body())).isEqualTo(3); } @Test @@ -50,6 +50,6 @@ void testLocalCounter() { // expected를 0이 아닌 올바른 값으로 바꿔보자. // 예상한 결과가 나왔는가? 왜 이런 결과가 나왔을까? - assertThat(Integer.parseInt(response.body())).isEqualTo(0); + assertThat(Integer.parseInt(response.body())).isEqualTo(1); } }