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단계] 마코(이규성) 미션 제출합니다. #345

Merged
merged 6 commits into from
Sep 13, 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
@@ -0,0 +1,5 @@
package webmvc.org.springframework.web.servlet.mvc.exception;

public class CanNotInstanceHandlerException extends RuntimeException {

}
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.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
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.mvc.exception.CanNotInstanceHandlerException;

public class AnnotationHandlerMapping {

private static final Logger log = LoggerFactory.getLogger(AnnotationHandlerMapping.class);
private static final Set<Class<?>> supportParameters = Set.of(HttpServletRequest.class,
HttpServletResponse.class);

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

public void initialize() {
final var reflections = new Reflections(basePackage);
final var classes = reflections.getTypesAnnotatedWith(Controller.class);
for (final Class<?> clazz : classes) {
final var handler = getHandlerInstance(clazz);
final var methods = clazz.getMethods();

Arrays.stream(methods)
.filter(this::supportParameters)
.filter(method -> method.isAnnotationPresent(RequestMapping.class))
.forEach(method -> putHandlerExecutions(handler, method));

Choose a reason for hiding this comment

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

https://tecoble.techcourse.co.kr/post/2020-05-14-foreach-vs-forloop/

foreach에 로직이 들어가면 안된다는데 어떻게 생각하시나요
근데 저도 foreach로 map에 넣는 작업을 수행하긴 했습니다

Copy link
Author

Choose a reason for hiding this comment

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

좋은 아티클 공유 감사합니다👍

for-loop보다 짧은 코드로 해결하려고 애써 외면하고 이 정도는 괜찮겠지 하고 타협했던 문제인데 충분히 로직을 빼고 스트림으로 해결할 수 있을 것 같네요. 2단계에서 리팩토링 해보겠습니다~

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

private boolean supportParameters(final Method method) {

Choose a reason for hiding this comment

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

이거 좀 hip하네요 👍

final var parameterTypes = Arrays.stream(method.getParameterTypes())
.collect(Collectors.toList());

return supportParameters.containsAll(parameterTypes) && parameterTypes.size() == 2;
}

private void putHandlerExecutions(final Object handler, final Method method) {
final var annotation = method.getDeclaredAnnotation(RequestMapping.class);
final var handlerExecution = new HandlerExecution(handler, method);

Arrays.stream(annotation.method())
.map(requestMethod -> new HandlerKey(annotation.value(), requestMethod))
.forEach(handlerKey -> handlerExecutions.put(handlerKey, handlerExecution));
}

private static Object getHandlerInstance(final Class<?> clazz) {
try {
return clazz.getConstructor().newInstance();
} catch (NoSuchMethodException |
IllegalAccessException |
InstantiationException |
InvocationTargetException e
) {
throw new CanNotInstanceHandlerException();
}
}

public Object getHandler(final HttpServletRequest request) {
return null;
String requestURI = request.getRequestURI();
RequestMethod requestMethod = RequestMethod.valueOf(request.getMethod());
HandlerKey handlerKey = new HandlerKey(requestURI, requestMethod);

return handlerExecutions.get(handlerKey);
}
}
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 {

public ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response) throws Exception {
return null;
private final Object handler;
private final Method method;

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

public ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response)
throws Exception {
return (ModelAndView) method.invoke(handler, 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);
}
}
10 changes: 9 additions & 1 deletion study/src/test/java/reflection/Junit3TestRunner.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
package reflection;

import com.sun.nio.sctp.PeerAddressChangeNotification.AddressChangeEvent;
import java.lang.reflect.Method;
import org.junit.jupiter.api.Test;

class Junit3TestRunner {

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

final var junit3 = (Junit3Test) clazz.getConstructor().newInstance();

// TODO Junit3Test에서 test로 시작하는 메소드 실행
for (final Method method : clazz.getDeclaredMethods()) {
if (method.getName().startsWith("test")) {
method.invoke(junit3);
}
}
}
}
9 changes: 9 additions & 0 deletions study/src/test/java/reflection/Junit4TestRunner.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package reflection;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import org.junit.jupiter.api.Test;

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

// TODO Junit4Test에서 @MyTest 애노테이션이 있는 메소드 실행
Junit4Test junit4Test = clazz.getConstructor().newInstance();
Method[] methods = clazz.getDeclaredMethods();
for (final Method method : methods) {
if (method.isAnnotationPresent(MyTest.class)) {
method.invoke(junit4Test);
}
}
}
}
52 changes: 30 additions & 22 deletions study/src/test/java/reflection/ReflectionTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package reflection;

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;
Expand All @@ -19,34 +22,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 +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);
}
Expand All @@ -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(0, "gugu", "제목2", "내용2", new Date(), 0);

assertThat(firstQuestion.getWriter()).isEqualTo("gugu");
assertThat(firstQuestion.getTitle()).isEqualTo("제목1");
Expand All @@ -82,15 +89,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 +106,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 = (Student) studentClass.getConstructor().newInstance();
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
12 changes: 12 additions & 0 deletions study/src/test/java/reflection/ReflectionsTest.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package reflection;

import java.util.Set;
import java.util.stream.Stream;
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 +19,12 @@ void showAnnotationClass() throws Exception {
Reflections reflections = new Reflections("reflection.examples");

// TODO 클래스 레벨에 @Controller, @Service, @Repository 애노테이션이 설정되어 모든 클래스 찾아 로그로 출력한다.
Set<Class<?>> controllerClasses = reflections.getTypesAnnotatedWith(Controller.class);
Set<Class<?>> serviceClasses = reflections.getTypesAnnotatedWith(Service.class);
Set<Class<?>> repositoryClasses = reflections.getTypesAnnotatedWith(Repository.class);

Stream.of(controllerClasses, serviceClasses, repositoryClasses)
.flatMap(Set::stream)
.forEach(clazz -> log.info(clazz.getName()));
}
}
6 changes: 4 additions & 2 deletions study/src/test/java/servlet/com/example/ServletTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ void testSharedCounter() {

// expected를 0이 아닌 올바른 값으로 바꿔보자.
// 예상한 결과가 나왔는가? 왜 이런 결과가 나왔을까?
assertThat(Integer.parseInt(response.body())).isEqualTo(0);
// 인스턴스 변수가 공유되어 같은 변수에 접근하여 body의 값이 3번 증가하여 3이 된다.
assertThat(Integer.parseInt(response.body())).isEqualTo(3);
}

@Test
Expand All @@ -50,6 +51,7 @@ void testLocalCounter() {

// expected를 0이 아닌 올바른 값으로 바꿔보자.
// 예상한 결과가 나왔는가? 왜 이런 결과가 나왔을까?
assertThat(Integer.parseInt(response.body())).isEqualTo(0);
// 메서드가 종료되면 stack 영역에 있는 지역 변수가 해제되기 때문
assertThat(Integer.parseInt(response.body())).isEqualTo(1);
}
}
Loading