spring MVC

ssongkim·2022년 1월 4일
1
post-thumbnail
post-custom-banner

앞서 Apache, Tomcat, 서블릿, MVC패턴, 프론트 컨트롤러 패턴과 DispatcherServlet에 대해 알아보았다. 첫 게시글부터 쭉 읽어보자..!
이번 시간에는 스프링이 클라이언트의 요청을 어떻게 처리하는지를 알아보며 spring MVC를 이해해보겠다.

spring MVC란

스프링 MVC는 스프링이 제공하는 MVC패턴을 따르는 서블릿 기반의 프레임워크다. 스프링 MVC는 유연한 아키텍쳐를 가지고 있으며, 장기적으로 많은 인원이 큰 규모의 시스템을 개발할 때 적합한 프레임워크다.

Spring Container

서블릿 컨테이너는 서블릿 인스턴스의 생명주기를 관리하는 역할을 한다면 스프링 컨테이너는 스프링 빈의 생명 주기를 관리하여 Spring 프레임워크의 특징인 IOC(제어역전)DI(의존성주입)을 제공해주는 역할한다. 스프링 컨테이너에서 관리하는 자바 객체를 스프링 빈이라고 한다.
스프링 컨테이너는 빈의 scope 옵션에 따라 다르지만 디폴트로는 서블릿 컨테이너 처럼 객체를 싱글톤으로 관리한다.
XML 방식의 설정파일이나 자바 방식의 설정파일을 통해 스프링 컨테이너를 생성해서 사용한다.
스프링 컨테이너에 대해서는 다음 장에서 자세히 이해해보도록 하겠다.

spring MVC 구조

spring MVC는 이와같은 구조를 가지고 있다. 뭔가 복잡복잡해 보인다. 톰캣에서부터 spring MVC에서 클라이언트의 요청을 어떻게 처리하는지 알아보며 그 구조를 이해해보자..!

톰캣에서부터 클라이언트의 요청 처리 과정


톰캣에서부터 spring MVC의 클라이언트의 요청 처리 단계는 다음과 같다.

1. Client -> WAS의 웹서버로 요청한다
2. WAS의 웹서버 -> Servlet container로 전달한다
3. Servlet container는 각 요청 별로 쓰레드 생성한다
4. Servlet container에 DispatcherServlet 서블릿 인스턴스가 존재하지 않을 경우 DispatcherServlet 생성하며 init() 호출한다
5. 생성된 쓰레드에서 DispatcherServlet service() 메서드 호출한다
6. DispatcherServlet는 HandlerMapping을 통해 요청을 매핑할 핸들러를 조회한다
7. 조회한 핸들러를 호출해 요청을 처리할 hander adapter을 구한다.
8. HandlerAdapter를 통해 매핑한 핸들러를 호출하여 요청을 처리한다
9. ViewResolver은 ViewName을 통해 알맞는 View를 찾는다.(핸들러가 ViewName을 리턴한 경우)
10. DispatcherServlet는 응답할 뷰의 Render를 지시하고 뷰는 로직을 처리한다
11. DispatcherServlet는 클라이언트에게 Rendering된 뷰를 응답한다

DispatcherServlet은 스프링 프레임워크에서 제공하는 Handler Mapping, Handler Adapter, View Resolver 전략 인터페이스들의 구현체를 받아 사용한다.
해당 빈들은 스프링 컨테이너에서 받아 사용하는데 전략 인터페이스의 빈 생성은 Handler Mapping, Handler Adapter 의 경우 <mvc:annotation-driven/>이나 자바설정파일의@EnableMvc 를 통해서, View Resolver는 스프링 컨테이너 설정 파일 내에서 bean태그를 통해 정의되어 DispatcherServlet이 initialize 되는 시점이다.

DispatcherServlet

SpringMVC에서 제공해 주고 있는 DispatcherServlet은 FrameworkServlet.java > HttpServlet.java > Servlet.java 를 상속받아 구현한 서블릿으로 Servlet Container에서 들어오는 모든 요청을 먼저 받아 중앙 집중식으로 처리해주는 프론트 컨트롤러이다.

클라이언트로부터 어떤 요청(Request)을 받으면 모든 요청을 한 곳에서 받아서 필요한 공통된 처리를 한 후, 요청에 맞는 Handler로 요청을 보내고(Dispatch), 해당 Handler의 실행 결과를 Http Response형태로 만드는 역할을 한다.

DispatcherServlet이 어떤 Controller를 사용할 지에 대한 전략은 Handler Mapping 전략을 통해서 이루어진다. 즉, 어떤 URL로 들어온 경우 어떤 컨트롤러를 사용할 지 결정한다.

HandlerMapping

request의 URL과 매칭되는, 이 요청을 처리할 handler를 선택하는 역할을 수행하는 인터페이스

HandlerMapping은 인터페이스이다. 이 인터페이스를 이용한 여러 구현체가 존재한다.

BeanNameUrlHandlerMapping:
URL과 일치하는 이름을 갖는 빈의 이름을 컨트롤러로 매핑하는 방법
ControllerBeanNameHandlerMapping:
URL과 일치하는 이름을 갖는 빈의 아이디를 컨트롤러로 사용하는 방법
SimpleUrlHandlerMapping:
URL패턴에 매핑되는 지정된 컨트롤러를 사용하는 방법, 위 2가지는 하나의 컨트롤러에 하나의 URL을 매핑했지만 이 매핑 전략은 하나의 컨트롤러에 여러 URL매핑 가능하다
RequestMappingHandlerMapping:
@RequestMapping 을 이용한 핸들러 매핑 전략이다. 우리가 주로 사용하는 매핑 전략으로 어노테이션을 이용해 URL과 컨트롤러를 매핑한다. 이 전략은 컨트롤러 안의 메서드와도 매핑이 가능하다

핸들러 매핑은 1개 이상을 동시에 사용할 수 있다. 1개 매핑으로 통일하는것이 가장 이상적이긴하나, 그렇지 않을 상황이 종종 있다. 2개 이상의 핸들러 매핑이 등록되었는데 URL이 중복 매치될 경우, order 프로퍼티를 통해 매핑 우선순위를 지정할 수 있다.

Hander Interceptor

핸들러 매핑의 역할은 기본적으로 URL과 요청정보로부터 컨트롤러 빈을 찾아주는 것이다. 하지만 이 기능 외에도 핸들러 인터셉터를 적용해주는 기능이 있다.
핸들러 인터셉터는 컨트롤러를 호출하기 전과 후에 요청과 응답을 참조하거나 가공할 수 있는 일종의 필터다.

HandlerMapping 전략을 이용해 처리할 핸들러(컨트롤러)를 파악한 후에는 HandlerMapping에 맞는 HandlerAdapter를 사용하여 컨트롤러를 호출한다.

HandlerMapping에서 클라이언트 요청을 처리할 Controller 객체를 가져오면 DispatcherServletdoDispatch메서드 영역에서 어떤 HandlerAdapter구현체를 이용해서 핸들러(컨트롤러)를 호출할지 HandlerAdapter 구현체를 구하고 해당 HandlerAdapter를 이용해 컨트롤러를 호출 결과를 받는 과정을 진행한다.

다음은 DispatcherServlet에서 적절한 HandlerAdapter을 구하는 과정이
1. supports메서드에 handler 파라미터를 넘겨 RequestMappingHandlerAdapter, HttpRequestHandlerAdapter, SimpleControllerHandlerAdapter 중 해당 핸들러(컨트롤러)를 지원하는 adapter를 반환한다.
2. (2)에서 구한 HandlerAdapterhandler를 호출한다.

HandlerAdapter

Handler Mapping이 찾아낸 Handler를 호출하고 처리하는 인터페이스

HandlerAdapter는 인터페이스이다. 이 인터페이스를 이용한 여러 구현체가 존재한다.
스프링은 4가지 방식의 컨트롤러를 지원하며 각 컨트롤러를 DispatcherServlet에 연결해주는 Adapter가 하나씩 있어야 하므로, handlerAdapter도 구현체가 4가지정도 존재한다. 그 중 SimpleServletHandlerAdapter를 제외한 3개의 핸들러 아답터가 DispatcherServlet의 default 전략으로 설정되어 있다.

RequestMappingHandlerAdapter
@RequestMapping annotation을 처리한다.
HttpRequestHandlerAdapter
HttpRequestHandler 처리한다
SimpleControllerHandlerAdapter
컨트롤러 인터페이스를 처리한다.
SimpleServletHandlerAdapter
표준 서블릿 인터페이스인 javax.servlet.Servlet을 구현한 서블릿 클래스를 스프링 MVC의 컨트롤러로 사용할 수 있다. 서블릿 타입의 컨트롤러는 모델과 뷰를 리턴하지 않는다. 스프링 MVC의 모델과 뷰라는 개념을 알지 못하는 표준 서블릿을 그대로 사용하기 때문이다. 디폴트가 아니기에 별도로 등록해주어야 한다.

adapter을 이용해 컨트롤러를 호출한 결과를 View객체에 담아야한다.

View

뷰는 MVC 아키텍쳐에서 모델이 가진 정보를 어떻게 표현해야 하는지에 대한 로직을 갖고 있는 컴포넌트

컨트롤러가 작업을 마치고 정보를 ModelAndView 타입 오브젝트에 담아서 DispatcherServlet에 돌려주는 방법은 크게 두 가지다. 하나는 View 타입의 오브젝트를 돌려주는 방법이고, 다른 하나는 뷰 이름을 돌려주는 방법이다. 뷰 이름을 돌려주는 경우에는 실제 사용할 뷰를 결정해주는 뷰 리졸버가 필요하다.

뷰 오브젝트

DispatcherServlet이 사용하는 뷰 오브젝트는 View 인터페이스를 구현해야 한다.
다양한 구현체가 존재한다.

  • InternalResourceView와 JstlView
return new ModelAndView("/WEB-INF/view/hello.jsp", model);
  • RedirectView
return new ModelAndView("redirect:/main")
  • VelocityView, FreeMarkerView
  • MappingJacksonJsonView

ViewResolver

Handler에서 반환하는 논리적인 View 이름에 해당하는 View를 찾아내는 view 처리 전략 인터페이스

ViewResolver는 인터페이스로 여러 구현체가 존재한다. 구현체 종류는 너무 많으니 따로 알아보도록 하자.
여러 구현체 중 InternalResourceViewResolver를 알아보도록 하겠다.

InternalResourceViewResolver는 디폴트로 등록되는 뷰 리졸버로 주로 JSP를 뷰로 사용하고자 할 때 쓰인다. properties로 prefix, suffix를 지정해 포워딩 할 뷰 이름을 축약할 수 있다.
컨트롤러가 return "index.jsp"; 와 같이 뷰 이름을 넘겨주더라도 viewResolver가 ModelAndView 객체로 변경해서 돌려준다.

Controller의 결과 값인 논리적인 뷰 이름을 실제 뷰 객체로 변환해주는 작업을 해준다.

참고자료

https://taes-k.github.io/2020/02/16/servlet-container-spring-container/

https://github.com/suhongkim98/Spring-Summary/tree/master/07_SpringMVC

https://ee-22-joo.tistory.com/20?category=815778

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=todoskr&logNo=220845006916

https://jess-m.tistory.com/15

https://m.blog.naver.com/todoskr/220856216311

profile
鈍筆勝聰✍️
post-custom-banner

0개의 댓글