그래도 이렇게 핵심 동작방식을 알아두어야 향후 문제가 발생했을 때 어떤 부분에서 문제가 발생했는지 쉽게 파악하고, 문제를 해결할 수 있다. 그리고 확장 포인트가 필요할 때, 어떤 부분을 확장해야 할지 감을 잡을 수 있다. 실제 다른 컴포넌트를 제공하거나 기능을 확장하는 부분들은 강의를 진행하면서 조금씩 설명하겠다. 지금은 전체적인 구조가 이렇게 되어 있구나 하고 이해하면 된다.
핸들러 매핑과 핸들러 어댑터
1 2
command +B // 선택된 함수 어디에서 호출되는지 조회가능 //단축키 활용해서 구조확인하자
핸들러 매핑과 핸들러 어댑터가 어떤 것들이 어떻게 사용되는지 알아보자.
지금은 전혀 사용하지 않지만, 과거에 주로 사용했던 스프링이 제공하는 간단한 컨트롤러로 핸들러 매핑과 어댑터를 이해해보자. (에너테이션 사용전)
Controller 인터페이스
org.springframework.web.servlet.mvc.Controller
과거 버전 스프링 컨트롤러
OldController
1 2 3 4 5 6 7 8
@Component("/springmvc/old-controller") //springbean에 이름을 url패턴과 맞춤 publicclassOldControllerimplementsController{ @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)throws Exception { System.out.println("OldController.handleRequest"); returnnull; } }
@Component : 이 컨트롤러는 /springmvc/old-controller 라는 이름의 스프링 빈으로 등록되었다.
빈의 이름으로 URL을 매핑할 것이다
Controller 인터페이스는 @Controller 에노테이션과 전혀다르다
이 컨트롤러가 어떻게 호출되었을까?
HandlerMapping(핸들러 매핑) 핸들러 매핑에서 이 컨트롤러를 찾을 수 있어야 한다. 예) 스프링 빈의 이름으로 핸들러를 찾을 수 있는 핸들러 매핑이 필요하다.
HandlerAdapter(핸들러 어댑터) 핸들러 매핑을 통해서 찾은 핸들러를 실행할 수 있는 핸들러 어댑터가 필요하다. 예) Controller 인터페이스를 실행할 수 있는 핸들러 어댑터를 찾고 실행해야 한다.
스프링 부트가 자동 등록하는 핸들러 매핑과 핸들러 어댑터(!!!)
실제로는 더 많지만, 중요한 부분위주로 설명하기위해 일부생략
따로 핸들러 매핑과 핸들러 어댑터를 만들필요없음
HandlerMapping
위에부터 우선순위순
1 2
0 = RequestMappingHandlerMapping // : 애노테이션 기반의 컨트롤러인 @RequestMapping에서 1 = BeanNameUrlHandlerMapping// : 스프링 빈의 이름으로 핸들러를 찾는다.
HandlerAdpter
위에부터 우선순위순
1 2 3
0 = RequestMappingHandlerAdapter //애노테이션 기반의 컨트롤러인 @RequestMapping에서ㅎ 1 = HttpRequestHandlerAdapter //HttpRequestHandler 처리 2 = SimpleControllerHandlerAdapter //Controller 인터페이스(애노테이션X, 과거에 사용) 처리
처리순서
핸들러 매핑으로 핸들러 조회 HandlerMapping 을 순서대로 실행해서, 핸들러를 찾는다.이경우빈이름으로핸들러를찾아야하기때문에이름그대로빈이름으로핸들러를찾아주는 BeanNameUrlHandlerMapping 가 실행에 성공하고 핸들러인 OldController 를 반환한다.
핸들러 어댑터 조회 HandlerAdapter 의 supports() 를 순서대로 호출한다. SimpleControllerHandlerAdapter 가 Controller 인터페이스를 지원하므로 대상이 된다.
핸들러 어댑터 실행 디스패처 서블릿이 조회한SimpleControllerHandlerAdapter 를 실행하면서 핸들러 정보도 함께 넘겨준다. 그다음 SimpleControllerHandlerAdapter 는 핸들러인 OldController 를 내부에서 실행하고, 그 결과 반환한다.
정리 - OldController 핸들러매핑, 어댑터
OldController 를 실행하면서 사용된 객체는 다음과 같다.
HandlerMapping = BeanNameUrlHandlerMapping
HandlerAdapter = SimpleControllerHandlerAdapter
@RequestMapping
조금 뒤에서 설명하겠지만, 가장 우선순위가 높은 핸들러 매핑과 핸들러 어댑터는RequestMappingHandlerMapping , RequestMappingHandlerAdapter 이다. @RequestMapping 의 앞글자를 따서 만든 이름인데, 이것이 바로 지금 스프링에서 주로 사용하는 애노테이션 기반의 컨트롤러를 지원하는 매핑과 어댑터이다. 실무에서는 99.9% 이 방식의 컨트롤러를 사용한다.
뷰 리졸버
위에 OldController에 코드추가한다
1 2 3 4 5 6 7 8
@Component("/springmvc/old-controller") //springbean에 이름을 url패턴과 맞춤 publicclassOldControllerimplementsController{ @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)throws Exception { System.out.println("OldController.handleRequest"); returnnew ModelAndView("new-form"); } }
스프링 부트는 InternalResourceViewResolver 라는 뷰 리졸버를 자동으로 등록하는데, 이때application.properties 에 등록한spring.mvc.view.prefix , spring.mvc.view.suffix 설정 정보를 사용해서 등록한다.
참고로 권장하지는 않지만 설정 없이 다음과 같이 전체 경로를 주어도 동작하기는 한다. return new ModelAndView(“/WEB-INF/views/new-form.jsp”);
정상 동작하는 것을 확인할수있다
뷰 리졸버 동작 방식
스프링 부트가 자동 등록하는 뷰 리졸버
(일부생략)
1 2
1 = BeanNameViewResolver : 빈 이름으로 뷰를 찾아서 반환한다. (예: 엑셀 파일 생성 기능에 사용) 2 = InternalResourceViewResolver : JSP를 처리할 수 있는 뷰를 반환한다.
핸들러 어댑터 호출 핸들러 어댑터를 통해 new-form 이라는 논리 뷰 이름을 획득한다.
ViewResolver 호출 new-form 이라는 뷰 이름으로 viewResolver를 순서대로 호출한다. BeanNameViewResolver 는 new-form 이라는 이름의 스프링 빈으로 등록된 뷰를 찾아야 하는데 없다. InternalResourceViewResolver 가 호출된다.
InternalResourceViewResolver 이 뷰 리졸버는 InternalResourceView 를 반환한다.
뷰 - InternalResourceView InternalResourceView 는 JSP처럼 포워드forward() 를 호출해서 처리할 수 있는 경우에 사용한다.
view.render() view.render() 가 호출되고 InternalResourceView 는 forward() 를 사용해서 JSP를 실행한다.
참고> InternalResourceViewResolver 는 만약 JSTL 라이브러리가 있으면 InternalResourceView 를 상속받은 JstlView 를 반환한다. JstlView 는 JSTL 태그 사용시 약간의 부가 기능이 추가된다.
참조> 다른 뷰는 실제 뷰를 렌더링하지만, JSP의 경우 forward() 통해서 해당 JSP로 이동(실행)해야 렌더링이 된다. JSP를 제외한 나머지 뷰 템플릿들은 forward() 과정 없이 바로 렌더링 된다.
참조> Thymeleaf 뷰 템플릿을 사용하면 ThymeleafViewResolver 를 등록해야 한다. 최근에는 라이브러리만 추가하면 스프링 부트가 이런 작업도 모두 자동화해준다.
스프링 MVC - 시작하기
@RequestMapping
스프링은 애노테이션을 활용한 매우 유연하고, 실용적인 컨트롤러를 만들었는데 이것이 바로 @RequestMapping 애노테이션을 사용하는 컨트롤러이다. 과거에는 스프링 프레임워크가 MVC 부분이 약해서 스프링을 사용하더라도 MVC 웹 기술은스트럿츠 같은 다른 프레임워크를 사용했었다. 그런데@RequestMapping 기반의 애노테이션 컨트롤러가 등장하면서, MVC 부분도 스프링의 완승으로 끝이 났다.
@RequestMapping
RequestMappingHandlerMapping
RequestMappingHandlerAdapter
앞서 보았듯이 가장 우선순위가 높은 핸들러 매핑과 핸들러 어댑터는 RequestMappingHandlerMapping , RequestMappingHandlerAdapter 이다.
@RequestMapping 의 앞글자를 따서 만든 이름인데, 이것이 바로 지금 스프링에서 주로 사용하는 애노테이션 기반의 컨트롤러를 지원하는 핸들러 매핑과 어댑터이다. 실무에서는 99.9% 이 방식의 컨트롤러를 사용한다.
변경(기존 프레임워크 → 스프링MVC)
지금까지 만들었던 프레임워크에서 사용했던 컨트롤러를@RequestMapping 기반의 스프링 MVC 컨트롤러 변경해보자.
SpringMemberFormControllerV1 - 회원 등록 폼
1 2 3 4 5 6 7 8 9 10 11 12 13
@Controller publicclassSpringMemberFormControllerV1{ @RequestMapping("/springmvc/v1/members/new-form") public ModelAndView process(){ returnnew ModelAndView("new-form"); } }@Controller publicclassSpringMemberFormControllerV1{ @RequestMapping("/springmvc/v1/members/new-form") public ModelAndView process(){ returnnew ModelAndView("new-form"); } }
3가지가 필요하다(빈 등록, 컨트롤러 조회 맵 등록, 지원하는 어뎁터 조회 가능 조건)
@Controller
스프링이 자동으로 스프링 빈으로 등록한다. (내부에 @Component 애노테이션이 있어서 컴포넌트스캔의 대상이 됨)
스프링 MVC에서 애노테이션 기반 컨트롤러로 인식한다.
@RequestMapping
요청 정보를 매핑한다. 해당 URL이 호출되면 이 메서드가 호출된다. 애노테이션을 기반으로 동작하기 때문에, 메서드의 이름은 임의로 지으면 된다.
ModelAndView
모델과 뷰 정보를 담아서 반환하면 된다.
중요한점(!!!)
RequestMappingHandlerMapping 은 스프링 빈 중에서 @RequestMapping 또는 @Controller 가 클래스 레벨에 붙어 있는 경우에 매핑 정보로 인식한다. 즉 아래와 같이 작성하여도 인식한다.
1 2 3 4 5 6 7 8
@Component//컴포넌트 스캔을 통해 스프링 빈으로 등록 @RequestMapping publicclassSpringMemberFormControllerV1{ @RequestMapping("/springmvc/v1/members/new-form") public ModelAndView process(){ returnnew ModelAndView("new-form"); } }
빈을 직접 등록해도 동작가능하다.
1 2 3 4 5 6 7
@RequestMapping publicclassSpringMemberFormControllerV1{ @RequestMapping("/springmvc/v1/members/new-form") public ModelAndView process(){ returnnew ModelAndView("new-form"); } }
1 2 3 4 5
//ServletApplication에서 스프링 빈 직접 등록 @Bean SpringMemberFormControllerV1 springMemberFormControllerV1(){ returnnew SpringMemberFormControllerV1(); }
SpringMemberSaveControllerV1 - 회원 저장
똑같이 클래스앞에 @Controller와 메서드앞에@RequestMapping(”/springmvc/v1/members/ssave”) 해주면된다.
/** * v3 * Model 도입 * ViewName 직접 반환 * @RequestParam 사용 * @RequestMapping -> @GetMapping, @PostMapping */ @Controller @RequestMapping("/springmvc/v3/members") publicclassSpringMemberControllerV3{ 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"; } }
Model 파라미터
save() , members() 를 보면 Model을 파라미터로 받는 것을 확인할 수 있다. 스프링 MVC도 이런 편의기능을 제공한다.
ViewName 직접 반환
뷰의 논리 이름을 반환할 수 있다.
@RequestParam 사용
스프링은 HTTP 요청 파라미터를 @RequestParam 으로 받을 수 있다. @RequestParam(“username”) 은 request.getParameter(“username”) 와 거의 같은 코드라 생각하면 된다. 물론 GET 쿼리 파라미터, POST Form 방식을 모두 지원한다.
@RequestMapping @GetMapping, @PostMapping
@RequestMapping 은 URL만 매칭하는 것이 아니라, HTTP Method도 함께 구분할 수 있다. 예를 들어서 URL이 /new-form 이고, HTTP Method가 GET인 경우를 모두 만족하는 매핑을 하려면 다음과 같이 처리하면 된다.