-
Notifications
You must be signed in to change notification settings - Fork 305
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단계] 에버(손채영) 미션 제출합니다. #694
Changes from 19 commits
df6fa40
cb172db
6e4c0e8
f0cda4b
2ecca46
e312664
4e45a35
e76390e
aa674d8
d4d274b
0118167
ba17506
de7370e
613a0c1
5b297ce
54f3b36
fdd3078
8ace1d8
6afede4
279fc92
21fcb37
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,14 @@ | ||
package com.interface21.web.bind.annotation; | ||
|
||
import java.util.Arrays; | ||
|
||
public enum RequestMethod { | ||
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE | ||
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE; | ||
|
||
public static RequestMethod find(String raw) { | ||
return Arrays.stream(values()) | ||
.filter(value -> value.name().equals(raw)) | ||
.findAny() | ||
.orElseThrow(() -> new IllegalArgumentException(String.format("유효하지 않은 요청 메서드입니다: %s", raw))); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,26 @@ | ||
package com.interface21.webmvc.servlet.mvc.tobe; | ||
|
||
import com.interface21.webmvc.servlet.ModelAndView; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import com.interface21.webmvc.servlet.ModelAndView; | ||
import java.lang.reflect.Method; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
public class HandlerExecution { | ||
|
||
private static final Logger log = LoggerFactory.getLogger(HandlerExecution.class); | ||
|
||
private final Object controller; | ||
private final Method handler; | ||
|
||
public HandlerExecution(Object controller, Method handler) { | ||
this.controller = controller; | ||
this.handler = handler; | ||
} | ||
|
||
public ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response) throws Exception { | ||
return null; | ||
log.info("handler execution handle"); | ||
return (ModelAndView) handler.invoke(controller, request, response); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,56 @@ | ||
package com.interface21.webmvc.servlet.mvc.tobe; | ||
|
||
import com.interface21.web.bind.annotation.RequestMethod; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Objects; | ||
|
||
public class HandlerKey { | ||
|
||
private final String url; | ||
private final RequestMethod requestMethod; | ||
private static final List<HandlerKey> CACHE = new ArrayList<>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 질문) 에버는 캐시 구현으로 List를 사용하셨네요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. List가 HandlerKey의 묶음을 나타내기 가장 적절한 자료구조라고 생각했어요! 물론 HashMap이 성능 상 이점은 있지만 Map에 대한 key를 생성하는 것이 불필요하다는 판단을 하기도 했습니다. 하지만 리니의 PR 코드와 함께 추가적으로 고민하다보니, url과 requestMethod를 조합해 Map의 key로 사용하여 성능을 높이는 방향도 충분히 좋다고 생각했어요. 따라서 HashMap을 통해 데이터를 관리하도록 수정해보겠습니다! |
||
|
||
public final String url; | ||
public final RequestMethod requestMethod; | ||
|
||
public HandlerKey(final String url, final RequestMethod requestMethod) { | ||
public static HandlerKey from(String url, RequestMethod requestMethod) { | ||
return CACHE.stream() | ||
.filter(handlerKey -> handlerKey.url.equals(url) && handlerKey.requestMethod == requestMethod) | ||
.findAny() | ||
.orElseGet(() -> new HandlerKey(url, requestMethod)); | ||
} | ||
|
||
private HandlerKey(final String url, final RequestMethod requestMethod) { | ||
this.url = url; | ||
this.requestMethod = requestMethod; | ||
CACHE.add(this); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "HandlerKey{" + | ||
"url='" + url + '\'' + | ||
", requestMethod=" + requestMethod + | ||
'}'; | ||
protected static void cleanCache() { | ||
CACHE.clear(); | ||
} | ||
|
||
@Override | ||
public boolean equals(Object o) { | ||
if (this == o) return true; | ||
if (!(o instanceof HandlerKey)) return false; | ||
HandlerKey that = (HandlerKey) o; | ||
return Objects.equals(url, that.url) && requestMethod == that.requestMethod; | ||
if (this == o) { | ||
return true; | ||
} | ||
if (!(o instanceof HandlerKey)) { | ||
return false; | ||
} | ||
HandlerKey handlerKey = (HandlerKey) o; | ||
return Objects.equals(url, handlerKey.url) && requestMethod == handlerKey.requestMethod; | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(url, requestMethod); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "HandlerKey{" + | ||
"url='" + url + '\'' + | ||
", requestMethod=" + requestMethod + | ||
'}'; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package com.interface21.web.bind.annotation; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||
|
||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Test; | ||
|
||
class RequestMethodTest { | ||
|
||
@DisplayName("전달받은 문자열과 형태가 같은 요청 메서드를 반환한다.") | ||
@Test | ||
void should_findRequestMethod_when_givenValidString() { | ||
// given | ||
String raw = "GET"; | ||
|
||
// when | ||
RequestMethod requestMethod = RequestMethod.find(raw); | ||
|
||
// then | ||
assertThat(requestMethod).isEqualTo(RequestMethod.GET); | ||
} | ||
|
||
@DisplayName("전달받은 문자열이 유효하지 않은 경우 예외가 발생한다.") | ||
@Test | ||
void should_throwException_when_givenInvalidString() { | ||
// given | ||
String raw = "INVALID"; | ||
|
||
// when & then | ||
assertThatThrownBy(() -> RequestMethod.find(raw)) | ||
.isInstanceOf(IllegalArgumentException.class) | ||
.hasMessage("유효하지 않은 요청 메서드입니다: " + raw); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HandlerKey도 Handler인스턴스처럼 요청마다 새로운 인스턴스가 생성될 것 같은데, 개선해보면 좋을 것 같습니다:)
(저도 제 리뷰어한테 받았던 리뷰랍니다 ㅎㅎ)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
좋은 생각이에요! 하나의 서비스에서 HandlerKey가 필드로 갖는 url과 requestMethod는 한정적이기 때문에 캐싱을 적용해 중복 객체를 생성하지 않도록 하였습니다.
d4d274b
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[질문] 캐싱 로직을 테스트하기 위해 HandlerKeyTest 테스트 코드를 작성했는데요! private으로 생성된 CACHE 데이터를 확인하기 위해서는 테스트만을 위한 메서드를 생성하거나, reflection을 활용하여 접근해야 했어요. 리니는 둘 중 어느 방법이 더 나은 방법이라 생각하시나요? 혹은 캐싱 로직을 테스트하기 위한 좋은 방법을 알고 계시나요? 궁금합니다! 😊
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
테스트를 위한 메서드가 production에 생기는 것보단, reflection을 활용하는 것이 더 좋아보여요! assertJ의
extract
메서드도 reflection으로 구현되어있다는 점을 생각한다면 reflection으로 검증하는 것도 괜찮다고 생각합니다!혹은 검증하고자 하는 것이
이미 존재한다면 있는 인스턴스를 반환하고, 아니라면 새로운 인스턴스를 생성한다
라면 다음과 같이 테스트를 작성해볼 수도 있을 것 같아요!(저도 이 부분은 테스트 없이 넘어갔었는데, 에버 덕분에 발견했습니다 ㅎㅎ)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오 두 객체의 동일성을 판단하는 메서드가 제공되는 줄 몰랐네요! isSameAs 활용해서 테스트 진행해야겠어요.
279fc92