[스프링 MVC 1편] MVC - 구조의 이해
by mini_min스프링 MVC 구조
스프링 MVC 의 구조는 다음과 같이 구성된다.
✨ DispatcherServlet
스프링 MVC 도 프론트 컨트롤러 패턴으로 구현되어 있다.
스프링 MVC의 프론트 컨트롤러가 바로 디스패처 서블릿이다.
이 디스패처 서블릿이 스프링 MVC의 핵심이다.
해당 서블릿도 부모 클래스에서 HttpServlet 을 상속 받아 사용하고 서블릿으로 동작한다.
스프링 부트는 디스패처 서블릿을 서블릿으로 자동 등록하면서 모든 경로에 대해서 매핑한다.
요청흐름
서블릿 호출 -> HttpServlet 이 제공하는 service() 호출
FrameworkServlet.service() 를 시작으로 여러 메서드가 호출되면서 DispacherServlet.doDispatch() 가 호출된다.
스프링 MVC 동작순서
1. 핸들러 조회: 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러(컨트롤러)를 조회한다.
2. 핸들러 어댑터 조회: 핸들러를 실행할 수 있는 핸들러 어댑터를 조회한다.
3. 핸들러 어댑터 실행: 핸들러 어댑터를 실행한다.
4. 핸들러 실행: 핸들러 어댑터가 실제 핸들러를 실행한다.
5. ModelAndView 반환: 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView로 변환해서 반환한다.
6. viewResolver 호출: 뷰 리졸버를 찾고 실행한다. JSP의 경우: InternalResourceViewResolver 가 자동 등록되고, 사용된다.
7. View 반환: 뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 바꾸고, 렌더링 역할을 담당하는 뷰 객체를 반환한다. JSP의 경우 InternalResourceView(JstlView) 를 반환하는데, 내부에 forward() 로직이 있다.
8. 뷰 렌더링: 뷰를 통해서 뷰를 렌더링 한다.
✨ 스프링 MVC 는 코드 분량도 많고, 내부 구조 파악도 쉽지 않아서 대부분의 기능은 이미 구현되어 있으니 그대로 사용하는 것이 좋다.
핸들러 매핑과 핸들러 어댑터
핸들러 매핑은 지금 거의 사용하지 않지만, 과거에는 주로 사용했었다.
package hello.servlet.web.springmvc.old;
import org.springframework.stereotype.Component;
import org.springframework.web.HttpRequestHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.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");
}
}
package hello.servlet.web.springmvc.old;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@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 new ModelAndView("new-form");
}
}
HaddlerMapping(핸들러 매핑)으로 컨트롤러를 먼저 찾을 수 있어야한다.
예) 스프링 빈의 이름으로 핸들러를 찾을 수 있는 핸들러 매핑이 필요하다.
HandlerAdapter(핸들러 어댑터)
핸들러 매핑을 통해 찾은 핸들러를 실행할 수 있는 핸들러 어댑터가 필요하다.
✨ 스프링은 이미 필요한 핸들러 매핑과 핸들러 어댑터를 대부분 구현해뒀다. 그래서 개발자가 직접 핸들러 매핑과 핸들러 어댑터를 만들 일이 없다.
스프링 MVC 시작하기
스프링이 제공하는 컨트롤러는 애노테이션 기반으로 동작해서 매우 유연하고 실용적이다.
@RequestMapping
실용적인 컨트롤러를 만드는데 기여한 애노테이션 기반의 컨트롤러를 지원하는 핸들러 매핑과 어댑터이다.
실무에서 99.9% 이 방식의 컨트롤러를 사용한다.
package hello.servlet.web.springmvc.v1;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class SpringMemberFormControllerV1 {
@RequestMapping("/springmvc/v1/members/new-form")
public ModelAndView process(){
return new ModelAndView("new-form");
}
}
@Controller
스프링이 자동으로 스프링 빈으로 등록되도록 한다.
애노테이션 기반 컨트롤러로 인식한다.
@RequestMapping
요청 정보를 매핑한다. 해당 URL 이 호출되면 이 메서드가 호출된다.
ModelAndView
모델과 뷰 정보를 담아서 반환하면 된다.
- 회원 저장과 리스트
package hello.servlet.web.springmvc.v1;
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
@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;
}
}
package hello.servlet.web.springmvc.v1;
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import java.util.List;
import java.util.Map;
@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;
}
}
스프링 MVC - 컨트롤러 통합
컨트롤러들을 하나로 통합해서 유연하게 관리할 수 있다.
package hello.servlet.web.springmvc.v2;
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
@Controller
@RequestMapping("/springmvc/v2/members")
public class SpringMemberControllerV2 {
private MemberRepository 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 mv = new ModelAndView("save-result");
mv.addObject("member", member);
return mv;
}
@RequestMapping("/members")
public ModelAndView members(){
List<Member> members = memberRepository.findAll();
ModelAndView mv = new ModelAndView("members");
mv.addObject("members", members);
return mv;
}
}
스프링 MVC - 실용적인 방식
Model 파라미터 : 모델앤뷰가 아닌 모델 파라미터를 직접 사용할 수 있다.
ViewName 반환 : 뷰의 논리 이름을 반환할 수 있다.
@RequestParam 사용 : request 객체를 사용하지 않고 바로 파라미터를 받을 수 있다.
@GetMapping, @PostMapping 으로 HTTP Method 도 함께 구분할 수 있다.
package hello.servlet.web.springmvc.v3;
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
@Controller
@RequestMapping("/springmvc/v3/members")
public class SpringMemberControllerV3 {
private MemberRepository memberRepository = MemberRepository.getInstance();
//모델앤뷰 반환 안하고 문자로 반환해도 된다. 뷰 이름으로 인식
@RequestMapping(value = "/new-form", method = RequestMethod.GET)
public String newForm(){
return "new-form";
}
@RequestMapping("/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";
}
@RequestMapping("")
public String members(Model model){
List<Member> members = memberRepository.findAll();
model.addAttribute("members", members);
return "members";
}
}
'Spring' 카테고리의 다른 글
[스프링 MVC 2편] 메시지, 국제화 핵심 정리 (1) | 2023.12.06 |
---|---|
[Spring MVC] 스프링 부트 로깅 (logging) 설정 (0) | 2023.08.22 |
[스프링 MVC 1편] MVC 프레임워크 만들기 (3) - 어댑터 패턴 (0) | 2023.07.01 |
[스프링 MVC 1편] MVC 프레임워크 만들기 (2) (0) | 2023.07.01 |
[스프링 MVC 1편] MVC 프레임워크 만들기 (1) (0) | 2023.06.27 |
블로그의 정보
개발자 미니민의 개발로그
mini_min