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단계] 이리내(성채연) 미션 제출합니다. #376

Merged
merged 7 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
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: ./gradlew clean build codeCoverageReport --info -x :study:build
run: ./gradlew clean build codeCoverageReport sonar --info -x :study:build
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
# @MVC 구현하기

- [x] 1단계 - @MVC 프레임워크 구현하기
- [x] 어노테이션 기반의 MVC 프레임워크를 구현
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
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.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;

public class AnnotationHandlerMapping {

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

private final Object[] basePackage;
private final Map<HandlerKey, HandlerExecution> handlerExecutions;

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

public void initialize() {
log.info("Initialized AnnotationHandlerMapping!");
collectAllControllerClasses().forEach(this::generateHandlerExecutions);
Copy link

Choose a reason for hiding this comment

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

메서드 분리 깔끔하게 잘 해주셨네요👍

}

private Set<Class<?>> collectAllControllerClasses() {
Reflections reflections = new Reflections(basePackage);
return reflections.getTypesAnnotatedWith(Controller.class);
}

private void generateHandlerExecutions(final Class<?> controllerClass) {
final Object controller = generateInstance(controllerClass);
final Method[] methods = controllerClass.getMethods();
for (Method method : methods) {
generateHandlerExecutions(controller, method);
}
}

private Object generateInstance(final Class<?> clazz) {
try {
return clazz.getConstructor().newInstance();
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new IllegalStateException("인스턴스 생성에 실패하였습니다.");
}
}

private void generateHandlerExecutions(final Object controller, final Method method) {
final RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
if (requestMapping == null) {
return;
}

final String path = requestMapping.value();
final RequestMethod[] requestMethods = requestMapping.method();
final List<HandlerKey> handlerKeys = generateHandlerKeys(path, requestMethods);
final HandlerExecution handlerExecution = new HandlerExecution(controller, method);
handlerKeys.forEach(key -> handlerExecutions.put(key, handlerExecution));
}

private List<HandlerKey> generateHandlerKeys(final String path, final RequestMethod[] requestMethods) {
return Arrays.stream(requestMethods)
.map(requestMethod -> new HandlerKey(path, requestMethod))
.collect(Collectors.toList());
}

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

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.lang.reflect.InvocationTargetException;
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 controller;
private final Method method;

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

public ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response)
throws InvocationTargetException, IllegalAccessException {
return (ModelAndView) method.invoke(controller, request, response);
}
}
13 changes: 12 additions & 1 deletion study/src/test/java/reflection/Junit3TestRunner.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package reflection;

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

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

// TODO Junit3Test에서 test로 시작하는 메소드 실행
final Junit3Test junit3Test = clazz.getConstructor().newInstance();
/* final Method test1 = clazz.getMethod("test1");
final Method test2 = clazz.getMethod("test2");*/
final Method[] methods = clazz.getMethods(); // 상속한 Object 클래스의 메서드까지 다 가져옴

for (Method method : methods) {
System.out.println("method.getName() = " + method.getName());
if(method.getName().startsWith("test")) {
method.invoke(junit3Test);
}
}
}
}
11 changes: 11 additions & 0 deletions 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.lang.reflect.Method;
import org.junit.jupiter.api.Test;

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

// TODO Junit4Test에서 @MyTest 애노테이션이 있는 메소드 실행
final Junit4Test junit4Test = clazz.getConstructor().newInstance();
final Method[] methods = clazz.getDeclaredMethods(); // 상속한 메서드 제외

for (Method method : methods) {
final MyTest myTest = method.getAnnotation(MyTest.class);
if(myTest != null) {
method.invoke(junit4Test);
}
Copy link

Choose a reason for hiding this comment

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

  if (method.isAnnotationPresent(MyTest.class)) {
      method.invoke(junit4Test);
  }

이렇게도 사용할 수 있습니다


}
}
}
57 changes: 33 additions & 24 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,41 @@ class ReflectionTest {
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.getSimpleName() = " + clazz.getSimpleName());
System.out.println("clazz.getName() = " + clazz.getName());
System.out.println("clazz.getCanonicalName() = " + clazz.getCanonicalName());
assertThat(clazz.getSimpleName()).isEqualTo("Question"); //패키지 정보x
assertThat(clazz.getName()).isEqualTo("reflection.Question"); //패키지 정보o
assertThat(clazz.getCanonicalName()).isEqualTo("reflection.Question"); //todo: getName과 차이
}

@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 +66,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 +75,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.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(2L, "gugu", "제목2", "내용2", null, 0);

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

// todo field에 접근 할 수 있도록 만든다.
final Student student = (Student) studentClass.getConstructor().newInstance();
final Field field = studentClass.getDeclaredField("age");
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
13 changes: 12 additions & 1 deletion 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 @@ -13,6 +17,13 @@ class ReflectionsTest {
void showAnnotationClass() throws Exception {
Reflections reflections = new Reflections("reflection.examples");

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

final Set<Class<?>> services = reflections.getTypesAnnotatedWith(Service.class);
log.info(services.toString());

final Set<Class<?>> repositories = reflections.getTypesAnnotatedWith(Repository.class);
log.info(repositories.toString());
}
}
Loading