직접 만든 프레임워크와 거의 동일
FrontController → DispatcherServlet
service()
메소드가 호출doDispatch()
가 호출됨서블릿을 제외한 나머지는 모두 인터페이스
HandlerMapping
HandlerAdapter
ViewResolver
View
핸들러 매핑에서 클라이언트 요청에 맞는 컨트롤러(핸들러)를 찾음
해당 핸들러를 지원하는 어댑터를 찾음
어댑터가 핸들러 호출
⇒ 스프링
이 대부분의 필요한 핸들러 매핑과 어댑터를 구현해놨기 때문에 개발자가 직접 만들 일은 거의 없음
RequestMapping
HandlerMapping(구현체)RequestMapping
HandlerAdapter(구현체)
HttpRequestHandlerAdapter
SimpleControllerHandlerAdapter
⇒ 자동 등록된 핸들러 매핑과 어댑터는 작성된 순서를 우선순위로 조회됨
⇒ 실무에서 어노테이션 기반의 컨트롤러가 99%
package org.springframework.web.servlet.mvc;
@FunctionalInterface
public interface Controller {
@Nullable
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
ModelAndView
⇒ 스프링도 예전에는 이렇게 딱딱한 형식의 컨트롤러를 사용했음
스프링 빈 이름의 컨트롤러를 등록
@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 new ModelAndView("new-form");
}
}
빈 이름
은 URL
Controller 인터페이스를 구현
함HandlerMapping을 순서대로 조회
없음
BeanNameUrl
HandlerMapping찾음
SimpleController
HandlerAdapter를 사용해 컨트롤러 실행
어노테이션 기반의 컨트롤러
를 등록
@Controller
@RequestMapping("/springmvc/v3/members")
public class SpringMemberControllerV3 {
private MemberRepository memberRepository = MemberRepository.getInstance();
// HTTP 메소드가 GET 일 때만 동작
// = @GetMapping
@RequestMapping(value = "/new-form", method = RequestMethod.GET)
public String newForm() {
return "new-form";
}
}
HandlerMapping을 순서대로 조회
RequestMapping
HandlerMapping찾음
RequestMapping
HandlerAdapter를 사용해 컨트롤러 실행
package org.springframework.web;
@FunctionalInterface
public interface HttpRequestHandler {
void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException;
}
스프링 빈 이름의 핸들러를 등록
@Component("/springmvc/request-handler")
public class MyHttpRequestHandler implements HttpRequestHandler {
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.printIn("MyHttpRequestHandler.handleRequest");
}
}
HandlerMapping을 순서대로 조회
없음
BeanNameUrl
HandlerMapping찾음
HttpRequest
HandlerAdapter를 사용해 컨트롤러 실행
뷰 리졸버
BeanName
ViewResolverInternalResource
ViewResolverspring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
컨트롤러가 만든 ModelAndView를 처리할 수 있는 뷰 리졸버를 찾음 (DispatcherServlet)
ViewResolver를 순서대로 조회
없음
InternalResource
ViewResolver찾음
=> InternalResource
View 리턴→ 라이브러리만 추가하면 스프링부트가 자동으로 추가해줌
@Controller
public class SpringMemberFormControllerV1 {}
@Controller
public class SpringMemberListControllerV1 {}
@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);
memberRepository.save(member);
ModelAndView mv = new ModelAndView("save-result");
mv.addObject("member", member);
return mv;
}
}
addObject
()ModelAndView를 리턴
@RequestMapping
어노테이션메소드 단위가 아닌 클래스 단위로 적용
→ 3개의 컨트롤러를 하나의 컨트롤러로 통합함
@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 list() {
List<Member> members = memberRepository.findAll();
ModelAndView mv = new ModelAndView("members");
mv.addObject("members", members);
return mv;
}
}
클래스의 @RequestMapping
메소드의 @RequestMapping
- 각 요청에 해당하는 세부 URL 맵핑
→ @RequestMapping은 메소드 단위로 적용됨
cf) 하나의 컨트롤러로 통합하고 메소드 레벨에서만 어노테이션을 지정해도 가능하긴 함
→ 중복 코드가 존재하므로 권장하진 않음
@RequestMapping("/springmvc/v2/members/save")
public ModelAndView save(){}
@Controller
@RequestMapping("/springmvc/v3/members")
public class SpringMemberControllerV3 {
private MemberRepository memberRepository = MemberRepository.getInstance();
// HTTP 메소드가 GET 일 때만 동작
// = @GetMapping
@RequestMapping(value = "/new-form", method = RequestMethod.GET)
public String newForm() {
return "new-form";
}
// HTTP 메소드가 POST 일 때만 동작
@PostMapping("/save")
public String save(
// =request.getParameter("userName")
// =Integer.parseInt(request.getParameter("age"))
@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 list(Model model) {
List<Member> members = memberRepository.findAll();
model.addAttribute("members", members);
return "members";
}
}
리턴 타입
viewName
String으로 viewName만 받아도 뷰의 논리주소로 인식함
→ ModelAndView를 반환하는게 아니므로 매개변수로 model을 받아야 함
매개 변수
@RequestParam(object) Object object
Model
@GetMapping
& @PostMapping