김영한님의 스프링 MVC 1편 강의를 듣고 정리하는 글입니다
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard
1. 개요
지난 포스팅에서 스프링 MVC와 유사한 MVC 프레임워크를 직접 만들어보았다.
https://soonmin.tistory.com/81
프론트 컨트롤러 패턴을 적용해서 모든 요청은 프론트 컨트롤러가 받고, 핸들러 매핑정보에서 핸들러를 찾아, 핸들러 어뎁터 목록에서 처리할 수 있는 핸들러 어뎁터를 조회하여, 컨트롤러(핸들러)를 호출하고, 뷰리졸버로 뷰의 물리이름을 논리이름으로 처리할 수 있도록 구현했었다.
2. 스프링 MVC 구조
스프링 MVC 구조는 어떤 식으로 되어있는지 보자.
실제 코드는 더욱 복잡하겠지만, 핵심내용은 거의 동일하다. 프론트 컨트롤러가 DispatcherServlet과 같은 역할을 한다.
스프링에서는 모든 요청을 DispatcherServlet에서 처리한다.
3. 핸들러 매핑과 핸들러 어뎁터
스프링에서 다양한 컨트롤러(핸들러)를 지원할 수 있는 이유는 핸들러 매핑과 핸들러 어뎁터의 역할 때문이다. 지난 포스팅에서 정리해 봤지만 중요한 개념이기 때문에 한번 더 정리해 보자.
Controller 인터페이스를 구현한 핸들러
- Bean이름 그대로 핸들러를 찾아주는 방식이다.
- 지금은 거의 사용하지 않는 방식의 컨트롤러이다.
@Component("/springmvc/old-controller")
public class OldController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
System.out.println("OldController.hadnleRequest");
return new ModelAndView("/WEB-INF/views/new-form.jsp");
}
}
애플리케이션을 구동하고 http://localhost:8080/springmvc/old-controller 으로 요청하면 해당 메서드가 실행될 것이다.
위 방식의 핸들러를 처리할 수 있는 이유는 이미 스프링에서 Controller 인터페이스 기반의 핸들러를 처리할 수 있도록 핸들러 어뎁터와 핸들러에 매핑을 구현해 두었기 때문이다.
HadnlerMapping
1. RequestMappingHandlerMapping: 어노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
2. BeanNameUrlHandlerMapping: 스프링 빈의 이름으로 핸들러를 찾음.
HandlerAdapter
1. RequestMappingHandlerAdapter: 어노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
2. SimpleControllerHandlerADAPTER: Controller 인터페이스 방식의 핸들러 처리
스프링 부트는 이외에도 다양한 핸들러 어뎁터와 핸들러매핑을 제공한다.
우선순위대로 처리되기 때문에 순서대로 찾고 없으면 다음으로 넘어간다.
4. 뷰 리졸버
뷰의 논리이름을 반환하도록 구현하였다. 호출해 보면 404가 발생할 것이다.
@Component("/springmvc/old-controller")
public class OldController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
System.out.println("OldController.hadnleRequest");
return new ModelAndView("new-form");
}
}
스프링 부트는 InternalResourceViewResolver라는 뷰 리졸버를 자동으로 등록한다.
이때 프로퍼티에 등록한 설정정보를 사용해서 등록한다.
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
5. 스프링 MVC
이전 포스팅에서 다루었던 회원관리 예제를 가지고, 요즘에 많이 사용하는 방식인 어노테이션 기반의 컨트롤러를 구현해 보자.
a. 어노테이션 기반 컨트롤(클래스 단위)
- 어노테이션 기반으로 작성한 컨트롤러이다.
- url 단위로 클래스로 분리되어 있다. 클래스 별로 분리되어 있기 때문에 사용할 url이 많으면 그만큼 관리해야 할 클래스도 많아지고, 같은 도메인에 해당되는 url이라면 하나의 클래스에서 관리하는 것이 좋은 방법인 것 같다.
- ModelAndView에 여전히 뷰의 논리이름을 주입해서 반환하는 것도 반복적이다.
- 단계별로 개선해 보자.
@Controller
public class SpringMemberFormControllerV1 {
@RequestMapping("/springmvc/v1/members/new-form")
public ModelAndView process() {
return new ModelAndView("new-form");
}
}
@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;
}
}
@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;
}
}
b. 어노테이션 기반 컨트롤러(메서드 단위)
- @RequestMapping을 클래스 단위가 아닌 메서드 단위로 적용했다.
- url을 보면 '/springmvc/v2/members' 중복되어 있었다. @RequestMapping을 클래스 레벨에 적용하면 클래스 레벨의 url + 메서드 레벨의 url으로 적용되어 중복된 url을 하나로 통합할 수 있다.
- "/springmvc/v2/members" + "/new-form" = "/springmvc/v2/members/new-form)
@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);
// Model에 데이터 보관
ModelAndView mv = new ModelAndView("save-result");
mv.addObject("member", member);
return mv;
}
@RequestMapping
public ModelAndView members() {
List<Member> members = memberRepository.findAll();
ModelAndView mv = new ModelAndView("members");
mv.addObject("members", members);
return mv;
}
}
c. 어노테이션 기반 컨트롤러(ModelAndView 제거, HTTP 메서드 분리)
- 요즘 스프링을 공부를 시작한 사람이라면 가장 많이 보는 패턴일 것이다.
- ModelAndView를 생성해서 뷰를 속성에 담고 반환하는 귀찮음을 덜어놓았다.
- HttpRequest에서 파라미터를 꺼내서 사용하지 않고, @RequestParam 어노테이션으로 변수에 바로 주입하도록 함.
- Model에 데이터를 담을 때 메서드 파라미터의 model.addAttribute(key, value)를 사용한다.
- HTTP 메서드 용도에 맞도록 Http 메서드로 구분함.
- @RequestMapping(value = url, method = RequestMethod.GET ) == @GetMapping
@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";
}
}
6. 마무리
지금까지 스프링 MVC의 동작 구조(핸들러 매핑, 핸들러 어댑터, 뷰 리졸버)에 대해 정리하고, 최근에 널리 사용되는 어노테이션 기반의 컨트롤러를 회원 관리 예제와 함께 다뤄보았다.
평소 스프링부트로 프로젝트를 진행하면서, view 이름을 String으로 반환하는데 뷰를 렌더링 하는 이유와 model에 데이터를 담는 방식, DispatcherServlet 등 궁금한 점이 많았는데, 김영한님의 스프링 mvc1편을 들으면서 많은 도움이 됐다.
'Programming > Spring' 카테고리의 다른 글
[Spring] 스프링 MVC 1편(스프링 MVC 기본기) (0) | 2024.04.09 |
---|---|
[Spring] Spring 단위 테스트 작성 (JUnit5, Mockito) (0) | 2024.04.04 |
[Spring] 스프링 MVC 1편(MVC 프레임워크 만들기) (4) | 2024.03.14 |
[Spring] 스프링 MVC 1편(서블릿, JSP MVC 패턴) (1) | 2024.02.27 |
[Spring] 스프링 MVC 1편(서블릿) (0) | 2024.02.02 |