
목차
1. 스프링 MVC 전체 구조
2. 핸들러 매핑과 핸들러 어댑터
3. 뷰 리졸버
4. 스프링 MVC- 시작하기
5. 스프링 MVC- 컨트롤러 통합
6. 스프링 MVC- 실용적인 방식
7. 정리

DispatcherServlet 도 부모클래스에서 HttpServlet을 상속 받아 사용하고 서블릿으로 동작한다DispatcherServlet을 서블릿으로 자동으로 등록하면서 모든 경로 urlPatterns="/" 에 대해서 매핑한다.요청 흐름
HttpServlet이 제공하는 service() 가 호출된다.DispatcherServlet의 부모인 FrameworkServlet 에서 service()를 오버라이드 해두었다.FrameworkServlet.servcie()를 시작으로 여러 메서드가 호출 되면서 DispatcherServlet.doDispatch()가 호출된다.DispatchServlet.doDispatch()
protected void doDistpatch(HttpSerlvetReuqest request, HttpSerlvetResponse response) throws Exception{
HttpServletRequest processRequest= request;
HandlerExcutionChain mappedHandler= null;
ModelAndView mv= null;
// 1. 핸들러 조회
mappedHandelr= getHandelr(processedRequest);
if(mappedHandelr ==null){
noHandlerfound(processedReqest, 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, HttpSrvletResponse response, HandelrExecutionChain mappedHandler, ModelAndview mv, Exception exception) throws Exception{
render(mv, reqeust, response);
//렌더 호출
}
protected void render(ModelAndView mv, HttpServlet request, HttpServletResponse response) throws Exception{
View view;
String viewName = mv.getViewName();
//6. 뷰 리졸버를 통해 뷰 찾기 7. View 반환
view = resolveViewName(viewName, mv.getMdodelInternal(), locale,
request);
view.render(mv.getModelInternal(),request,response);
}
}
동작 순서
1. 핸들러 조회 : 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러(컨트롤러)를 조회한다. (스프링은 URL 뿐만 아니라 HTTP의 Header에 있는 정보도 활용할 수 있다, 우리가 앞서 한 예시 보다 훨씬 고도화가 되어 있다. )
2. 핸들러 어댑터 조회 : 핸들러를 실행할 수 있는 핸들러 어댑터를 조회한다.
3. 핸들러 어댑터 실행:
4. 핸들러 실행
5. ModelAndView 반환 : 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView로 변환 후 반환하다.
6. viewResolver 호출 :
7. View 변환 : 뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 바꾸고, 렌더링 역할을 담당하는 뷰 객체를 반환한다.
8. 뷰 렌더링 : 뷰를 통해 뷰를 렌더링한다.
인터페이스 살펴보기
DispatcherServlet 코드의 변경 없이, 원하는 기능을 변경하거나 확장할 수 있다는 점이다. 지금까지 설명한 대부분을 확장 가능한 인터페이스로 제공한다.DispatcherServlet 에 등록하면 본인만의 컨트롤러 또한 만들 수 있다.주요 인터페이스 목록
org.springframework.web.servlet.HandlerMappingMap 으로 구현 했지만 인터페이스로 작동한다.org.springframework.web.servlet.ViewResolverorg.springframework.web.servlet.View정리
스프링 MVC는 코드 분량도 많고, 복잡해서 내부 구조를 파악하는 것은 쉽지 않다. 사실 해당 기능을 직접 확장하거나 나만의 컨트롤러를 만드는 일은 없다. 대부분의 기능은 이미 구현이 다 되어 있다. 허나 핵심 동작 방식을 알아두어야 향후 문제가 발생시 어떤 부분에서 문제가 발생했는지를 쉽게 파악하고, 문제를 해결할 수 있다. 아울러 확장 포인트가 필요할 때 어떤 부분을 확장해야 할지 감을 잡을 수 있다. 실제 다른 컴포넌트를 제공하거나 기능을 확장하는 부분은 이후 강의에서 설명되어질 예정이다.
어노테이션이 나오기 전 Controller Interface로 컨트롤와 핸들러 매핑 어댑터를 이해 해보자.
과거 버전 스프링 컨트롤러
org.springfreamwork.web.servlet.mvc.Controller
public interface Controller{
ModelAndView handleRequest(HttpServletRequest request, HttpServeletResponse response) throw Exception;
}
어노테이션 Controller와는 전혀 다른 개념의 Interface
@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;
}
}
HandlerMapping(핸들러 매핑)
HandlerAdpater(핸들러 어댑터)
Controller 인터페이스를 실행할 수 있는 핸들러 어댑터를 찾고 실행해야 한다.스프링은 이미 필요한 매핑과 핸들러 어댑터를 대부분 구현 해놓았다. 개발자가ㅏ 직접 핸들러 매핑과 핸들러 애댑터를 만드는 일은 거의 없다.
0 = RequestMappingHandlerMapping : 애노테이션 기반의 컨트롤러인 @RequestMapping 에서 사용
//통상 사용하는 메서드
1= BeanNameUrlHandlerMapping : 스프링 빈의 이름으로 핸들러를 찾는다.
//이번 예시에서 쓰는 메서드
0 = RequestMappingHandlerAdapter :어노테이션 기반의 @RequestMapping 에서 사용
1 = HttpRequestHandlerAdapter : HttpRequestHandler 처리
2 = SimpleControllerHandlerAdapter : Controller 인터페이스 (애노테이션X, 과거에 사용 ) 처리
핸들러 매핑도, 핸들러 어댑터도 모두 순서대로 찾고 만약 없으면 다음 순서로 넘어간다.
HandlerMapping 을 순서대로 실행해서, 핸들러를 찾는다BeanNameUrlHanderMapping이 실행에 성공하고 핸들러인 OldController를 반환한다HandlerAdapter의 supports()를 순서대로 호출한다.SimpleControllerHandlerAdapter가 Controller 인터페이스를 지원하므로 대상이 된다.SimpleControllerHandlerAdapter는 핸들러인 OldeController 를 내부에서 실행하고, 그 결과를 반환한다정리 - OldController 핸들러 매핑, 어댑터
OldeController를 실행하며 사용된 객체
HandlerMapping=BeanNameUrlHanlderMapping
HandlerAdapter=SimpleControllerHandlerAdapter
Controller 인터페이스가 아닌 다른 핸들러를 써보는데
HttpRequestHandler(컨트롤러)는 서블릿과 가장 유사한 형태 의 핸들러다.
public interface HttpRequestHandler{
void handleRequest(HttpServelrRequest request, HttpservletResponse response)throws ServeletException, IOExceoption
}
@Component("/springmvc/reuqest-handler")
pbulic class HttpRequestHandler implements
HttpRequestHanlder{
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)throws IOE, ServletException{
}
실행 흐름은 Old-Controller 와 똑같다
MyHttpRequestHandler를 사용하며 실행된 객체
HandlerMapping=BeanNameUrlHandlerMapping
HandlerAdpater=HttpRequstHandlerAdpater
@RequestMapping
RequestMappingHandlerMappingRequestmappingHandlerAdapter@RequestMapping의 앞글자를 따 만든 이름이며, 현재 가장 실무에서 제일 많이 쓰이는 애노테이션이다. 조회할 수 있도록 변경 해본다
@Component("/srpingmvc/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");
}
}
view를 사용할 수 있도록 다음 코드가 변경 되었다
return new ModelAndView("new-form)
http://localhost?:8080/springmvc/old-controller로 실행 하면 웹브라우저에 Whitelabel Error Page가 나오rㅗ 콘솔에OldeController만 출력이 된다.
application.properties에 아래 코드를 추가하게 되면 뷰가 제대로 작동하게 된다.
spring.mvc.view.prefix=/WEB_INF/view/
spring.mvc.vie.suffix=.jsp
어떻게 이렇게 작동하는 거지?
스프링 부트는
InternalResourceViewResolver라는 뷰 리졸버를 자동으로 등록하는데, 이때application.properties에 등록한spring.mvc.view.prefix,spring.mvc.view.suffix설정 정보를 사용해서 등록한다
권장하지 않는 방법으로 아래 방식으로 작동하기도 한다.
return new ModelAndView("/WEB-INF/view/new-form.jsp");
실행
폼은 나오지만
save같은 기능은 제대로 작동하지 않는다
@Bean
InternalResourceViewResolver internalResourceViewResolver(){
return new InternalResourceViewReslver("/WEB-INF/view/","jsp")
}
스프링 부트가 위 코드를 대신 기능해준다.
실제로는 더 많다
1 = BeanNameViewResolver : 빈 이름으로 찾아서 반환
2 = InternalResourceViewResolver : JSP를 처리할 수 있는 뷰 반환
우리가 작성한 예제에는 new-form 이라는 빈이 없으므로 JSP를 반환할 수 있는 뷰가 작동된다
1. 핸들러 어댑터 호출
핸들러 어댑터를 통해 new-form 이라는 논리 뷰 이름을 획득한다.
2. ViewResolver 호출
new-form 이라는 뷰 이름으로 viewResolver를 순서대로 호출한다.
BeanNameViewResolver 는 new-form 이라는 이름의 스프링 빈으로 등록된 뷰를 찾아야 하는데 없다. InternalResourceViewResolver 가 호출된다.
3. InternalResourceViewResolver
이 뷰 리졸버는 InternalResourceView 를 반환한다.
4. 뷰 - InternalResourceView
InternalResourceView 는 JSP처럼 포워드 forward() 를 호출해서 처리할 수 있는 경우에 사용한다.
5. view.render()
view.render() 가 호출되고 InternalResourceView 는 forward() 를 사용해서 JSP를 실행한다
스프링의 컨트롤러는
어노테이션기반으로 작동하는데, 매우 유연하고 실용적이다.
@RequestMapping
RequestMappingHandlerMappingRequestMappingHandlerAdapter핸들러를 식별하고, 핸들러에 맞는 어답터를 실행하는 사용 빈도가 가장 높은 어노테이션 기반의 컨트롤러를 지원하는 핸들러 매핑과 어댑터이다.
거의 모든 실무에서 이 핸들러와, 어답터를 사용한다.
@Controller
public class SrpingMemberFormControllerV1 {
@RequestMapping("/springmvc/v1/members/new-form")
public ModelAndView process() {
return new ModelAndView("new-form");
}
@Controller
@Component상속받아 컴포넌트 스캔의 대상)RequestMappingHandlerMapping 의 대상이 되어 매핑 정보로 인식한다.@RequestMapping
ModelAndView@RequestMapping 을 잘 보면 클래스 단위가 아니라 메서드 단위에 적용이 되었는데, 컨트롤러 클래스를 유연하게 하나로 통합할 수 있다.
통합을 넘어 조합도 할수 있다.
SpringMemberControllerV2
@Controller
@RequestMapping("/springmvc/v2/members")
public class SpringMemberControllerV2 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@RequestMapping("/new-form")
public ModelAndView process() {
return new ModelAndView("new-form");
}
@RequestMapping("/save")
public ModelAndView process(HttpServletRequest request, HttpServletResponse response) {
String username = request.getParameter("username");
// 이전에 해왔 듯 HttpServletRequest.getParametr()로 꺼내는 게 아니라 X // 위의 정보는 프론트 컨트롤러에서 처리하고
// 파람 맵에다가 요청 파라메터 정보를 전부 담아 넘겨줄 것이다.
// 여기서는 단순히 꺼내서 쓰기만 하면 된다.
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 process(Map<String, String> paramMap) {
List<Member> members = memberRepository.findAll();
ModelAndView mv = new ModelAndView("members");
mv.addObject("members", members);
return mv;
}
}
각기 흩어져 있던 메서드들이 전부 하나의 클래스로 통합되었다.
클래스 레벨에RequestMapping을 태그해 매핑을 조합해서 쓸수 있다.
v3에서
ModelView를 개발자가 직접 생성해서 반환하
@RequestParam 사용@RequestMapping -> @GetMapping @PostMapping@Controller
@RequestMapping("/springmvc/v3/members")
public class SrpingMemberControllerV3 {
private MemberRepository memberRepository = MemberRepository.getInstance();
// @RequestMapping(value = "/new-form",method = RequestMethod.GET)
@GetMapping("/new-form")
public String newForm() {
return "new-form";
}
@RequestMapping("/save")
public String 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);
model.addAttribute("member", member);
return "save-result";
}
// @RequestMapping(method = RequestMethod.GET)
@GetMapping
public String members(Model model) {
List<Member> members = memberRepository.findAll();
model.addAttribute("members",members);
ModelAndView mv = new ModelAndView("members");
mv.addObject("members", members);
return "members";
}
}
RequestParam 은 request.getParameter와 같다@RequestMapping(value="new-form",method = RequestMethod.GET) 을 @GetMapping("/new-form")으로 축약할 수 있다.요약
1. 스프링 MVC 프레임워크 전체 구조
- 스프링 MVC는 DispatcherServlet을 중심으로 동작한다.
- DispatcherServlet은 핸들러 매핑, 핸들러 어댑터, 뷰 리졸버 등을 이용하여 요청을 처리한다.
- 주요 인터페이스로 `HandlerMapping`, `HandlerAdapter`, `ViewResolver`, `View`가 있다.
핸들러 매핑과 핸들러 어댑터
뷰 리졸버
InternalResourceViewResolver를 통해 JSP와 같은 뷰를 해석하고 처리한다.스프링 MVC 시작하기
@Controller와 @RequestMapping을 이용하여 컨트롤러를 작성한다.스프링 MVC 컨트롤러 통합
@RequestMapping을 사용하여 요청을 조합한다.스프링 MVC 실용적인 방식
Model을 사용하여 데이터를 전달하고, View의 이름을 직접 반환한다.suffix,prefix 자동으로 추가@RequestParam을 사용하여 요청 파라미터를 받아온다.@GetMapping과 @PostMapping을 사용하여 GET과 POST 요청을 각각 처리한다.