Front Controller Pattern
으로 구현되어 있다.Front Controller
→ DispatcherServlet
💡 DispatcherServlet
= Spring MVC의 핵심
DispacherServlet
도 부모 클래스에서 HttpServlet
을 상속 → Servlet으로 동작DispacherServlet
을 Servlet으로 자동 등록(urlPatterns="/")
에 대해서 Mapping📌 참고
DispatcherServlet
의 부모인 FrameworkServlet
에서service() Override
DispacherServlet.doDispatch()
호출Code
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);
}
동작 순서
Handler 조회
Handler Adapter 조회
Handler Adapter 실행
Handler 실행
ModelAndView 반환
viewResolver 호출
📌 JSP의 경우, InternalResourceViewResolver
가 자동 등록되고, 사용된다.
View 반환
📌 JSP의 경우, InternalResourceView(JstlView)
를 반환
forward()
로직이 있음
View Rendering
📌 Spring MVC의 큰 강점
DispatcherServlet
코드의 변경 없이, 원하는 기능을 변경하거나 확장할 수 있다Interface
로 제공DispatcherServlet
에 등록 → 새로운 Controller
를 만들 수 있음HandlerMapping
HandlerAdapter
ViewResolver
View
스프링이 제공하는 간단한 컨트롤러로 Handler Mapping & Handler Adapter를 이해해보자.
과거에 주로 사용했던 스프링이 제공하는 간단한 컨트롤러
org.springframework.web.servlet.mvc.Controller
public interface Controller {
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
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 null;
}
}
📌 @Component
를 사용해 요청 URL을 이름으로 스프링 빈 등록
HandlerMapping
에서 Handler
조회
이 경우 빈 이름으로 Handler
를 찾아야 함
→ 빈 이름으로 Handler
를 찾아주는BeanNameUrlHandlerMapping
실행
→ OldController
반환
HandlerAdapter 조회
HandlerAdapter
의 supports()
를 순서대로 호출SimpleControllerHandlerAdapter
가 Controller Interface
를 지원하므로 대상이 됨
HandlerAdapter
실행
dispatcherServle
이 조회한 SimpleControllerHandlerAdapter
를 실행
Handler
정보도 함께 넘겨준다.SimpleControllerHandlerAdapter
는 핸들러인 OldController
를 내부에서 실행
HttpRequestHandler
⇒Servlet
과 가장 유사한 형태의 핸들러
public interface HttpRequestHandler {void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
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");
}
}
HandlerMapping
에서 Handler
조회Handler
를 찾아야 한다. Handler
를 찾아주는BeanNameUrlHandlerMapping
실행MyHttpRequestHandler
반환HandlerAdapter
조회HandlerAdapter
의 supports()
를 순서대로 호출SimpleControllerHandlerAdapter
가 HttpRequestHandler Interface
를 지원하므로 대상이 된다.HandlerAdapter
실행dispatcherServle
이 조회한 HttpRequestHandlerAdapter
를 실행Handler
정보도 함께 넘겨준다.HttpRequestHandlerAdapter
는 핸들러인 MyHttpRequestHandler
를 내부에서 실행Controller
가 호출되려면 크게 두 가지가 필요하다.HandlerMapping
HandlerMapping
에서 해당 Handler(Controller)
를 찾을 수 있어야 함HandlerAdapter
HandlerMapping
을 사용해 해당 Handler(Controller)
를 실행할 HandlerAdapter
가 필요스프링은 이미 필요한
HandlerMapping
과HandlerAdapter
를 대부분 구현해두었다.개발자가 직접
HandlerMapping
과 핸HandlerAdapter
를 만드는 일은 거의 없다.
Handler Mapping
Handler Adapter
@RequestMapping
에서 사용@RequestMapping
- 가장 우선순위가 높은 Handler Mapping, Handler Adapter ⇒ RequestMappingHandlerMapping , RequestMappingHandlerAdapter
- Spring에서 주로 사용하는 Annotation 기반의 Controller를 지원하는 Mapping & Adapter
- 거의 대부분 이 방식의 Controller 사용
Spring 은
ViewResolver
와View
를Interface
로 제공
Spring Boot가 자동으로 등록하는 View Resolver (일부)
- BeanNameViewResolver
- 빈 이름으로 View를 찾아서 반환
- InternalResourceViewResolver
- JSP를 처리할 수 있는 View 반환
InternalResourceViewResolver
동작 과정
forward()
를사용해서 JSP 실행Spring은 Annotation을 활용해 유연하고, 실용적인 컨트롤러를 만들었는데 이것이 바로
@RequestMapping
Annotation을 사용하는 Controller이다.
RequestMappingHandlerMapping
, RequestMappingHandlerAdapter
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");
}
}
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;
@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); System.out.println("member = " + member);
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;
@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;
}
}
@Controller
@RequestMapping
Mapping
@RequestMapping
이 붙은 Method 호출ModelAndView
RequestMappingHandlerMapping은 Spring Bean 중에서 @RequestMapping 또는 @Controller 가 클래스 레벨에 붙어 있는 경우에 Mapping 정보로 인식
@RequestMapping가 클래스 단위가 아니라 Method 단위에 적용된 것을 확인할 수 있다.
→ Controller 클래스를 하나로 통합할 수 있다.
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;
/**
* 클래스 단위 -> 메서드 단위
* @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");
}
@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 mav = new ModelAndView("save-result");
mav.addObject("member", member);
return mav;
}
@RequestMapping
public ModelAndView members() {
List<Member> members = memberRepository.findAll();
ModelAndView mav = new ModelAndView("members");
mav.addObject("members", members);
return mav;
}
}
@Controller
@RequestMapping("/springmvc/v2/members")
public class SpringMemberControllerV2{
...
...
}
스프링 MVC는 개발자가 편리하게 개발할 수 있도록 수 많은 편의 기능을 제공한다.
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.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/**
* v3
* 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";
}
}
@RequestParam
으로 받을 수 있다음@RequestMapping
의 기능HTTP Method
구분 기능 → @GetMapping
, @PostMapping
등을 사용해 더 편리하게 사용 가능