IT recording...
[스프링 MVC1] 5. MVC 패턴 본문
원문 링크
https://adorable-aspen-d23.notion.site/MVC1-5-MVC-7377de4e5388412ab6d5988e16dc0a8f
김영한님의 [스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술] 강의를 듣고 작성한 글입니다.
스프링 MVC 구조 살펴보기
→ 우리가 만들었던 프레임워크와 스프링MVC는 매우 유사한 모양을 띄는 것을 알 수 있다.
동작 순서
- 핸들러 조회 : 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러(컨트롤러)를 조회한다.
- 핸들러 어댑터 조회 : 핸들러를 실행할 수 있는 핸들러 어댑터를 조회한다.
- 핸들러 어댑터 실행 : 핸들러 어댑터를 실행한다. (모양 맞추기 위해)
- 핸들러 실행 : 핸들러 어댑터가 실제 핸들러를 실행한다.
- ModelAndView반환 : 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView로 변환해서 반환한다.
- ViewResolver호출 : 뷰 리졸버를 찾고 실행한다.
- View 반환 : 뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 바꾸고, 렌더링 역할을 담당하는 뷰 객체를 반환한다.
- 뷰 렌더링 : 뷰를 통해서 뷰를 렌더링한다.
1. Dispatcher Servlet
: 기존에 FrontController의 역할을 한다.
- HttpServlet을 상속 받아 사용하며 서블릿을 동작한다.
- 스프링 부트는 DispatcherServlet을 서블릿으로 자동으로 등록하면서, 모든 경로 (urlPatterns=”/”)에 대해 매핑한다. 따라서 모든 요청은 dispatcher servlet을 통해 들어오게 된다.
- DispathcerServlet.doDispatch()함수
protected void doDispatch(HttpServletRequest request, HttpServletResponse
response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
ModelAndView mv = null;
**// 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);**
}
2. HandlerMapping, HandlerAdapter
** 스프링은 이미 필요한 핸들러 매핑과 핸들러 어댑터를 대부분 구현해두었으므로,
개발자가 직접 핸들러 매핑과 핸들러 어댑터를 구현하는 일은 거의 없다.
1) HandlerMapping
: 요청된 주소를 통해서 들어와서 어떤 controller(handler)가 실행이 되어야 하는지를 확인한다.
0 = **RequestMappingHandlerMapping** : 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = BeanNameUrlHandlerMapping : 스프링 빈의 이름으로 핸들러를 찾는다.
2) HandlerAdapter
: 찾은 controller(handler)를 처리할 수 있는 adapter가 존재하는지를 확인한다.
0 = **RequestMappingHandlerAdapter** : 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = HttpRequestHandlerAdapter : HttpRequestHandler 처리
2 = SimpleControllerHandlerAdapter : Controller 인터페이스(과거 사용)
** 실무의 99.9%는 @RequestMapping이 사용하는 RequestMappingHandlerMapping, RequestMappingHandlerAdapter를 사용한다.
example
: 스프링MVC 패턴에서 HandlerMapping과 HandlerAdapter가 어떤 방식으로 찾아지고 동작하는지 예시를 살펴보자.
- OldController
@Component("/springmvc/old-controller") //spring 빈의 이름을 이렇게 지정(urlPattern이랑 맞춤)
public class OldController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("OldController.handleRequest");
return new ModelAndView("new-form");
}
}
: @Component("/springmvc/old-controller") 로 스프링 빈으로 등록되었으므로, 빈의 이름으로 URL을 매핑한다.
-
- 핸들러 매핑으로 핸들러 조회
- HandlerMapping을 순서대로 실행해서, 핸들러를 찾는다.
- 빈의 이름으로 URL이 등록되었으므로 BeanNameUrlHandlerMapping이 실행에 성공하고 핸들러인 OldController를 반환한다.
-
- 핸들러 어댑터 조회
- HandlerAdapter의 supports()를 순서대로 호출한다.
- OldController은 Controller를 상속받은 형태인데, SimpleControllerHandlerAdapter가 Cotroller 인터페이스를 지원하므로 대상이 된다.
-
- 핸들러 어댑터 실행
- 디스패처 서블릿이 조회한 SimpleControllerAdapter를 실행하면서 핸들러 정보도 함께 넘겨준다.
- 어댑터는 핸들러인 OldController를 내부에서 실행하고 그 결과를 반환한다.
-
- 그 후 view 등 공통 처리를 진행한다.
- HttpRequestHandler
@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");
}
}
: @Component("/springmvc/request-handler") 로 스프링 빈으로 등록되었으므로, 빈의 이름으로 URL을 매핑한다.
-
- 핸들러 매핑으로 핸들러 조회
- HandlerMapping을 순서대로 실행해서, 핸들러를 찾는다.
- 빈의 이름으로 URL이 등록되었으므로 BeanNameUrlHandlerMapping이 실행에 성공하고 핸들러인 MyHttpRequestHandler를 반환한다.
-
- 핸들러 어댑터 조회
- HandlerAdapter의 supports()를 순서대로 호출한다.
- HttpRequestHandlerAdapter가 HttpRequestHandler 인터페이스를 지원하므로 대상이 된다.
-
- 핸들러 어댑터 실행
- 디스패쳐 서블릿이 조회한 어댑터를 실행하면서 핸들러 정보도 함께 넘겨준다.
- 어댑터는 핸들러인 MyHttpRequestHandler를 내부에서 실행하고, 그 결과를 반환한다.
-
- 그 후 view등 공통 처리를 진행한다.
3. ViewResolver
- application.properties
**spring.mvc.view.prefix**=/WEB-INF/views/
**spring.mvc.view.suffix**=.jsp
→ 스프링 부트는 InternalResourceViewResolver라는 뷰 리졸버를 자동으로 등록하는데, 이때 이 정보를 이용해서 등록한다.
(따라서 뷰의 논리 이름으로 사용할 수 있다.)
1 = BeanNameViewResolver : 빈 이름으로 뷰를 찾아서 반환한다. (예: 엑셀 파일 생성 기능에 사용)
2 = InternalResourceViewResolver : JSP를 처리할 수 있는 뷰를 반환한다.
-
- 핸들러 어댑터 호출
- 핸들러 어댑터를 통해 ‘new-form’ 이라는 뷰의 논리 이름을 획득한다.
-
- ViewResolver 호출
- new-form이라는 뷰 이름으로 viewResolver를 순서대로 호출한다.
- BeanNameViewResolver는 new-form이라는 이름의 스프링 빈으로 등록된 뷰를 찾아야 하는데 없다.
- 따라서 InternalResourceViewResolver가 호출된다.
-
- InternalResouceVeiwResolver
- 뷰 리졸버는 InternalResourceView를 반환한다.
-
- 뷰
- InternalResourceView는 JSP처럼 포워드를 호출해서 처리할 수 있는 경우에 사용한다.
-
- view.render()
- view.render()가 호출되고 InternalResourceView는 forward()를 사용해서 JSP를 실행한다.
스프링 MVC 진짜 사용해보자!
: 스프링이 제공하는 컨트롤러는 애노테이션 기반으로 동작한다 (짱짱맨)
→ 매우 유연하고 실용적이다.
@RequestMapping
- RequestMappingHandlerMapping
- RequestMappingHandlerAdapter
1. 기존 프레임워크 → @RequestMapping 기반 스프링MVC - V1
**@Controller**
public class SpringMemberFormControllerV1 {
**@RequestMapping("/springmvc/v1/members/new-form")**
public ModelAndView process(){
return new ModelAndView("new-form"); //자동으로 viewResolver가 view 반환해줌
}
}
- @Controller
- 스프링이 자동으로 빈으로 등록한다. (내부에 @Component가 있기 때문)
- 스프링 MVC에서 애노테이션 기반 컨트롤러로 인식한다.
- @RequestMapping
- 요청 정보를 매핑한다.
- ModelAndView
- 모델과 뷰 정보를 담아 반환한다.
** RequestMappingHandlerMapping은 스프링 빈 중에서 @RequestMapping 또는 @Controller가 클래스 레벨에 붙어 있는 경우에 매핑 정보로 인식한다.
**@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);
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")**
protected ModelAndView process(){
List<Member> members = memberRepository.findAll();
**ModelAndView mv = new ModelAndView("members");
mv.addObject("members",members);
return mv;**
}
}
2. 스프링 MVC - 컨트롤러 통합
: @RequestMapping이 메서드 단위이므로 컨트롤러 클래스를 유연하게 하나로 통합할 수 있다.
**@Controller
@RequestMapping("/springmvc/v2/members")**
public class SpringMemberControllerV2 {
private MemberRepository memberRepository = MemberRepository.getInstance();
**@RequestMapping("/new-form")**
public ModelAndView newForm(){
return new ModelAndView("new-form"); //자동으로 viewResolver가 view 반환해줌
}
**@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 mv = new ModelAndView("save-result");
mv.addObject("member",member);
return mv;
}
**@RequestMapping**
protected ModelAndView members(){
List<Member> members = memberRepository.findAll();
ModelAndView mv = new ModelAndView("members");
mv.addObject("members",members);
return mv;
}
}
3. 스프링 MVC - 실용적인 방식
변화된 것
- 각 RequestMapping 에서 인자로 request, response를 받았었는데 이렇게 하지 않고
- @RequestParam을 이용하여 request를 받고,
- model을 이용하여 response를 전달할 수 있다.
- View의 논리적인 이름만 반환한다.
⇒ RequestMappingAdapter에서 Model을 파라미터로 받는 방식, @RequestParam을 사용하는 방식, 뷰의 논리적인 이름을 리턴하는 방식을 모두 지원하므로 가능하다.
⇒ 각 컨트롤러에서 이와 같이 넘겨주어도 RequestMappingAdapter에서 해당 내용을 변환해서 DispatcherServlet이 동작하므로 정상적으로 동작 가능하다.
- GET,POST,PUT,DELETE 등 각 요청은 목적이 분명하다.
- @GetMapping(”/주소”)
- @PostMapping(”/주소”)
@Controller
@RequestMapping("/springmvc/v3/members")
public class SpringMemberControllerV3 {
private MemberRepository memberRepository = MemberRepository.getInstance();
//@RequestMapping(value = "/new-form", method = RequestMethod.GET) // GET일때만 호출됨
**@GetMapping("/new-form")**
public String newForm(){
//그냥 문자를 반환해도 view이름으로 알고 알아서 진행해줌
**return "new-form";** //자동으로 viewResolver가 view 반환해줌
}
//@RequestMapping(value = "/save", method = RequestMethod.POST)
@PostMapping("/save")
public String save(**@RequestParam("username") String username, //httprequest이런거 안받아도 됨(애노테이션 강점)
@RequestParam("age") int age,
Model model**){
//비즈니스 로직
Member member = new Member(username,age);
memberRepository.save(member);
//모델에 담기
**model.addAttribute("member",member);
return "save-result";**
}
//@RequestMapping(method = RequestMethod.GET)
@GetMapping
protected String members(Model model){
//비즈니스 로직
List<Member> members = memberRepository.findAll();
**model.addAttribute("members",members);
return "members";**
}
}
'Spring' 카테고리의 다른 글
[스프링 MVC1] 7. 웹 페이지 만들기 (0) | 2022.02.07 |
---|---|
[스프링 MVC1] 6. MVC 기본 기능 (0) | 2022.01.15 |
[스프링 MVC1] 4. MVC 프론트 컨트롤러 패턴 (0) | 2022.01.14 |
[스프링 MVC1] 3. 서블릿,JSP,MVC 패턴 (0) | 2022.01.14 |
[스프링 MVC1] 2. 서블릿 (0) | 2022.01.14 |