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 구현하기 - 2, 3단계] 케로(김지현) 미션 제출합니다. #578

Merged
merged 14 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 0 additions & 51 deletions app/src/main/java/com/techcourse/DispatcherServlet.java

This file was deleted.

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 All @@ -17,7 +18,7 @@ public class DispatcherServletInitializer implements WebApplicationInitializer {

@Override
public void onStartup(final ServletContext servletContext) {
final var dispatcherServlet = new DispatcherServlet();
final var dispatcherServlet = new DispatcherServlet(getClass().getPackageName());

final var registration = servletContext.addServlet(DEFAULT_SERVLET_NAME, dispatcherServlet);
if (registration == null) {
Expand Down
35 changes: 0 additions & 35 deletions app/src/main/java/com/techcourse/ManualHandlerMapping.java

This file was deleted.

17 changes: 17 additions & 0 deletions app/src/main/java/com/techcourse/controller/IndexController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
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 webmvc.org.springframework.web.servlet.ModelAndView;
import webmvc.org.springframework.web.servlet.view.JspView;

@Controller
public class IndexController {

@RequestMapping(value = "/")
public ModelAndView show(HttpServletRequest req, HttpServletResponse res) {

Choose a reason for hiding this comment

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

다른 코드를 봤을 때는 final 컨벤션으로 사용하시는 것 같아요 이런 부분도 채워주면 어떨까요?

Copy link
Author

Choose a reason for hiding this comment

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

머리로는 알지만... 마음은.... 귀찮아서 그랬습니다 함번만 봐주쇼

return new ModelAndView(new JspView("/index.jsp"));
}
}
35 changes: 25 additions & 10 deletions app/src/main/java/com/techcourse/controller/LoginController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,51 @@

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 login(final HttpServletRequest req, final HttpServletResponse res) {
if (UserSession.isLoggedIn(req.getSession())) {
return "redirect:/index.jsp";
return new ModelAndView(new JspView("redirect:/index"));
}

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

private String login(final HttpServletRequest request, final User user) {
private ModelAndView login(final HttpServletRequest request, final User user) {
if (user.checkPassword(request.getParameter("password"))) {
final var session = request.getSession();
session.setAttribute(UserSession.SESSION_KEY, user);
return "redirect:/index.jsp";
return new ModelAndView(new JspView("redirect:/index"));
}
return "redirect:/401.jsp";
return new ModelAndView(new JspView("redirect:/401"));
}

@RequestMapping(value = "/login/view")
public ModelAndView loginView(final HttpServletRequest req, final HttpServletResponse res) {
return UserSession.getUserFrom(req.getSession())
.map(user -> {
log.info("logged in {}", user.getAccount());
return new ModelAndView(new JspView("redirect:/index"));
})
.orElse(new ModelAndView(new JspView("/login")));
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
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 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")
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:/index"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +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 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 save(HttpServletRequest req, 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 show(HttpServletRequest req, HttpServletResponse res) {
return new ModelAndView(new JspView("/register.jsp"));
}

@RequestMapping(value = "/register/view")
public ModelAndView show2(HttpServletRequest req, HttpServletResponse res) {
return new ModelAndView(new JspView("/register.jsp"));
Comment on lines +24 to +34

Choose a reason for hiding this comment

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

show(), show2() 는 어떤 차이인가요?

Copy link
Author

@jyeost jyeost Sep 26, 2023

Choose a reason for hiding this comment

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

사실 큰 차이는 없습니다 !!!
레거시 코드를 수정해가면서 이미 배포된 api라면 두가지 버전을 다 유지 시키는 게 맞지 않을까? 해서 /register/view uri를 살려봤습니다.

블랙캣상도 안드로이드 팀인만큼 프로젝트에서 api가 바뀔때 어떤 처리를 해주고 계신지 궁금!!! 합니다!!!

Choose a reason for hiding this comment

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

저희는 현재 안드로이드와 배포를 같이 하기 때문에 아직 api 버저닝을 하진 않습니다.
다만 고민을 해보면

  1. api에 버전을 명시해서 api 명세를 작성하고 구버전 api 웹 어플리케이션 서버를 특정 기간동안 살려둔다.
    • nginX 와 같은 웹 서버를 통해서 프록시 처리를 한다.
  2. 케로의 방법 처럼 하나의 웹 어플리케이션에 구버전 api 코드를 유지한다.

케로의 팀은 어떤 방식을 사용하시나요?

Copy link
Author

Choose a reason for hiding this comment

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

저희는 2번 방법인데 헤더에 특정 값을 넣어서 안드로이드 팀이랑 맞췄습니다 ㅎㅅㅎ
아직 버전2를 배포하지 않아서 두 버전 전부 유지 해본 경험은 없슴돠...
한번 배포한 api를 닫는건 굉장히 어렵다고 들었는데,, 버전이 여러개라면 모든 서버를 동시에 유지하기엔 비용이 아까울 것 같아요 ...

}
}

This file was deleted.

31 changes: 31 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,31 @@
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public void doFilter(final ServletRequest request, final ServletResponse respons
log.debug("path : {}", path);
requestDispatcher.forward(request, response);
} else {
chain.doFilter(request, response);
doChain(request, response, chain);
}
}

Expand All @@ -58,6 +58,15 @@ private boolean isResourceUrl(final String url) {
return false;
}

private void doChain(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(request, response);
} catch (Exception e) {
log.error("Exception : {}", e.getMessage(), e);
throw new ServletException(e.getMessage());
}
}

Comment on lines +61 to +69

Choose a reason for hiding this comment

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

어떤 예외가 발생할 수 있고 여기서 예외를 잡아주는 이유가 무엇인가요?

Copy link
Author

Choose a reason for hiding this comment

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

기존 코드의 DispatcherServlet에서 예외를 로깅해주고 Servlet예외로 바꿔서 던져주는 것 때문에 메인 로직이 덜 보인다고 생각되어 필터에서 해주도록 옮겼슴돠!
아마 뷰를 그리는데 실패하거나... 덩덩 수많은 예외가 잇을수 잇겟습니다....

Choose a reason for hiding this comment

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

DispatcherServlet 에서 발생하는 예외를 Filter 에서 잡아준다고 이해하면 될까요...?

Copy link
Author

Choose a reason for hiding this comment

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

네엥~ 지금 하는 작업이 로깅바께 없는것 같아서 필터에서 잡아주는것이 더 낫겟다고 판단했스영

@Override
public void destroy() {
}
Expand Down
46 changes: 46 additions & 0 deletions app/src/test/java/com/techcourse/HandlerMappingRegistryTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.techcourse;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import webmvc.org.springframework.web.servlet.mvc.exception.HandlerMappingNotFoundException;
import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerExecution;
import webmvc.org.springframework.web.servlet.mvc.tobe.HandlerMappingRegistry;
import webmvc.org.springframework.web.servlet.mvc.tobe.Request;

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

@SuppressWarnings("NonAsciiCharacters")
class HandlerMappingRegistryTest {

private final HandlerMappingRegistry handlerMappingRegistry = new HandlerMappingRegistry();

@BeforeEach
void init() {
handlerMappingRegistry.initialize("com.techcourse");
}

@Test
void 어노테이션_핸들러매핑이_제대로_되어지는지_확인한다() {
final Request request = new Request("/register", "POST");
final Object handler = handlerMappingRegistry.getHandler(request);

assertThat(handler).isInstanceOf(HandlerExecution.class);
}

@Test
void Uri_매핑이_중복된다면_어노테이션_매핑이_우선시된다() {
final Request request = new Request("/register", "GET");
final Object handler = handlerMappingRegistry.getHandler(request);

assertThat(handler).isInstanceOf(HandlerExecution.class);
}

@Test
void 매핑되지_않은_요청이라면_예외가_발생한다() {
final Request request = new Request("/registerrrr", "GET");

assertThatThrownBy(() -> handlerMappingRegistry.getHandler(request))
.isExactlyInstanceOf(HandlerMappingNotFoundException.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@
public @interface RequestMapping {
String value() default "";

RequestMethod[] method() default {};
RequestMethod[] method() default {RequestMethod.GET};

Choose a reason for hiding this comment

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

method 의 default 를 GET 으로 두신 이유가 궁금해요!

Copy link
Author

Choose a reason for hiding this comment

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

리팩터링을 하다가 get 메서드를 매번 지정해주기 귀찮아져서
스프링의 @RequestMapping 의 메서드를 지정해주지 않아도 get처리 되는 것이 떠올라서 가장 쉬운 방법으로 구현했습니다.
(실제 스프링에서는 어노테이션에서 디폴트 처리를 해주진 않습니다... )

}
Loading
Loading