-
Notifications
You must be signed in to change notification settings - Fork 305
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[MVC 구현하기 - 2단계] 스플릿-박상현 미션 제출합니다. (#480)
* refactor: mvc모듈에서 새로운 class 로드를 막기 위한 reflection 로직 변경 getHandler() 리턴 객체 타입 변경 * refactor: 기존 유저, UserRepository 변경 * refactor: JspView 에 viewName 필드, render 기능 완성 * refactor: DispatcherServlet 에 AnnotationHandlerMapping 필드 추가, 처리 로직 변경 * refactor: RegisterController 어노테이션 기반으로 변경 * refactor: /register 경로에 대한 처리 제거 * test: Controller 테스트 작성 * feat: HandlerExecution 생성시 클래스, 메서드 검증 로직 추가 * refactor: Exception 검증 범위를 좁히기 위한 메서드 분리 * fix: validateHandlerMethod 메서드 검증 로직 수정 * refactor: HandlerMapping, HandlerAdapter 추상화 * feat: sonarcloud report 설정 추가 * refactor: 쓰지않는 메서드, JsonView render 메서드 처리 변겨 * refactor: 사용되지 않는 메서드, 필드 제거, JsonView render 메서드 처리 변경
- Loading branch information
1 parent
093c61c
commit 25abc90
Showing
29 changed files
with
671 additions
and
131 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,51 +1,63 @@ | ||
package com.techcourse; | ||
|
||
import jakarta.servlet.RequestDispatcher; | ||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.http.HttpServlet; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import java.io.IOException; | ||
import java.util.Collections; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import webmvc.org.springframework.web.servlet.view.JspView; | ||
import webmvc.org.springframework.web.servlet.ModelAndView; | ||
import webmvc.org.springframework.web.servlet.View; | ||
import webmvc.org.springframework.web.servlet.mvc.tobe.exception.HandlerAdapterNotExistException; | ||
import webmvc.org.springframework.web.servlet.mvc.tobe.exception.HandlerNotExistException; | ||
import webmvc.org.springframework.web.servlet.mvc.tobe.handleradapter.AnnotationHandlerAdapter; | ||
import webmvc.org.springframework.web.servlet.mvc.tobe.handleradapter.ControllerHandlerAdapter; | ||
import webmvc.org.springframework.web.servlet.mvc.tobe.handleradapter.HandlerAdapters; | ||
import webmvc.org.springframework.web.servlet.mvc.tobe.handlermapping.AnnotationHandlerMapping; | ||
import webmvc.org.springframework.web.servlet.mvc.tobe.handlermapping.HandlerMappings; | ||
|
||
public class DispatcherServlet extends HttpServlet { | ||
|
||
private static final long serialVersionUID = 1L; | ||
private static final Logger log = LoggerFactory.getLogger(DispatcherServlet.class); | ||
|
||
private ManualHandlerMapping manualHandlerMapping; | ||
|
||
public DispatcherServlet() { | ||
} | ||
private HandlerMappings handlerMappings = new HandlerMappings(); | ||
private HandlerAdapters handlerAdapters = new HandlerAdapters(); | ||
|
||
@Override | ||
public void init() { | ||
manualHandlerMapping = new ManualHandlerMapping(); | ||
manualHandlerMapping.initialize(); | ||
final String packageName = getClass().getPackageName(); | ||
handlerMappings.addHandlerMapping(new ManualHandlerMapping()); | ||
handlerMappings.addHandlerMapping(new AnnotationHandlerMapping(packageName)); | ||
handlerAdapters.addAdapter(new ControllerHandlerAdapter()); | ||
handlerAdapters.addAdapter(new AnnotationHandlerAdapter()); | ||
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, IOException { | ||
final String requestURI = request.getRequestURI(); | ||
log.debug("Method : {}, Request URI : {}", request.getMethod(), requestURI); | ||
|
||
try { | ||
final var controller = manualHandlerMapping.getHandler(requestURI); | ||
final var viewName = controller.execute(request, response); | ||
move(viewName, request, response); | ||
} catch (Throwable e) { | ||
log.error("Exception : {}", e.getMessage(), e); | ||
final Object handler = handlerMappings.getHandler(request); | ||
final ModelAndView modelAndView = handlerAdapters.handle(handler, request, response); | ||
final View view = modelAndView.getView(); | ||
view.render(Collections.emptyMap(), request, response); | ||
} catch (HandlerNotExistException | HandlerAdapterNotExistException exception) { | ||
setNotFound(request, response); | ||
} catch (Exception e) { | ||
throw new ServletException(e.getMessage()); | ||
} | ||
} | ||
|
||
private void move(final String viewName, final HttpServletRequest request, final HttpServletResponse response) throws Exception { | ||
if (viewName.startsWith(JspView.REDIRECT_PREFIX)) { | ||
response.sendRedirect(viewName.substring(JspView.REDIRECT_PREFIX.length())); | ||
return; | ||
} | ||
|
||
final var requestDispatcher = request.getRequestDispatcher(viewName); | ||
private void setNotFound(final HttpServletRequest request, final HttpServletResponse response) | ||
throws ServletException, IOException { | ||
final RequestDispatcher requestDispatcher = request.getRequestDispatcher("404.jsp"); | ||
response.setStatus(404); | ||
requestDispatcher.forward(request, response); | ||
} | ||
} |
30 changes: 21 additions & 9 deletions
30
app/src/main/java/com/techcourse/ManualHandlerMapping.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,47 @@ | ||
package com.techcourse; | ||
|
||
import com.techcourse.controller.*; | ||
import com.techcourse.controller.LoginController; | ||
import com.techcourse.controller.LoginViewController; | ||
import com.techcourse.controller.LogoutController; | ||
import com.techcourse.controller.RegisterViewController; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import webmvc.org.springframework.web.servlet.mvc.asis.Controller; | ||
import webmvc.org.springframework.web.servlet.mvc.asis.ForwardController; | ||
import webmvc.org.springframework.web.servlet.mvc.tobe.handlermapping.HandlerMapping; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
public class ManualHandlerMapping { | ||
public class ManualHandlerMapping implements HandlerMapping { | ||
|
||
private static final Logger log = LoggerFactory.getLogger(ManualHandlerMapping.class); | ||
|
||
private static final Map<String, Controller> controllers = new HashMap<>(); | ||
|
||
@Override | ||
public void initialize() { | ||
controllers.put("/", new ForwardController("/index.jsp")); | ||
controllers.put("/login", new LoginController()); | ||
controllers.put("/login/view", new LoginViewController()); | ||
controllers.put("/logout", new LogoutController()); | ||
controllers.put("/register/view", new RegisterViewController()); | ||
controllers.put("/register", new RegisterController()); | ||
|
||
log.info("Initialized Handler Mapping!"); | ||
controllers.keySet() | ||
.forEach(path -> log.info("Path : {}, Controller : {}", path, controllers.get(path).getClass())); | ||
.forEach(path -> log.info("Path : {}, Controller : {}", path, | ||
controllers.get(path).getClass())); | ||
} | ||
|
||
public Controller getHandler(final String requestURI) { | ||
@Override | ||
public Optional<Object> getHandler(final HttpServletRequest request) { | ||
final String requestURI = request.getRequestURI(); | ||
log.debug("Request Mapping Uri : {}", requestURI); | ||
return controllers.get(requestURI); | ||
|
||
if (controllers.containsKey(requestURI)) { | ||
return Optional.of(controllers.get(requestURI)); | ||
} | ||
return Optional.empty(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 10 additions & 7 deletions
17
app/src/main/java/com/techcourse/repository/InMemoryUserRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,30 @@ | ||
package com.techcourse.repository; | ||
|
||
import com.techcourse.domain.User; | ||
|
||
import java.util.Map; | ||
import java.util.Optional; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
import java.util.concurrent.atomic.AtomicLong; | ||
|
||
public class InMemoryUserRepository { | ||
|
||
private static final Map<String, User> database = new ConcurrentHashMap<>(); | ||
private static final AtomicLong RESERVED_ID = new AtomicLong(0); | ||
|
||
public static final Map<String, User> database = new ConcurrentHashMap<>(); | ||
|
||
static { | ||
final var user = new User(1, "gugu", "password", "[email protected]"); | ||
database.put(user.getAccount(), user); | ||
save(new User("gugu", "password", "[email protected]")); | ||
} | ||
|
||
public static void save(User user) { | ||
public static void save(final User user) { | ||
user.setId(RESERVED_ID.incrementAndGet()); | ||
database.put(user.getAccount(), user); | ||
} | ||
|
||
public static Optional<User> findByAccount(String account) { | ||
public static Optional<User> findByAccount(final String account) { | ||
return Optional.ofNullable(database.get(account)); | ||
} | ||
|
||
private InMemoryUserRepository() {} | ||
private InMemoryUserRepository() { | ||
} | ||
} |
77 changes: 77 additions & 0 deletions
77
app/src/test/java/com/techcourse/controller/LoginControllerTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package com.techcourse.controller; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import org.apache.http.HttpResponse; | ||
import org.apache.http.client.entity.UrlEncodedFormEntity; | ||
import org.apache.http.client.methods.HttpPost; | ||
import org.apache.http.impl.client.CloseableHttpClient; | ||
import org.apache.http.impl.client.HttpClients; | ||
import org.apache.http.message.BasicNameValuePair; | ||
import org.junit.jupiter.api.Assertions; | ||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Test; | ||
|
||
@DisplayName("/login 경로 요청 테스트") | ||
class LoginControllerTest extends UsingTomcatTest { | ||
|
||
@DisplayName("POST로 로그인 정보를 form-data 형식으로 본문에 담아 요청시 유저가 있을 경우, 302 응답 코드와 Location 헤더에 /index.jsp 를 담아 응답한다.") | ||
@Test | ||
void login_success() { | ||
try (CloseableHttpClient httpClient = HttpClients.createDefault()) { | ||
//given | ||
final HttpPost httpPost = new HttpPost(tomcatUrl + "/login"); | ||
|
||
final List<BasicNameValuePair> formData = new ArrayList<>(); | ||
formData.add(new BasicNameValuePair("account", "gugu")); | ||
formData.add(new BasicNameValuePair("password", "password")); | ||
|
||
final UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(formData); | ||
httpPost.setEntity(urlEncodedFormEntity); | ||
|
||
//when | ||
final HttpResponse response = httpClient.execute(httpPost); | ||
|
||
//then | ||
final int actualStatusCode = response.getStatusLine().getStatusCode(); | ||
final String actualLocationHeaderValue = response.getFirstHeader("Location").getValue(); | ||
|
||
assertThat(actualStatusCode).isEqualTo(302); | ||
assertThat(actualLocationHeaderValue).isEqualTo("/index.jsp"); | ||
} catch (Exception e) { | ||
Assertions.fail(); | ||
e.printStackTrace(); | ||
} | ||
} | ||
|
||
@DisplayName("POST로 로그인 정보를 form-data 형식으로 본문에 담아 요청시 유저가 없을 경우, 302 응답 코드와 Location 헤더에 /401.jsp 를 담아 응답한다.") | ||
@Test | ||
void login_fail() { | ||
try (CloseableHttpClient httpClient = HttpClients.createDefault()) { | ||
//given | ||
final HttpPost httpPost = new HttpPost(tomcatUrl + "/login"); | ||
|
||
final List<BasicNameValuePair> formData = new ArrayList<>(); | ||
formData.add(new BasicNameValuePair("account", "gugu")); | ||
formData.add(new BasicNameValuePair("password", "wrong")); | ||
|
||
final UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(formData); | ||
httpPost.setEntity(urlEncodedFormEntity); | ||
|
||
//when | ||
final HttpResponse response = httpClient.execute(httpPost); | ||
|
||
//then | ||
final int actualStatusCode = response.getStatusLine().getStatusCode(); | ||
final String actualLocationHeaderValue = response.getFirstHeader("Location").getValue(); | ||
|
||
assertThat(actualStatusCode).isEqualTo(302); | ||
assertThat(actualLocationHeaderValue).isEqualTo("/401.jsp"); | ||
} catch (Exception e) { | ||
Assertions.fail(); | ||
e.printStackTrace(); | ||
} | ||
} | ||
} |
Oops, something went wrong.