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..e7006a5934 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,16 +1,18 @@ package webmvc.org.springframework.web.servlet.mvc.tobe; +import context.org.springframework.stereotype.Controller; import jakarta.servlet.http.HttpServletRequest; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.reflections.Reflections; +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 { - private static final Logger log = LoggerFactory.getLogger(AnnotationHandlerMapping.class); - private final Object[] basePackage; private final Map handlerExecutions; @@ -19,11 +21,40 @@ public AnnotationHandlerMapping(final Object... basePackage) { this.handlerExecutions = new HashMap<>(); } + public void initialize() { - log.info("Initialized AnnotationHandlerMapping!"); + getAnnotatedClasses().forEach(this::registerMethods); + } + + private Set> getAnnotatedClasses() { + Reflections reflections = new Reflections(basePackage); + return reflections.getTypesAnnotatedWith(Controller.class); + } + + private void registerMethods(final Class clazz) { + for (Method method : clazz.getDeclaredMethods()) { + if (method.isAnnotationPresent(RequestMapping.class)) { + registerMethod(clazz, method); + } + } + } + + private void registerMethod(final Class clazz, final Method method) { + RequestMapping requestMapping = method.getAnnotation(RequestMapping.class); + final String value = requestMapping.value(); + for (RequestMethod requestMethod : requestMapping.method()) { + handlerExecutions.put( + new HandlerKey(value, requestMethod), + new HandlerExecution(clazz, method)); + } } public Object getHandler(final HttpServletRequest request) { - return null; + final HandlerKey handlerKey = new HandlerKey(request.getRequestURI(), RequestMethod.valueOf(request.getMethod())); + return handlerExecutions.keySet().stream() + .filter(handlerKey::equals) + .map(handlerExecutions::get) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("존재하지 않은 요청입니다.")); } } 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..a5e1e08a0d 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 Class clazz; + private final Method method; + + public HandlerExecution(Class clazz, final Method method) { + this.clazz = clazz; + this.method = method; + } public ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response) throws Exception { - return null; + final Object instance = clazz.getDeclaredConstructor().newInstance(); + return (ModelAndView) method.invoke(instance, request, response); } } diff --git a/study/src/main/java/servlet/com/example/CharacterEncodingFilter.java b/study/src/main/java/servlet/com/example/CharacterEncodingFilter.java index cf4d886974..6649a16ea1 100644 --- a/study/src/main/java/servlet/com/example/CharacterEncodingFilter.java +++ b/study/src/main/java/servlet/com/example/CharacterEncodingFilter.java @@ -10,6 +10,8 @@ public class CharacterEncodingFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + request.setCharacterEncoding("UTF-8"); + response.setCharacterEncoding("UTF-8"); request.getServletContext().log("doFilter() 호출"); chain.doFilter(request, response); } diff --git a/study/src/test/java/di/stage3/context/DIContainer.java b/study/src/test/java/di/stage3/context/DIContainer.java index b62feb1ed3..b49576f34e 100644 --- a/study/src/test/java/di/stage3/context/DIContainer.java +++ b/study/src/test/java/di/stage3/context/DIContainer.java @@ -1,5 +1,6 @@ package di.stage3.context; +import java.util.HashSet; import java.util.Set; /** @@ -10,11 +11,16 @@ class DIContainer { private final Set beans; public DIContainer(final Set> classes) { - this.beans = Set.of(); + this.beans = Set.of(classes.toArray()); } @SuppressWarnings("unchecked") public T getBean(final Class aClass) { + for (Object bean : beans) { + if (aClass.isAssignableFrom(bean.getClass())) { + return (T) bean; + } + } return null; } } diff --git a/study/src/test/java/reflection/Junit3TestRunner.java b/study/src/test/java/reflection/Junit3TestRunner.java index b4e465240c..370031a265 100644 --- a/study/src/test/java/reflection/Junit3TestRunner.java +++ b/study/src/test/java/reflection/Junit3TestRunner.java @@ -2,12 +2,23 @@ import org.junit.jupiter.api.Test; +import java.lang.reflect.Method; + class Junit3TestRunner { @Test void run() throws Exception { Class clazz = Junit3Test.class; - // TODO Junit3Test에서 test로 시작하는 메소드 실행 + // Junit3Test 클래스의 모든 메소드를 가져옵니다. + Method[] methods = clazz.getDeclaredMethods(); + + for (Method method : methods) { + String methodName = method.getName(); + // 메소드 이름이 "test"로 시작하는 경우 실행합니다. + if (methodName.startsWith("test")) { + method.invoke(clazz.newInstance()); + } + } } } diff --git a/study/src/test/java/reflection/Junit4TestRunner.java b/study/src/test/java/reflection/Junit4TestRunner.java index 8a6916bc24..b0d3b2c239 100644 --- a/study/src/test/java/reflection/Junit4TestRunner.java +++ b/study/src/test/java/reflection/Junit4TestRunner.java @@ -2,6 +2,8 @@ import org.junit.jupiter.api.Test; +import java.lang.reflect.Method; + class Junit4TestRunner { @Test @@ -9,5 +11,12 @@ void run() throws Exception { Class clazz = Junit4Test.class; // TODO Junit4Test에서 @MyTest 애노테이션이 있는 메소드 실행 + final Method[] declaredMethods = clazz.getDeclaredMethods(); + + for (final Method declaredMethod : declaredMethods) { + if (declaredMethod.isAnnotationPresent(MyTest.class)) { + declaredMethod.invoke(clazz.newInstance()); + } + } } } diff --git a/study/src/test/java/reflection/ReflectionTest.java b/study/src/test/java/reflection/ReflectionTest.java index 370f0932b9..f704ba0e61 100644 --- a/study/src/test/java/reflection/ReflectionTest.java +++ b/study/src/test/java/reflection/ReflectionTest.java @@ -7,9 +7,13 @@ 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; +import static org.assertj.core.api.InstanceOfAssertFactories.DATE; class ReflectionTest { @@ -18,26 +22,28 @@ class ReflectionTest { @Test void givenObject_whenGetsClassName_thenCorrect() { final Class clazz = Question.class; - - assertThat(clazz.getSimpleName()).isEqualTo(""); - assertThat(clazz.getName()).isEqualTo(""); - assertThat(clazz.getCanonicalName()).isEqualTo(""); + System.out.println(clazz.getCanonicalName()); + 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::getName) + .collect(Collectors.toList()); assertThat(actualFieldNames).contains("name", "age"); } @@ -45,8 +51,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::getName) + .collect(Collectors.toList()); assertThat(actualMethods) .hasSize(3) @@ -56,20 +64,24 @@ void givenClass_whenGetsMethods_thenCorrect() { @Test void givenClass_whenGetsAllConstructors_thenCorrect() { final Class questionClass = Question.class; - final Constructor[] constructors = null; + final Constructor[] constructors = questionClass.getDeclaredConstructors(); assertThat(constructors).hasSize(2); } + @Test void givenClass_whenInstantiatesObjectsAtRuntime_thenCorrect() throws Exception { final Class questionClass = Question.class; - final Constructor firstConstructor = null; - final Constructor secondConstructor = null; + final Constructor[] constructors = questionClass.getDeclaredConstructors(); + + final Constructor firstConstructor = constructors[0]; + final Constructor secondConstructor = constructors[1]; + + final Question firstQuestion = (Question) firstConstructor.newInstance("gugu", "제목1", "내용1"); - final Question firstQuestion = null; - final Question secondQuestion = null; + final Question secondQuestion = (Question) secondConstructor.newInstance(1L, "gugu", "제목2", "내용2", new Date(), 3); assertThat(firstQuestion.getWriter()).isEqualTo("gugu"); assertThat(firstQuestion.getTitle()).isEqualTo("제목1"); @@ -82,7 +94,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 +102,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 +111,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 +119,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,15 +127,16 @@ 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 = new Student(); + final Field field = studentClass.getDeclaredField("age"); // todo field에 접근 할 수 있도록 만든다. + field.setAccessible(true); assertThat(field.getInt(student)).isZero(); assertThat(student.getAge()).isZero(); - field.set(null, null); + field.setInt(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..bf12830978 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,12 @@ void showAnnotationClass() throws Exception { Reflections reflections = new Reflections("reflection.examples"); // TODO 클래스 레벨에 @Controller, @Service, @Repository 애노테이션이 설정되어 모든 클래스 찾아 로그로 출력한다. + Set> annotatedClasses = reflections.getTypesAnnotatedWith(Controller.class, false); + annotatedClasses.addAll(reflections.getTypesAnnotatedWith(Service.class, false)); + annotatedClasses.addAll(reflections.getTypesAnnotatedWith(Repository.class, false)); + + for (Class annotatedClass : annotatedClasses) { + log.info("Annotated class: {}", annotatedClass.getName()); + } } } 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); } }