Skip to content

Commit

Permalink
[MVC 미션 1단계] 포이(김보준) 미션 제출합니다. (#361)
Browse files Browse the repository at this point in the history
* test: 리플렉션 테스트 완료

* test: 서블릿, 필터 학습 테스트 완료

* feat: 어노테이션 기반으로 HandlerMapping을 초기화해주는 기능 추가
  • Loading branch information
poi1649 authored Sep 13, 2023
1 parent e7c21ab commit 949fac1
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 48 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
package web.org.springframework.web.bind.annotation;

public enum RequestMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
GET,
HEAD,
POST,
PUT,
PATCH,
DELETE,
OPTIONS,
TRACE
;
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
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 java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
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;

public class AnnotationHandlerMapping {

Expand All @@ -21,9 +30,49 @@ public AnnotationHandlerMapping(final Object... basePackage) {

public void initialize() {
log.info("Initialized AnnotationHandlerMapping!");
final var reflections = new Reflections(basePackage);
final var methodsPerClass = groupingMethodsByClass(reflections);

for (var classAndMethods : methodsPerClass.entrySet()) {
final var clazz = classAndMethods.getKey();
final var methods = classAndMethods.getValue();
try {
initializeEachClassExecutions(clazz, methods);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
}

private Map<? extends Class<?>, List<Method>> groupingMethodsByClass(final Reflections reflections) {
return reflections.getTypesAnnotatedWith(Controller.class).stream()
.map(Class::getDeclaredMethods)
.flatMap(Stream::of)
.filter(method -> method.isAnnotationPresent(RequestMapping.class))
.collect(Collectors.groupingBy(Method::getDeclaringClass));
}

private void initializeEachClassExecutions(
final Class<?> clazz,
final List<Method> methods
) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
final var parentPath = clazz.getAnnotation(Controller.class).path();
final var target = clazz.getConstructor().newInstance();
for (var method : methods) {
initializeEachMethodExecution(method, parentPath, target);
}
}

private void initializeEachMethodExecution(final Method method, final String parentPath, final Object target) {
final var requestMapping = method.getAnnotation(RequestMapping.class);
final var path = parentPath + requestMapping.value();
Arrays.stream(requestMapping.method())
.map(requestMethod -> new HandlerKey(path, requestMethod))
.forEach(key -> handlerExecutions.put(key, new HandlerExecution(target, method)));
}


public Object getHandler(final HttpServletRequest request) {
return null;
return handlerExecutions.get(new HandlerKey(request.getRequestURI(), RequestMethod.valueOf(request.getMethod().toUpperCase())));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,20 @@

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import webmvc.org.springframework.web.servlet.ModelAndView;

public class HandlerExecution {

private final Object target;
private final Method method;

public HandlerExecution(Object target, final Method method) {
this.target = target;
this.method = method;
}

public ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response) throws Exception {
return null;
return (ModelAndView) method.invoke(target, request, response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
import jakarta.servlet.annotation.WebFilter;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

@WebFilter("/*")
public class CharacterEncodingFilter implements Filter {

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
request.getServletContext().log("doFilter() 호출");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
chain.doFilter(request, response);
}
}
15 changes: 13 additions & 2 deletions study/src/test/java/reflection/Junit3TestRunner.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
package reflection;

import java.util.Arrays;
import org.junit.jupiter.api.Test;

class Junit3TestRunner {

@Test
void run() throws Exception {
Class<Junit3Test> clazz = Junit3Test.class;
final var clazz = Junit3Test.class;

// TODO Junit3Test에서 test로 시작하는 메소드 실행
// Junit3Test에서 test로 시작하는 메소드 실행
Arrays.stream(clazz.getDeclaredMethods())
.filter(method -> method.getName().startsWith("test"))
.forEach(method -> {
try {
method.invoke(clazz.getDeclaredConstructor().newInstance());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
);
}
}
13 changes: 12 additions & 1 deletion study/src/test/java/reflection/Junit4TestRunner.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package reflection;

import java.util.Arrays;
import org.junit.jupiter.api.Test;

class Junit4TestRunner {
Expand All @@ -8,6 +9,16 @@ class Junit4TestRunner {
void run() throws Exception {
Class<Junit4Test> clazz = Junit4Test.class;

// TODO Junit4Test에서 @MyTest 애노테이션이 있는 메소드 실행
// Junit4Test에서 @MyTest 애노테이션이 있는 메소드 실행
Arrays.stream(clazz.getDeclaredMethods())
.filter(method -> method.isAnnotationPresent(MyTest.class))
.forEach(method -> {
try {
method.invoke(clazz.getDeclaredConstructor().newInstance());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
);
}
}
99 changes: 64 additions & 35 deletions study/src/test/java/reflection/ReflectionTest.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package reflection;

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.assertj.core.api.Assertions.assertThat;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import java.util.Arrays;
import java.util.Date;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

class ReflectionTest {

Expand All @@ -19,44 +20,47 @@ class ReflectionTest {
void givenObject_whenGetsClassName_thenCorrect() {
final Class<Question> 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<String> actualFieldNames = null;
final var student = new Student();
final var fields = student.getClass().getDeclaredFields();
final var actualFieldNames = Arrays.stream(fields)
.map(Field::getName)
.collect(Collectors.toList());

assertThat(actualFieldNames).contains("name", "age");
}

@Test
void givenClass_whenGetsMethods_thenCorrect() {
final Class<?> animalClass = Student.class;
final Method[] methods = null;
final List<String> actualMethods = null;

assertThat(actualMethods)
.hasSize(3)
.contains("getAge", "toString", "getName");
final var animalClass = Student.class;
final var methods = animalClass.getDeclaredMethods();
final var actualMethods = Arrays.stream(methods)
.map(Method::getName)
.collect(Collectors.toList());

assertThat(actualMethods).hasSize(3)
.contains("getAge", "toString", "getName");
}

@Test
void givenClass_whenGetsAllConstructors_thenCorrect() {
final Class<?> questionClass = Question.class;
final Constructor<?>[] constructors = null;
final Constructor<?>[] constructors = questionClass.getDeclaredConstructors();

assertThat(constructors).hasSize(2);
}
Expand All @@ -65,11 +69,34 @@ void givenClass_whenGetsAllConstructors_thenCorrect() {
void givenClass_whenInstantiatesObjectsAtRuntime_thenCorrect() throws Exception {
final Class<?> questionClass = Question.class;

final Constructor<?> firstConstructor = null;
final Constructor<?> secondConstructor = null;

final Question firstQuestion = null;
final Question secondQuestion = null;
final Constructor<?> firstConstructor = questionClass.getConstructor(
String.class,
String.class,
String.class
);
final Constructor<?> secondConstructor = questionClass.getConstructor(
long.class,
String.class,
String.class,
String.class,
Date.class,
int.class
);

final Question firstQuestion = (Question) firstConstructor.newInstance(
"gugu",
"제목1",
"내용1"
);
final Question secondQuestion = (Question) secondConstructor.newInstance(
1L,
"gugu",
"제목2",
"내용2",
new Date(),
0
);

assertThat(firstQuestion.getWriter()).isEqualTo("gugu");
assertThat(firstQuestion.getTitle()).isEqualTo("제목1");
Expand All @@ -82,15 +109,15 @@ 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);
}

@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");
Expand All @@ -99,31 +126,33 @@ 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");
}

@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");
}

@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 = studentClass.getDeclaredField("age");

field.setAccessible(true);

// todo field에 접근 할 수 있도록 만든다.
// 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);
Expand Down
11 changes: 9 additions & 2 deletions study/src/test/java/reflection/ReflectionsTest.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
package reflection;

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

import org.junit.jupiter.api.Test;
import org.reflections.Reflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reflection.annotation.Controller;
import reflection.annotation.Repository;
import reflection.annotation.Service;

class ReflectionsTest {

private static final Logger log = LoggerFactory.getLogger(ReflectionsTest.class);

@Test
void showAnnotationClass() throws Exception {
void showAnnotationClass() {
Reflections reflections = new Reflections("reflection.examples");

// TODO 클래스 레벨에 @Controller, @Service, @Repository 애노테이션이 설정되어 모든 클래스 찾아 로그로 출력한다.
// 클래스 레벨에 @Controller, @Service, @Repository 애노테이션이 설정되어있는 모든 클래스 찾아 로그로 출력한다.
reflections.get(TypesAnnotated.of(Controller.class, Service.class, Repository.class))
.forEach(clazz -> log.info("{}", clazz));
}
}
Loading

0 comments on commit 949fac1

Please sign in to comment.