들어가면서..
Spring Framework로 웹 개발을 할 땐 기본적으로 MVC 패턴을 따른다.
MVC 패턴은 Model, View, Controller 이 세 가지로 나뉘고, 역할을 분할하여 처리한다.
역할을 나누어 처리하기 때문에 서로의 결합도가 낮아져서 좋은 코드가 되며 유지보수도 하기 편해진다.
Spring MVC Architecture와 Spring Framework의 설정 파일을 공부하기 앞서 MVC 패턴의 방식과 개념에 대해 알아보자.
MVC 패턴이란?
MVC 패턴은 Model, View, Controller 개념이 합쳐지면서 생긴 방식으로 소프트웨어 공학에서 사용되는 디자인 패턴이다.
디자인 패턴이란?
건축으로치면 공법에 해당하는 것으로, 소프트웨어의 개발 방법을 공식화 한 것이다.
소수의 뛰어난 엔지니어가 해결한 문제를 다수의 엔지니어들이 처리 할 수 있도록 한 규칙이면서,
구현자들 간의 커뮤니케이션의 효율성을 높이는 기법이다.
<출처-위키피디아>
- Model
- 백그라운드에서 동작하는 로직을 처리한다. (데이터를 가진 객체, 파라미터로 주로 쓰인다.)
- DB의 테이블과 대응하는 경우가 많다.
- View
- 사용자에게 보여지는 화면, UI를 말하며, Model로부터 정보를 얻고 표시한다. 즉 데이터를 받으면 화면에 표시해주는 역할만 담당한다.
- 일반적으로 HTML, JSP... 에 해당한다.
- Controller
- 사용자의 입력처리와 흐름 제어를 담당한다. (사용자가 접근한 URL에 따라서 사용자의 요청사항을 파악한 후, 그 요청에 맞는 데이터를 Model에 의뢰하고, 데이터를 View에 반영해서 사용자에게 알려준다.) 즉, Model과 View의 다리역할을 담당한다.
이렇게 Model, View, Controller의 세 가지 요소로 나누어진 패턴을 MVC 패턴이라고 하는데,
여기서 MVC 패턴은 Model 1 구조와 Spring이 채택한 Model 2 구조로 나뉘게 된다.
Model 1 구조 & Model 2 구조
- Model 1
Model 1의 경우 View와 Controller를 모두 JSP가 담당하는 형태를 가진다. 즉 JSP 하나로 유저의 요청을 받고 응답을 처리하므로 구현 난이도가 쉽다는 장점이 있다.
그러나 단순한 프로젝트에는 괜찮겠지만 내용이 복잡하고 거대해질수록 이 패턴의 장점은 사라지게 된다.
그 이유는 JSP 하나에서 MVC 가 모두 이루어지다보니 재사용성이 떨어지고, 읽기도 힘들어지기 때문이다.
즉 유지보수에 있어서 문제가 발생하는 현상이 생기게 된다.
- Model 2
Model 2는 널리 표준으로 사용되는 패턴이다. 요청을 하나의 컨트롤러(Servlet)가 먼저 받는다. 즉 Model 1과는 다르게 Controller, View가 분리되어 있는 특징이 있다. 따라서 역할이 분리됐기 때문에 Model 1에서의 단점을 보완할 수 있게 됐다. 그러므로 개발자는 Model, View, Controller 중에서 수정해야 할 부분이 있다면, 해당 부분만 꺼내어 수정하면 면 되기 때문에 유지보수에 있어서 큰 이점을 가지게 된다.
Model 1
· 장점 : Model 1을 채택하면 빠르고 쉽게 개발할 수 있다는 장점이 있다.
· 단점 : JSP파일 자체가 너무 비대해지고, Controller와 View가 혼재하므로 향후 유지보수에 어려움을 겪을 수 있다.
Model 2
· 장점 : Model 2는 View와 Controller를 분리하는 방식이다. 따라서 디자이너와 개발자의 분업이 가능하며 유지보수에 유리하다.
· 단점 : 설계에서 어려움을 겪을 수 있고, 개발 난이도가 높다는 단점이 있다.
Model 2는 Model 1보다 구조가 복잡해질 수 있지만, 개발자가 세부적인 구성까지 신경쓰지 않을 수 있도록 각종 프레임워크들이 지금까지 잘 발전되어 왔고, 그 중에서 대표적인 것이 바로 스프링 프레임워크다.
Spring MVC Architecture
현재 대부분의 Spring 프로젝트는 Model 2 구조를 따른다. 하지만 Spring Framework는 Model2와는 차이가 있다.
그 차이가 생기는 부분은 바로 DispatcherServlet의 존재 유무다.
그러면 Spring MVC Architecture를 한 번 살펴보도록 하자.
동작 순서는 다음과 같다.
1. 클라이언트(Client)가 서버에 어떤 요청(Request)을 하면 Spring에서 제공하는 DispatcherServlet 이라는 클래스가 요청을 가로챈다.
(web.xml에 살펴보면 모든 url ( / )에 서블릿 매핑을하여 모든 요청을 DispatcherServlet이 가로채게 해둠(변경 가능))
2. DispatcherServlet은 받은 요청을 HandlerMapping에게 던져준다. 요청받은 URL을 분석하여 HandlerMapping 적합한 Controller를 선택하여 반환한다.
(HandlerMapping은 servlet-context.xml에서 @Controller로 등록한 것들을 스캔해서 찾아놨기 때문에 어느 컨트롤러에게 요청을 위임해야할지 알고 있다)
3. 요청에 매핑된 컨트롤러가 있다면 DispatcherServlet는 다음으로 HandlerAdapter를 호출한다. HandlerApdater는 @RequestMapping을 통하여 요청한 URL에 맞는 처리할 메서드에 도달한다.
4. 컨트롤러에서는 해당 요청을 처리할 Service를 주입(DI)받아 비즈니스로직을 Service에게 위임한다. Service에서는 요청에 필요한 작업 대부분(코딩)을 담당하며 데이터베이스에 접근이 필요하면 DAO를 주입받아 DB처리는 DAO에게 위임한다. DAO는 MyBatis(또는 hibernate등) 설정을 이용해서 SQL 쿼리를 날려 DB에 저장되어있는 정보를 받아 서비스에게 다시 돌려준다.
(이 때, 보통 Request와 함께 날아온 DTO 객체(@RequestParam, @RequestBody, ...)로 부터 DB 조회에 필요한 데이터를 받아와 쿼리를 만들어 보내고, 결과로 받은 Entity 객체를 가지고 Response에 필요한 DTO객체로 변환한다.)
모든 비즈니스 로직을 끝낸 서비스가 결과물을 컨트롤러에게 넘기고 그 결과물을 View 객체에 전달할 수 있도록 Model에 저장한다.
5. 결과물을 받은 컨트롤러는 필요에 따라 Model객체에 결과물 넣거나, 어떤 view(jsp)파일을 보여줄 것인지등의 View name을 담아 DispatcherServlet에게 리턴한다.
6. DispatcherServlet은 ViewResolver를 호출하여 Controller가 리턴한 View name을 기반으로 적합한 View를 찾아 DispatcherServlet에게 알려준다.
(servlet-context.xml에서 suffix, prefix를 통해 /WEB-INF/views/index.jsp 이렇게 만들어주는 것도 ViewResolver)
7. DispatcherServlet은 View 객체에 처리결과를 넘겨 최종 결과를 보여주도록 요청한다.
8. View 객체는 해당하는 View를 호출하며, View는 Model 객체에서 화면 표시에 필요한 객체를 가져와 화면 표시를 처리하고 Client에게 넘겨준다.
동작 순서 중간중간에 설정 파일의 대한 내용을 언급 했는데 밑에서 설정 파일에 관하여 살펴보도록 하겠다.
Spring Framework의 설정 파일 (web.xml & servlet-context.xml)
Spring Project를 생성하면 src 밑에 web.xml, servlet-context.xml, root-context.xml이 존재한다.
- web.xml
1. Servlet은 웹 프로그래밍에서 Client의 요청을 처리하고 그 결과를 Client에게 전송하는 기술이다. Java를 이용하여 Web을 만들기 위해서 필요한 기술이다.
Servlet은 다음과 같은 특징을 가지고 있다.
- Client의 요청에 대해 동적으로 작동한다.
- HTML을 사용하여 요청에 응답한다.
- Java Thread를 이용하여 동작한다.
- MVC 패턴에서 Controller로 이용된다.
Spring Framework에선 Servlet을 DispatcherServlet을 이용한다. DispatcherServlet은 Front Controller를 담당하며 모든 HTTP 요청을 받아들여 다른 객체들 사이의 흐름을 제어한다. Servlet의 요청에 관련된 객체를 정의하는 곳이 servlet-context.xml이 된다. 즉 Controller나 Annotation, ViewResolver 등을 설정해준다.
2. servlet 별칭을 통해 DispatcherServlet을 mapping 해준다. url-pattern를 '/' 설정하였기에 Root 경로로 들어온 모든 요청을 처리할 수 있게 된다.
3. 모든 Servlet 및 Filter가 공유하는 Root Spring Container를 정의한다. ContextConfigLocation라는 파라미터를 이용함으로써 ContextLoader가 호출할 수 있는 설정 파일을 여러 개 쓸 수 있다. 여기서 사용되는 XML은 root-context.xml로 Root Context를 구성한다. 주로 Service나 Repository(DAO), DB 등 Business Logic과 관련된 설정을 해준다.
4. 모든 Servlet 및 Filter가 공유하는 Spring Container를 생성한다.
ContextLoaderLoaderListener와 DispatcherServlet은 각자 WebApplicationContext를 생성한다.
ContextLoaderListener가 생성한 Context가 Root Context가 되고 DispatcherServlet이 생성한 인스턴스는 Root Context를 부모로 하는 자식 Context가 된다. 자식 Context들은 Root Context의 설정 Bean을 사용할 수 있다.
따라서 ContextLoaderListener를 공통으로 사용하는 Bean을 설정한다.
이러한 context의 특징을 알고 Bean을 설정할 때 component-scan을 통한 등록을 주의해야한다.
양쪽 Context설정에서 component scan을 통해 Bean을 등록할 때 controller와 service, reposity의 각 Bean들이 등록되어야 하는데 context에 맞게 등록이 되도록 해야한다.
단순히 base-package에 대한 설정만으로 Scan을 하면 같은 Bean이 양쪽 context에 모두 등록되어 불필요한 Bean등록이 발생한다.
따라서 component scan을 통한 각 context에 대한 Bean 등록시 exclude, include를 적절히 사용하여 불필요한 중복 Bean이 등록되지 않도록 해야한다.
- servlet-context.xml
- 적합한 Controller Annotation을 인식하도록 <annotation-driven />을 사용한다. HandlerMapping 역할을 한다.
- HTTP GET 요청을 통해 Spring에서 정적인 리소스 인 CSS, HTML 등 파일들을 처리할 수 있도록 등록한다.
- Controller가 반환한 View name을 기반으로 적합한 View(JSP)를 찾을 수 있도록 경로를 지정한다. ViewResolver 역할을 한다.
- component-scan은 XML에 각 Bean을 일일이 지정하지 않고 @Component를 통해 자동으로 Bean을 등록시켜준다. base-package 경로를 기반으로 탐색하여 Annotation을 식별하여 Bean을 생성한다.
[ Reference ]
'Spring' 카테고리의 다른 글
[JPA] @Transactional 멀티스레드 테스트 및 동시성 이슈 돌파 (0) | 2024.03.11 |
---|---|
[Spring] IoC(Inversion of Control : 제어의 역전)컨테이너란? (0) | 2021.09.29 |
[Spring] 스프링 빈(Bean)이란? (0) | 2021.09.24 |
댓글