Spring MVC/01.Spring @MVC

01. Spring MVC 개요

  • -
반응형

Spring MVC

Spring을 이용하는 애플리케이션은 역시 Web 기반의 엔터프라이즈 애플리케이션인 경우가 많다.  Spring MVCFront Controller Pattern에 기초한 웹 MVC 프레임워크이다. Spring 3.0 부터 annotation 기반으로 동작한다는 의미로 Spring@MVC라고 하기도 한다.

front controller pattern

 

Spring MVC는 Model, View, Controller를 명확한 클래스로 분할하고 loose coupling 시켜놓아서 매우 유연하고 확장성이 좋게 만들어 놓았다.

사실 이런 유연함은 일반 프로젝트를 진행하면서는 잘 느끼지 못한다. (커스텀 프레임워크를 만드는 상황이 아니고서는..)  또한 View와 관련해서도 Model, Controller의 구성과 무관하게 JSP, Thymeleaf, Freemarker, Groovy 등을 쉽게 연동할 수 있게 구성해 놓았는데 프로젝트를 Thymeleaf 기반으로 진행하다 느닷없이 JSP로 바꾸는 일도 거의 발생하지 않는 일이긴 하다.

 

Spring MVC의 구성 요소

 

Spring MVC를 구성하고 있는 요소들에 대해 알아보자.

DispatcherServlet

SpringMVC가 front controller 패턴에 기반하고 있다고 했는데 바로 그 front controller가 DispatcherServlet이다. 가히 SpringMVC에서 가장 중추적인 역할을 담당한다고 할수 있는데 클라이언트의 모든 request를 접수'만'하는 녀석이  DispatcherServlet이다.

DispatcherServlet은 spring-webmvc에 이미 잘 작성돼서 포함되어있기 때문에 우리가 수정할 필요는 없다. 다만 이녀석의 동작을 이해해두는 것이 좋다.

dispatch의 영어 뜻은 '보내다, 파견하다' 정도 된다. 즉 클라이언트의 모든 요청을 받아들여서 사실상 해야할 일이 엄청나게 많을텐데 그 일에 대한 처리를 다른 전문가들(Infrastructure Component)에게 보내서 위임하고 정작 본인은 길잡이만 처리하는 조금은 뻔뻔한 녀석이다.

 

Infrastructure Components

Infrastructure Component는 DispatcherServlet으로 부터 요청을 위임받는 3대장을 의미한는데 HandlerMapping, HandlerAdapter, ViewResolver 3 녀석이다. 일단 Handler라는 것은 웹과 관련된 여러 업무를 실제 처리하는 아주 중요한 녀석이고 뒤에는 Controller라고 불린다.

  • HandlerMapping은 사용자의 request를 처리할 Controller가 누구인지를 DispatcherServlet에게 알려준다. 
  • HandlerAdapter는 사용자의 request를 처리해줄 Controller를 호출하고 결과를 받아온다.
  • ViewResolver는 사용자의 요청에 적합한 View를 반환해서 결과를 보여줄 수 있게 한다.

 

앞서 밝혔듯이 이들의 동작은 내부적이기 때문에 잘 이해 되지 않아도 프로그래밍은 가능하지만 면접에서도 자주 나오기 때문에 알아두는 것이 좋겠다.

 

그래서 어떻게 동작한다고?

아래 그림은 Spring MVC의 동작 방식이다. 매우 복잡해보이고 할게 많아 보이지만 보라색으로 표시된 부분은 이미 스프링이 만들어 놓은 부분이고 개발자는 오렌지색 즉 Service(Model),  Handler(Controller), View(화면)만 만들면 된다.

  1. 클라이언트 브라우저에서 요청이 발생한다. 그 요청은 모조리 DispatcherServlet(이하 D.S)이 접수한다.
  2. D.S은 그냥 길잡이만 하니까 그 요청을 처리할 Handler가 누구인지 HandlerMapping에게 물어보면 HandlerMapping이 적절한 Handler를 알려준다. 
  3. 여전히 수줍음 많은 D.S은 직접 Handler를 호출하지 못하고 HandlerAdapter에게 요청을 전달한다.
  4. HandlerAdapter가 드디어 Handler를 호출하면서 request 처리를 요청한다.
  5. Handler는 요청을 분석 후 필요한 서비스를 호출하면서 드디어 모델 영역과 소통을 하게 된다.
  6. 서비스에서 반환된 데이터는 View에서 사용되는 경우가 많은데  View에서 쉽게 접근할 수 있도록 HttpServletRequest에 저장해 두는 것이 좋다. 이를 위해 스프링에서는 Model 타입의 객체를 사용한다. 
  7. 데이터 저장까지 끝났다면 그 데이터를 확인할 View 페이지의 이름을 HandlerAdapter를 통해 D.S에 전달한다.
  8. D.S은 다시 해당 View를 어떻게 호출할 수 있는지 ViewResplver에게 문의 후 결과를 받는다.
  9. 실제 해당 View를 호출하면 템플릿 엔진이 동작해서 HTML을 구성하기 시작한다.
  10. HTML을 구성하면서 Service의 동작 결과를 참조하기 위해 6에서 만들어 놓은 Model의 데이터를 사용한다.
  11. 마지막으로 View의 내용이 클라이언트에 전달되면 처리가 종료된다.

 

프로젝트 구성

이제 Spring MVC기반의 프로젝트를 만들어보면서 필요한 요소들과 그것들의 배치에 대해 알아보자.

프로젝트 생성과 dependency 설정

SpringMVC는 Spring Core와  분리되어 있기 때문에 spring-web, spring-webmvc와 같은 별도의 maven dependency가 필요하다. 스프링 부트에서는 이들을 추가히기 위해 spring-boot-starter-web(Spring Web)을 추가해준다.

 

재밋는 점은 spring-boot-starter-web을 사용하면 내부적으로 spring-boot-starter-tomcat을 포함하고 있어서 애플리케이션을 위한 별도의 was 환경을 고민할 필요가 없다.(Spring Boot application의 배포 형태가 war가 아닌 jar임을 상기시켜보자. 이미 자체로 다 가지고 있다.) spring boot의 auto configuration의 힘은 참으로 막강하다.

 

View(사용자에게 보여지는 부분)을 만들기 위한 기술을 선정하는 부분은 Template Engine 이다. 스프링 이전에 자바에서 웹을 개발해본 경험이 있다면 당연히 JSP를 찾아보겠지만 SpringBoot에서는 JSP를 권장하지 않고 따라서 기본으로 지원하지도 않는다. 대신 Thymeleaf 같은 녀석을 추천한다. 그 이유는 나중에 차차 살펴보자. 

하지만 실무에서는 여전히 JSP에 대한 수요가 있기 때문에 JSP에 대한 설정이 필요한 경우가 더 많다.

 

https://goodteacher.tistory.com/258

 

04. 웹과 관련된 설정

웹 관련 설정과 WebMvcConfigurer 스프링 부트에서의 설정은 크게 웹과 관련된 설정과 웹과 무관한 설정 둘로 나누어서 관리하는 것을 권장한다. 편의상 앞으로 웹과 관련된 설정 파일은 MVCConfig라고

goodteacher.tistory.com

 

 

웹과 관련된 리소스의 배치

웹과 관련된 리소스들의 배치에는 src/main/resources 아래 static과 tempaltes 경로가 사용된다.

  • src/main/resources/static
    • 템플릿 엔진이 변경하지 않는 CSS, JavaScript, Image등 정적 리소스들이 위치한다.
  • src/main/resources/templates
    • 템플릿 엔진에 의해 변경되는 파일들이 위치한다.

 

Thymeleaf와 index.html

간단히 Thymeleaf 기반의 템플릿 페이지인 index.html를 만들어보자. Thymeleaf는 파일의 확장자로 .html을 사용한다.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>

<!-- / 는 context root를 나타낸다. -->
<link rel="stylesheet" href="../static/css/common.css" th:href="@{/css/common.css}">

</head>
<body>
	<img src="../static/img/springboot.png" th:src="@{/img/springboot.png}">
	<h2 th:text="${message}">메시지 표시 영역</h2>
</body>
<script src="../static/js/common.js" th:src="@{/js/common.js}" ></script>
</html>

 

  • xmlns:th="http://www.thymeleaf.org"
    • th라는 namespace를 선언한다. 이제 html 내에 th:를 사용할 수 있고 이 내용은 Thymeleaf에 의해 해석된다.
  • href vs th:href
    • 일반 href는 템플릿 엔진을 통하지 않고 파일을 브라우저로 직접 실행했을 때 동작한다.
    • th:href는 템플릿 엔진을 사용했을 때만 Thymeleaf에 의해 해석되며 일반 속성을 덮어쓰게 된다.
    • src와 th:src도 동일한 개념이다.
    • th:href에서 @내부에서 사용된 / 는 context root를 나타낸다.
  • th:text
    • th:text 내용은 템플릿 엔진에서 해석된 후 html 태그의 inner text를 대체한다.
    • ${message} 내용은 EL과 유사하게 HttpServletRequest 영역에 message라는 이름으로 저장된 값으로 대체된다.

 

빈 및 설정 정보 파일의 배치

이제 자바 영역으로 돌아가서 빈 및 설정 정보 파일의 배치에 대해 살펴보자.

 

XXApplication.java에 의해 하위 패키지가 스캔되면 여러 빈들이 등록될 텐데 이제까지는 Model을 위한 @Repository, @Service 그리고 추가적인 설정을 위한 @Configuration 3 종류가 사용되었다. 

이제 추가로 웹을 작성하면서 변경될 부분에 대해 살펴보자.

일단 설정인 @Configuration에서는 웹과 관련된 설정과 웹과 무관한 설정 둘로 나눌 수 있다. 웹과 무관한 설정은 이전처럼 ApplicationConfig로 작성하고 웹과 관련된 설정은 MVCConfig로 작성하자.(물론 이름은 변경될 수 있다. 개념적인 분리가 필요하다는 이야기이다.) 

앞선 예제에서는 Model까지만 만들었고 이에 대한 클라이언트는 단위 테스트로만 작성했는데 이제는 웹 브라우저가 새로운 클라이언트로 등장한다. 클라이언트의 요청을 처리하는 빈을 만들기 위해 @Controller들이 등장한다. 위 그림에서는 com.eshome.mvc.controller.HelloController가 그녀석이고 @Service를 주입 받아서 사용함으로써 모델(Model)과 연결된다.

 

Service와 Controller

먼저 간단한 Service를 작성해보자.

package com.eshome.mvc.model.service;

public interface HelloService {
    public String sayHello();

    public String echo(String msg);
}
package com.eshome.mvc.model.service;

import org.springframework.stereotype.Service;

@Service
public class HelloServiceImpl implements HelloService {

    @Override
    public String sayHello() {
        return "Hell Spring@MVC";
    }

    @Override
    public String echo(String msg) {
        return String.format("from service: %s%n", msg);
    }
}

 

다음은 Controller 부분이다.

package com.eshome.mvc.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.eshome.mvc.model.service.HelloService;

// 이 클래스는 컨트롤러입니다.
@Controller
public class HelloController {
    // 모델 연동 처리
    @Autowired
    HelloService service;

    // 사용자의 request 경로가 / 라면.
    @RequestMapping("/")
    public String welcome(Model model) {
        // model은 MVC의 M이 아니라 M에서 나온 결과를 저장할 객체(request scope에 해당)
        model.addAttribute("message", service.sayHello());
        // 결과를 보여줄 template 페이지의 이름
        return "index";
    }
}

 

처음으로 만들어본 Controller라 생소한 부분이 많다.

일단 @Controller이 클래스가 컨트롤러로 사용되는 클래스임을 알려주고 있다. Controller 내부에는 @RequestMapping을 갖는 handler method들이 있는데 말 그대로 사용자의 요청(request)에 매핑되는 메서드들을 정의해준다. 위 예에서는 '/' 요청에 대해 동작하는 메서드가 작성되었다. 메서드의 파라미터로 선언된 Model model은 View와의 소통에 매우 중요한 역할을 한다. 

앞으로 MVC의 Model은 대문자로 쓰고 Model 타입의 model은 소문자로 쓰겠다.

model은 Model을 연동한 결과(여기서는 service.sayHello()의 리턴값)를 View에서 참조하기 위해 저장해 놓는 객체이다. model에 저장한 값은 HttpServletRequest 스코프에 저장된다.

앞서 index.html에서 ${message}로 참조하는 부분이 바로 model에 저장해 둔 값이다.

마지막으로 메서드에서 리턴하는 값은 "index"라는 문자열이다. 이 값은 templates/index.html과 연결된다. 

신기한 점은 index.html이 아니라 index라는 점이다. 이 부분이 View 기술과 Controller를 분리시키는 부분이다. 만약 리턴 값이 index.html이었고 template 기술이 Thymeleaf에서 JSP로 변경된다면 index.html 부분을 index.jsp로 변경해야만 했을 것이다. 하지만 index라고만 리턴하고 실제 View와의 연결(html인지 jsp인지)은 나타나지 않기 때문에 View 기술이 변경되어도 Controller에는 전혀 영향을 주지 않는다.

 

실행

드디어 실행해볼 때가 되었다. 프로젝트 오른 클릭 후 Run As -> Spring Boot App을 선택해서 애플리케이션을 실행시켜보자.

 

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.1.RELEASE)

. . .
14:23:01 [ INFO] Tomcat initialized with port(s): 8080 (http)
14:23:01 [ INFO] Starting service [Tomcat]
. . .
14:23:01 [ INFO] Tomcat started on port(s): 8080 (http) with context path ''

로그에서 주의깊게 살펴볼 부분은 Tomcat이 8080 포트에서 동작하고 있고 이때 context path는 ""라는 점이다.

물론 이런 값들은 필요하다면 application.properties에서 변경할 수 있다.

server.port=9090
server.servlet.context-path=/mvctest

서버가 성공적으로 싱행되었다면 브라우저를 통해 요청을 날려보자.

아래와 같이 화면이 출력되면 대 성공이다.!!

 

이번 포스트에서는 SpringMVC의 개요에 대해 알아보았다. 다음 포스트에서는 @Controller에 대해 보다 자세히 알아보자.

반응형

'Spring MVC > 01.Spring @MVC' 카테고리의 다른 글

06. 파라미터의 formatting  (0) 2020.07.07
05. Handler Interceptor  (1) 2020.07.03
04. 웹과 관련된 설정  (0) 2020.07.02
03. Controller에 대한 단위 테스트  (2) 2020.07.01
02. Controller  (0) 2020.06.30
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.