


Front Controller Pattern으로 구현되어 있다.Front Controller → DispatcherServlet 💡 DispatcherServlet = Spring MVC의 핵심
DispacherServlet 도 부모 클래스에서 HttpServlet 을 상속 → Servlet으로 동작DispacherServlet을 Servlet으로 자동 등록(urlPatterns="/")에 대해서 Mapping📌 참고
DispatcherServlet의 부모인 FrameworkServlet에서service() OverrideDispacherServlet.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를 만들 수 있음HandlerMappingHandlerAdapterViewResolverView스프링이 제공하는 간단한 컨트롤러로 Handler Mapping & Handler Adapter를 이해해보자.
과거에 주로 사용했던 스프링이 제공하는 간단한 컨트롤러
org.springframework.web.servlet.mvc.Controllerpublic 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가 호출되려면 크게 두 가지가 필요하다.HandlerMappingHandlerMapping에서 해당 Handler(Controller)를 찾을 수 있어야 함HandlerAdapterHandlerMapping을 사용해 해당 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을 활용해 유연하고, 실용적인 컨트롤러를 만들었는데 이것이 바로
@RequestMappingAnnotation을 사용하는 Controller이다.
RequestMappingHandlerMapping , RequestMappingHandlerAdapterpackage 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@RequestMappingMapping@RequestMapping 이 붙은 Method 호출ModelAndViewRequestMappingHandlerMapping은 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 등을 사용해 더 편리하게 사용 가능