Spring boot를 학습하기 위해 여러 강의들을 찾아 돌아다녔지만 DI, AOP 등 추상적인 개념들이 서두에 나와 나를 괴롭게 하였다. 항상 개념들을 보고 대충 따라하다가 관두면서 스프링 공부를 항상 미뤄왔다. 의존성을 한 곳으로 어떻게 위임는지, 어떤 장점이 있는지 대충 이해는 가지만 실제 개발을 통해 와닿지 않는 점이 나를 지치게 했다.
그러던 중 김영한 선생님의 인프런 강의를 접하게 됐는데 일단 코드부터 따라하면서 시작하는 점이 쉽게 다가왔다. 강의 튜토리얼에서 하신 말씀 중 '이론공부를 하다가는 졸아도 코드를 짜다가 졸지는 않는다'라는 이야기에 적극 공감한다. 실습 위주로 강의를 들으며 얕고 쉽게 공부할 예정이다.
개발 공부하면서 느끼는 거지만 IT지식은 밑바닥부터 차근차근 쌓아 올리는 것이 아니라 퍼즐처럼 맞춰지는 대로 끼워 넣다보면 어느새 그림이 보일 때가 많다. 퍼즐에서 모서리 부분을 제일 먼저 끼워 넣는거 처럼 웹 개발에서도 엣지 단에서 시작하는게 쉽게 다가온다. 스프링이 브라우저에 컨텐츠를 어떻게 전달하는지를 먼저 알아보자.
스프링이 브라우저에 컨텐츠를 전달하는 방법은 크게 세 가지다.
- 정적 컨텐츠를 그대로 띄운다.
- MVC와 템플릿 엔진을 사용한다.
- API를 통해 객체를 전달한다.
1. 정적 컨텐츠를 그대로 띄우는 방법
말 그대로 정적 html 파일을 그대로 브라우저에 띄우는 방식이다. 스프링 부트 프로젝트 내의 정적 파일은 '/src/main/resources/static'에 위치 시킨다.
가장 기본적인 html 파일(static/hello.html)을 생성하였다. 프로젝트 빌드 후 localhost:8080/hello.html로 접속하면 우리가 예상하는 그 화면이 뜰 것이다.
원하는 정적 파일을 넣기만 하면 브라우저에 화면이 그대로 출력된다. 하지만 정적 페이지가 그렇듯 프로그래밍적 요소를 첨가할 수 없다. 스프링 부트 내부적인 구조를 보면 아래와 같이 동작한다.
웹 브라우저에서 localhost:8080에 정적 파일을 요청하면 내장 톰켓 서버가 요청을 받아서 스프링 컨테이너로 넘긴다. 스프링 컨테이너는 해당 정적 파일(hello-static)이 컨트롤러에 존재하는지를 먼저 확인한다.(우선순위가 컨트롤러에 있다) 컨트롤러를 확인하지 못했을 때, static 디렉터리의 정적 파일을 찾고 그대로 브라우저에 보내준다.
2. MVC와 템플릿 엔진
정적 html파일을 그대로 전달하는 것이 아닌 서버에서 프로그래밍하여 템플렛 엔진에 전달, html을 동적으로 바꿔서 보여준다.
기본적으로 Model-View-Controller 삼권분립 체제다. 과거에는 controller와 view가 분리되어 있지 않았고 view(jsp)에서 모든 것을 처리했다.(model 1 방식) 하지만 프로젝트의 규모가 커질수록 책임의 분할에 따른 개발, 유지보수의 효율성이 중요시 되면서 veiw는 화면에 보여지는 것만 처리하고 분리된 controller는 비지니스 로직을 담당하게 되었다.
@GetMapping은 HTTP GET 요청을 처리하는 메서드를 맵핑하는 어노테이션이다. 메서드(url)에 따라 어떤 페이지를 보여줄 것인지 결정하는 역할을 한다. "hello-mvc"라는 url을 받았을 때, get 방식으로 넘어온 parameter "name"을 전달 받는다. 그리고 모델에 담은 "name" attribute를 "hello-template"로 넘긴다.
타임리프 템플릿 엔진을 쓰는 html 파일이다. 넘겨 받은 name을 통해 동적으로 웹 페이지를 만든다. <p> 태그 안에 들어가 있는 "hello! empty"는 서버 없이 껍데기만 출력할 때, 표시해 주는 역할을 한다. 마찬가지로 localhost:8080을 통해 접속해준다.
![]() |
get 요청의 파라미터로 넘긴 자몽워터가 정상적으로 브라우저에 출력되는 것을 확인할 수 있다.
정적 컨텐츠와 마찬가지로 브라우저의 요청을 톰켓 서버가 가장 먼저 받는다. 그리고 우선적으로 controller를 확인하고hello-mvc와 매핑이 된 메소드를 찾아 호출한다. 그리고 리턴 템플릿(hello-template)과 모델을 veiwResolver에게 넘기고 이를 받은 해결사는 view를 찾아주고 템플릿 엔진과 연결을 시켜준다. 템플릿 엔진 타임리프는 전달받은 파라미터 name을 반영하여 렌더링을 하고 변환된 html을 웹 브라우저에 반환한다.
3. API를 통해 객체를 전달
API를 통해 프론트 쪽에 데이터만 뿌려주는 방식이다. Json 형태로 주로 많이 전달이 된다.
Android, ios 클라이언트 측과 함께 개발을 할 시 view 단은 온전히 fe개발자에게 맡겨야 한다. 그 밖에도 Vue.js, React.js 등 프론트에서 완전히 화면에 보이는 부분을 처리한다면 api를 통해 데이터만 뿌려준다. Node로 서버를 구축할 때 이와 같은 방식으로 개발했던 기억이 난다.
이전 MVC와 비슷한 형식으로 "hello-string"과 매핑시켜서 코드를 작성했다. 다른 점은 @ResponseBody 어노테이션을 사용했다는 것과 리턴을 템플릿 이름을 해주는 것이 아닌 parameter를 적용한 단순 스트링이라는 점이다. 위의 코드를 빌드하여 실행시키면 아래와 같이 나온다.
위의 2번의 방식과 똑같은 출력이 브라우저에 나온다. 하지만 브라우저에서 소스코드를 확인하면 차이점이 보인다.
2번 방식으로 타임리프에 의해 변환된 html을 받은 브라우저는 html 코드가 나타난다. 하지만 @ResponseBody 어노테이션을 이용한 api 전달 방식은 리턴했던 데이터만 브라우저에 덩그러니 있다. @ResponseBody는 url을 통해 받은 http 요청에 대한 응답을 response body에 담아서 보내주는 역할을 한다.(html <body>가 아니다!)
주로 객체를 생성하여 리턴을 하고 HTTPMessageConverter에 의해 Json으로 변환되어 body에 실리게 된다. 아래는 Hello라는 객체를 생성하여 리턴했을 때, 브라우저에 나타나는 모습이다.
예상했듯이 Json 형태로 브라우저에 나타난다. 아래는 API 방식의 내부 처리 구조이다.
HTTP 요청을 받은 컨트롤러는 @ResponseBody 어노테이션에 따라 HttpMessageConverter로 리턴 객체를 전달한다. 전달 받은 리턴 타입이 기본 문자 타입이라면 'StringConverter'가 처리를 하고 객체일 경우에는 'MappingJackson2HttpMessageConverter'가 처리를 해준다. 이렇게 응답한 데이터들을 프론트에서 멋지게 요리하면 된다.
게시글의 모든 내부 처리 로직 이미지는 김영한 선생님의 인프런 강의 '스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술' 자료 출처임을 밝힙니다.