일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 설탕 배달
- 백준
- 다이나믹 프로그래밍
- 맛집 투어
- Spring
- 호유동
- dp
- 맛집
- 2638
- 1로 만들기
- 2839
- 고모네 콩탕
- mvc
- 2020 KAKAO BLIND
- 프로그래머스
- HTTP API
- 2589
- 알고리즘
- 서블릿
- 쓰레드 풀
- 스프링
- BFS
- 양꼬치
- 완도산회
- Servlet
- 문자열 압축
- 투어
- 포두부 보쌈
- 스프링 MVC
- 동적 프로그래밍
- Today
- Total
프로그래밍 공방
[Spring] 5. 스프링 MVC - 구조 이해 본문
스프링 MVC 구조
이전에 포스팅한 글의 MVC 구조와 비교하면 아래 내용들이 변경되었다
- FrontController -> DispatcherServlet
- handlerMappingMap -> HandlerMapping
- MyHandlerAdapter -> HandlerAdapter
- ModelView -> ModelAndView
- viewResolver -> ViewResolver
- MyView -> View
DispatcherServlet 구조
스프링 MVC도 Front Controller 패턴으로 구현되어 있고 스프링 MVC의 Front Controller가 DispatcherServlet 이다
DispatcherServlet도 부모 클래스에서 HttpServlet을 상속 받고 서블릿으로 동작한다
* 스프링 부트는 DispatcherServlet을 서블릿으로 자동 등록하면서 모든 경로(urlPatterns="/")에 대해 매핑한다
DispatcherServlet의 핵심 메소드인 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);
}
스프링 MVC의 동작 순서
1. 핸들러 조회 : 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러를 조회한다
2. 핸들러 어댑터 조회 : 핸들러를 실행할 수 있는 핸들러 어댑터를 조회한다
3. 핸들러 어댑터 실행 : 핸들러 어댑터를 실행한다
4. 핸들러 실행 : 핸들러 어댑터가 실제 핸들러를 실행한다
5. ModelAndView 반환 : 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView로 변환해서 반환한다
6. viewResolver 호출 : 뷰 리졸버를 찾고 실행한다
- JSP의 경우 : InternalResourceViewResolver가 자동 등록되고 사용된다
7. View 반환 : 뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 바꾸고, 렌더링 역할을 담당하는 뷰 객체를 반환한다
- JSP의 경우 InternalResourceView(JstlView)를 반환하는데, 내부에 forward() 로직이 있다
8. 뷰 렌더링 : 뷰를 통해서 뷰를 렌더링 한다
스프링 제공 인터페이스
스프링 MVC는 아래 인터페이스 목록들을 확장 가능할 수 있게 제공하여 DispatcherServlet 코드의 변경 없이 원하는 기능을 변경하거나 확장할 수 있다
주요 인터페이스 목록
- 핸들러 매핑 : org.springframework.web.servlet.HandlerMapping
- 핸들러 어댑터 : org.springframework.web.servlet.HandlerAdapter
- 뷰 리졸버 : org.springframework.web.servlet.ViewResolver
- 뷰 : org.springframework.web.servlet.View
핸들러 매핑과 핸들러 어댑터
과거에 주로 사용했던 기능들에 대해 간단히 알아보자
Controller 인터페이스
org.springframework.web.servlet.mvc.Controller
public interface Controller {
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
초기에는 위 컨트롤러 인터페이스를 아래와 같이 구현해서 사용했었다
@Component("/springmvc/old-controller")
public class OldController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
return new ModelAndView("new-form");
}
}
* @Component("/springmvc/old-controller")로 매핑할 url과 동일한 이름으로 스프링 빈을 등록해주었다
위 컨트롤러가 호출되려면 아래 2가지가 필요하다
HandlerMapping : 요청 들어온 url을 처리하는 핸들러를 찾을 수 있어야 한다
HandlerAdapter : 핸들러를 실행할 수 있는 핸들러 어댑터를 찾아야 한다
스프링에는 아래와 같이 이미 필요한 핸들러 매핑과 핸들러 어댑터를 대부분 구현해두었다
HandlerMapping
0 = RequestMappingHandlerMapping : 어노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = BeanNameUrlHandlerMapping : 스프링 빈의 이름이 URL과 동일한 핸들러를 찾는다
HandlerAdapter
0 = RequestMappingHandlerAdapter : 어노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = HttpRequestHandlerAdapter : HttpRequestHandler 처리
2 = SimpleControllerHandlerAdapter : Controller 인터페이스를 구현한 핸들러를 처리
* HandlerMapping, HandlerAdapter 모두 순서대로 찾고 없으면 다음 순서로 넘어간다
HttpRequestHandler
아래와 같이 서블릿과 가장 유사한 형태의 핸들러이다
public interface HttpRequestHandler {
void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
View Resolver
아래 OldController 코드에서 보이듯이 View를 사용할 수 있도록 ModelAndView 코드를 추가해서 사용한다
@Component("/springmvc/old-controller")
public class OldController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
return new ModelAndView("new-form");
}
}
* 스프링 부트는 InternalResourceViewResolver라는 View Resolver를 자동으로 등록하는데 이때 application.properties에 등록한 spring.mvc.view.prefix, spring.mvc.view.suffix 설정 정보를 사용해서 등록한다
스프링 부트는 아래 뷰 리졸버 등을 자동으로 등록해서 사용한다
1 = BeanNameViewResolver : 빈 이름으로 뷰를 찾아서 반환한다
2 = InternalResourceViewResolver : JSP를 처리할 수 있는 뷰를 반환한다
* InternalResourceViewResolver는 InternalResourceView를 반환하는데 이 View는 JSP처럼 forward()를 호출해서 처리할 수 있는 경우에 사용한다(view.render()가 호출되고 InternalResourceView는 forward()를 사용해서 JSP를 실행한다)
참고 : Thymeleaf 뷰 템플릿을 사용하면 ThymeleafViewResolver를 등록해야 한다. 최근에는 라이브러리만 추가하면 스프링 부트가 자동으로 등록해준다
@RequestMapping
과거에는 스프링을 사용하더라도 MVC 웹 기술 등은 다른 프레임 워크를 사용했었지만 @RequestMapping 기반의 어노테이션 컨트롤러가 등장하면서 MVC 부분도 스프링을 사용하게 되었다
@RequestMapping 어노테이션을 사용하게 된다면 컨트롤러를 아래와 같이 작성할 수 있다
@Controller
public class SpringMemberSaveController {
/* ... */
@RequestMapping("/springmvc/members/save")
public ModelAndView process(HttpServletRequest request, HttpServletResponse response) {
/* ... */
ModelAndView modelAndView = new ModelAndView("save-result");
modelAndView.addObject("member", member);
return modelAndView;
}
}
@Controller
스프링 MVC에서 어노테이션 기반 컨트롤러로 인식한다
@RequestMapping
- 요청 정보를 매핑해서 해당 URL이 호출되면 이 메서드가 호출된다(RequestMappingHandlerMapping)
- 어노테이션을 기반으로 동작하기 때문에 메서드의 이름은 임의로 지으면 된다
* RequestMappingHandlerMapping은 아래와 같이 스프링 빈 중에서 @RequestMapping 또는 @Controller가 클래스 레벨에 붙어 있는 경우에 매핑 정보로 인식한다. 따라서 @Controller 대신 @Component 와 @RequestMapping을 붙여주면 동일하게 동작한다
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping implements MatchableHandlerMapping, EmbeddedValueResolverAware {
@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
}
ModelAndView
- 모델과 뷰 정보를 담아서 반환
addObject
- ModelAndView를 통해 Model 데이터를 추가할 때는 addObject를 사용하면 된다
스프링 MVC - 컨트롤러 통합
@RequestMapping을 보면 클래스 단위가 아닌 메서드 단위에 적용되므로 연관된 컨트롤러 클래스를 아래와 같이 하나로 통합할 수 있다
@Controller
@RequestMapping("/springmvc/members")
public class SpringMemberController {
/* ... */
@RequestMapping("/new-form")
public ModelAndView newForm() {
/* ... */
}
@RequestMapping("/save")
public ModelAndView save(HttpServletRequest request, HttpServletResponse response) {
/* ... */
}
@RequestMapping
public ModelAndView members() {
/* ... */
}
}
@RequestMapping
- 기존과 동일하게 메서드에만 @RequestMapping을 사용하는 경우 URL 부분에 중복이 생길 수 있는데
클래스 레벨에 @RequestMapping을 붙여서 중복되는 URL을 조합할 수 있다
스프링 MVC - 실용적인 방식
위에서는 ModelAndView를 생성해서 반환하기 때문에 불편한 부분이 있다. 스프링 MVC는 개발자가 편리하게 개발할 수 있도록 수많은 편의 기능을 제공한다
* 실무에서는 아래 방식을 주로 사용
@Controller
@RequestMapping("/springmvc/members")
public class SpringMemberController {
private MemberRepository 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 = memberRepository.findAll();
model.addAttribute("members", members);
return "members";
}
}
Model 파라미터
- HttpServletRequest 등을 사용하게 되면 서블릿에 종속적이게 되므로 Model을 파라미터로 받아서 사용한다
ViewName
- View의 논리 이름을 직접 반환할 수 있다
@RequestParam
- request.getParameter 대신 @RequestParam을 사용해서 HTTP 요청 파라미터를 받을 수 있다
* GET 쿼리 파라미터, POST Form 방식 모두 지원
@GetMapping, @PostMapping
- 기존에 사용하던 @RequestMapping은 method를 따로 @RequestMapping(method = RequestMethod.GET)와 같이 지정해주지 않으면 GET, POST 등 모든 타입의 요청을 동일하게 지원한다
이 부분을 @GetMapping, @PostMapping 등을 통해 더 편리하게 사용할 수 있다
* 어노테이션을 들어가보면 내부적으로 @RequestMapping을 가지고 있다
'개발 > 스프링' 카테고리의 다른 글
[Spring] 6-2. 스프링 MVC - 기본 기능 (0) | 2021.11.02 |
---|---|
[Spring] 6-1. 스프링 MVC - Logging (0) | 2021.10.31 |
[Spring] 4. 프레임워크 만들기 (0) | 2021.10.25 |
[Spring] 3. Servlet, JSP, MVC 패턴 (0) | 2021.10.18 |
[Spring] 2. Servlet / 서블릿 (0) | 2021.10.15 |