-
SpringMVC (5) - 스프링 MVC 구조 이해Web/Spring 2023. 10. 2. 16:43
- 직접 만든 프레임 워크와 스프링 MVC는 큰 차이가 없다
- 이름만 조금 다를 뿐 구조는 매우 비슷함.
> DispatcherServlet 구조 살펴보기
-org,springframwork.web.servlet.DispatcherServlet
프론트 컨트롤러 패턴으로 구현됨
스프링 MVC의 핵심
- dispatcherservlet도 httpservlet을 상속받아서 사용하고, 서블릿으로 동작한다.
- 스프링 부트는 dispatcherservlet을 서블릿으로 자동등록하며, 모든 경로에 대해서 맵핑한다.
* 더 자세한 경로가 우선순위 높음 따라서 기존에 등록한 서블릿도 함께 동작함
> 요청 흐름
- 서블릿이 호출되면, service()가 호출됨
- frameworkServlet.service()를 시작으로 여러 메서드 호출 -> dispacherServlet에 doDispatch()호출됨
- 이 메서드가 dispatcher의 핵심코드이다.
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { // 1. 핸들러 조회 mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // 2. 핸들러 어댑터 조회 - 핸들러를 처리할 수 있는 어댑터 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // 3. 핸들러 어댑터 실행 -> 4. 핸들러 어댑터를 통해 핸들러 실행 -> 5. ModelAndView 반환 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception { // 뷰 렌더링 호출 render(mv, request, response); } protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { View view; String viewName = mv.getViewName(); // 6. 뷰 리졸버를 통해서 뷰 찾기, 7. View 반환 view = resolveViewName(viewName, mv.getModelInternal(), locale, request); // 8. 뷰 렌더링 view.render(mv.getModelInternal(), request, response); }
핸들러 조회 -> 어댑터 조회 -> 어댑터 실행 -> 핸들러 실행 -> ModelAndView반환 -> viewResolver호출 -> view 객페 반환 -> 뷰 랜더링
- 인터페이스 : 스프링 MVC의 강점은 DispatcherServlet의 코드 변경없이 기능 확장 가능 아래 인터페이스만 구현해서 Dispatcher에 등록하면 컨트롤러 만들 수 있다.
주요 인터페이스 목록
핸들러 매핑:org.springframework.web.servlet.HandlerMapping
핸들러 어댑터: org.springframework.web.servlet.HandlerAdapter
뷰 리졸버: org.springframework.web.servlet.ViewResolver
뷰: org.springframework.web.servlet.View
2. 핸들러 매핑과 핸들러 어댑터
- 핸들러 매핑과 어댑터가 어떤 것들이 사용되는지 알아봅시다.
- 과거에 주로 사용했던, 간단한 컨트롤러로 핸들러 매핑과 어댑터 알아보자
2.1 Controller 인터페이스
org.springframwork.web.servlet.mvc.Controller
public interface Controller { ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception; }
- OldController
@Component("/springmvc/old-controller") public class OldController implements Controller{ @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { System.out.println("OldController.handleRequest"); return null; } }
- @Conponent : 이 컨트롤러는 /springmvc/old-controller라는 이름으로 스프링 빈 등록
- 빈의 이름으로 url 매핑함
이 컨트롤러가 호출되려면 2가지가 필요함
- HandlerMapping, HandlerAdapter
스프링은 oldController형식의 컨트롤러를 처리할 수 있는 핸들러맵핑방식과 어댑터를 구현해 둠
- 직접 맵핑정보를 등록하지 않아도 스프링 빈으로 등록시, 자동으로 등록됨
- 어댑터 또한 직접 구현하지 않아도, Controller인터페이스 구현한 핸들러를 처리할 수 있는 어댑터가 이미 있음 이걸로 처리
* 핸들러 맵핑은 어노테이션 @RequestMapping에서 한번 찾고 없으면 -> 빈 이름으로 핸들러 찾음
핸들러 어댑터는 -> handlerAdapter의 supports()호출하는데, 2번에서 걸림
핸들러 어댑터 조회에 성공했으면, 핸들러 어댑터 실행하면서 핸들러(OldController) 넘김
결론적으로 Controller 사용하면
BeanNameUrlHandlerMapping과 SimpleControllerHandlerAdapter가 작동해서 처리해준다.
따라서 Contoller 구현 + @Conpnent만 잘 지키자
2.2 HtpRequestHandler
- 서블릿과 가장 유사한 형태의 핸들러이다.
public interface HttpRequestHandler { void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException; }
@Component("/springmvc/request-handler") public class MyHttpRequestHandler implements HttpRequestHandler { @Override public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("MyHttpRequestHandler.handleRequest"); } }
HttpRequestHandler를 구현한 컨트롤러 (핸들러)를 등록하면,
BeanNameUrlHandlerMapping과 HttpRequestHandlerAdapter가 작동하여 핸들러를 처리함
2.3 @RequestMapping
- 가장 우선순위 높은 RequestMappingHandlerMapping과 Adapter을 통해 실행됨
- 거의 위 어노테이션 기반으로 컨트롤러와 메서드들을 등록함
3. 뷰 리졸버
- OldController를 통해 뷰를 조회하도록 바꿔보자
@Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { System.out.println("OldController.handleRequest"); return new ModelAndView("new-form"); }
- 컨트롤러는 정성 호출 -> 랜더는 실패 -> 추가 설정 필요
application.properties에 다음 코드 추가
spring.mvc.view.prefix=/WEB-INF/views/ spring.mvc.view.suffix=.jsp
> 뷰 리졸버 - InternalResourceViewResolver
스프링 부트가 자동 등록하는 뷰 리졸버임
//ServletApplcation 내에서 아래와 같은 뷰리졸버 등록한다 -> 뷰리졸버가 따로 등록된게 없으면 이거 씀 @Bean ViewResolver internalResourceViewResolver(){ return new InternalResourceViewResolver("prefix","suffix") }
스프링 부트가 얘를 호출해줌
3.1 스프링 부트가 자동으로 등록하는 뷰 리졸버
1. 핸들러 어댑터를 통해 new-form이라는 논리 뷰 이름 획득
2. new-form이름을 빈으로 등록된 뷰객체 가 있는지 찾기 -> 없으면 다음 뷰 리졸버 호출(이미 빈에 자동 등록된)
3. InternalResourceViewResolver가 InternalResourceView객체를 반환
4. InternalResourceView는 JSP처럼 포워드 forwad() 호출해서 처리할 수 있는 경우 사용 (MyView랑 거의 같은 역할)
4. view.render() -> 포워딩해서 jsp 실행 (이때 모델에 담긴 값 request로 옮김)
* 만약 jstl라이브러리가 있으면, JstlView가 반환됨 (InternalResourceViewResolver를 통해)
* View인터페이스를 구현한 View 객체를 빈으로 등록해두고, 컨트롤러가 해당 view객체 빈 이름을 return하면, 뷰 리졸버가 해당 View객체 리턴함
4. 스프링 MVC 시작하기
- 스프링이 제공하는 컨트롤러는 어노테이션 기반으로 동작함, 매우 유연하고 실용적
- @RequestMapping : 컨트롤러는 이 어노테이션을 사용함
- @Controller : 어노테이션 기반 컨트롤러로 인식 -> @RequestMapping을 처리할 핸들러 맵핑 맵과, 어댑터 동작하도록 알림
> 클래스 레벌에 @RequestMapping, @Controller를 매핑 정보로 인식한다.
* @Contorller를 통해 어노테이션 기반 매핑맵과, 어댑터 사용해야함 알림
> 해당 컨트롤러 처리할 어댑터를 통해 내부적으로 다시 request에서 url 뽑고
> 컨트롤러내에 url매핑된 메서드 실행
*이전까지 구조를 보면서 프론트 컨트롤러 어댑터 부분들 알아봤다면,
이제 스프링MVC에서 제공하는 기능을 사용하는 방법임 (컨트롤러 구성방법)
어노테이션 기반 컨트롤러 인식하고 처리하는 맵핑맵과 어댑터 있기 때문에
@Controller와 @RequestMapping을 통해 보다 자유롭게
컨트롤러 만들 수 있음 (Controller 구현x, 메서드 명 및 매개변수 자유)
어댑터가 내부적으로 매핑정보에 따라 메서드 호출 + 매개변수 주입 해준다.
4.1 회원 등록 폼
@Controller public class SpringMemberFormControllerV1 { @RequestMapping("/springmvc/v1/members/new-form") public ModelAndView process() { return new ModelAndView("new-form"); } }
@Controller : 스프링 빈으로 등록
어노테이션 기반 컨트롤러(핸들러)임을 인식
@RequestMapping: 요청 정보 매핑
해당 url이 호출되면, 이 메서드가 호출됨
ModelAndView : 모델과 뷰 정보를 담아서 변환.
RequestMappingHandlerMapping은 스프링 빈 중에서 @RequestMapping 또는 @Controller가 클래스 레벨에 붙어있는 경우 매핑 정보로 인식한다.
따라서 아래 코드도 정확하게 동일하게 작동
@Component //컴포넌트 스캔을 통해 스프링 빈으로 등록 @RequestMapping public class SpringMemberFormControllerV1 { @RequestMapping("/springmvc/v1/members/new-form") public ModelAndView process() { return new ModelAndView("new-form"); } }
// ServletApplication // componentscan없이 직접 등록 @Bean SpringMemberFormControllerV1 springMemberFormControllerV1() { return new SpringMemberFormControllerV1(); } @RequestMapping public class SpringMemberFormControllerV1 { @RequestMapping("/springmvc/v1/members/new-form") public ModelAndView process() { return new ModelAndView("new-form"); } }
* 스프링 3.0부터는 이거 안됨 @Controller 필요
4.2 회원 저장 및 목록
@Controller public class SpringMemberSaveControllerV1 { private MemberRepository memberRepository = MemberRepository.getInstance(); @RequestMapping("/springmvc/v1/members/save") public ModelAndView process(HttpServletRequest request, HttpServletResponse response) { String username = request.getParameter("username"); int age = Integer.parseInt(request.getParameter("age")); Member member = new Member(username, age); System.out.println("member = " + member); memberRepository.save(member); ModelAndView mv = new ModelAndView("save-result"); mv.addObject("member", member); return mv; } / 회원 목록 @Controller public class SpringMemberListControllerV1 { private MemberRepository memberRepository = MemberRepository.getInstance(); @RequestMapping("/springmvc/v1/members") public ModelAndView process() { List<Member> members = memberRepository.findAll(); ModelAndView mv = new ModelAndView("members"); mv.addObject("members", members); return mv; } } }
mv.addObject()를 사용하면, ModelAndView 내부 Model에 손쉽게 데이터 추가 가능
5. 스프링 MVC - 컨트롤러 통합
- @RequestMapping은 클래스 단위가 아니라 메서드 단위로 적용 따라서 컨트롤러 클래스를
하나로 통합할 수 있다.
@Controller @RequestMapping("/springmvc/v2/members") public class SpringMemberControllerV2{ private MemberRepositroy memberRepository = MemberRepository.getInstance(); @RequestMapping("/new-form") public ModelAndView newForm(){ return new ModelAndView("new-form"); } @RequestMapping("/save") public ModelAndView save(HttpServletRequest request, HttpServletResponse response){ String username = request.getParameter("username"); int age = Integer.parseInt(request.getParameter("age")); Member member = new Member(username,age); memberRepository.save(member); ModelAndView mav = new ModelAndView("save-result"); mav.addObject("member",member); return mav; } @RequestMapping public ModelAndView members(){ List<Member> members = memberRepository.findAll(); ModelAndView mav = new ModelAndView("members"); mav.addObject("members", members); return mav; } }
6. 스프링 MVC 실용적인 방식
-보통 이 방식 사용
@controller @RequestMapping("springmvc/v3/members") public class SpringMemberControllerV3{ private MemberRepositroy memberRepository = MemberRepository.getInstance(); @GetMapping("/new-form") public String newForm(){ return "new-form"; } @PostMapping("/save") public String save(@RequestParam("username") String username, @RequestParam("age") int age, Model model){ Member member = new Member(username,age); memberRepository.save(member); model.addAttribute("member",member); return "save-result"; } @GetMapping public String members(Model model){ List<Member> members = memberRepositroy.findAll(); model.addAttribute("members",members); return "members"; } }
- Model
: 어댑터가 만들어서 넣어줌 (예전 ControllerV4처럼)
- ViewName 직접 반환
: V4처럼 컨트롤러가 ViewName만 반환하면 알아서 이를 토대로
ModelAndView만들어줌 (어댑터)
- RequestParam
: Http요청 파라미터를 위와 같이 받을 수 있음 (request.getParameter랑 같음)
- @RequestMapping, @GetMapping,@PostMapping
: HTTP method 구분할 수 있음
*Controller 인터페이스 구현해서 컨트롤러 만들면 메서드 오버라이드 해야해서 유연성 떨어지고,
Mapping별로 컨트롤러 구현체 만들어야함
어노테이션 기반: pojo도 하나만 두고 어댑터가 여러 형식을 처리를 지원해줘서
String,ModelAndView 리턴해도되고, 메서드명 자유임!
매개변수도 request,response,Model 등등 약간의 자유가 있다.
> 어댑터는 컨트롤러(핸들러) 실행 결과로 ModelAndView만 만들면 그만임
-- 디스팻쳐 역할---
리졸버 호출 -> 리졸버가 ModelAndView에서 viewName꺼낸 후 View객체 만듦
랜더 호출 -> View에 render는 ModelAndView에서 Model꺼내가서 랜더링함
> 어댑터는 요청 아까도 봤듯이 컨트롤러(핸들러)에 맞춤형으로 파라미터들 생성해서 넘겨주고
결과 받아서 ModelAndView 리턴하는 역할
참고자료:https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard
'Web > Spring' 카테고리의 다른 글
Spring MVC (6) - 스프링 MVC 기본기능 (2) : Http Body (0) 2023.10.14 Spring MVC (6) - 스프링 MVC 기본 기능 (0) 2023.10.09 SpringMVC (4.5) - FrontController V5 [어댑터 패턴 - 중요] (0) 2023.10.02 SpringMVC (4) - MVC 프레임워크 만들기 [중요] (0) 2023.10.02 Spring MVC (3) - 서블릿, JSP, MVC 패턴 (0) 2023.10.02