Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MVC 구현하기 - 1단계] 쥬니(전정준) 미션 제출합니다. #421

Merged
merged 4 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
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 jakarta.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
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 webmvc.org.springframework.web.servlet.ModelAndView;

public class AnnotationHandlerMapping {

private static final Logger log = LoggerFactory.getLogger(AnnotationHandlerMapping.class);
private static final int REQUEST_INDEX = 0;
private static final int RESPONSE_INDEX = 1;

private final Object[] basePackage;
private final Map<HandlerKey, HandlerExecution> handlerExecutions;
Expand All @@ -20,10 +32,73 @@ public AnnotationHandlerMapping(final Object... basePackage) {
}

public void initialize() {
Set<Class<?>> controllerClasses = findClassesAnnotatedController();

initializeHandlerExecutions(controllerClasses);

log.info("Initialized AnnotationHandlerMapping!");
}

private Set<Class<?>> findClassesAnnotatedController() {
Reflections reflections = new Reflections(basePackage);

return reflections.getTypesAnnotatedWith(Controller.class);
}

private void initializeHandlerExecutions(Set<Class<?>> controllerClasses) {
controllerClasses.forEach(controlerClass -> {
List<Method> methods = extractMethods(controlerClass);

initializeHandlerExecutionsByMethods(methods);
});
}

private List<Method> extractMethods(Class<?> controllerClasses) {
return Arrays.stream(controllerClasses.getDeclaredMethods())
.filter(method -> method.isAnnotationPresent(RequestMapping.class))
.filter(method -> method.getReturnType().equals(ModelAndView.class))
.filter(method -> validateParameterType(method.getParameterTypes()))
.collect(Collectors.toList());
Comment on lines +56 to +61
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스트림을 사용해서 깔끔하게 작성해주셨네요.
덕분에 '무엇을 하고 싶은지?'가 정확하게 보여요!

}

private boolean validateParameterType(Class<?>[] parameters) {
return parameters[REQUEST_INDEX].equals(HttpServletRequest.class) &&
parameters[RESPONSE_INDEX].equals(HttpServletResponse.class);
}
Comment on lines +64 to +67
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

인자 검증까지 해주셨네요 👍
그런데 메서드명을 보면 검증을 통해 예외가 발생할 것 같다는 생각이 들어요.
'boolean 값 반환'이라는 행위에 맞게 메서드명을 수정해보면 어떨까요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validateParameterType -> isValidateParameterType 으로 변경했습니다 !


private void initializeHandlerExecutionsByMethods(List<Method> methods) {
methods.forEach(this::setupHandlerExecutions);
}

private void setupHandlerExecutions(Method method) {
RequestMapping requestMappingAnnotation = method.getAnnotation(RequestMapping.class);
String url = requestMappingAnnotation.value();
RequestMethod[] requestMethods = requestMappingAnnotation.method();

try {
for (RequestMethod requestMethod : requestMethods) {
HandlerKey handlerKey = new HandlerKey(url, requestMethod);
Object classInstance = extractClassInstance(method);
HandlerExecution handlerExecution = new HandlerExecution(method, classInstance);

handlerExecutions.put(handlerKey, handlerExecution);
}
} catch (Exception e) {
log.error("ERROR : {}", e.getMessage());
}
Comment on lines +78 to +88
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

예외를 처리하는 것도 하나의 행위라고 생각합니다.
그래서 이 부분을 별도의 메서드로 추상화해보면 더 서술적이고 인덴트도 줄어들어서 좋을 것 같아요.
throws Exception이 명시된 함수형 인터페이스 Callable<>를 활용해보시면 어떨까ㅛ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extractClassInstance 메서드에서 예외 처리 로직을 수행하도록 수정하였습니다 !

}

private Object extractClassInstance(Method method) throws Exception {
Class<?> declaringClassForMethod = method.getDeclaringClass();

return declaringClassForMethod.getDeclaredConstructor().newInstance();
}

public Object getHandler(final HttpServletRequest request) {
return null;
String uri = request.getRequestURI();
RequestMethod method = RequestMethod.valueOf(request.getMethod());

return handlerExecutions.get(new HandlerKey(uri, method));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,21 @@

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 Method handler;
private final Object object;

public HandlerExecution(Method handler, Object object) {
this.handler = handler;
this.object = object;
}

public ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response) throws Exception {
return null;
return (ModelAndView) handler.invoke(object, request, response);
}

}
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
package servlet.com.example;

import jakarta.servlet.*;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.annotation.WebFilter;

import java.io.IOException;

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

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
request.getServletContext().log("doFilter() 호출");
response.setCharacterEncoding("UTF-8");
chain.doFilter(request, response);
}
}
14 changes: 14 additions & 0 deletions study/src/test/java/reflection/Junit3TestRunner.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package reflection;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;

class Junit3TestRunner {
Expand All @@ -9,5 +13,15 @@ void run() throws Exception {
Class<Junit3Test> clazz = Junit3Test.class;

// TODO Junit3Test에서 test로 시작하는 메소드 실행
Junit3Test junit3Test = new Junit3Test();
Method[] methods = clazz.getMethods();

List<Method> methodsStartWithTest = Arrays.stream(methods)
.filter(method -> method.getName().startsWith("test"))
.collect(Collectors.toList());

for (Method method : methodsStartWithTest) {
method.invoke(junit3Test);
}
}
}
14 changes: 14 additions & 0 deletions study/src/test/java/reflection/Junit4TestRunner.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package reflection;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;

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

// TODO Junit4Test에서 @MyTest 애노테이션이 있는 메소드 실행
Junit4Test junit4Test = new Junit4Test();

Method[] methods = clazz.getMethods();
List<Method> methodsWithMyTestAnnotation = Arrays.stream(methods)
.filter(method -> method.isAnnotationPresent(MyTest.class))
.collect(Collectors.toList());

for (Method method : methodsWithMyTestAnnotation) {
method.invoke(junit4Test);
}
}
}
64 changes: 37 additions & 27 deletions study/src/test/java/reflection/ReflectionTest.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
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.Arrays;
import java.util.Date;
import java.util.List;

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

class ReflectionTest {

Expand All @@ -19,34 +21,38 @@ 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 Field[] fields = student.getClass().getDeclaredFields();
final List<String> 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;
final Method[] methods = animalClass.getDeclaredMethods();
final List<String> actualMethods = Arrays.stream(methods)
.map(Method::getName)
.collect(Collectors.toList());

assertThat(actualMethods)
.hasSize(3)
Expand All @@ -56,7 +62,7 @@ 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);
}
Expand All @@ -65,11 +71,14 @@ 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.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 = null;
final Question secondQuestion = null;
final Question firstQuestion = (Question) firstConstructor.newInstance("gugu", "제목1", "내용1");
final Question secondQuestion = (Question) secondConstructor.newInstance(1, "gugu", "제목2", "내용2", new Date(),
0);

assertThat(firstQuestion.getWriter()).isEqualTo("gugu");
assertThat(firstQuestion.getTitle()).isEqualTo("제목1");
Expand All @@ -82,15 +91,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 +108,32 @@ 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 = 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.set(student, 99);

assertThat(field.getInt(student)).isEqualTo(99);
assertThat(student.getAge()).isEqualTo(99);
Expand Down
19 changes: 19 additions & 0 deletions study/src/test/java/reflection/ReflectionsTest.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package reflection;

import java.util.Set;
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 {

Expand All @@ -14,5 +18,20 @@ void showAnnotationClass() throws Exception {
Reflections reflections = new Reflections("reflection.examples");

// TODO 클래스 레벨에 @Controller, @Service, @Repository 애노테이션이 설정되어 모든 클래스 찾아 로그로 출력한다.
Set<Class<?>> classesWithController = reflections.getTypesAnnotatedWith(Controller.class);
for (Class<?> classWithController : classesWithController) {
log.info("Controller : {}", classWithController.getSimpleName());
}

Set<Class<?>> classesWithService = reflections.getTypesAnnotatedWith(Service.class);
for (Class<?> classWithService : classesWithService) {
log.info("Service : {}", classWithService.getSimpleName());
}

Set<Class<?>> classesWithRepository = reflections.getTypesAnnotatedWith(Repository.class);
for (Class<?> classWithRepository : classesWithRepository) {
log.info("Repository : {}", classWithRepository.getSimpleName());
}

}
}
Loading
Loading