-
Notifications
You must be signed in to change notification settings - Fork 43
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 - Step 1] 효오 미션 제출합니다. #28
Conversation
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.
안녕하세요! 정훈님이 개인적인 사정이 있어서 대신 리뷰남겨요~ 😀
상당부분 잘 구현하셨는데요. 이번 미션 진행하면서 구현한 기능들에 대한 테스트코드를 작성하지 않아 아쉽네요.
어렵더라도 작성해보는 연습을 해보시는게 좋을거 같아요.
몇가지 피드백 남겨두었으니 확인해보시고 궁금한 점 있으면 Comment남기시면 포비가 답변주실거에요 😈
Field[] fields = clazz.getDeclaredFields(); | ||
for (Field field : fields) { | ||
logger.debug("필드 이름 : {}, 접근 제어자 : {}", field.getName(), field.getModifiers()); | ||
} | ||
|
||
Constructor<?>[] constructors = clazz.getDeclaredConstructors(); | ||
for (Constructor<?> constructor : constructors) { | ||
logger.debug("생성자 이름 : {}, 접근 제어자 : {}, 파라미터 타입 : {}", constructor.getName(), constructor.getModifiers(), constructor.getParameterTypes()); | ||
} | ||
|
||
Method[] methods = clazz.getDeclaredMethods(); | ||
for (Method method : methods) { | ||
logger.debug("메서드 이름 : {}, 접근 제어자 : {}, 파라미터 타입 : {}", method.getName(), method.getModifiers(), method.getParameterTypes()); | ||
} |
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.
미션이 출력을 하는 것들 이었지만, 값을 비교하는 테스트를 작성하는 것이 더 좋다고 생각합니다.
테스트 코드를 작성하는 것은 리팩토링하는 과정을 돕기도 하지만, 배포 파이프라인에서도 의미를 갖는다고 생각하는데요.
단순히 출력하는 테스트는 리팩토링 과정에서는 사용자의 확인을 필요로하고, 배포 파이프라인에서는 별다른 의미를 갖지 않게 된다고 생각합니다.
addController("/users", RequestMethod.POST); | ||
addController("/users", RequestMethod.GET); | ||
addController("/users/loginForm", RequestMethod.GET); | ||
addController("/users/login", RequestMethod.POST); | ||
addController("/users/logout", RequestMethod.GET); |
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.
Reflection을 적극적으로 사용하면 RequestMethod를 인자로 받는 부분을 없앨 수 있지 않을까요
그리고 현재는 addController 메서드에서 매번 ControllerScanner.scan 메서드를 호출하고 있네요.
아래와 같이 Controller별로 Annotation이 붙은 메서드만 찾는 것은 어떨까요
Method[] methods = Arrays.stream(controller.getDeclaredMethods())
.filter(it -> it.isAnnotationPresent(RequestMapping.class))
.toArray(Method[]::new);
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.
다시 생각해보니 인자도 받을 필요가 없었고, 매번 ControllerScanner의 메소드를 호출할 필요도 없었네요! 좋은 피드백 감사합니다!
궁금한게 있습니다. 저는 Controller에서 Mapping(@GetMapping, @RequestMapping....) 관련 어노테이션이 붙은 메서드 접근 제어자를 public으로 두고,
로직을 분리할 때 분리한 메서드 접근 제어자를 대부분 private로 선언 했었습니다.
그래서 접근 불가능한 메서드를 가져오지 않아도 괜찮다고 생각해서 getMethods를 사용했는데
피드백 주신 코드에서 getDeclaredMethods()를 사용한 이유가 있을까요?
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.
아니요. getMethods()를 사용하시면 됩니다 ㅎㅎ;
} | ||
|
||
@Override | ||
public HandlerExecution getHandler(HttpServletRequest request) { |
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.
method 순서에 대해서 다시 한번 고민해보셔요.
https://stackoverflow.com/questions/4668218/are-there-any-java-method-ordering-conventions
import java.util.Map; | ||
|
||
public class JspView implements View { | ||
private static final String DEFAULT_REDIRECT_PREFIX = "redirect:"; |
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.
JspView와 redirect는 별개로 보이는데요. redirect의 경우 dispatch도 필요없어 보이구요
RedirectView를 별도로 작성하는 것은 어떨까요
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE | ||
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE; | ||
|
||
public static RequestMethod of(String requestMethod) { |
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.
enum의 valueOf 메서드를 활용해도 되지 않을까요
if (handlerExecutions.containsKey(handlerKey)) { | ||
return handlerExecutions.get(handlerKey); | ||
} | ||
throw new IllegalArgumentException(); |
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.
RequestMapping에 Method를 설정하지 않았을 시에는 요구사항에 있는 것처럼 해당 URL에 대해 모든 Method로 요청할 수 있습니다. 이 기능이 구현되지 않았네요.
그리고 특정 Method로 설정해두었을 경우 그 외의 Method로 요청시에는 405 로 응답합니다.
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.
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
String value() default "";
RequestMethod[] method() default {};
}
|
||
public interface HandlerMapping { | ||
void initialize(); | ||
|
||
Controller getHandler(String requestUri); | ||
Object getHandler(HttpServletRequest request); |
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.
HandlerExecution으로 리턴 받는다면 DispatcherServlet에서 if 분기 처리하는 상당부분, instaceof 등이 제거될 수 있겠어요
if (user == null) { | ||
request.setAttribute("loginFailed", true); | ||
return new ModelAndView(new JspView("/user/login.jsp")); | ||
} |
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.
아래의 코드와 중복이네요
|
||
@WebServlet(name = "dispatcher", urlPatterns = "/", loadOnStartup = 1) | ||
public class DispatcherServlet extends HttpServlet { | ||
private static final long serialVersionUID = 1L; | ||
private static final Logger logger = LoggerFactory.getLogger(DispatcherServlet.class); | ||
private static final String DEFAULT_REDIRECT_PREFIX = "redirect:"; | ||
|
||
private HandlerMapping rm; | ||
private List<HandlerMapping> handlerMappings; |
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.
👍
@@ -11,40 +14,59 @@ | |||
import javax.servlet.http.HttpServletRequest; | |||
import javax.servlet.http.HttpServletResponse; | |||
import java.io.IOException; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
|
|||
@WebServlet(name = "dispatcher", urlPatterns = "/", loadOnStartup = 1) | |||
public class DispatcherServlet extends HttpServlet { |
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.
DispatcherServlet은 HandlerMapping 인터페이스의 getHandler 메서드의 리턴타입 변경 후 구조적으로 상당부분 개선이 될 것으로 보입니다.
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.
리턴 타입을 HandlerExecution 으로 바꾸면서, 여러번 코드를 작성했다 지웠다를 반복했습니다.
이러한 과정을 겪으니 확실히 코드가 전보다 깔끔해지고, 이해도가 높아졌습니다.
리턴 타입만 바꿨는데 DispatcherServlet에서 분기 처리하는 부분이 없어져서 전보다 깔끔하네요!
좋은 피드백 감사합니다!
- 매개변수 제거 - 매번 ControllerScanner 메서드 호출 -> 1번 호출
- UpdateLoginController 상수 제거 - JspView redirect 로직 제거
- Object -> HandlerExecution - method.invoke(...) -> 리턴 타입 String -> ModelAndView로 변경 - DispatcherServlet 불필요한 메서드 제거
테스트 코드는 PR 보내고 추가하겠습니다! |
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.
피드백 반영 잘해주셨네요! 👍
다음 단계 진행하시면 됩니다~ :)
ControllerScanner.scan(basePackage, Controller.class).forEach(controller -> | ||
Arrays.stream(controller.getMethods()) | ||
.filter(method -> method.isAnnotationPresent(RequestMapping.class)) | ||
.forEach(method -> putHandlerExecution(controller, method)) |
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.
stream api의 foreach는 가급적 출력 용도로만 활용해주시기 바랍니다.
로직을 수행할 거면 반복문을 사용하시는게 객체지향적인 설계를 가능케 한다고 생각합니다.
foreach에서 로직이 수행될 경우 동시성 보장이 어려워지고 가독성이 떨어져 effective java item 46에서는 주의해서 사용하길 권하고 있습니다.
추가적으로, stream api는 지연연산을 하므로 일반적인 loop와 다르게 동작하므로 결과가 예측과 다를 수 있습니다.
https://www.popit.kr/java8-stream은-loop가-아니다/
안녕하세요! 효오 입니다.
아직 부족하지만 많은 지적 부탁드리겠습니다!
먼저 PR보내놓고 계속해서 리팩토링 하겠습니다.