Skip to content

Commit

Permalink
[MVC 구현하기 - 1단계] 히이로(문제웅) 미션 제출합니다. (#390)
Browse files Browse the repository at this point in the history
* test: reflection 학습 테스트 진행 완료

* docs: README 기능 요구사항 명세 작성

* feat: AnnotationHandlerMapping 클래스 initialize 기능 구현

* feat: AnnotationHandlerMapping 클래스 getHandler 기능 구현

* feat: HandlerExecution 클래스 handle 기능 구현

* test: user 테스트 추가

* test: servlet 학습 테스트 완료
  • Loading branch information
MoonJeWoong authored Sep 13, 2023
1 parent e7c21ab commit 0e98fe1
Show file tree
Hide file tree
Showing 16 changed files with 176 additions and 41 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,16 @@
# @MVC 구현하기

# 기능 요구사항 명세
- [] 어노테이션 기반의 MVC 프레임워크를 구현한다.
- [] mvc 모듈과 app 모듈의 영역이 잘 구분되어야 한다.
- [] 새로운 컨트롤러가 생겨도 MVC 프레임워크 영역까지 영향이 미치면 안된다.
- [x] AnnotationHandlerMapping 구현 사항
- [x] initialize 기능 구현
- [x] basePackage 하위에 존재하는 @Controller 어노테이션 선언 클래스 탐색
- [x] Controller 클래스 내 선언된 @requestMapping 어노테이션 선언 메서드 탐색
- [x] 각 Method 객체를 조합으로 가지는 HandlerExecution 객체를 생성해서 저장
- [x] getHandler 기능 구현
- [x] HttpServletRequest 객체를 인자로 받아서 HandlerKey 객체 생성
- [x] 생성된 HandlerKey 객체를 key로 가지는 value 값에 해당하는 handler를 반환
- [x] HandlerExecution 구현 사항
- [x] handler 객체에서 request, response 객체를 인자로 받아 적합한 비즈니스 로직을 수행하는 기능을 구현한다.
33 changes: 33 additions & 0 deletions app/src/test/java/com/techcourse/domain/UserTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.techcourse.domain;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;

@SuppressWarnings("NonAsciiCharacters")
class UserTest {

@Test
void 입력한_비밀번호가_사용자_정보와_일치하는지_확인할__있다() {
//given
User user = new User(1, "gugu", "password", "[email protected]");

//when
boolean result = user.checkPassword("password");

//then
assertThat(result).isTrue();
}

@Test
void 입력한_비밀번호가_사용자_정보와_불일치하는지_확인할__있다() {
//given
User user = new User(1, "gugu", "password", "[email protected]");

//when
boolean result = user.checkPassword("wrong password");

//then
assertThat(result).isFalse();
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
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.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
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 @@ -20,10 +26,38 @@ public AnnotationHandlerMapping(final Object... basePackage) {
}

public void initialize() {
Set<Class<?>> controllers = getAnnotatedControllerClasses();
for (Class<?> controller : controllers) {
addAnnotatedHandlerExecution(controller);
}
log.info("Initialized AnnotationHandlerMapping!");
}

private void addAnnotatedHandlerExecution(Class<?> controller) {
for (Method method : controller.getMethods()) {
if (!method.isAnnotationPresent(RequestMapping.class)) {
continue;
}
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
HandlerKey handlerKey = new HandlerKey(requestMapping.value(),
requestMapping.method()[0]);
HandlerExecution handlerExecution = new HandlerExecution(controller, method);
handlerExecutions.put(handlerKey, handlerExecution);
}
}

private Set<Class<?>> getAnnotatedControllerClasses() {
Set<Class<?>> controllers = new HashSet<>();
for (Object packagePath : basePackage) {
Reflections reflections = new Reflections((String) packagePath);
controllers.addAll(reflections.getTypesAnnotatedWith(Controller.class));
}
return controllers;
}

public Object getHandler(final HttpServletRequest request) {
return null;
String requestURI = request.getRequestURI();
RequestMethod requestMethod = RequestMethod.valueOf(request.getMethod().toUpperCase());
return handlerExecutions.get(new HandlerKey(requestURI, requestMethod));
}
}
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 Class<?> clazz;
private final Method method;

public HandlerExecution(Class<?> clazz, Method method) {
this.clazz = clazz;
this.method = method;
}

public ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response) throws Exception {
return null;
return (ModelAndView) method.invoke(clazz.getDeclaredConstructor().newInstance(), request, response);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package servlet.com.example;
package reflection.servlet.com.example;

import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
Expand All @@ -11,6 +11,7 @@ public class CharacterEncodingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
request.getServletContext().log("doFilter() 호출");
response.setCharacterEncoding("UTF-8");
chain.doFilter(request, response);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package servlet.com.example;
package reflection.servlet.com.example;

import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletException;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package servlet.com.example;
package reflection.servlet.com.example;

import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletException;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package servlet.com.example;
package reflection.servlet.com.example;

public class ServletApplication {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package servlet.com.example;
package reflection.servlet.com.example;

import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletException;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package servlet.com.example;
package reflection.servlet.com.example;

import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
Expand Down
8 changes: 8 additions & 0 deletions 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 @@ -9,5 +10,12 @@ void run() throws Exception {
Class<Junit3Test> clazz = Junit3Test.class;

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

for (Method method : tests) {
if (method.getName().startsWith("test")) {
method.invoke(clazz.getDeclaredConstructor().newInstance());
}
}
}
}
7 changes: 7 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,11 @@ void run() throws Exception {
Class<Junit4Test> clazz = Junit4Test.class;

// TODO Junit4Test에서 @MyTest 애노테이션이 있는 메소드 실행
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method method : declaredMethods) {
if (method.isAnnotationPresent(MyTest.class)) {
method.invoke(clazz.getDeclaredConstructor().newInstance());
}
}
}
}
66 changes: 39 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 -> 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,16 @@ 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.getDeclaredConstructor(String.class,
String.class, String.class);
final Constructor<?> secondConstructor = questionClass.getDeclaredConstructor(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(1L, "gugu", "제목2",
"내용2",
new Date(), 0);

assertThat(firstQuestion.getWriter()).isEqualTo("gugu");
assertThat(firstQuestion.getTitle()).isEqualTo("제목1");
Expand All @@ -82,15 +93,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 +110,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 = student.getClass().getDeclaredField("age");
field.trySetAccessible();

// todo 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
Loading

0 comments on commit 0e98fe1

Please sign in to comment.