Skip to content

[MVC 구현하기 - 2단계] 리비(이근희) 미션 제출합니다. #779

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

Merged
merged 34 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a63c0b4
refactor: controllers 불변 맵을 반환하도록 수정
Libienz Sep 28, 2024
72d2c84
refactor: 불필요한 ConcurrentMap HashMap으로 변경
Libienz Sep 28, 2024
a7b85bf
refactor: 의미 있는 상수 변수 추출 개선 및 inline 개선
Libienz Sep 28, 2024
b88810f
feat: HandlerMapping 타입 인터페이스 정의 및 AnnotationHandlerMapping 클래스 타입 정의
Libienz Sep 28, 2024
396230b
feat: ManualHandlerMapping이 HandlerMapping 인터페이스를 구현하도록 수정
Libienz Sep 28, 2024
7afb900
feat: HandlerMappingRegistry 구현
Libienz Sep 28, 2024
cfb38ea
feat: DispatcherServlet이 복수의 HandlerMapping을 처리하도록 구현
Libienz Sep 28, 2024
9c2d0ca
feat: HandlerAdapter 타입 선언
Libienz Sep 28, 2024
a45941e
feat: RequestMappingHandlerAdapter 구현
Libienz Sep 28, 2024
c00a55e
feat: ManualHandlerAdapter 구현
Libienz Sep 28, 2024
b872375
feat: HandlerAdapterRegistry 구현
Libienz Sep 28, 2024
e708747
temp:
Libienz Sep 28, 2024
60da14b
feat: HandlerMapping 인터페이스에 initialize 추가
Libienz Sep 28, 2024
24b4318
feat: HandlerAdapterRegistry, HandlerMappingRegistry 기반 DispatcherSer…
Libienz Sep 28, 2024
16f7583
feat: RegisterController 어노테이션 기반으로 구현 변경
Libienz Sep 28, 2024
973cb28
refactor: HandlerMapping정보를 가져오는 방식 추상화
Libienz Sep 28, 2024
89e7045
test: HandlerAdapterRegistryTest 작성
Libienz Sep 28, 2024
92dc9ee
test: ManualHandlerAdapterTest 작성
Libienz Sep 28, 2024
e572cfc
test: RequestMappingHandlerAdapterTest 작성
Libienz Sep 28, 2024
a1fe327
test: HandlerMappingRegistryTest 작성
Libienz Sep 28, 2024
f1705f1
test: ControllerScannerTest 불변 테스트 추가 작성
Libienz Sep 28, 2024
c037eaf
test: 실패하는 테스트 수정
Libienz Sep 28, 2024
0e4f6f3
refactor: 여러 곳에서 사용되는 속성 변수 추출 개선
Libienz Sep 29, 2024
636b21f
refactor: Stream 처리에서 반복적인 get 호출 제거
Libienz Sep 29, 2024
84e7424
feat: DispatcherServlet 예외 처리 로그 추가
Libienz Sep 29, 2024
3ea2fba
test: DisplayName 수정
Libienz Sep 29, 2024
7575bb6
refactor: logging과 rethrow를 함께 하지 않도록 DispatcherServlet 메서드 수정
Libienz Sep 29, 2024
df3008c
refactor: 불필요한 try-catch 제거 개선
Libienz Sep 29, 2024
a9de050
feat: RegisterController 페이지 반환 기능 구현
Libienz Sep 29, 2024
6258284
refactor: 컨트롤러 스캔 대상을 Application 클래스로 제한
Libienz Sep 29, 2024
723f5c4
refactor: modelAndView가 null인 경우의 분기를 처리하도록 DispatcherServlet로직 개선
Libienz Sep 29, 2024
f159ffe
refactor: modelAndView 책임을 모델과 뷰를 묶어놓는 전달 객체로 한정
Libienz Sep 29, 2024
7ed1d12
refactor: 사용하지 않는 RegisterViewController 제거 개선
Libienz Sep 29, 2024
fea477e
refactor: 메서드 파라미터 final 키워드 사용 통일 개선
Libienz Sep 29, 2024
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
42 changes: 25 additions & 17 deletions app/src/main/java/com/techcourse/DispatcherServlet.java
Copy link
Member

Choose a reason for hiding this comment

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

(생각해볼 점)
DispatcherServlet은 우리가 mvc 프레임워크에서 담당하는 것으로 알고 있죠. 왜 지금은 해당 파일을 프레임워크 패키지가 아니라, 웹 개발자가 다루는 패키지에 두었을까요?

Copy link
Author

Choose a reason for hiding this comment

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

결국 웹개발자가 만들고 있는 것은 DispatcherServlet이라고 생각합니다~
저희가 만든 DispatcherServlet이 톰캣의 서블릿 컨테이너에 배포됨으로써 웹 서버가 동작하는 것이죠.

이와 같은 흐름으로 볼 때 DispatcherServlet을 클라이언트가 만드는 것이 위화감 없다는 것이 제 생각이에요!

다만 스프링에서 만든 mvc프레임워크는 저희의 코드로부터 DispatcherServlet을 만드는 과정을 추상화해놓았습니다.
때문에 지금까지 저희가 이와 같은 형태의 코드를 만나보지 못했던 것일테죠!

결론

  • 어쨌든 웹 개발자가 만드는 것은 DispatcherServlet이다!
  • 다만 DispatcherServlet을 만드는 과정은 잘~ 만든 MVC 프레임워크에서 추상화되어 있다.
  • 아직 추상화가 부족한 현재의 미션 코드에서 DispatcherServlet을 만드는 과정이 app 모듈에 있는 것은 어색하지 않다~

제 결론은 이런데 어색한 부분이 있다면 아루가 코멘트 더해주시면 감사하겠습니다~

Copy link
Member

Choose a reason for hiding this comment

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

거의 일치합니다 👍🏻 웹 개발자는 자신이 비즈니스 로직이 아닌 부분까지 구현하고 싶지 않았고, 그래서 DispatcherServlet을 만들어 모든 처리를 위임했다고 볼 수도 있겠네요. 아직은 제어 권한이 웹 개발자에게 있다는 이야기군요! 💯

Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package com.techcourse;

import com.interface21.webmvc.servlet.view.JspView;
import com.interface21.webmvc.servlet.ModelAndView;
import com.interface21.webmvc.servlet.View;
import com.interface21.webmvc.servlet.mvc.tobe.adapter.HandlerAdapter;
import com.interface21.webmvc.servlet.mvc.tobe.adapter.HandlerAdapterRegistry;
import com.interface21.webmvc.servlet.mvc.tobe.mapping.HandlerMappingRegistry;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.HashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -14,32 +17,37 @@ public class DispatcherServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final Logger log = LoggerFactory.getLogger(DispatcherServlet.class);

private ManualHandlerMapping manualHandlerMapping;
private final HandlerMappingRegistry handlerMappingRegistry;
private final HandlerAdapterRegistry handlerAdapterRegistry;

public DispatcherServlet() {

public DispatcherServlet(
HandlerMappingRegistry handlerMappingRegistry,
HandlerAdapterRegistry handlerAdapterRegistry
) {
this.handlerMappingRegistry = handlerMappingRegistry;
this.handlerAdapterRegistry = handlerAdapterRegistry;
}

@Override
public void init() {
manualHandlerMapping = new ManualHandlerMapping();
manualHandlerMapping.initialize();
handlerMappingRegistry.initialize();
}

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

log.debug("Method : {}, Request URI : {}", request.getMethod(), request.getRequestURI());
try {
final var controller = manualHandlerMapping.getHandler(requestURI);
final var viewName = controller.execute(request, response);
final var jspView = new JspView(viewName);

jspView.render(new HashMap<>(), request, response);
} catch (Throwable e) {
log.error("Exception : {}", e.getMessage(), e);
throw new ServletException(e.getMessage());
Object handler = handlerMappingRegistry.getHandler(request);
HandlerAdapter handlerAdapter = handlerAdapterRegistry.getHandlerAdapter(handler);
ModelAndView modelAndView = handlerAdapter.handle(request, response, handler);
if (modelAndView != null) {
View view = modelAndView.getView();
view.render(modelAndView.getModel(), request, response);
}
} catch (Exception e) {
throw new ServletException("요청을 처리하는 것에 실패했습니다", e);
}
}
}
21 changes: 19 additions & 2 deletions app/src/main/java/com/techcourse/DispatcherServletInitializer.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package com.techcourse;

import com.interface21.web.WebApplicationInitializer;
import com.interface21.webmvc.servlet.mvc.tobe.adapter.HandlerAdapterRegistry;
import com.interface21.webmvc.servlet.mvc.tobe.adapter.ManualHandlerAdapter;
import com.interface21.webmvc.servlet.mvc.tobe.adapter.RequestMappingHandlerAdapter;
import com.interface21.webmvc.servlet.mvc.tobe.mapping.AnnotationHandlerMapping;
import com.interface21.webmvc.servlet.mvc.tobe.mapping.HandlerMappingRegistry;
import jakarta.servlet.ServletContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.interface21.web.WebApplicationInitializer;

/**
* Base class for {@link WebApplicationInitializer}
Expand All @@ -17,7 +22,7 @@ public class DispatcherServletInitializer implements WebApplicationInitializer {

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

final var registration = servletContext.addServlet(DEFAULT_SERVLET_NAME, dispatcherServlet);
if (registration == null) {
Expand All @@ -30,4 +35,16 @@ public void onStartup(final ServletContext servletContext) {

log.info("Start AppWebApplication Initializer");
}

private DispatcherServlet createDispatcherServlet() {
HandlerMappingRegistry handlerMappingRegistry = new HandlerMappingRegistry();
handlerMappingRegistry.addHandlerMapping(new ManualHandlerMapping());
handlerMappingRegistry.addHandlerMapping(new AnnotationHandlerMapping(Application.class));

HandlerAdapterRegistry handlerAdapterRegistry = new HandlerAdapterRegistry();
handlerAdapterRegistry.addHandlerAdapter(new RequestMappingHandlerAdapter());
handlerAdapterRegistry.addHandlerAdapter(new ManualHandlerAdapter());

return new DispatcherServlet(handlerMappingRegistry, handlerAdapterRegistry);
}
}
20 changes: 12 additions & 8 deletions app/src/main/java/com/techcourse/ManualHandlerMapping.java
Original file line number Diff line number Diff line change
@@ -1,34 +1,38 @@
package com.techcourse;

import com.techcourse.controller.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.interface21.webmvc.servlet.mvc.asis.Controller;
import com.interface21.webmvc.servlet.mvc.asis.ForwardController;

import com.interface21.webmvc.servlet.mvc.tobe.mapping.HandlerMapping;
import com.techcourse.controller.LoginController;
import com.techcourse.controller.LoginViewController;
import com.techcourse.controller.LogoutController;
import jakarta.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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()));
}

public Controller getHandler(final String requestURI) {
@Override
public Controller getHandler(final HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.debug("Request Mapping Uri : {}", requestURI);
return controllers.get(requestURI);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
package com.techcourse.controller;

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

public class RegisterController implements Controller {
@com.interface21.context.stereotype.Controller
public class RegisterController {

@Override
public String execute(final HttpServletRequest req, final HttpServletResponse res) throws Exception {
private static final String REGISTER_JSP = "/register.jsp";

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

@RequestMapping(value = "/register", method = RequestMethod.POST)
public ModelAndView registerUser(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";
View view = new JspView("redirect:/index.jsp");
return new ModelAndView(view);
Comment on lines +32 to +33
Copy link
Member

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.

아루처럼 한스텝씩 개발자의 편의사항을 위한 프레임워크를 고려하다 보면 결국 스프링의 구현에 가까워진다는 것이 너무 신기해요..!

별생각 없이 넘어갔던 부분인데 좋은 인사이트 주셔서 감사해요~

Copy link
Member

@donghoony donghoony Sep 29, 2024

Choose a reason for hiding this comment

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

제어의 역전의 대표적인 예시로 프레임워크를 말할 수 있다면, 미션을 잘 해나간 것이라고 생각해요 🚀

}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,28 @@

import com.interface21.context.stereotype.Controller;
import com.interface21.webmvc.servlet.exception.ControllerScanException;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.reflections.Reflections;

public class ControllerScanner {

private static final Class<? extends Annotation> CONTROLLER_ANNOTATION = Controller.class;

private final Object[] basePackage;
private final Map<Class<?>, Object> controllers = new ConcurrentHashMap();
private final Map<Class<?>, Object> controllers = new HashMap<>();
Copy link
Member

Choose a reason for hiding this comment

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

(스프링에서 병렬적으로 빈을 등록하면 어떨까? 에 대한 이슈도 10년 전에 올라왔었습니다 ㅎㅎ)

Copy link
Author

Choose a reason for hiding this comment

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

많은 빈을 운용하는 경우 Spring 애플리케이션의 시작 시간이 점점 느려지고 있어 이슈를 열었던 사람이 있군요!
이슈 생성자가 병렬 처리를 통해 Spring의 컴포넌트 스캔과 빈 초기화 작업을 개선할 필요가 있다고 제안한 것 확인했습니다.

현재 스프링에서 병렬적으로 빈을 등록하지는 않고 백그라운드 작업등을 제공하고 있는 것으로 확인했어요~
좋은 코멘트 감사합니다 🙇🏻‍♂️


public ControllerScanner(Object... basePackage) {
this.basePackage = basePackage;
scanControllers();
scanController();
}

private void scanControllers() {
private void scanController() {
Reflections reflections = new Reflections(basePackage);
Set<Class<?>> annotatedControllerTypes = reflections.getTypesAnnotatedWith(Controller.class);
annotatedControllerTypes.forEach(this::addController);
reflections.getTypesAnnotatedWith(CONTROLLER_ANNOTATION)
Copy link
Member

Choose a reason for hiding this comment

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

Controller.class를 상수로 뽑은 이유는 무엇인가요? 한 곳에서밖에 사용하지 않고, 매직 넘버도 아니지 않은가요?

Copy link
Author

Choose a reason for hiding this comment

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

어떤 대상을 스캔하는지 클래스 상단에서 보이면 좋겠다고 생각했어요..!
이런 측면에서는 매직넘버라고 저는 생각했습니다~

.forEach(this::addController);
}

private void addController(Class<?> annotatedControllerType) {
Expand All @@ -33,6 +36,6 @@ private void addController(Class<?> annotatedControllerType) {
}

public Map<Class<?>, Object> getControllers() {
return controllers;
return Collections.unmodifiableMap(controllers);
Copy link
Member

Choose a reason for hiding this comment

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

👍🏻

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.interface21.webmvc.servlet.mvc.tobe.adapter;

import com.interface21.webmvc.servlet.ModelAndView;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

public interface HandlerAdapter {

boolean supports(Object handler);

ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.interface21.webmvc.servlet.mvc.tobe.adapter;

import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;

public class HandlerAdapterRegistry {

private final List<HandlerAdapter> handlerAdapters = new ArrayList<>();

public HandlerAdapterRegistry() {
}

public void addHandlerAdapter(HandlerAdapter handlerAdapter) {
handlerAdapters.add(handlerAdapter);
}

public HandlerAdapter getHandlerAdapter(Object handler) {
return handlerAdapters.stream()
.filter(handlerAdapter -> handlerAdapter.supports(handler))
.findFirst()
.orElseThrow(() -> new NoSuchElementException("핸들러를 처리할 수 있는 Adapter를 찾지 못했습니다"));
Copy link
Member

Choose a reason for hiding this comment

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

Exception은 언제 일어나야 할까요? 핸들러어댑터를 등록하고 핸들러를 찾아오는 객체와 같은 일급 컬렉션이 존재하지만, 이 친구가 핸들러를 못 찾아 직접 예외를 터뜨리는 게 나을지, 그 책임을 사용하는 객체에게 위임하는 것이 나을지도 생각해볼 만한 점이겠네요!

Copy link
Author

Choose a reason for hiding this comment

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

재밌는 관점이네요~

저는 핸들러를 찾는 쪽에서 직접 예외를 터뜨리는 게 낫다고 생각합니다.
자신이 보유하고 있는 핸들러를 아는 객체가 이런거 없어요~라고 판단하는 편이 자연스럽기 때문이에요.

그걸 호출하는 쪽에서 null을 잡는다면 객체의 책임이 분산된 결과 아닐까요!?

아루의 구현도 확인했는데요~ 아루는 저와 다른 생각을 하고 계신 것 같아요..! (추가적으로 스프링도 아루와 비슷한 생각을 한 것 같아요!)
아루의 생각도 들려주실 수 있을까요??

Copy link
Member

@donghoony donghoony Sep 29, 2024

Choose a reason for hiding this comment

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

ㅎㅎㅎ 저는 XXXRegistry를 하나의 컬렉션으로 보았습니다. 실제 자바 Collection들의 명세를 살펴보면 내부 값을 가져오는 메서드가 @Nullable한 것을 확인할 수 있어요. HandlerAdapter를 저장하고, 반환하는 그 자체가 목표를 완성한 것이라고 보고, 더 이상의 책임을 주지 않아도 좋지 않을까? 라는 생각을 하게 되었어요.

그걸 호출하는 쪽에서 null을 잡는다면 객체의 책임이 분산된 결과 아닐까요!?

저는 개인적으로 객체 스스로간의 응집도를 조금 내려두고, DispatcherServlet을 조금 절차적으로 가져가면서 위에서 아래로 흐름을 읽기 쉽도록 하는 데에 의미를 두었어요. null을 받는다면 DispatcherServlet에서 적절하게 처리하고 (핸들러가 없는 경우 404를 담아준다는 등) 뷰도 직접 받아서 렌더하는 방식으로요. 객체지향과는 조금 멀 수 있지만, 코드를 유지보수한다는 점에서는 DispatcherServlet만을 확인하면 알 수 있기 때문에 괜찮지 않을까? 라고 보았어요.

Copy link
Author

Choose a reason for hiding this comment

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

아루가 말씀하신 것처럼 하나의 메서드를 읽기 위해 여러 클래스의 명세를 참고해야 한다면 유지보수 비용이 있을 수 있겠습니다..
아루는 하나의 메서드에서 위에서 아래로 이어지는 흐름을 중요시 하신 것 같네요!

다만 아직까지 저는 null 여부에 따른 분기를 선호하진 않아서 현행 유지하고자 합니다.
자바의 컬렉션과 스프링 진영에 맞서는 느낌이라 영 찝찝하긴 한데요..! 우리 모두가 하나의 정답을 따라야 하는 것은 아니니까요~

}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.interface21.webmvc.servlet.mvc.tobe.adapter;

import com.interface21.webmvc.servlet.ModelAndView;
import com.interface21.webmvc.servlet.mvc.asis.Controller;
import com.interface21.webmvc.servlet.view.JspView;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

public class ManualHandlerAdapter implements HandlerAdapter {

@Override
public boolean supports(Object handler) {
return handler instanceof Controller;
}

@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String viewName = ((Controller) handler).execute(request, response);
JspView jspView = new JspView(viewName);
return new ModelAndView(jspView);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.interface21.webmvc.servlet.mvc.tobe.adapter;

import com.interface21.webmvc.servlet.ModelAndView;
import com.interface21.webmvc.servlet.mvc.tobe.HandlerExecution;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

public class RequestMappingHandlerAdapter implements HandlerAdapter {

@Override
public boolean supports(Object handler) {
return handler instanceof HandlerExecution;
}

@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
HandlerExecution handlerExecution = (HandlerExecution) handler;
return handlerExecution.handle(request, response);
}
}
Loading