-
Notifications
You must be signed in to change notification settings - Fork 304
[MVC 구현하기 - 2, 3단계] 포츈(정윤성) 미션 제출합니다. #97
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
Conversation
98bbd3d
to
834708f
Compare
@RequestMapping(value = "/login/view", method = RequestMethod.GET) | ||
public ModelAndView view(HttpServletRequest req, HttpServletResponse res) { | ||
return UserSession.getUserFrom(req.getSession()) | ||
.map(user -> { | ||
LOGGER.info("logged in {}", user.getAccount()); | ||
View view = new JspView("redirect:/index.jsp"); | ||
return new ModelAndView(view); | ||
}) | ||
.orElse(new ModelAndView(new JspView("/login.jsp"))); | ||
} | ||
|
||
@RequestMapping(value = "/login", method = RequestMethod.POST) | ||
public ModelAndView login(HttpServletRequest req, HttpServletResponse res) { | ||
if (UserSession.isLoggedIn(req.getSession())) { | ||
return "redirect:/index.jsp"; | ||
View view = new JspView("redirect:/index.jsp"); | ||
return new ModelAndView(view); | ||
} |
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.
/login
과 /login/view
는
prefix url이 같으니, 하나의 컨트롤러로 합쳤어요.
@Controller | ||
public class MultiInputController { | ||
|
||
private static final Logger LOGGER = LoggerFactory.getLogger(MultiInputController.class); | ||
|
||
@RequestMapping(value = "/api/multi", method = RequestMethod.GET) | ||
public ModelAndView show(HttpServletRequest request, HttpServletResponse response) { | ||
String input1 = request.getParameter("input1"); | ||
String input2 = request.getParameter("input2"); | ||
|
||
LOGGER.debug("input1 : {}", input1); | ||
LOGGER.debug("input2 : {}", input2); | ||
|
||
ModelAndView modelAndView = new ModelAndView(new JsonView()); | ||
|
||
modelAndView.addObject("input1", input1); | ||
modelAndView.addObject("input2", input2); | ||
return modelAndView; | ||
} | ||
} |
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.
2개 입력에 대한 테스트가 용이하도록, 컨트롤러를 추가해주었어요.
ObjectMapper OBJECT_MAPPER = new ObjectMapper(); | ||
|
||
@Override | ||
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { | ||
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); | ||
PrintWriter writer = response.getWriter(); | ||
|
||
if (model.size() == 1) { | ||
writer.print(OBJECT_MAPPER.writeValueAsString(model.values().toArray()[0])); | ||
return; | ||
} | ||
writer.print(OBJECT_MAPPER.writeValueAsString(model)); | ||
} | ||
} |
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.
1개 입력, 2개 이상의 입력을 따로 처리하도록 JsonView를 구현했어요.
public class HandlerExecutionHandlerAdapter implements HandlerAdapter { | ||
|
||
@Override | ||
public boolean supports(final Object handler) { | ||
return handler instanceof HandlerExecution; | ||
} | ||
|
||
@Override | ||
public ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) throws Exception { | ||
return ((HandlerExecution) handler).handle(request, response); | ||
} | ||
} |
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.
기존 if / else 로 분기 처리되던것을 어뎁터패턴을 이용해 개선했어요.
이전 레거시 컨트롤러는 삭제당해버렸지만...
} No newline at end of file | ||
} |
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.
인비가 이야기 한것이 요것인것을 나중에서야 알았습니다 ㅋ_ㅋ..
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.
안녕하세요, 포츈!!
2, 3단계 구현 잘 해주셨네요. 💯
몇 가지 리뷰 남겼으니 참고 부탁드릴게요.
저도 이따가 데모발표를 하는데 포츈도 하시는군요.. ㅎㅎ
같이 화이팅 해요!! 💪 💪
테스트코드를 더 추가하면 좋을 것 같은데요, 이 부분은 포츈의 선택으로 남겨둘게요. 😉
바쁘신 와중에 미션 하시느라 고생 많으셨습니다!!
데모발표 화이팅 하시고, 추석 잘 보내세요!!!
|
||
@RequestMapping(value = "/", method = RequestMethod.GET) | ||
public ModelAndView execute(HttpServletRequest req, HttpServletResponse res) throws Exception { | ||
return new ModelAndView(new JspView("/index.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.
"/index.jsp"
등의 View name들이 여러 컨트롤러에 반복적으로 쓰이고 있어요.
mvc 모듈의 nextstep.web.support
패키지의 MediaType.class
처럼,
별도의 클래스 상수용 클래스를 만들어 View name을 관리하면 어떨까요?
@RequestMapping(value = "/login/view", method = RequestMethod.GET) | ||
public ModelAndView view(HttpServletRequest req, HttpServletResponse res) { | ||
return UserSession.getUserFrom(req.getSession()) | ||
.map(user -> { | ||
LOGGER.info("logged in {}", user.getAccount()); | ||
View view = new JspView("redirect:/index.jsp"); | ||
return new ModelAndView(view); | ||
}) | ||
.orElse(new ModelAndView(new JspView("/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.
아래의 POST /login
요청에서 쓰고있는
if (UserSession.isLoggedIn(req.getSession())) {
View view = new JspView("redirect:/index.jsp");
return new ModelAndView(view);
}
를 사용한다면, 가독성이 좋아지고 통일성이 생길 것 같아요!
@@ -13,13 +13,14 @@ | |||
|
|||
@Controller | |||
public class RegisterController { | |||
|
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.
이미 로그인이 된 사용자라면, GET /register/ivew
, POST /register
요청을 했을 때 /index.jsp
로 redirect 하도록 할 수도 있을 것 같아요.
LoginController
와 TestController
는 메서드가 GET, POST 순으로 있는데, 여기에는 POST, GET 순으로 되어있네요.
여기도 GET, POST 순으로 통일시키면 어떨까요?
@Controller | ||
public class UserController { | ||
|
||
private static final Logger LOGGER = LoggerFactory.getLogger(UserController.class); | ||
|
||
@RequestMapping(value = "/api/user", method = RequestMethod.GET) | ||
public ModelAndView show(HttpServletRequest request, HttpServletResponse response) { | ||
final String account = request.getParameter("account"); | ||
LOGGER.debug("user id : {}", account); | ||
|
||
final ModelAndView modelAndView = new ModelAndView(new JsonView()); | ||
final User user = InMemoryUserRepository.findByAccount(account) | ||
.orElseThrow(); | ||
|
||
modelAndView.addObject("user", user); | ||
return modelAndView; | ||
} | ||
} |
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.
final ModelAndView modelAndView = new ModelAndView(new JsonView());
modelAndView.addObject("user", user);
return modelAndView;
이렇게 ModelAndView
사용 부분을 모아두면 가독성이 더 좋아질 것 같아요.
final User user = InMemoryUserRepository.findByAccount(account)
.orElseThrow();
의 orElseThrow();
에 특정 Exception을 메세지를 포함해 던지도록 하면 에러 핸들링, 디버깅 등에 더 유리할 것 같아요. :)
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.
제가 작성한 코드가 아니고, 요구조건에서 주어진 class라서 그냥 곧이 그대로 가져왔었네요. ㅎㅎ;;
수정 하도록할게용
private UserSession() { | ||
} |
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.
private 생성자의 접근제어자가 private 이다 보니까
자동포맷팅 시에 public 메서드를 위에, private 메서드를 아래에 두면서 이렇게 밑으로 내려가더라구요.
생성자니까 멤버 변수 아래, 메서드 위에 두는게 어떨까요?
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.
이부분은 자동 포매팅 때문에 종종 놓치게 되네요 ㅋㅋ;
public class JsonView implements View { | ||
|
||
ObjectMapper OBJECT_MAPPER = new ObjectMapper(); |
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.
앞에 private static final
이 필요할 것 같아요!!
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.
헛, 넣은줄알고 있었는데 새벽에 졸면서 코딩하다보니 뺴먹었나 봅니다 ㅠㅠ
private final List<HandlerMapping> handlerMappings; | ||
private final List<HandlerAdapter> handlerAdapters; | ||
|
||
public DispatcherServlet() { | ||
this.handlerMappings = new ArrayList<>(); | ||
handlerMappings = new ArrayList<>(); | ||
handlerAdapters = new ArrayList<>(); | ||
} |
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.
handlerMappings
와 handlerAdapters
를 각각 일급컬렉션으로 빼면 어떨까요?
그러면 객체의 역할과 책임이 분리되면서
service()
메서드의 indent 3
을 indent 1
까지 없앨 수 있고,
getHandlerMapping()
메서드도 HandlerMappings
일급 컬렉션 안으로 숨길 수 있을 것 같아요!
SonarCloud Quality Gate failed. |
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.
디엠으로 이야기한내용 제외 + 인비가 준 피드백을 적용해보았어요.
휴일이시니 천천히 리뷰해주셔도 정말 괜찮습니다~
명절 잘보내세요 즐추!
package nextstep.web.support; | ||
|
||
public enum JspPage { | ||
INDEX("/index.jsp"), | ||
LOGIN("/login.jsp"), | ||
REGISTER("/register.jsp"), | ||
UNAUTHORIZED("/401.jsp"), | ||
GET_TEST("/get-test.jsp"), | ||
POST_TEST("/post-test.jsp"); | ||
|
||
private final String page; | ||
|
||
JspPage(final String page) { | ||
this.page = page; | ||
} | ||
|
||
public String value() { | ||
return page; | ||
} | ||
|
||
public String redirect() { | ||
return String.format("redirect:%s", page); | ||
} | ||
} |
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에서 처리하도록 했어요.
private UserSession() { | ||
} | ||
|
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.
생성자 위치 수정완료!
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); | ||
|
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.
이런 기본적인 실수를 했었다니 ㅎㅎ;
public HandlerAdapter getHandlerAdapter(final Object handler) { | ||
return handlerAdapters.stream() | ||
.filter(handlerAdapter -> handlerAdapter.supports(handler)) | ||
.findFirst() | ||
.orElseThrow(() -> 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.
HandlerAdapters 를 일급컬렉션화하고, DisPatcherServlet에서 하고 있던 많은 작업을 이쪽으로 이관시켰어요.
import java.util.List; | ||
import java.util.Objects; | ||
|
||
public class 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.
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.
안녕하세요, 포츈!!!
피드백 반영해주신 부분 확인했습니다 ㅎㅎ
명절에도 빠르게 반영해주셔서 감사해요!!
미션 요구사항을 모두 만족시키셨다고 판단되어 이만 머지 할게요!!
남은 미션도 화이팅하시고, 명절 잘 보내세요!!
고생 많으셨습니다. 🙇♂️
이번 데모데이 발표자라서, 미션이 조금 후순위로 밀렸던지라 이제야 PR을 보내네요!
2단계 요구사항에서 조금 부족했던
HandlerAdapter 인터페이스 를 이용해 추상화를 하는 작업을 진행했구요.
하지만 ControllerAdapter는 step3 하면서 삭제 당해버린..3단계 요구사항인 JoinView를 구현했어요.
model에 데이터가 1개면 값을 그대로 반환하고 2개 이상이면 Map 형태 그대로 JSON으로 변환해서 반환한다.
를 확인할 수 있는 url은 아래에 적어두었습니다.
이번에도 역시, 요구사항을 충족하는것을 최우선적인 목표로 잡았습니다.
인비도 추석 잘보내요!