FrontController
패턴으로 구현된 DispatcherServlet
이 구현되어있음.
DispatcherServlet의 구조
DispatcherServlet
가 바로 스프링의 프론트 컨트롤러. DispatcherServlet
도 부모 클래스에서 HttpServlet
을 상속받아서 사용하고, 서블릿으로 동작. DispatcherServlet
을 서블릿으로 자동 등록하면서 모든경로(urlPatterns="/")에 대해 매핑한다요청 흐름
//doDispatch method 의 호출
protected void doDispatch(HttpServletRequest request,
HttpServletResponse response) throws Exception {
//1. 핸들러 조회 Handler,
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) throw 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);
}
spring의 주요 인터페이스 : HandlerMapping , HandlerAdapter, ViewResolver, View
Spring은 오랜 기간 개발되어 왔다. 대부분의 개발자들이 필요하는 기능은 개발되어 있다. 따라서 내가 필요한 기능들이 웬만하면 다 구현되어있다. 필요한 핸들러 매핑과 핸들러 어댑터등은 대부분 구현되어있음.
실제로는 더 많지만, 중요한것 위주로 살펴보자.
HandlerMapping
0 = RequestMappingHandlerMapping - 애노테이션 기반의 컨트롤러에서 사용 (최근엔 거의 전부 이방식)
1 = BeanNameUrlHandlerMapping - 스프링 빈의 이름으로 핸들러를 찾는다.
HandlerAdapter
0 = RequestMappingHandlerAdapter : 애노테이션 기반의 컨트롤러에서 사용
1 = HttpRequestHandlerAdapter : HttpRequestHandler 처리
2 = SimpleControllerHandlerAdapter : Controller 인터페이스(애노테이션 x, 옛날에 사용) 처리
핸들러 매핑, 어댑터들을 순서대로 매칭을 찾고, 없으면 다음으로 넘어간다.
실제로는 현재 @RequestMapping의 앞글자를 딴 RequestMappingHandlerMapping 과 RequestMappingHandlerAdpater를 거의 99.9퍼 사용한다고 보면 된다.
스프링부트는 InternalResourceViewResolver
라는 뷰 리졸버를 자동으로 등록하는데, 이때
application.yml(properties) 설정파일에 등록한 spring.mvc.view.prefix & suffix 설정정보를 사용해서 등록.
1 = BeanNameViewResolver : 빈 이름으로 뷰를 찾아서 반환한다.
2 = InternalResourceViewResolver : JSP를 처리할 수 있는 뷰를 반환한다.
참고
다른 뷰는 실제 뷰를 렌더링, JSP의 경우forward()
를 통해서 해당 jSP로 이동(실행)해야 렌더링 된다. JSP를 제외한 나머지 뷰 템플릿들은forward()
과정 없이 바로 렌더링됨.
Thymeleaf 뷰 템플릿을 사용하면ThymeleafViewResolver
를 등록해야함. 최근에는 라이브러리만 추가하면 스프링 부트가 이런 작업도 모두 자동화 해준다.
스프링이 제공하는 컨트롤러는 애노테이션 기반으로 동작해서, 매우 유연하고 실용적. 과거에는 자바 언어에 애노테이션이 없기도 했고, 스프링도 처음부터 이런 유연한 컨트롤러를 제공한 것은 아니다. 그 이후 스프링 WEB이 평정해버렸고, 거의 다른 프레임워크를 찾기 힘들어짐.
@RequestMapping
스프링은 애노테이션을 활용한 매우 유연하고 실용적인 컨트롤러. 사용하기도 쉬우며 간편하다.
@Controller
public class MemberController () {
@RequestMapping("/springmvc/members/new-form")
public ModelAndView process() {
return new ModelAndView("new-form");
}
@RequestMapping("/springmvc/members/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;
}
}
@Controller
@RequestMapping
ModelAndView
@Controller = @Component , @RequestMapping 이 붙어 있는것과 같은 것이라고 보면 된다.
훨씬 더 간편하고, 이름을 지어줘서 더 확실하게 인식 가능.
이 외에도 @RequestMapping을 붙이고, 따로 @Bean으로 등록도 가능하다.
클래스 단위에 @RequestMapping("/spring/v2") 이렇게 붙여주면 그 밑의 메서드에 경로를 추가해주는 역할. 중복되는 패스는 미리 클래스에 추가해놓을 수 있음
ex ) http://localhost:8080/spring/v2
사실 위에 MovdelView를 직접 보낼 필요도 없다. 이미 Spring은 이런 것을 다 처리 시켜준다.
@Controller
@RequestMapping("/springmvc/member")
public class MemberController () {
@RequestMapping("/new-form")
public String newForm() {
return "new-form";
}
@RequestMapping("/save")
public String save(@RequestParam String username,
@RequestParam int age,
Model model) {
Member member = new Member(username,age);
memberRepository.save(member);
model.addAttribute("member", member);
return "save-result";
}
}
위의 방식과 차이가 보이는가? ModelAndView를 직접 보낼필요 없이 String type의 리턴으로 그저 ModelAndView의 물리적 경로의 이름을 넣으면 바로 연결된다.
거기다 HttpServletResponse와 HttpServletRequest를 받을 필요가 없이 바로 파라미터로 쿼리 파라미터를 받을 수 있다. 심지어 Type에 대한 처리도 전부 스프링이 처리해준다.
하지만 이 방식에서도 조금 고칠 수 있는 방법이 있다.
@RequestMapping
의 HTTP Method를 정확하게 알기 힘들다. GET, POST뿐 아니라 HTTP API를 사용할시 DELETE , PUT, PATHCH등까지 사용할수 있는데 그것을 어떻게 하는가?
@RequestMapping(value = "/save", method = RequestMethod.GET)
이런 식으로 뒤에 method가 무슨 HTTP method인지 알려 줄 수있다. 하지만 이것은 너무 길고 적어야 할게 많다.
@GetMapping, @PostMapping 등을 사용하면 된다. 딱 봐도 무엇을 의미하는지 알 수 있다.
GetMapping은 RequsetMapping의 get버전이고, PostMapping은 post 버전이다.
우리는 이것을 사용하면 된다.
참고 : 본 글은 김영한님의 스프링 강의를 정리한 것이다.