SpringMVC 구조
직접 만든 프레임워크 스프링 MVC 비교
FrontController => DispatcherServlet
handlerMappingMap => HandlerMapping
MyHandlerAdapter => HandlerAdapter
ModelView => ModelAndView
viewResolver => ViewResolver
MyView => View
DispatcherServlet
스프링 MVC의 프론트 컨트롤러가 바로 디스패처 서블릿(DispatcherServlet)이다. (스프링 MVC의 핵심)
그림과 같이 DispatcherServlet은 부모 클래스에서 HttpServlet 을 상속 받아서 사용하고, 서블릿으로 동작한다.
- DispatcherServlet => FrameworkServlet => HttpServletBean => HttpServlet
스프링 부트는 DispacherServlet 을 서블릿으로 자동으로 등록하면서 모든 경로( urlPatterns="/" )에 대해서 매핑한다.
요청흐름
서블릿이 호출되면 HttpServlet 이 제공하는 serivce() 가 호출된다.
스프링 MVC는 DispatcherServlet 의 부모인 FrameworkServlet 에서 service() 를 오버라이드 해두었다. FrameworkServlet.service() 를 시작으로 여러 메서드가 호출되면서 DispacherServlet.doDispatch() 가 호출된다.
DispacherServlet . doDispatch() 동작 순서 (=스프링 MVC 동작 원리)
1. 핸들러 조회
핸들러 매핑을 통해 요청 URL에 매핑된 핸들러(컨트롤러)를 조회한다.
2. 핸들러 어댑터 조회
핸들러 어댑터 목록에서 핸들러를 실행할 수 있는 핸들러 어댑터를 조회한다.
3. 핸들러 어댑터 실행
핸들러 어댑터를 실행한다. => 핸들러 호출
4. 핸들러 실행
핸들러 어댑터가 실제 핸들러를 실행한다. => 비지니스 로직 처리
5. ModelAndView 반환
핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView로 변환해서 반환한다.
6. viewResolver 호출
뷰 리졸버를 찾고 실행한다. => 뷰 논리 이름을 가지고 실제 물리 경로를 반환한다.
- JSP의 경우: InternalResourceViewResolver 가 자동 등록되고, 사용된다.
7. View 반환
뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 바꾸고, 렌더링 역할을 담당하는 뷰 객체를 반환한다.
8. 뷰 렌더링
뷰를 통해서 뷰를 렌더링 한다.
<주요 인터페이스 목록>
인터페이스 명 | 경로 |
핸들러 매핑 | org.springframework.web.servlet.HandlerMapping |
핸들러 어댑터 | org.springframework.web.servlet.HandlerAdapter |
뷰 리졸버 | org.springframework.web.servlet.ViewResolver |
뷰 | org.springframework.web.servlet.View |
핸들러 매핑과 핸들러 어댑터에서 핸들러 찾는 순서
핸들러 매핑도, 핸들러 어댑터도 모두 순서대로 찾고 만약 없으면 다음 순서로 넘어간다.
HandlerMapping
1순위 : RequestMappingHandlerMapping
=> 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
2순위 : BeanNameUrlHandlerMapping
=> 스프링 빈의 이름 (url 이름)으로 핸들러를 찾는다.
HandlerAdapter
1순위 : RequestMappingHandlerAdapter
=> 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
2순위 : HttpRequestHandlerAdapter
=> HttpRequestHandler 처리
3순위 : SimpleControllerHandlerAdapter
=> Controller 인터페이스(애노테이션X, 과거에 사용) 처리
뷰 리졸버 - ViewResolver
스프링 부트는 InternalResourceViewResolver 라는 뷰 리졸버를 자동으로 등록하는데, 이때 application.properties 에 등록한 spring.mvc.view.prefix , spring.mvc.view.suffix 설정 정보를 사용해서 등록한다.
- 예제 코드
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("OldController.handleRequest");
return new ModelAndView("new-form");
}
- new-form.jsp
<!--WEB-INF 아래에 있는 하위 자원들은 외부에서 호출해도 그냥 호출되지 않는다. (WAS 서버의 규칙)-->
<!--즉, 서블릿(컨트롤러)를 거쳐서 내부에서 forward를 하거나 해야 호출이 가능하다.-->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- 상대경로 사용, [현재 URL이 속한 계층 경로 + /save] -->
<form action="save" method="post">
username: <input type="text" name="username" />
age: <input type="text" name="age" />
<button type="submit">전송</button>
</form>
</body>
</html>
스프링부트가 자동 등록하는 뷰 리졸버
- 1순위 BeanNameViewResolver : 빈 이름으로 뷰를 찾아서 반환한다. (예: 엑셀 파일 생성 기능에 사용)
- 2순위 InternalResourceViewResolver : JSP를 처리할 수 있는 뷰를 반환한다.
뷰 리졸버 동작 방식
1. 핸들러 어댑터 호출
- 핸들러 어댑터를 통해 new-form 이라는 논리 뷰 이름을 획득한다.
2. ViewResolver 호출
- new-form 이라는 뷰 이름으로 viewResolver를 순서대로 호출한다.
- BeanNameViewResolver 는 new-form 이라는 이름의 스프링 빈으로 등록된 뷰를 찾아야 하는데 없다.
- InternalResourceViewResolver 가 호출된다. -> JSP 처리 가능
3. InternalResourceViewResolver
- 이 뷰 리졸버는 InternalResourceView 를 반환한다.
4. 뷰 - InternalResourceView
- InternalResourceView 는 JSP처럼 포워드 forward() 를 호출해서 처리할 수 있는 경우에 사용한다.
5. view.render()
- view.render() 가 호출되고 InternalResourceView 는 forward() 를 사용해서 JSP를 실행한다.
스프링 MVC
@RequestMapping
이 어노테이션이 있으면 RequestMappingHandlerMapping과 RequestMappingHandlerAdapter를 사용한다.
(우선순위가 높은 두 매핑과 핸들러를 사용한다.)
RequestMappingHandlerMapping은 스프링 빈 중에서 @RequestMapping 또는 @Controller가 클래스 레벨에 붙어있는 경우에 매핑정보로 인식한다.
* 스프링 3.0 이상에서는 클래스 레벨에 @RequestMapping이 있어도 스프링 컨트롤러로 인식하지 않는다. (오직 @Controller만 인식함!) => RequestMappingHandlerMapping에서 @RequestMapping은 인식하지 않음.
@Controller
- 해당 어노테이션이 붙은 클래스는 스프링이 자동으로 스프링 빈으로 등록한다.
=> @Controller는 내부적으로 @Component 어노테이션이 있어 컴포넌트 스캔 대상이 되기 때문이다. - RequestMappingHandlerMapping에서 사용되는 역할을 한다.
예시
package hello.servlet.web.springmvc.v1;
/**
* @Controller
* -컴포넌트 스캔의 대상이 된다.
* -RequestMappingHandlerMapping에서 사용되는 역할을 한다.
*/
@Controller //스프링이 자동으로 스프링 빈으로 등록.(내부적으로 @Component가 있어서 자동으로 스캔 대상이 되기 때문)
public class SpringMemberFormControllerV1 {
@RequestMapping("/springmvc/v1/members/new-form") //요청 정보를 매핑한다. (해당 url이 호출되면 이 메서드가 실행된다.)
public ModelAndView process(){
return new ModelAndView("new-form"); //뷰리졸버에서 뷰이름으로 페이지를 찾아 렌더링함.
}
}
- @RequestMapping 어노테이션 뒤의 호출할 경로를 매핑하면 해당 경로로 요청이 들어왔을 때 해당 메서드가 실행된다.
스프링 MVC - 컨트롤러 통합
@RequestMapping 클래스 레벨과 메서드 레벨 조합
- RequestMapping은 메서드 레벨로 실행되기 때문에 한 클래스(컨트롤러)에 여러 개의 메서드들을 넣을 수 있다.
=> 되도록 연관된 메서드들을 모아서 작성하는게 좋음. - 또한, url의 중복된 경로를 클래스 레벨에 선언하여 중복을 제거할 수 있다. (ex: /springmvc/v2/members)
=> 클래스 레벨 RequestMapping + 메서드 레벨 RequestMapping으로 경로 호출.
(/springmvc/v2/members + /save || /new-form ... )
예시
@Controller
@RequestMapping("/springmvc/v2/members") //공통 경로를 클래스 레벨에서 작성.
public class SpringMemberControllerV2 {
private MemberRepository memberRepository = MemberRepository.getInstance();
//("/springmvc/v2/members/new-form")
@RequestMapping("/new-form") //요청 정보를 매핑한다. (해당 url이 호출되면 이 메서드가 실행된다.)
public ModelAndView newForm(){
return new ModelAndView("new-form"); //뷰리졸버에서 뷰이름으로 페이지를 찾아 렌더링함.
}
//("/springmvc/v2/members/save")
@RequestMapping("/save")
public ModelAndView save(HttpServletRequest request, HttpServletResponse
response) {
//request로부터 받은 인자 값을 변수에 할당.
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));
//변수를 가지고 member 객체 생성.
Member member = new Member(username, age);
System.out.println("member = " + member);
//member를 저장(회원 등록).
memberRepository.save(member);
//ModelAndView에 member 객체를 담아 뷰를 출력.
ModelAndView mv = new ModelAndView("save-result");
mv.addObject("member", member);
return mv;
}
//("/springmvc/v2/members")
@RequestMapping
public ModelAndView list(){
List<Member> members = memberRepository.findAll();
ModelAndView mv = new ModelAndView("members");
mv.addObject("members", members);
return mv;
}
}
- 클래스 레벨 @RequestMapping("/springmvc/v2/members")
- 메서드 레벨 @RequestMapping("/new-form") => /springmvc/v2/members/new-form
- 메서드 레벨 @RequestMapping("/save") => /springmvc/v2/members/save
- 메서드 레벨 @RequestMapping => /springmvc/v2/members
스프링 MVC - 실용적인 방식
@RequestParam 사용
- 스프링에서는 HTTP 요청 파라미터를 인자로 바로 받을 수 있다.
- @RequestParam은 request.getParameter와 거의 같은 코드라 볼 수 있다.
- GET 쿼리 파라미터, POST Form 방식을 모두 지원한다.
@GetMapping, @PostMapping 사용
- @RequestMapping은 url만 매칭하는 것이 아닌 HTTP Method도 함께 구분할 수 있다.
- 예 ) @RequestMapping(value = "/new-form", method = RequestMethod.GET)
=> GET 방식의 url일 경우 호출. - HTTP Method 방식을 지정하지 않는 경우 어떤 메서드 방식이든 해당 url 경로이면 다 실행된다.
- 예 ) @RequestMapping(value = "/new-form", method = RequestMethod.GET)
- 이러한 방식을 HTTP Method의 이름을 명시한 어노테이션으로 더 편리하게 사용이 가능하다.
- @GetMapping
- @PostMapping
- @DeleteMaping
- @PutMapping
- ...
예시
/**
* v3
* Model 도입
* ViewName 직접 반환
* @RequestParam 사용
* @RequestMapping -> @GetMapping, @PostMapping
*/
@Controller
@RequestMapping("/springmvc/v3/members")
public class SpringMemberControllerV3 {
private MemberRepository memberRepository = MemberRepository.getInstance();
//("/springmvc/v3/members/new-form")
//method = RequestMethod.GET => GET 방식의 요청만 허용한다.
//@RequestMapping(value = "/new-form", method = RequestMethod.GET)
@GetMapping("/new-form")
public String newForm(){
return "new-form"; //뷰리졸버에서 뷰이름으로 페이지를 찾아 렌더링함.
}
//("/springmvc/v3/members/save")
//method = RequestMethod.POST => POST 방식의 요청만 허용한다.
//@RequestMapping(value = "/new-form", method = RequestMethod.POST)
@PostMapping("/save")
public String save(@RequestParam String username, @RequestParam int age, Model model) {
//변수를 가지고 member 객체 생성.
Member member = new Member(username, age);
System.out.println("member = " + member);
//member를 저장(회원 등록).
memberRepository.save(member);
//Model에 member 객체를 담아 뷰를 출력.
model.addAttribute("member", member);
return "save-result";
}
//("/springmvc/v3/members")
//method = RequestMethod.GET => GET 방식의 요청만 허용한다.
//@RequestMapping(value = "/new-form", method = RequestMethod.GET)
@GetMapping
public String list(Model model){
List<Member> members = memberRepository.findAll();
model.addAttribute("members", members);
return "members";
}
}
'Spring > SpringMVC' 카테고리의 다른 글
스프링 MVC 기본 기능 2 (0) | 2023.12.18 |
---|---|
스프링MVC의 기본 기능 (0) | 2023.12.07 |
MVC 프레임워크 (0) | 2023.10.22 |
Spring으로 간단한 회원관리 만들기(3) - MVC 패턴으로 회원관리 화면 구성 (0) | 2023.09.28 |
Spring으로 간단한 회원관리 만들기(3) -멤버 컨트롤러 (0) | 2023.09.28 |