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단계] 메리(최승원) 미션 제출합니다. #518

Merged
merged 38 commits into from
Sep 24, 2023

Conversation

swonny
Copy link

@swonny swonny commented Sep 20, 2023

안녕하세요 에단!
메리입니다.
남겨주신 리뷰 반영하고 2단계 구현하고 리뷰 요청합니다!
리뷰 잘 부탁드리겠습니다.

감사합니다!!

kang-hyungu and others added 30 commits September 11, 2023 15:29
* test: 파일 테스트 통과

* test: IOStream 학습 테스트

* feat: index.html 응답하기 구현

* feat: CSS 지원하기 구현

* feat: login.html 응답 구현

* refactor: 로그인 쿼리 스트링 구분

* feat: 로그인 후 리다이렉트 기능 구현

* refactor: 로그인 컨트롤러, view resolver 생성

* feat: register html 응답 구현
* feat: index.html 응답하기 구현

* feat: CSS 지원하기 구현

* feat: login.html 응답 구현

* refactor: 로그인 쿼리 스트링 구분

* feat: 로그인 후 리다이렉트 기능 구현

* refactor: 로그인 컨트롤러, view resolver 생성

* feat: register html 응답 구현

* fix: body 전송 구현

* refactor: HttpRequest 객체 생성 및 파싱 기능 분리

* feat: 유저 등록 기능 구현

* feat: 로그인 메소드 POST로 변경

* refactor: 회원가입 성공 시 redirect로 응답하도록 변경

* feat: 쿠키 생성 및 로그인 시 쿠키에 세션 저장

* feat: session을 통해 로그인 페이지 redirect하는 기능 구현

* refactor: 패키지 이동

* refactor: 불필요한 필드 삭제

* refactor: 로그 포맷 변경

* refactor: 컨트롤러 핸들러 생성

* refactor: 불필요한 enum 필드 삭제 및 메소드 분리

* refactor: 예외 처리 분기문 추가

* refactor: 파일 경로 조회 메서드 분리

* refactor: GET, POST 요청이 아닌 경우 예외 반환

* fix: 잘못된 유저 정보 입력 시 401 리다이렉트 되지 않는 문제 해결

* refactor: session을 포함한 redirect 응답값 생성 메소드 분리

* refactor: 확장자 검증 메서드 Extension으로 이동

* refactor: 메서드 순서 변경

* refactor: cookie 존재 여부 확인 분기문 getOrDefault로 변경

* refactor: FileController 정팩메 ControllerHandler에 등록

* refactor: HttpRequest에 해당하는 request line, headers, body 클래스 생성

* refactor: controller 인스턴스 저장 방식 변경

* fix: CRLF 읽는 불필요한 로직 삭제

* style: 메서드 순서 재정렬 및 공백 제거 및 네이밍 변경

* refactor: HttpResponse 클래스 생성 및 response 생성 로직 분리

* refactor: 불필요한 메서드, 필드 삭제

* fix: 실패하는 테스트 수정

* fix: 잘못된 타입 수정

* refactor: Controller 인터페이스로 변경
* feat: index.html 응답하기 구현

* feat: CSS 지원하기 구현

* feat: login.html 응답 구현

* refactor: 로그인 쿼리 스트링 구분

* feat: 로그인 후 리다이렉트 기능 구현

* refactor: 로그인 컨트롤러, view resolver 생성

* feat: register html 응답 구현

* fix: body 전송 구현

* refactor: HttpRequest 객체 생성 및 파싱 기능 분리

* feat: 유저 등록 기능 구현

* feat: 로그인 메소드 POST로 변경

* refactor: 회원가입 성공 시 redirect로 응답하도록 변경

* feat: 쿠키 생성 및 로그인 시 쿠키에 세션 저장

* feat: session을 통해 로그인 페이지 redirect하는 기능 구현

* refactor: 패키지 이동

* refactor: 불필요한 필드 삭제

* refactor: 로그 포맷 변경

* refactor: 컨트롤러 핸들러 생성

* refactor: 불필요한 enum 필드 삭제 및 메소드 분리

* refactor: 예외 처리 분기문 추가

* refactor: 파일 경로 조회 메서드 분리

* refactor: GET, POST 요청이 아닌 경우 예외 반환

* fix: 잘못된 유저 정보 입력 시 401 리다이렉트 되지 않는 문제 해결

* refactor: session을 포함한 redirect 응답값 생성 메소드 분리

* refactor: 확장자 검증 메서드 Extension으로 이동

* refactor: 메서드 순서 변경

* refactor: cookie 존재 여부 확인 분기문 getOrDefault로 변경

* refactor: FileController 정팩메 ControllerHandler에 등록

* refactor: HttpRequest에 해당하는 request line, headers, body 클래스 생성

* refactor: controller 인스턴스 저장 방식 변경

* fix: CRLF 읽는 불필요한 로직 삭제

* style: 메서드 순서 재정렬 및 공백 제거 및 네이밍 변경

* refactor: HttpResponse 클래스 생성 및 response 생성 로직 분리

* refactor: 불필요한 메서드, 필드 삭제

* fix: 실패하는 테스트 수정

* fix: 잘못된 타입 수정

* refactor: Controller 인터페이스로 변경

* refactor: Controller 인터페이스 파라미터 변경, AbstractController 생성

* feat: ThreadPool 적용 및 동시성 컬렉션 사용

* style: 불필요한 개행 제거

* refactor: 상속한 클래스에서 재정의하는 메서드 abstract로 변경

* fix: cookie가 아닌 SESSION이 존재할 때 redirect 하도록 수정

* test: Response 문자열 순서가 다른 문제 해결을 위해 테스트 수정

* refactor: HttpResponse 필드 raw 타입에서 객체로 변경

* fix: 누락된 ResponseHeaders 커밋 추가
Copy link

@cookienc cookienc left a comment

Choose a reason for hiding this comment

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

안녕하세요 메리~ 코드를 깔끔하게 짜주셔서 이해하기 편했습니다.
이번에는 같이 얘기해보고 싶은게 있어서 Request Changes 남깁니다.
확인해주시고 다시 요청주세요~ 주말 잘 보내세요🙇‍♂️🙇

manualHandlerMapping = new ManualHandlerMapping();
manualHandlerMapping.initialize();
handlerMappers.add(new ManualHandlerMapping());
handlerMappers.add(new AnnotationHandlerMapping("com.techcourse.controller"));

Choose a reason for hiding this comment

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

이건 사소한 부분인데요. 스프링에서 @controller가 Controller 패키지에 없어도 동작하던데, 패키지 경로를 자세히 쓰면 스프링처럼 동작하지 않을것 같아요!

Copy link
Author

Choose a reason for hiding this comment

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

반영하여 수정했습니다!

.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 handler입니다."));
}

private Method findControllerExecution(final Class<?> controllerType) {

Choose a reason for hiding this comment

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

findControllerExecution 인데 HandlerExecution이 아니라 Method를 반환하고 있네요

Copy link
Author

Choose a reason for hiding this comment

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

이 부분도 메서드 네이밍과 반환값이 일치하도록 변경했습니다.

.filter(DispatcherServlet::isExecuteMethod)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("컨트롤러 실행 메서드가 존재하지 않습니다."));
}

Choose a reason for hiding this comment

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

controller가 object 클래스여서 HandlerExecution 과 Controller가 둘 다 반환될 수 있어 메서드를 직접 찾아서 반환하는걸로 이해했습니다. 이렇게 되면 요청이 들어올때마다 리플랙션을 사용해서 Method를 반환할것 같아요.
controllerType이 둘 중 하나니까 이 부분만 확실하면 타입 캐스팅을 통해서 바로 메서드 호출 할 수 있을 것 같은데 이 부분에 대해서는 어댑터 패턴을 학습하시면 해결하실 수 있을것 같습니다👍

Copy link
Author

Choose a reason for hiding this comment

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

감사합니다 에단!
남겨주신 리뷰 보고 힌트를 보고 어댑터 패턴을 적용해보았는데 확실히 구조가 훨씬 깔끔해진 것 같습니다!!
기존에 disapatcherServlet에서 리플렉션으로 실행 메서드를 찾는 로직이 HandlerMapping 구현체에 따라 찾는 로직으로 변경되면서 코드도 훨씬 깔끔해지고 dispatcherServlet의 책임도 적절히 분리가 된 것 같아요!🙇🏻‍♀️

이 부분도 반영하여 리팩토링 완료헀습니다.

@@ -21,15 +26,15 @@ public void initialize() {
controllers.put("/login/view", new LoginViewController());
controllers.put("/logout", new LogoutController());
controllers.put("/register/view", new RegisterViewController());
controllers.put("/register", new RegisterController());
// controllers.put("/register", new RegisterController());

Choose a reason for hiding this comment

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

주석이 들어갔어요~

Comment on lines 36 to 38
public Controller getHandler(final HttpServletRequest request) {
log.debug("Request Mapping Uri : {}", request);
return controllers.getOrDefault(request.getRequestURI(), null);

Choose a reason for hiding this comment

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

제가 저번 리뷰때 언급해서 바꾸신것 같은데 제가 답글을 너무 늦게 달았나보네용
Map에서 없는 key로 조회를 하면 예외가 아니라 null을 반환하더라구요~ 그래서 getOrDefalut는 따로 안써도 될 것 같습니다
다음 부터는 빨리 답변하도록 노력할게요..🙇‍♂️

Copy link
Author

Choose a reason for hiding this comment

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

그렇군요..!
저는 getOrDefault()를 말씀해주셔서
해당 메서드에서 null을 반환했을 때의 null 처리에 대해 조금 고민해보게 되었습니다!
말씀하신 것처럼 getOrDefault()를 하지 않아도 null을 반환하는 부분이 걸려서 리팩토링 과정에서 Optional.ofNullable()을 반환하도록 변경했습니다.

그러다가 HandlerMapping의 일급컬렉션인 HandlerMappingRegistry를 생성하면서 한 번 더 변경이 있었습니다.
HandlerMappingRegistry에서 HandlerMapping을 찾을 때 HandlerMapping 객체에게 요청에 매핑되는 HandlerMapping이 존재하는지 묻고, 존재한다면 해당 핸들러를 반환하는 검증 로직이 추가되어 getHandler()를 호출하는 순간에는 null이 반환될 일이 없어졌습니다. 따라서 반환값이었던 Optional을 Object로 다시 한번 변경해주었습니다!

남겨주신 리뷰 덕분에 놓치고 있던 부분에 대해 한 번 더 생각해 볼 수 있어 좋았어요 감사합니다!!

public Object getHandler(final HttpServletRequest request) {
return handlerExecutions.get(
public HandlerExecution getHandler(final HttpServletRequest request) {
return handlerExecutions.getOrDefault(

Choose a reason for hiding this comment

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

여기도 위와 마찬가지입니다!

Comment on lines 37 to 39
Arrays.stream(controller.getMethods())
.filter(method -> method.isAnnotationPresent(RequestMapping.class))
.forEach(methods::add);

Choose a reason for hiding this comment

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

저는 foreach를 사용해서 외부에 영향을 끼치게 하는 편은 지양하는 편입니다. 왜냐하면 스트림은 함수형 프로그래밍을 위해 나왔는데 이를 위반한다고 생각해요. 주변 크루들을 보면 그럼에도 사용하시는 분들도 있고 나름의 이유도 존재하는데요. 메리는 어떤 입장이신가요?

Copy link
Author

Choose a reason for hiding this comment

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

저도 forEach의 사용이 외부로 영향을 미치면 안된다는 것에 동의합니다.
그리고 해당 로직이 프로그램이 복잡해지면 관리가 어려워질 것 같다고 생각했습니다!
따라서 초반에는 해당 로직을 메서드로 분리하여 리스트를 반환하는 메서드로 리팩토링 했다가,
현재는 해당 로직이 HandlerMappingRegistry로 이동한 상태입니다!

Copy link

@cookienc cookienc left a comment

Choose a reason for hiding this comment

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

메리~ 고생하셨습니다! 다음 단계로 넘어가시죠!

Comment on lines +50 to +53
final HandlerMapping handlerMapping = handlerMappingRegistry.getHandlerMapping(request);
final HandlerAdapter handlerAdapter = handlerAdapterRegistry.getHandlerAdapter(request, handlerMapping);
final ModelAndView view = handlerAdapter.handle(handlerMapping, request, response);
move(view, request, response);

Choose a reason for hiding this comment

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

Dispatcher 서블릿이 역할이 많이 줄었네요👍👍

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class HandlerAdapterRegistryTest {

Choose a reason for hiding this comment

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

오 테스트까지.. 대단하시네요!

final List<Method> methods,
final Class<?> controller
) {
final HashMap<HandlerKey, HandlerExecution> handlerExecutions = new HashMap<>();

Choose a reason for hiding this comment

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

Map으로 받을 수 있을거 같아요!

@cookienc cookienc merged commit 0fc2310 into woowacourse:swonny Sep 24, 2023
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants