MVC(Model-View-Controller)패턴이란?
전통적인 GUI(Graphic User interface) 기반의 애플리케이션을 구현하기 위한 디자인 패턴입니다.
MVC 구조는 사용자의 입력을 받아서 입력 에 대한 처리를 하고, 그 결과를 다시 사용자에게 표시하기 위한 최적화된 설계를 제시해 줍니다.
웹 애플리케이션에는 서블릿/JSP와 함께 일반 자바 클래스도 같이 사용합니다.
가장 이상적인 구조
자바 클래스
– 데이터베이스와 통신/주요 비즈니스 로직 구현
- 비즈니스 로직, 주로 데이터베이스와 통신하는 DAO(Data Access Object)가 존재
서블릿
– 사용자의 요청 처리/제어 컨트롤러 구현
- 컨트롤러, 사용자로부터 요청된 데이터를 검증하고 모델을 호출한 후 뷰에 데이터를 보내 응답하도록 함
JSP
– 화면에 보이는 페이지 작성
- 뷰, 데이터를 받아 화면에 보이게 코드를 작성하거나 사용자가 입력할 수 있도록 HTML 폼(form)을 작성
- 항상 MVC를 분리하지는 않음 – Model 1은 컨트롤러와 뷰가 존재
이와 같은 구조를 MVC, Model View Controller 라고 명칭합니다.
MVC 패턴의 요소
Model • JavaBean |
• 컨트롤러(Controller)가 넘겨준 로직 처리 • 로직을 가지는 부분 • DB와의 연동을 통해서 데이터를 가져와 어떤 작업을 처리 • 처리한 작업의 결과를 데이터로서 DB에 저장하는 일을 처리 • 자바빈(JavaBean), 처리 로직인 자바 클래스(Java class)가 이에 해당 |
• 웹브라우저의 요청을 받는 진입점 • 사용자의 요청을 받아서 요구사항을 분석 후 로직 처리를 모델로 보냄 • 로직의 처리 결과를 모델로부터 받아서, 사용자에게 응답하기 위해 뷰로 보냄 |
모델(Model)의 작업 처리과정 ① 컨트롤러(Controller)의 요청을 받음 ② 모델에서 로직을 처리 ③ 처리한 로직의 결과를 컨트롤러(Controller)로 반환 |
||
View • JSP |
• 화면에 내용을 표시 • 정보를 보여주는 역할만을 담당 • JSP 페이지가 이에 해당 |
• 요청에 대한 응답 결과를 표시 • JSP 페이지의 request는 컨트롤러(Controller)인 서블릿(Servlet)과 같은 객체로 공유 • ${requestScope.result} 또는 request.getAttribute("result")와 같이 사용해서 결과를 화면에 표시 |
Controller • Servlet |
• 어플리케이션의 흐름을 제어 • 사용자의 요청을 받아서 모델(Model)에 넘겨줌 • 모델(Model)이 처리한 작업의 결과를 뷰(View)로 보냄 • 서블릿(Servlet)이 이에 해당 |
• 웹브라우저의 요청을 받는 진입점 • 사용자의 요청을 받아서 요구사항을 분석 후 로직 처리를 모델로 보냄 • 로직의 처리 결과를 모델로부터 받아서, 사용자에게 응답하기 위해 뷰로 보냄 |
컨트롤러(Controller)의 작업 처리 과정 ① 웹 브라우저의 요청을 받음 → 웹브라우저의 요청은 서블릿의 서비스 메소드인 doGet() 또는 doPost()메소드가 받음 ② 웹 브라우저가 요구하는 작업을 분석 → 사용자가 요구한 작업에 맡는 로직이 실행되도록, 웹브라우저의 요구 작업을 분석 ③ 모델을 사용해서 요청한 작업을 처리 → 요청한 작업에 해당하는 로직을 처리 ④ 로직 처리 결과를 request객체의 속성에 저장 → 처리 결과는 같은 request 객체 영역에서 공유 ⑤ 적당한 뷰(JSP페이지)를 선택 후 해당 뷰로 포워딩(forwarding) 처리 결과를 저장한 request객체를 뷰로 전달 |
MVC 구조의 장점
- 프로그래머가 비즈니스 로직을 구현하기 편함
- 확장성, 유지보수성이 뛰어남
- JSP와 서블릿의 단점을 한꺼번에 해결
- 많은 프레임워크들을 통해 쉽고 빠르게 배우거나 개발 가능
( Struts 프레임워크, Spring MVC 프레임워크, JavaServer Faces 등)
MVC Model1 Architecture
웹 브라우저의 요청(request)을 받아들이고, 웹 브라우저에 응답(response)하는 것을 JSP페이지가 단독으로 처리하는 구조
대표사진 삭제
MVC모델1 Architecture JSP Servlet
서버컴퓨터와 서버의 자원을 활용하는 클라이언트(브라우저) 측이 있습니다.
클라이언트는 다수가 될수 있습니다.
클라이언트(사용자)는 우리 서버의 자원에 접근해서 웹어플리케이션을 사용합니다.
클라이언트가 요청을 서버로 보냈을때 모델1구조에서는 jsp파일이 컨트롤러 역할을 해주면서
jsp파일에서 요청을 분석하고 받아들여 거기에 맞는 모델(DAO,Service)을 찾아줍니다
DataAccess를 할수있는 DAO, Service를 연동해서 DB에 접근하고, DB에 있는 자료를 활용하여 화면에 응답한다던가, 추가 수정 삭제 등의 행동을 하는 Model1Architecture 구조입니다
MVC Model2 Architecture
요청(request) 처리, 데이터 접근(data access), 비즈니스 로직(business logic)을 포함하고 있는 컨트롤러와 뷰는 엄격히 구분
MVC기반 Model2 방식은 확장성을 위한 패턴을 추가한 개념입니다.
프론트 컨트롤러 라는 패턴을 이용해서 모든 요청을 하나의 단일 진입점에서 처리하도록 합니다.
브라우저에서 요청(글쓰기, 글내용보기,목록보기 ,수정,삭제 등 다수의 모든요청)을 보내면 하나의 컨틀롤러가 받아서 처리 합니다. 이것이 모델2의 핵심입니다.
프론트 컨트롤러 라는 객체가 모든 요청을 받아들여서 확장자패턴(*.do) 등을 이용합니다.
*,do혹은 *.~로 끝나는 요청들을 하나의 컨트롤러가 받아서 요청분석(uri를 통해)을 하여 알맞은 서비스를 찾아주고, 서비스에 맞는 DAO DataAcess를 어떻게 할것인지에 대한 메서드를 호출하여 각 계층별로 확실하게 역할을 분기시켜준 후 데이터 베이스 작업을 하고 돌려받은 결과를 가지고 컨트롤러는 사용자화면에 어떻게 보여줄 것인가 어떤화면에 보여줄 것인가, 어떤 jsp파일을 사용자에게 보여줄 것인가에 대한 view페이지를 찾아낸 후. 응답을하여 사용자는 특정 데이터가 들어있는 화면을 볼수있게 됩니다.
스프링 컨트롤러 에서는 기본적으로 Model2 아키텍처를 사용하는 패턴을 내부적으로 지원하고 있으며,
모델2를 기반으로 프로그램을 작성 해야합니다.
컨트롤러 : 두뇌 , 서버의 뇌, 모든커맨드를 처리
서비스 : 로그인 값을 읽어봐! -DB에서 값 가져와야해 DAO 일로와바 -다시 돌려받아서 컨트롤러에게 로그인가능을 알린다.
DAO : DAO 메서드를 호출하여 정보 가져오기
view : 사용자에게 데이터의 흐름과 유효성을검증시켜 사용자에게 화면에 실어 보여준다. (부동산 복덕방 중개아저씨)
사용자(view)로부터 로그인요청! 이 들어오면 Controller가 처리합니다.
처리 후 컨트롤러는 Service에게 로그인값을 읽어보라고 시킵니다.
Service는 로그인값을 읽어 본 후 DB에서 값을 가져와야하기 때문에 DAO의 메서드를 통해 DB의값을 비교하고 로그인 가능유무를 컨트롤러에게 알립니다.
View는 사용자로부터 로그인을 요청받을수 있도록 화면을 띄워주기도 하며 로직코드가 다 진행되고 나온 결과 데이터값을 사용자에게 보여주거나 유효성을 검증시키기 위해 사용자의 화면에 실어 보여줍니다.
스프링 MVC 프레임워크 설계 구조
Dispatcherservlet
서블릿에서 만들었던 FrontController의 역할을 해줍니다.
로그인로직,회원관리로직,게시판로직,갤러리로직,관리자페이지로직,파일업로드로직 등등의 서블릿을 직접 코드로 구현하지 않고 해당 작업내용 코드는 개발자의 영역인 컨트롤러에 담습니다. 사용자로부터 들어오는 요청은 어떠한 요청이든 가장 처음으로 받습니다. 회원관리요청,댓글관리요청.게시글작성요청,수정요청,삭제요청, 등등.
HandlerMapping
DispatcherServlet에 모든 요청이 들어왔을때 BoardController UserController ReplyController UploadController 등 컨트롤러들을 개발자가 만들어 놨다는 가정하에 DispatcherServlet이 어떤 컨트롤러가 필요한지 분석하고 HandlerMapping 주소창에 들어온 url을 분석하고 컨트롤러로부터 uri에대한 정보들을 얻어냅니다.
이때 기준이 되는것은 @RequestMapping , @GetMapping, @PostMapping어노테이션 입니다.
HandlerAdapter
HandlerMapping에 대응하여 HandlerMapping이 가지고 있는 uri정보에 맞는 메서드를 찾아 매핑시켜 줍니다.
viewResolver
로직코드 구현 이후 문자열로 간단히 반환해주면 들어오는 문자열에 따라 분석,조립해서 화면을 열어준다.
forwording이나 sendredirect등 특정 문자열에 따라 해석해서 알아서 응답해줍니다.
그렇다면 내부적으로 어떻게 설정되어있는지 하나하나 파악하면서 정확한 순서를 확인해보겠습니다.
우선 내부적인 설정은 SpringToolSuit를 다운받고 생성된 xml파일들이 해줍니다.
대표적으로 web.xml, servlet-context.xml,root-context.xml세가지를 확인할 수 있습니다.
경로는 src-main-webapp-WEB-INF폴더에 web.xml파일이 저장되어 있으며, 그 하위폴더인 spring폴더에root-context.xml파일과 한단계 더 하위폴더인 appServlet폴더에 servlet-context.xml파일이 저장되어 있습니다.
먼저 WEB-INF폴더의 web.xml의 코드를 보겠습니다.
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>
<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Processes application requests -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>
<context-param>이라는 태그 안에<parm-name>으로 contextConfiLocation 이라고 등록 되어있고
<param-value>에 /WEB-INF/spring/root-context.xml 라고 등록되어있습니다
root-context.xml파일은 웹 어플리케이션을 시작 했을때 mvc패턴과 관련된 스프링Bean 등록이라던가 DB 혹은 ConnectionPool Bean등록 이런 설정파일의 위치를 지정해주는 아이입니다.
그렇다면 이번엔 root-context.xml파일을 열고 코드를 확인해 보겠습니다.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- Root Context: defines shared resources visible to all other web components -->
</beans>
아직 아무런 DB세팅이나 Bean등록을 안해줬습니다.
앞서 말 했듯이 여기에는 대표적으로 Controller,DAO,DB설정파일에대한 Bean등록을 이 root-context.xml파일에서 설정하는곳 입니다. 보틍은 root-context.xml파일의 이름을 변경해서 사용하는 편입니다.
다시 web.xml파일을 열어볼까요?
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>
<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Processes application requests -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
설정파일이 선언된 바로아래에 위치한 <listener>태그는 왠만해서는 우리가 변경할 영역이 아닙니다.
기본설정대로 쓰시면 됩니다.
그 바로 아래를 보시면 <!-- Processes application requests --> 주석이 위치하고 있습니다.
이 곳은 디스패처 서블릿 등록을 설정하는 영역입니다.
모든브라우저의 요청을 하나의 서블릿에서 받게되는데 이것의 이름은 DispatcherServlet이며 FrontController의 역할을 해줍니다. 이미 만들어져 내장되있기 때문에 스프링에서는 DispatcherServlet을 굳이 만들 필요가 없습니다.
<!-- Processes application requests -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<servlet-class>태그를 보시면 Springframework에서 만들어져있다고 등록되어 있습니다.
org.springframework.web.servlet.DispatcherServlet 을 등록을 이곳에서 하고있습니다.
<servlet-name>서블릿이름은 appServlet이라고 등록되어있습니다.
<itit-param> 태그에는 DispatcherServlet/FrontController가 실행되었을때 어떤 설정파일을 읽을것인가 파일가칭과 파일의 경로를 등록합니다.
서블릿으로등록될때contextConfigLocation이름으로 초기화파라미터를 servlet-context.xml 로 지정하고있는데 이때 지정된 servlet-context.xml 파일이 스프링 설정의 역할을 하는 파일 입니다.
<param-value>바로 이 태그에 어떠한 설정파일에서 설정을 읽겠다고 파일경로를 선언하는 태그입니다.
태그 안에는 /WEB-INF/spring/appServlet/servlet-context.xml파일을 읽겠다고 등록되어 있습니다.
만약 우리가 서블릿 관련 설정 파일의 이름등을 변경하게되면 <param-value>태그에 선언된 파일경로와 파일명과 고칩니다.
<servlet-mapping>태그가 있습니다. <servlet-name>appServlet이라고 등록되어 있는것을 보니DispatcherServlet을 선언할때의 <servlet-name>의 이름과 같습니다. 즉, DispatcherServlet을 가리키고 있는것 입니다.
만약 DispatcherServlet 의 이름이 지정된 <servlet-name>태그에 이름이 banana로 지정되어있다면
이곳의 <servlet-name>도 banana로 지정해줘야만 DispatcherServlet을 참조할수있게 됩니다.
이것은 이름이기때문에 함부로 지정이 가능하긴 합니다.
<url-pattern>/</url-pattern>url-pattern이 /로 지정되어있는데 이것이 뭐냐하면
기존의 서블릿에서 @WebServlet("*.do")를 xml로 처리한것이 바로 이것입니다.
url패턴 매핑주소입니다.
'/' 이렇게 되어있으면 모든경로에 있는 모든요청을 다 처리하겠다는 뜻입니다.
<load-on-startup>1</load-on-startup>누구보다도 이 서블릿을 가장 먼저 실행시키겠다고 설정되어있습니다.
회원관리요청이든 게시판관련요청이든, 댓글관련 요청이든, 로그인요청이든 모든요청을 이곳에서 모두 다 제일 처음으로 받겠다는 뜻입니다.
이건 그대로 두고 사용하시면 됩니다.
이 설정이 등록되어 있기 때문에 Servlet으로 만든 FrontController를 따로 만들지 않아도 된다는것입니다.
만들지않고 스프링이 만들어놓은것을 활용만 할수있도록 web.xml에서 설정만 하면 된다는것입니다.
메이븐에서 올렸다면 직접 코드작성을 했어야 했지만 sts에서는 손쉽게 사용할수 있도록 메이븐 프로젝트를 빌드하면서 미리 등록을 해주는것입니다.
이번엔 web.xml에 등록되어있는 servlet-context.xml파일을 열어보겠습니다.
스프링 설정 파일은 클래스로 부터 객체 (빈:bean)를 생성 하고 조립하는 역할을 한다고 학습 했습니다.
servlet-context.xml 에서도 마찬가지로 프로젝트에 필요한 객체 (빈:bean) 를 생성하고 조립합니다.
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />
<!-- Handles HTTP GET requests for "/resources/**" by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<context:component-scan base-package="com.spring.web" />
</beans:beans>
이 파일은 FrontController의 기능을 해주는 DispatcherServlet 관련 설정을 하는 파일입니다.
<annotation-driven />태그는 <context:annotation-config/>태그와 똑같은 기능을 해주는
Annotation을통한 자동 빈 주입 명령을 처리하는 태그입니다.
<resourecs>태그는 정적자원(html,css,img,js)들을 절대경로로 쉽게매핑하는 태그입니다.
아래에 <beans:bean>태그는 bean태그입니다. bean을 하나 등록하고 있는것입니다.
해당 빈태그 안을 보시면 <property>태그가 선언되어 있습니다 즉, setter로 주입하고 있는것이죠.
이곳은 뷰 리졸버 빈을 등록하고 설정하는 곳 입니다.
InternalResourceViewResolver인터페이스는 컨트롤러가 리턴해준 문자를 받습니다.
<property> 태그의 name 속성으로 prefix와 suffix가 있습니다.
prefix는 접두어 이며 suffix는 접미어 역할을 합니다.
앞서 말했듯 InterResoureceViewResorvler에 컨트롤러가 리턴해준 문자를 받고 해당 문자열 앞 뒤에 각각 setter로 설정해둔 prefix와 suffix를 붙혀 뷰페이지 파일경로를 조립해줍니다.
<context:component-scan>태그는 아주 강력한기능인 Annotation을 사용하여 자동으로 bean등록을 하게 해주는 설정태그입니다. 아주 핵심적인 부분입니다.
나중에 자동 빈 등록에 대해 설명할때 더 자세히 알아봅시다.
그렇다면 설정과 맞물리는 기능들을 하나하나 설명해보겠습니다.
DispatcherServlet 에서 컨트롤러로 위임
자바의 서블릿 프론트 컨테이너는 HTTP 프로토콜을 통해 들어오는 모든 요청 (URL 패턴에 맞게)을DispatcherServlet에 전달합니다.
전달 받은 DispatcherServlet은 어떻게 해당Controller에 작업을 전달할까요?
DispatcherServlet은 URL, HTTP 메서드 및 정보, 파라미터 등을 참고해서 해당 컨트롤러에게 작업을 위임할지 결정합니다.
해당 컨트롤러를 선정하는 것은 DispatcherServlet의 핸들러 매핑 전략을 사용합니다.
실질적은 인터페이스는 HandlerMapping라는 인터페이스가 담당하고 있습니다.
이 인터페이스는 해당 컨트롤러에 대한 정보들을 가지고 있습니다.
해당하는 컨트롤러의 클래스, 메서드, 리턴타입, 파라미터 타입등 결정된 컨트롤러의 모든 정보들을 가지고 있습니다.
컨트롤러가 결정 되었다면 실제로 해당되는 컨트롤러의 메서드를 호출해야 되는데 컨트롤러 호출 방법은 타입에 따라 다르기 때문에 컨트롤러를 결정했다고 해도 DispatcherServlet은 알 길이 없습니다.
그래서 컨트롤러 타입을 지원하는 HandlerAdapter라는 인터페이스가 존재 하는 것입니다.
이 인터페이스는 적합한 파라미터를 추출해 해당 컨트롤러의 메서드를 호출해 주는 역할을 합니다.
HandlerMapping
위에서도 말했지만 요청 정보를 기준으로 어떤 컨트롤러를 사용할 것인가를 결정하는 인터페이스이다. DispatcherServlet에서는 한개 이상의 HandlerMapping을 가질수가 있습니다.
꽤 많은 HandlerMapping인터페이스 구현체가 있으니 참고하시고, 기본적으로 흔히 사용하는 @Controller와 @RequestMapping어노테이션을 통해 결정되는 컨트롤러의 경우에는 RequestMappingHandlerMapping구현체에 의해 핸들러가 결정됩니다.
거기에 대응되는 Adapter로는 RequestMappingHandlerAdapter클래스에 의해 컨트롤러의 메서드가 호출됩니다.
HandlerAdapter
자주 사용하는 구현체로는 위에서 잠깐 언급한 RequestMappingHandlerAdapter클래스 입니다. 어댑터 또한 한개 이상을 가질수 있습니다.
RequestMappingHandlerMapping과 대응되는 클래스이므로 두 클래스가 쌍으로 존재해야 합니다. 하지만 다른 핸들러 매핑과 어댑터는 서로 관련 있어도 되고 없어도 됩니다.
여기서는 컨트롤러의 타입을 결정해야 하므로 파라미터를 알아야 한다. 그래서 해당 어댑터(RequestMappingHandlerAdapter)에서는HandlerMethodArgumentResolver인터페이스와 HandlerMethodReturnValueHandler인터페이스를 가지고 있습니다.
URL주소는 ContextRoot뒤부터 보면 됩니다.
Client(view페이지)로부터 / 요청이 온 순간 이 요청은 DispatcherServlet이 받습니다.
이것은 내부적으로 xml설정파일에 의해 돌아갑니다.
아까 확인했던 web.xml파일에서 FrontController의 역할을 해주는 DispatcherServlet을 선언해주고
FrontController의 공통으로 처리해줘야할 url을 /로 매핑처리를 해줬기 때문입니다.
요청은 상당히 많을 수 있습니다.
회원가입,로그인,비번변경,글수정,회원정보 등등 너무 많게되면 DispatcherServlet이 할일이 많아지게 되겠죠
그럼 "우리 일좀나누자, 개발자가 컨트롤러를 좀 만들어주면 안될까요?" 하고 기본적으로 HomeController를 만든 것 입니다.
BoardController,ReplyController 등등 개발자가 클라이언트(사용자)로 부터 요청을 받아 처리하고 응답해줄수 있는 컨트롤러를 만들면 DispatcherServlet이 일을 좀 분산할수 있게되며 이렇게, 분기하여 연결을 해주는것은 HandlerMapping과 HandlerAdapter가 해줍니다.
@RequestMapping어노테이션에 써놓았던 URI를 가져오려면 거기 있는 URI들을 매핑해서 관리하는 클래스가 있습니다. Application Context (스프링 빈 컨테이너!)가 가지고 있는 bean중에RequestMappingHandlerMapping 클래스가 uri 정보를 들고 있습니다.
HandlerMapping이 바로 이 클래스 소속입니다.
요청 정보를 기준으로 어떤 컨트롤러를 사용할 것인가를 결정하는 인터페이스 입니다.
HandlerAdapter는 무엇을 찾아주냐면 우리가 주소창에 URL패턴 매핑주소를 /test라고 바꿨을때 /test를 처리해주는 메서드를 찾아줍니다.
@requstMapping이나 @GetMapping@PostMapping등의 어노테이션등을 내부적으로 참조해서 연결을 해줍니다.
JSP서블릿에서는 직접 들어오는 URL을 검색해서 if 혹은 switch case문 같은 분기문으로 갈라 접근했습니다.
여기서는 URI를 보고 어노테이션으로 매핑된것을 확인하고 자동으로 찾아줍니다.
컨트롤러의 메서드가 요청을 넘겨받고 넘겨받은 값들로 모든 작업을 수행하고 return을 해줍니다.
예를들어 메서드 내부에 return "home"; 으로 선언되어 반환된 home문자열은 viewResolver로 들어옵니다.
그러면 viewResolver라는 클래스가 작동을 하게됩니다.
servlet-context.xml파일의 InternalResourceViewResolver인터페이스는 컨트롤러가 리턴해준 문자를 받습니다.
지금 home이 들어왔죠? 그리고 setter로 접두어와 접미어를 지정해줬습니다.
prefix가 접두어이고 suffix가 접미어입니다.
/WEB-INF/views/ + home + .jsp →/WEB-INF/views/home.jsp 의 경로가 잡히게 됩니다.
home이라는 문자열이 뷰리졸버에 등록되게되면 /WEB-INF/views/home.jsp 뷰페이지 파일이 이렇게 경로설정이 됩니다.
이 경로를 따라가면 해당 파일이 존재합니다!
기존 서블릿에서는 DispatcherForward를 통해서 db.forward(); 를 해서 뷰페이지 파일의 경로까지 직접 작성하여 연결해줬습니다.
하지만 viewresolver가 컨트롤러가 리턴해주는 문자열만 확인하고 prefix suffix각각의 setter메서드를 조합하여 페이지를 response해줍니다.
그렇다면 DispatcherServlet이 어떻게 HomeController를 잡아냈을까요 ??
DispatcherServlet이 HomeController와 의존관계가 설정되어있습니다.
어딘가에서 의존성주입 설정이 되어있다는것입니다.
bean으로 등록되어 있습니다.
우리는 HomeController를bean으로 등록한 코드를 작성한 적이 없습니다.
servlet-context.xml에서 밑에보시면 beans Graph가 있습니다.
어? 빈등록을 해놓은적이없는데 어떻게 빈으로 등록된것이지...?
정답은 serlvet-context.xml에 있습니다.
<context:component-scan base-package="com.spring.web"/>태그 입니다.
component-scan : 컴포넌트(요소)들을 스캔해라.
base-package : 어디서 스캔하는가? com.spring.web패키지에서스캔하라.
그리고 home컨트롤러에서 @Controller가 <bean>을 대체하고 있는 것입니다.
@Controller에 마우스를 올려보면 @Component어노테이션이 적혀있습니다.
일단은 component와 연관이 있다는 얘기입니다.
기본적으로 @Component어노테이션은 해당 클래스가 스프링에서 객체로 만들어서 관리하는 대상임을 명시하는 어노테이션입니다.
component-scan태그가 해당 패키지를 스캔하고 패키지에있는 컨트롤러에 접근하여 선언된 어노테이션중 component를 수행해주는어노테이션을 찾으면 객체화 시켜주는데 @Controller에 @Component가 선언되어있는것을 보면 component의 기능을 함께 가지고 있다 라고 유추할수 있습니다.
그렇게 @Controller어노테이션이 선언된 컨트롤러가 bean객체화 되는것입니다.
다른 설명글들을 참조해보면 @Controller@Service@Repository어노테이션이 모두 @Componet어노테이션을 상속받고 있다고 설명이 되어있습니다.
그리고 해당 어노테이션들로 등록된 스프링 컨테이너에 의해 자동으로 생성되어 스프링 빈으로 등록된다고 설명이 적혀있습니다.
순서적으로 보았을때 servlet-context.xml에 등록된 <context:component-scan>태그로 해당 패키지에 있는 컨트롤러 클래스들 중 선언된 @Controller, @Service, @Repository어노테이션들을 찾아 이 어노테이션들이 등록된 컨트롤 클래스들을 모두 컨테이너에 bean등록처리하여 객체화합니다.
이렇게 객체화 하여 bean 등록 된 것들은 모두 다 DispatcherServlet과 의존관계가 됩니다.
내부적으로 이렇게 등록되어 있게되고
url매핑주소를 입력 하였을 때 DispatcherServlet이 가장 먼저 요청을 받은 다음 HandlerMapping 인터페이스가 작동하여 URL에 입력된 url주소 정보들을 갖게 됩니다. @RequestMapping와 그곳에 등록된 매핑주소를 기준으로 컨트롤러,메서드,리턴타입,파라미터 타입등 결정된 컨트롤러의 모든 정보들을 가지고 있습니다.
컨트롤러가 결정되면 실제로 해당하는 컨트롤러의 메서드를 호출해야 되는데 컨트롤러 호출 방법은 타입에 따라 다르기 때문에 컨트롤러를 결정했다고 해도 DispatcherServlet은 알 길이 없습니다.
그래서 컨트롤러 타입을 지원하는 HandlerAdapter라는 인터페이스가 존재 합니다. 이 인터페이스는 적합한 파라미터를 추출해 해당 컨트롤러의 메서드를 호출해 주는 역할을 한다.
결국 이러한 컴포넌트 의존적 객체의 정보들이 컨테이너에 등록되고 의존관계를가진 후 URL이 들어오면 Handler가 내부적으로 작동하여 컨테이너를 찾고 메서드 찾아 작업을 실행하여 하나의 뷰페이지를 출력하게됩니다.
'SpringFramework > BASIC' 카테고리의 다른 글
스프링의 어노테이션의 특징과 컨트롤러의 공통 URL 설정 (0) | 2020.08.29 |
---|---|
요청 파라미터 처리법 : Sevlet 자바API request.getParameter() 과 SpringAPI 어노테이션 @RequestParam / command객체 생성 방식 + @ModelAttribute (0) | 2020.08.29 |
자동 스캔 명령태그 / 자동 의존성 주입 어노테이션 context:annotation-config @Autowired/@Qulifier/@Resource/@Inject (0) | 2020.08.29 |
싱글톤타입/프로토타입 Bean객체의 범위 (0) | 2020.08.29 |
DI 의존성 주입 - Setter() (0) | 2020.08.29 |