[Inflearn] 스프링 MVC (2) 구조 이해
김영한 강사님의 '스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술' 강의 정리
강의를 듣고 개인적으로 정리한 글입니다. 코드와 그림 출처는 김영한 강사님께 있습니다. 문제 있을 시 알려주세요.
DispatcherServlet
DispatcherServlet
은 스프링이 구현해놓은 FrontController 역할을 해주는 녀석으로, 부모 클래스FrameworkServlet
에서 HttpServlet을 상속받아 서블릿으로 동작한다.- 스프링 부트는
DispatcherServlet
을 서블릿으로 자동등록하면서 모든 경로(urlPatterns="\")에 대하여 매핑한다. - 요청흐름 : 서블릿 호출 ->
service()
호출 ->FrameworkServlet.service()
호출 ->DispatcherServlet.doDispatch()
호출
그리고 나서 아래 그림과 같이 동작하게 된다.
가장 호출 우선순위가 높은 핸들러 매핑과 핸들러 어댑터
RequestMappingHandlerMapping
와 RequestMappingHandlerAdapter
는
스프링 실무에서 주로 사용하는 애노테이션 기반 컨트롤러를 지원하는 매핑과 어댑터이다.
스프링부트가 자동등록하는 ViewResolver
BeanNameViewResover //빈 이름으로 뷰를 찾아서 반환 (우선순위 0)
InternalResourceViewResolver// Jsp를 처리할 수 있는 뷰 반환(우선순위 1)
스프링 부트는 <application.properties>를 사용해서 InternalResourceViewResolver
를 자동등록한다.
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.surffix=.jsp
Jsp를 제외한 뷰 템플릿들은 forward() 과정 없이 바로 렌더링 된다.
@RequestMapping
- 요청 정보를 매핑한다. 해당 URL이 호출되면
@RequestMapping
이 붙은 메서드가 호출된다. - 즉 메서드 기반으로 동작한다. (이름은 임의로 지으면 된다)
- 따라서 (회원가입/저장/조회 등의) 컨트롤러를 유연하게 클래스 하나로 통합할 수 있다.
- 스프링에서 만든 애노테이션 기반 컨트롤러로 매우 유연하고 실용적이다.
RequestMappingHandlerMapping
은 스프링 빈 중에서@RequestMapping
또는@Controller
가 클래스 레벨에 붙어 있는 경우에 매핑 정보로 인식한다.
@Controller를 클래스에 붙이면 스프링이 자동으로 스프링 빈으로 등록한다
@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
public ModelAndView members() {
List<Member> members = memberRepository.findAll();
ModelAndView mv = new ModelAndView("members");
mv.addObject("members",members);
return mv;
}
}
@RequestParam
스프링은 HTTP 요청 파라미터를 HttpServletRequest 객체를 받아서 getParameter("username") 메서드를 사용해 얻을 수 있었다.
@RequestParam
을 사용하면 더 간단한 코드로 요청 파라미터를 얻을 수 있다.
@RequestMapping("/save")
public ModelAndView save(
@RequestParam("username") String username,
@RequestParam("age") int age,
Model model) {
// 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;
}
ViewName(논리뷰) 직접 반환
ModelAndView
객체를 일일히 만들어서 반환하기가 귀찮다.
그래서 FrontController, 즉 DispatcherServlet
에서 Model
객체를 만들어서 Controller에 보낸다.
Controller는 model에 데이터를 저장하고 논리뷰 자체를 직접 반환한다.
이렇게 하면 코드가 더 간단해지지 않았는가? 스프링부트는 이 모든 방식을 지원한다.
@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";
}
@GetMapping @PostMapping
특정 HTTP 메서드만 호출되도록 제약을 걸어놓는 것이 좋은 설계라고 할 수 있다. (그렇지 않으면 어떤 메서드로든지 호출될 수 있으며 이로인해 여러 문제가 발생할 수 있다)
@RequestMapping
은 URL 매칭 뿐만 아니라 HTTP 메서드도 함께 구분할 수 있다.
GET 메서드로만 호출되게 하려면 다음과 같이 처리한다.
@RequestMapping("/new-form", method = RequestMethod.GET)
더 간단한 방법은 @GetMapping
을 사용하는 것이다. 다른 메서드들도 다 사용가능하다.
@GetMapping("/new-form")
사실은 애노테이션의 애노테이션이다. @GetMapping
코드는 위의@RequestMapping
코드로 되어있다ㅎㅎ..
위의 과정을 거쳐 탄생한 코드는 다음과 같다!
/*
* Model 도입
* ViewName 직접 반환
* @RequestParam 사용
* @RequestMapping -> @GetMapping, @PostMapping
*/
@Controller
@RequestMapping("/springmvc/v3/members")
public class SpringMemberControllerV3 {
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";
}
}