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 구현 - 3단계] 바론(이소민) 미션 제출합니다. #560

Merged
merged 8 commits into from
Sep 27, 2023
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import web.org.springframework.web.WebApplicationInitializer;
import webmvc.org.springframework.web.servlet.DispatcherServlet;

/**
* Base class for {@link WebApplicationInitializer}
Expand Down
40 changes: 0 additions & 40 deletions app/src/main/java/com/techcourse/ManualHandlerMapping.java

This file was deleted.

18 changes: 18 additions & 0 deletions app/src/main/java/com/techcourse/controller/HomeController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.techcourse.controller;

import context.org.springframework.stereotype.Controller;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import web.org.springframework.web.bind.annotation.RequestMapping;
import web.org.springframework.web.bind.annotation.RequestMethod;
import webmvc.org.springframework.web.servlet.ModelAndView;
import webmvc.org.springframework.web.servlet.view.JspView;

@Controller
public class HomeController {

@RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView index(final HttpServletRequest req, final HttpServletResponse res) {
return new ModelAndView(new JspView("/index.jsp"));
}
}
41 changes: 30 additions & 11 deletions app/src/main/java/com/techcourse/controller/LoginController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,35 @@

import com.techcourse.domain.User;
import com.techcourse.repository.InMemoryUserRepository;
import context.org.springframework.stereotype.Controller;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import webmvc.org.springframework.web.servlet.mvc.asis.Controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import web.org.springframework.web.bind.annotation.RequestMapping;
import web.org.springframework.web.bind.annotation.RequestMethod;
import webmvc.org.springframework.web.servlet.ModelAndView;
import webmvc.org.springframework.web.servlet.view.JspView;

public class LoginController implements Controller {
@Controller
public class LoginController {

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

@Override
public String execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception {
@RequestMapping(value = "/login", method = RequestMethod.POST)
public ModelAndView doLogin(final HttpServletRequest req, final HttpServletResponse res) {
if (UserSession.isLoggedIn(req.getSession())) {
return "redirect:/index.jsp";
return new ModelAndView(new JspView("redirect:/index.jsp"));
}

return InMemoryUserRepository.findByAccount(req.getParameter("account"))
.map(user -> {
log.info("User : {}", user);
return login(req, user);
})
.orElse("redirect:/401.jsp");
final String loginResult = InMemoryUserRepository.findByAccount(req.getParameter("account"))
.map(user -> {
log.info("User : {}", user);
return login(req, user);
})
.orElse("redirect:/401.jsp");

return new ModelAndView(new JspView(loginResult));
}

private String login(final HttpServletRequest request, final User user) {
Expand All @@ -34,4 +41,16 @@ private String login(final HttpServletRequest request, final User user) {
}
return "redirect:/401.jsp";
}

@RequestMapping(value = "/login", method = RequestMethod.GET)
public ModelAndView viewLogin(final HttpServletRequest req, final HttpServletResponse res) {
final String foundLogin = UserSession.getUserFrom(req.getSession())
.map(user -> {
log.info("logged in {}", user.getAccount());
return "redirect:/index.jsp";
})
.orElse("/login.jsp");

return new ModelAndView(new JspView(foundLogin));
}
}

This file was deleted.

15 changes: 10 additions & 5 deletions app/src/main/java/com/techcourse/controller/LogoutController.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
package com.techcourse.controller;

import context.org.springframework.stereotype.Controller;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import webmvc.org.springframework.web.servlet.mvc.asis.Controller;
import web.org.springframework.web.bind.annotation.RequestMapping;
import web.org.springframework.web.bind.annotation.RequestMethod;
import webmvc.org.springframework.web.servlet.ModelAndView;
import webmvc.org.springframework.web.servlet.view.JspView;

public class LogoutController implements Controller {
@Controller
public class LogoutController {

@Override
public String execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception {
@RequestMapping(value = "/logout", method = RequestMethod.GET)
public ModelAndView logout(final HttpServletRequest req, final HttpServletResponse res) {
final var session = req.getSession();
session.removeAttribute(UserSession.SESSION_KEY);
return "redirect:/";
return new ModelAndView(new JspView("redirect:/"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,30 @@

import com.techcourse.domain.User;
import com.techcourse.repository.InMemoryUserRepository;
import context.org.springframework.stereotype.Controller;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import webmvc.org.springframework.web.servlet.mvc.asis.Controller;
import web.org.springframework.web.bind.annotation.RequestMapping;
import web.org.springframework.web.bind.annotation.RequestMethod;
import webmvc.org.springframework.web.servlet.ModelAndView;
import webmvc.org.springframework.web.servlet.view.JspView;

public class RegisterController implements Controller {
@Controller
public class RegisterController {

@Override
public String execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception {
@RequestMapping(value = "/register", method = RequestMethod.POST)
public ModelAndView register(final HttpServletRequest req, final HttpServletResponse res) {
final var user = new User(2,
req.getParameter("account"),
req.getParameter("password"),
req.getParameter("email"));
InMemoryUserRepository.save(user);

return "redirect:/index.jsp";
return new ModelAndView(new JspView("redirect:/index.jsp"));
}

@RequestMapping(value = "/register", method = RequestMethod.GET)
public ModelAndView findRegisterView(final HttpServletRequest req, final HttpServletResponse res) {
return new ModelAndView(new JspView("/register.jsp"));
}
}

This file was deleted.

32 changes: 32 additions & 0 deletions app/src/main/java/com/techcourse/controller/UserController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.techcourse.controller;

import com.techcourse.domain.User;
import com.techcourse.repository.InMemoryUserRepository;
import context.org.springframework.stereotype.Controller;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import web.org.springframework.web.bind.annotation.RequestMapping;
import web.org.springframework.web.bind.annotation.RequestMethod;
import webmvc.org.springframework.web.servlet.ModelAndView;
import webmvc.org.springframework.web.servlet.view.JsonView;

@Controller
public class UserController {

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

@RequestMapping(value = "/api/user", method = RequestMethod.GET)
public ModelAndView show(HttpServletRequest request, HttpServletResponse response) {
final String account = request.getParameter("account");
log.debug("user id : {}", account);

final ModelAndView modelAndView = new ModelAndView(new JsonView());
final User user = InMemoryUserRepository.findByAccount(account)
.orElseThrow();

modelAndView.addObject("user", user);
return modelAndView;
}
}
2 changes: 1 addition & 1 deletion app/src/main/webapp/register.jsp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
</form>
</div>
<div class="card-footer text-center py-3">
<div class="small"><a href="/login/view">이미 가입하셨나요? 로그인 하러가기</a></div>
<div class="small"><a href="/login">이미 가입하셨나요? 로그인 하러가기</a></div>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package com.techcourse;
package webmvc.org.springframework.web.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import webmvc.org.springframework.web.servlet.ModelAndView;
import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerAdapter;
import webmvc.org.springframework.web.servlet.view.JspView;
import webmvc.org.springframework.web.servlet.mvc.adapter.HandlerAdapter;
import webmvc.org.springframework.web.servlet.mvc.adapter.HandlerAdapters;
import webmvc.org.springframework.web.servlet.mvc.handlermapping.HandlerMappings;

public class DispatcherServlet extends HttpServlet {

Expand All @@ -25,33 +25,29 @@ public DispatcherServlet(final Object... basePackages) {

@Override
public void init() {
handlerMappings.initialize();
handlerMappings.initialize();
}

@Override
protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException {
protected void service(final HttpServletRequest request, final HttpServletResponse response)
throws ServletException {
log.debug("Method : {}, Request URI : {}", request.getMethod(), request.getRequestURI());

try {
final Object handler = handlerMappings.getHandler(request);
final HandlerAdapter handlerAdapter = handlerAdapters.getHandlerAdapter(handler);
final ModelAndView modelAndView = handlerAdapter.handle(request, response, handler);
move(modelAndView, request, response);
render(modelAndView, request, response);
} catch (Exception e) {
log.error("Exception : {}", e.getMessage(), e);
throw new ServletException(e.getMessage());
}
}
Comment on lines 47 to 49
Copy link

Choose a reason for hiding this comment

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

사실 이정도만 해줘도 되지만, 만약 존재하지 않는 경로로 요청을 한다면 어떻게 404.jsp를 띄어줄 수 있을까요? 저도 이부분 고민을 좀 해봐서 같이 의견 나눠보면 좋을것 같아요!~

Copy link
Author

Choose a reason for hiding this comment

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

안녕하세요 에코:) 너무 맛있는.. 코멘트였습니다. 🍰

에코가 주신 리뷰를 보면서 app에서 발생하지만 처리되지 않는 예외, DispatcherServlet 처리 과정에서 핸들링을 제대로 하지 못해 발생하는 예외, 그리고 에러 페이지 반환에 대해 많이 고민을 해보았는데요...!

처음에는 간단하게 DispatcherServlet에서 직접 jsp 파일을 읽고, response에 write 하는 방법 또는 302상태코드와 함께 예외 페이지로 redirect를 보내는 방법을 생각했었습니다.

그런데 갑자기 "DispatcherServlet이 app모듈에 개발자가 정의한 페이지를 읽어 반환하는 것이 맞을까?" 라는 의문이 들더라구요!

제 생각은 다음과 같습니다.

  1. DispatcherServlet은 app단에서 발생한 예외를 handle할 책임이 없습니다.
    app(즉 개발자가 비즈니스 로직을 직접 작성하는 부분) 에서 발생하는 예외를 어떻게 처리할 것인지 (어떤 예외 페이지를 반환할지) 에 대해서는 app모듈에서 결정되어야 한다고 생각해요!
    저희가 Spring 을 사용할 때 ExceptionHandler 를 정의하지 않으면 Internal Server Error 라는 메세지가 담긴 기본 오류 페이지 (White Label Page) 가 반환되는 것 처럼, 지금 코드에서도 app에서 예외가 발생하면 DispatcherServlet은 그저 "처리되지 않은 서버 예외 중 하나" 로 취급하고 Servlet 처리할 수 있는 방법으로 핸들링하면 될 것 같아요. app에서 발생한 오류를 app에서 핸들링하는 로직은 지금 controller의 orElse -> redirect:/500.jsp 같은 코드로 처리가 잘 되어있는 것 같구요 :)

  2. DispatcherServlet의 역할에서 발생하는 예외는 DispatcherServlet에서 핸들링하는 것이 맞지만, app 모듈에 작성된 예외 페이지를 반환하는 것 보다 Servlet 또는 Servlet Container가 제공하는 기본 예외 페이지를 반환하는 것이 옳지 않을까? 라는 생각이 듭니다.
    이건 에코가 말씀해주신 "존재하지 않는 경로로 요청을 한 경우" 에 해당하는 상황이 될 것 같아요 :)
    app모듈 (개발자가 직접 개발하는 영역) 외에 DispatcherServlet에서 발생하는 오류 (존재하지 않는 경로로의 요청) 는 저도 에코의 리뷰처럼 당연히 DispatcherServlet에서 처리하는 것이 맞다고 생각해요!
    하지만, 이 때도 app모듈에 있는 개발자가 직접 추가한 예외 페이지를 읽어서 반환하는 것은 DispatcherServlet의 책임이 아니라고 생각합니다.

그래서 결론적으로는 DispatcherServlet에서 적절한 핸들러를 찾지 못해 발생하는 예외는 sendError 를 통해 404 상태코드와 함께 기본 에러 페이지를 표시하도록 했고, 그 외의 처리되지 않은 오류들 (ex. app단에서 발생했지만 handle 되지 않은 예외)는 500 상태코드와 함께 기본 에러 페이지를 표시하도록 구성해보았어요.

에코는 어떤 방향으로 고민을 하셨고, 어떻게 해결하셨는지 의견을 들어볼 수 있으면 너무 좋을 것 같아요!
구구절절 긴 글 읽느라 고생많으셨습니다 😅 💦 너무 재밌는 코멘트 감사합니다 😸

Copy link

Choose a reason for hiding this comment

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

ㅎㅎ 좋은 고민거리를 남겨드린 것 같아서 뿌듯하네요! 바론 얘기해주신대로 직접 jsp 페이지를 Dispatcher 서블릿에서 반환하는 것은 의존관계가 맞지 않는 것 같아요! 저 또한 Dispatcher 서블릿에서는 sendError로 상태코드만 전달하도록 하는 형식으로 구현했습니다! 저도 의존관계를 생각하면서 예외를 처리해주려고 하니 어렵더라구요 ㅜ 그래도 깊은 고민과 생각을 하신 것 같아서 다행인것 같습니다!


private void move(final ModelAndView modelAndView, final HttpServletRequest request, final HttpServletResponse response) throws Exception {
final String viewName = modelAndView.getViewName();

if (viewName.startsWith(JspView.REDIRECT_PREFIX)) {
response.sendRedirect(viewName.substring(JspView.REDIRECT_PREFIX.length()));
return;
}

final var requestDispatcher = request.getRequestDispatcher(viewName);
requestDispatcher.forward(request, response);
private void render(final ModelAndView modelAndView, final HttpServletRequest request,
final HttpServletResponse response)
Copy link

Choose a reason for hiding this comment

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

이 부분은 ModelAndView의 역할로 넘겨줘도 되지 않을까 싶습니다! 단순히 ModelAndView에서 Model만 꺼내서 render하니깐요. ModelAndView에서면 내부 필드만 사용하면 되기때문에도 좋을꺼 같아요!

Copy link
Author

Choose a reason for hiding this comment

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

오호 좋습니다!! 불필요한 getter 지우기.. 그것도 말씀해주신대로 내부적으로 view.render 를 호출하도록 하면 되니 아주 쉽게 수정이 되는군용 ㅋ.ㅋ 👍 👍 감사합니다!!

throws Exception {
final View view = modelAndView.getView();
view.render(modelAndView.getModel(), request, response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,4 @@ public Map<String, Object> getModel() {
public View getView() {
return view;
}

public String getViewName() {
return view.getViewName();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.util.Map;

public interface View {
void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
String getViewName();

void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception;
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package webmvc.org.springframework.web.servlet.mvc.tobe;
package webmvc.org.springframework.web.servlet.mvc.adapter;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import webmvc.org.springframework.web.servlet.ModelAndView;
import webmvc.org.springframework.web.servlet.mvc.handlermapping.HandlerExecution;

public class AnnotationHandlerAdapter implements HandlerAdapter {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package webmvc.org.springframework.web.servlet.mvc.tobe;
package webmvc.org.springframework.web.servlet.mvc.adapter;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
package com.techcourse;
package webmvc.org.springframework.web.servlet.mvc.adapter;

import java.util.List;
import webmvc.org.springframework.web.servlet.mvc.tobe.AnnotationHandlerAdapter;
import webmvc.org.springframework.web.servlet.mvc.tobe.ControllerAdapter;
import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerAdapter;

public class HandlerAdapters {

private final List<HandlerAdapter> adapters;

public HandlerAdapters() {
this.adapters = List.of(
new ControllerAdapter(),
new AnnotationHandlerAdapter()
);
}
Expand Down
Loading
Loading