본문 바로가기
프로그래밍/Spring

Spring DispatcherServlet과 MVC아키첵처

by Mr-후 2019. 3. 28.
반응형

이 내용은 토비의 스프링 3.0 1021페이지부터의 내용을 발췌해서 옮겨둔다. 이번주 내내 스프링 시큐리어티로 관리자 인증 페이지를 개발하면서 버벅되고 이해안가고 어려웠던 그런 것들의 기초 지식이기때문에 책에서도 말하듯이 꼭 이해하고 넘어가야하는 내용들이라 아침에 읽고 토론하고 오후에 또 읽으면서 타이핑해서 머리속에 한번더 각인시키고자 블로그에 포스팅을 함. 

Spring DispatcherServlet과 MVC아키첵처

스프링의 웹 기술은 MVC아키텍처를 근간으로 하고 있다. MVC는 프레젠테이션 계층의 구성요소를 정보를 담은 모델(M), 화면 출력 로직을 담은 뷰(V), 그리고 제어로직을 담은 컨트롤러(C)로 분리하고 이 세가지 요소가 서로 협력해서 하나의 웹 요청을 처리하고 응답을 만들어내는 구조다. 

MVC아키첵터는 보통 프론트 컨트롤러(front controller)패턴과 함께 사용된다. 프론트 컨트롤러 패턴은 중앙집중형 컨트롤러를 프레젠테이션 계층의 제일 앞에 둬서 서버로 들어오는 모든 요청을 먼저 받아서 처리하게 만든다. 프론트 컨트롤러는 클라이언트가 보낸 요청을 받아서 공통적인 작업을 먼저 수행한 후에 적절한 세부 컨트롤러로 작업을 위임해주고, 클라이언트에게 보낼 뷰를 선택해서 최종 결과를 생성하는 등의 작업을 수행한다. 

예외가 발생했을 때 이를 일관된 방식으로 처리하는 것도 프론트 컨트롤러의 역할이다. 프론트 컨트롤러는 컨트롤러와 뷰, 그리고 그 사이에서 주고 받는 모델 세가지를 이용해서 작업을 수행하는게 일반적이다. 

스프링이 제공하는 스프링 서블릿/MVC의 핵심은 DispatcherServlet이라는 프론트 컨트롤다. 이 DispatcherServlet은 MVC아키텍처로 구성된 프레젠테이션 계층을 만들 수 있도록 설계되어 있다. 

서버가 브라우저나 여타 HTTP 클라이언트로부터 HTTP요청을 받기 시작해서 다시 HTTP로 결과를 응답해주기까지의 과정. 

(1) DispatcherServlet의 HTTP요청 접수. 
자바 서버의 서블릿 컨테이너는 HTTP 프로토콜을 통해 들어오는 요청이 스프링의 DispatcherServlet에 할당된 것이라면 HTTP 요청 정보를 DispatcherServlet에 전달해준다. web.xml에는 보통 DispatcherServlet이 잔달받을 URL의 패턴이 정의되어 있다. 

<servlet-mapping>
	<servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
    <url-pattern>/app/*</url-pattern>
</servlet-mapping>

이 서블릿-매핑은 URL이 /app로 시작하는 모든 요청을 스프링의 프론트 컨트롤러인 DispatcherServlet에게 할당해주는 것이다. 특정 폴더 아래의 내용을 매핑하는 방법도 가능하고,  *.do와 같이 특정 확장자만을 매핑해주는 방법도 쓸 수 있다. 

HTTP요청은 HTTP명령에 따라서 GET과 POST로 구분된다. 어떤 명령을 사용하든 HTTP요청에는 해당 요청을 통해서 사용자가 전달하려고 하는 정보가 전송된다. 이 정보는 URL의 '?' 뒤에 이어지는 쿼리 스트링이나 폼 파라미터, 쿠키나 헤더 정보를 통해 전달할 수 있다. 

DispatcherServlet은 모든 요청에 대해 공통적으로 진행해야 하는 전처리 작업이 등록된 것이 있다면 이를 먼저 수행한다. 공통적으로 이용 가능한 보안이나 파라미터 조작, 한글 디코딩과 같은 작업이 적용된다. 

(2)DispatcherServlet에서 컨트롤러로 HTTP요청 위임 
DispatcherServlet은 URL이나 파라미터 정보, HTTP명령 등을 참고로 해서 어떤 컨트롤러에게 작업을 위임할지 결정한다. 컨트롤러를 선정하는 것은 DispatcherServlet의 핸들러 매핑 전략을 이용한다. 스프링에서는 컨트롤러를 핸들러라고도 부른다. 웹의 요청을 다루는 오브젝트라는 의미다. 사용자 요청을 기다준으로 어떤 핸들러에게 작업을 위임할지를 결정해주는 것을 핸들러 매핑 전략이라고 한다. 

이를 전략이라고 부르는 이유는 DI의 가장 대표적인 용도라고 할 수 있는 전략 패턴이 적용되어 있기 때문이다. DispatcherServlet의 수정 없이도 DI를 통해 얼마든지 확장 가능하다. 어떤 URL이 들어오면 어떤 컨트롤러 오브젝트가 이를 처리하게 할지를 매핑해주는 전략을 만들어서 DI로 제공해주기만 하면 된다. 엄밀히 말하면 DispatcherServlet은 그 자체로 스프링 컨텍스트에 등록되는 빈이 아니므로 DI가 일어나는 것은 아니다. 하지만 마치 DI가 적용되는 것처럼 서블릿 애플리케이션 컨텍스트의 빈을 가져와 사용한다. 이때 특정 인터페이스를 구현한 빈을 자동으로 찾오는 자동와이어링 기법을 이용한다. 

어떤 컨트롤러/핸들러가 요청을 처리하게 할지를 결정했다면, 다음은 해당 컨트롤러 오브젝트의 메소드를 호출해서 실제로 웹 요청을 처리하는 작업을 위임할 차례다. 그런데 DispatcherServlet이 매핑으로 찾은 컨트롤러를 가져와 실행하려면 컨트롤러 메소드를 어떻게 호출할지를 알고 있어야 한다. 그렇다면 DispatcherServlet에서 호출 가능한 컨트롤러는 특정 인터페이스를 구현해야 한다는 식의 규약을 따라서 작성해야해야 할까? 그건 아니다. DispatcherServlet이 요청을 위임하는 대상인 컨트롤러에는 아무런 제약이나 선결조건이 없다는 뜻이다. DispatcherServlet은 어떤 종류의 오브젝트라도 컨트롤러로 사용할 수 있다. 이것은 DispatcherServlet이 갖는 무한한 확장성의 비결이다. DispatcherServlet에는 어떤 컨트롤러라도 사용 가능하다. 하지만 자바의 오브젝트 사이에 무엇인가 요청이 전달되려면 메소드가 호출돼야하고 그러러면 DispatcherServlet이 컨트롤러 오브젝트의 메소드를 호출할 수 있는 방법이 있어야 하지 않는가? 어떻게 제각각 다른 메서드를 호출 할 수 있는 컨트롤러로 만들어 놓고 DispatcherServlet이 이를 알아서 호출하게 만들 수 있을까? 

해결책은 어댑터를 이용하는 것이다. 전형적인 오브젝트 어댑터 패턴을 사용해서, 특정 컨트롤러를 호출해야 할 때는 해당 컨트롤러 타입을 지원하는 어댑터를 중간에 껴서 호출하는 것이다. 그러면 DispatcherServlet은 항상 일정한 방식으로 컨트롤러를 호출하고 결과를 받을 수 있다. 

DispatcherServlet은 컨트롤러가 어떤 메소들르 가졌고 어떤 인터페이스를 구현했는지 전혀 알지 못한다. 대신 컨트롤러의 종류에 따라 적절한 어댑터를 사용한다. 각 어댑터는 자신이 담당하는 컨트롤러에 맞는 호출 방법을 이용해서 컨트롤러에 작업 요청을 보내고 결과를 돌려받아서 DispatcherServlet에게 다시 돌려주는 기능을 갖고 있다. 이렇게 하면 하나의 DispatcherServlet이 동시에 여러 가지 타입의 컨트롤러를 사용할 수 있다. 스프링 서블릿/MVC확장구조의 기본은 바로 어댑터를 통한 컨트롤러 호출 방식이다. 어떤 어댑터를 사용할지는 DispatcherServlet전략의 하나인 핸들러 어댑터 전략을 통해 결정한다. 당연히 DI를 통해 자유롭게 확장 가능하다. 

DispatcherServlet이 핸들러 어댑터에 웹 요청을 전달할 때는 모든 웹 요청 정보가 담긴 HttpServletRequest 타입의 오브젝트를 전달해준다. 이를 어댑터가 적절히 변환해서 컨트롤러의 메소드가 받을 수 있는 파라미터로 변환해서 전달해주는 것이다. HttpServletResponse도 함께 전달해준다. 드물긴 하지만 컨트롤러가 결과를 리턴값으로 돌려주는 대신 HttpServletResponse오브젝트 안에 직접 집어넣을 수도있기때문이다. 

(3)컨트롤러의 모델 생성과 정보 등록 
MVC패턴의 장점은 정보를 담고 있는 모델과 정보를 어떻게 뿌려줄지를 알고 있는 뷰가 분리된다는 점이다. 같은 모델이지만 다른 뷰에 전달되면 다른 방식으로 모델의 정보가 출력되게 할 수 있다. 테이블 뷰에 전달되면 ㅌ테이블 구조로 나타날 것이고 그래프 뷰에 전달되면 그래프로, 엑셀 뷰라면 엑셀 문서로 표현된다. 

컨트롤러의 작업은 먼저 사용자 요청을 해석하는 것, 그에 따라 실제 비지니스 로직을 수행하도록 서비스 계층 오브젝트에게 작업을 위임하는 것, 그리고 결과를 받아서 모델을 생성하는 것, 마지막으로 어떤 뷰를 사용할지 결정하는 것의 네 가지로 분류할 수 있다. 모델을 생성하고 모델에 정보를 넣어주는 게 컨트롤러가 해야할 마지막 중요한 두 가지 작업 중 하나다. 컨트롤러가 어떤 식으로든 다시 DispatcherServlet에 돌려줘야 할 두 가지 정보가 있는데, 그중 하나가 모델이고 다른 하나가 뷰다. 

모델은 보통 맵에 담긴 정보라고 생각하면 된다. 이름과 그에 대응되는 값의 쌍으로 정보를 만드는 것이다. 물론 복잡한 오브젝트 그래프를 갖는 컬렉션을 하나의 모델 값으로 추가해도 상관없다. 모델은 이름과 오브젝트 값의 쌍으로 만들어진다는 사실만 기억하자. 

(4)컨트롤러의 결과 리턴: 모델과 뷰 
모델이 준비됐으면 다음은 뷰를 결정할 차례다. MVC의 모든 요소가 그렇듯이 뷰도 하나의 오브젝트다. 컨트롤러가 뷰 오브젝트를 직접 리턴할 수도 있지만, 보통은 뷰의 논리적인 이름을 리턴해주면 DispatcherServlet의 전략인 뷰 리졸버가 이를 이용해 뷰 오브젝트를 생성해준다. 컨트롤러가 뷰에 대한 정보를 돌려준다는 사실만 기억두자. 

컨트롤러가 리턴해주는 정보는 결국 모델과 뷰 두 가지다. 스프링에는 ModelAndView라는 이름의 오브젝트가 있는데, 이 ModelAndView가 DispatcherServlet이 최종적으로 어댑터를 통해 컨트롤러로부터 돌려받는 오브젝트다. 이름 그대로 모델과 뷰, 두 가지 정보를 담고 있다. 모델과 뷰를 넘기는 것으로 컨트롤러의 책임은 끝이다. 다시 작업은 DispatcherServlet으로 넘긴다. 

(5)DispatcherServlet의 뷰 호출과(6) 모델 참조 
DispatcherServlet이 컨트롤러로부터 모델과 뷰를 받은 뒤에 진행하는 작업은,, 뷰 오브젝트에게 모델을 전달해주고 클라이언트에게 돌려줄 최종 결과물을 생성해달라고 요청하는 것이다. 보통은 브라우저를 통해 사용자가 결과를 볼 테니 브라우저에서 나타날 HTML을 생성하는 일이 가장 흔한 뷰의 작업이다. JSP를 이용해서 결과물을 만들어주는 JstlView는 컨트롤러가 돌려준 JSP뷰 템플릿의 이름을 가져다 HTML을 생성하는데 그중에 동적으로 생성되도록 표시된 부분은 모델의 내용을 참고로 해서 내용을 채워준다. 예를 들어 컨트롤러가 모델에 name이라는 이름으로 'Spring'이라는 값을 넣어뒀다면 JstlView는 JSP에서 다음과 같은 내용을 만나면 모델의 name에 해당하는 값을 가져와 ${name}자리에 대신 넣어준다. 

<div>이름 : ${name} </div>

모델의 내용을 참고로 해서 뷰가 작업한 최종 결과는 다음과 같이 될 것이다. 

<div>이름 : Spring </div>

JSP를 사용하는 뷰 외에도 엑셀, PDF와 같은 파일 형태로 결과물을 만드는 뷰는 JSP 파일 같은 템플릿을 사용하는 대신 뷰 생성 기능을 가진 뷰 오브젝트를 이용하기도 한다. 또, 모델의 정보를 RSS, Atom이나 최근 많이 사용되는 JSON으로 생성해주는 뷰도 있다. 모델은 같을지라도 어떤 뷰가 선택되느냐에 따라 다른 형태의 결과물이 만들어진다. 기술적으로 보자면 뷰 작업을 통한 최종 결과물은 HttpServletResponse오브젝트 안에 담긴다. 

(7)HTTP응답 돌려주기 
뷰 생성까지의 모든 작업을 마쳤으면 DispatcherServlet은 등록된 후처리기가 있는지 확인하고, 있다면 후처리기에서 후속 작업을 진행한 뒤에 뷰가 만들어준 HttpServletResponse에 담긴 최종 결과를 서블릿 컨테이너에게 돌려준다. 서블릿 컨테이너는 HttpServletResponse에 담긴 정보를 HTTP응답으로 만들어 사용자의 브라우저나 클라이언트에게 전송하고 작업을 종료한다. 

프론트 컨트롤러인 DispatcherServlet과 모델, 뷰, 컨트롤러가 공동으로 사용자의 요청을 처리하는 이 과정은 꼭 기억해두자. 

스프링MVC에서 진행되는 작업은 코드나 설정으로 바로 파악하기 쉽지 않다. 많은 부분이 MVC 프레임워크 내부에서 진행되고, 상당수의 작업은 선언적으로 지정되며, 코드 또한 컴포넌트화돼서 쪼개져 있기 때문이다. 그런데 이런 작업 흐름을 이해하지도 못한채로 샘플 코드만 가져다가 이리저리 고쳐가면서 사용하려고 하면 오히려 개발시간은 더 걸리고 고생만 할 뿐이다. 

출처 : - 토비의 스프링 3.0 중에서 -

반응형