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단계] 비버(전인표) 미션 제출합니다. #337

Merged
merged 5 commits into from
Sep 14, 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,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<HandlerKey, HandlerExecution> handlerExecutions;

Expand All @@ -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<Class<?>> 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();

Choose a reason for hiding this comment

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

request mapping 어노테이션에 value로 정의되어 있으니 value라는 변수명으로 uri를 저장해도 괜찮다고 생각합니다.
주관적인 생각입니다! 취향 차이라 생각이 다르시다면 굳이 수정하지 않으셔도 괜찮습니당.

HandlerKey의 생성자 매개변수로 들어갈 변수이기 때문에 value보다 HandlerKey 필드에 맞는 uri가 어떨까 해요.
현재 registerMethod()의 역할 중 하나는 HandlerKey 를 생성하는 것인데요. 이때 생성자의 인자를 받기 위해 value를 만들었잖아요. 그러면 registerMethod()에서 value 변수가 정의된 순간부터 value는 HandlerKey를 만들기 위한 부품이라고 생각해요. 이전의 "RequestMapping의 필드" 역할을 쭈욱 이어받는 것이 아니라용. 그래서 uri가 역할에 더 적합한 변수명이 아닐까 생각했습니다.

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()));

Choose a reason for hiding this comment

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

final 키워드 컨벤션을 통일해보면 어떨까요?

return handlerExecutions.keySet().stream()
.filter(handlerKey::equals)
.map(handlerExecutions::get)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("존재하지 않은 요청입니다."));
Comment on lines +54 to +58

Choose a reason for hiding this comment

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

예외처리 짱이네요. 저는 예외 하나도 잡지 않았거든요ㅋㅋ

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
8 changes: 7 additions & 1 deletion study/src/test/java/di/stage3/context/DIContainer.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package di.stage3.context;

import java.util.HashSet;
import java.util.Set;

/**
Expand All @@ -10,11 +11,16 @@ class DIContainer {
private final Set<Object> beans;

public DIContainer(final Set<Class<?>> classes) {
this.beans = Set.of();
this.beans = Set.of(classes.toArray());
}

@SuppressWarnings("unchecked")
public <T> T getBean(final Class<T> aClass) {
for (Object bean : beans) {
if (aClass.isAssignableFrom(bean.getClass())) {
return (T) bean;
}
}
return null;
}
}
13 changes: 12 additions & 1 deletion study/src/test/java/reflection/Junit3TestRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,23 @@

import org.junit.jupiter.api.Test;

import java.lang.reflect.Method;

class Junit3TestRunner {

@Test
void run() throws Exception {
Class<Junit3Test> 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());
}
}
}
}
9 changes: 9 additions & 0 deletions study/src/test/java/reflection/Junit4TestRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,21 @@

import org.junit.jupiter.api.Test;

import java.lang.reflect.Method;

class Junit4TestRunner {

@Test
void run() throws Exception {
Class<Junit4Test> 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());
}
}
}
}
59 changes: 36 additions & 23 deletions study/src/test/java/reflection/ReflectionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -18,35 +22,39 @@ class ReflectionTest {
@Test
void givenObject_whenGetsClassName_thenCorrect() {
final Class<Question> 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<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,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");
Expand All @@ -82,15 +94,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 +111,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.setInt(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
Expand Up @@ -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 {

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

// TODO 클래스 레벨에 @Controller, @Service, @Repository 애노테이션이 설정되어 모든 클래스 찾아 로그로 출력한다.
Set<Class<?>> 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());
}
}
}
4 changes: 2 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,7 @@ void testSharedCounter() {

// expected를 0이 아닌 올바른 값으로 바꿔보자.
// 예상한 결과가 나왔는가? 왜 이런 결과가 나왔을까?
assertThat(Integer.parseInt(response.body())).isEqualTo(0);
assertThat(Integer.parseInt(response.body())).isEqualTo(3);
}

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

// expected를 0이 아닌 올바른 값으로 바꿔보자.
// 예상한 결과가 나왔는가? 왜 이런 결과가 나왔을까?
assertThat(Integer.parseInt(response.body())).isEqualTo(0);
assertThat(Integer.parseInt(response.body())).isEqualTo(1);
}
}
Loading