지난 포스팅에 이어, 오늘은 스프링 웹 개발 기초에 대해 알아 보겠다. 웹을 개발한다는건 크게 보면 3가지가 있다.

 

  • 정적 컨텐츠
  • MVC와 템플릿 엔진
  • API

📍정적 컨텐츠

정적 컨텐츠란 저번에 만들었던 hello 페이지와 같이 아무런 동작이나 상호작용을 하지 않는 말 그대로 '정적'인 컨텐츠이다. 웹페이지 파일을 클라이언트에게 '그대로' 전달 해주는 것이다.

 

먼저, 기본적으로 스프링부트는 정적 컨텐츠를 static이라 불리우는 디렉토리에서 찾아 제공해 준다. 예시를 한번 들어보자.

 

위 이미지처럼 resources/static 디렉토리 안에 hello_static.html이라는 파일을 만들어 주고, 코드는 아래와 같이 작성 해준다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
정적 컨텐츠 입니다.
</body>
</html>

 

IntelliJ에서 서버로 파일을 넘겨주고, 크롬 브라우저를 열어 localhost:8080/hello_static.html을 치면 아래와 같은 화면이 보인다.

 

이처럼 정적 파일을 그대로 클라이언트에게 가져다 준다. 하지만 여기서는 글자만 보이듯이, 우리가 평소에 웹 애플리케이션에서 할 수 있는 다른 활동은 하지 못한다. 말 그대로, 이 정적 웹 페이지에서는 코딩을 해서 다른 기능을 구현 할 순 없다.

 

이제 이 정적 페이지가 어떻게 렌더링 되는지 그림으로 살펴보자.

 

1. 웹 브라우저에 localhost:8080/hello_static.html을 치면, 

 

2. 내장된 Tomcat서버가 요청을 받고, 이제 이 요청을 스프링에게 넘겨준다.

 

3. 이때, 위에 1번이라고 써있는 부분에서는 이전 포스팅에서 언급한 "컨트롤러"가 hello_static.html 파일이 존재하는지 찾아본다.(컨트롤러가 우선순위를 가진다.)

 

4. 하지만, 저번 포스팅을 보면 알겠지만 아래와 같이 hello에 관한 컨트롤러는 있었으나, hello_static에 관한 컨트롤러는 없다.

 

5. 이제 컨트롤러가 이 hello_staic에 관한 컨트롤러가 없는것을 알게 되면, 이제 2번에 쓰여진 resources/static/hello_static.html을 찾는다. 

 

6. 이제 resources/static/hello_static.html 여기에는 이 파일이 존재하는것을 알 수 있다. 이 파일을 찾았으니 다시 클라이언트에게 반환해 준다.

 

정적 컨텐츠는 비교적 간단한 방식으로 동작한다.

 

📍MVC, 템플릿 엔진

MVC는 Model, View, Controller의 약자이다. 저번 학기에 Kotlin으로 모바일 앱을 개발할때 봤던 기억이 난다. 이 방법은 웹서버에서 HTML파일을 조금 더 동적으로 바꿔 클라이언트에게 전달 해주는것을 의미한다.

 

자 그럼, 이번에는 새로운 컨트롤러를 하나 더 만들어 보자. 저번 포스팅에서 이미 만들었던 HelloController라는 자바 클래스에 새로운 컨트롤러를 아래와 같이 만들어 준다.

 

package Jihoo.hello_spring.controller;

import org.springframework.ui.Model;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class HelloController {

    @GetMapping("hello")
    public String hello(Model model) {
        model.addAttribute("data", "hello!");
        return "hello";
    }

    @GetMapping("hello-mvc")
    public String helloMvc(@RequestParam("name") String name, Model model) {
        model.addAttribute("name", name);
        return "hello-template";
    }

}

 

더보기

@GetMapping("hello-mvc")

- `@GetMapping` 어노테이션은 HTTP GET 요청을 처리하는 메서드를 정의할 때 사용된다.
- `"hello-mvc"`는 이 메서드가 처리할 URL 경로를 지정한다. 즉, `http://localhost:8080/hello-mvc`로 들어오는 GET 요청을 이 메서드가 처리한다.


public String helloMvc(@RequestParam("name") String name, Model model) {


- `public String`은 메서드가 `String` 타입의 값을 반환한다는 것을 의미한다. 이 반환값은 뷰의 이름을 나타낸다.
- `@RequestParam("name") String name`은 요청 파라미터를 메서드의 인자로 받아온다. URL에 `?name=값` 형태로 전달된 파라미터 값을 `name` 변수에 저장한다.
- `Model model`은 뷰에 데이터를 전달하기 위한 인터페이스이다. 이 메서드에서는 모델 객체를 사용하여 뷰에 데이터를 추가한다.

model.addAttribute("name", name);

- `model.addAttribute("name", name);`는 모델에 데이터를 추가하는 코드이다.
- `"name"`은 뷰에서 사용할 데이터의 키값이다.
- `name`은 `@RequestParam`으로부터 전달받은 값이다. 이 값이 뷰에 전달된다.

return "hello-template";

- `return "hello-template";`는 이 메서드가 반환하는 값으로, 뷰의 이름을 나타낸다.
- Spring MVC는 이 반환값을 기반으로 뷰를 찾는다. 예를 들어, 이 경우 `resources/templates/hello-template.html` 파일이 뷰로 사용된다.


이 메서드는 `http://localhost:8080/hello-mvc?name=값` 형태로 들어오는 GET 요청을 처리한다. 요청 URL에서 `name` 파라미터 값을 읽어와서, 이를 `Model` 객체에 추가한 뒤 `hello-template`이라는 이름의 뷰를 반환한다. 뷰 템플릿에서는 모델에 추가된 `name` 값을 사용하여 사용자에게 응답을 생성한다.

이제, 컨트롤러를 추가한 후, resources/templates에 hello-template.html 파일을 하나 만들어 준다.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Hello MVC</title>
</head>
<body>
<p th:text="'hello ' + ${name}">hello! empty</p>
</body>
</html>

 

여기서 눈여겨볼 부분이 하나 있는데, 

<html xmlns:th="http://www.thymeleaf.org">

 

 

이 부분을 보면, 맨 처음 포스팅때 나왔던 의존성, 라이브러리 설치때 설치했던 thymeleaf라는 라이브러리를 추가해야 한다. 이건 템플릿 엔진으로, 위에서 정적인 html파일을 서빙했던것과 달리, html파일을 조작하여 클라이언트에게 응답해준다.

thymeleaf를 사용하면 html 파일을 동적으로 조작할 수 있다. 즉, 서버에서 데이터를 받아 html 파일에 삽입하거나 html 구조를 변경한 후 클라이언트에게 응답할 수 있다. 이를 통해 더 유연하고 동적인 웹 페이지를 만들 수 있다.

예를 들어, 사용자 정보나 데이터베이스에서 가져온 데이터를 html에 쉽게 삽입할 수 있고, 조건문이나 반복문을 사용하여 html 구조를 동적으로 생성할 수도 있다.

 

이제, IDE에서 실행을 눌러 서버에 파일을 전달 해보자. 전달 후, localhost:8080/hello-mvc.html을 들어가보면...

이런 에러가 뜬다. 에러가 뜨면 항상 로그를 잘 확인해 보자.

 

로그를 확인 해보니 

친절하게도 이렇게 알려줬다. required필드인 name에 아무 값이 없어서 발생한 에러이다.

Mac에서는 Cmd + p를 누르면 이렇게 정보를 볼 수 있는데, boolen required()는 디폴트값이 true이다. 즉, 이 필드는 꼭 무언가를 받아야 하는 필드이며, 받지 않으면 방금처럼 오류가 발생한다.

 

자 그럼, 이제 name파라미터를 넘겨 줘보자.

 

짜잔! ?name=spring! 과 같이 name 파라미터의 값을 넘겨주니 웹 페이지에 방금 넘겨준 spring!이라는 글자가 보인다.

 

🔧 동작 방식은 어떻게 될까?

다시한번 컨트롤러의 코드를 살펴보자.

@GetMapping("hello-mvc")
    public String helloMvc(@RequestParam("name") String name, Model model) {
        model.addAttribute("name", name);
        return "hello-template";
    }

위에서 name=spring!과 같이 파라미터의 값을 넘겨주면, 이 컨트롤러가 먼저 이 파라미터를 받는다. 그러면,

 

- String name부분의 name은 spring!이라는 값이 담길 것이고, 

- 이제 이 컨트롤러에서 name이라는 변수는 내부적으로 spring!이라는 값을 담고있다.

- 이제 또 다른 파라미터로 받은 model에 방금 받은 name객체를 추가 해준다.

 

- 그리고 이제 return hello-template를 통해 hello-template.html로 넘어가면, 

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Hello MVC</title>
</head>
<body>
<p th:text="'hello ' + ${name}">hello! empty</p>
</body>
</html>

여기서 ${name}이 spring!으로 바뀌어 화면에 출력 될 것이다. 여기서 이 달러사인을 보면, 이 달러사인은 모델의 키값을 참조하는데, 이전에 컨트롤러에서 모델에다가 키값이 name인 attribute를 추가해줬으니 여기서 달러사인으로 name을 참조하면, 모델이 담고있는 객체중, 키값이 name인 객체의 값(spring!)을 가져온다.

 

잘 이해가 가지 않으니 아래 그림으로 다시 한번 보자.

 

- 먼저, 브라우저에 localhost:8080/hello-mvc을 입력하면, 내장된 Tomcat서버가 이 요청을 받아 스프링에게 요청을 넘긴다.

 

- 톰캣으로부터 요청을 받은 스프링은, 이 hello-mvc라는 컨트롤러가 helloController.java에 매핑이 되어있는것을 확인하고, 그 메서드를 호출한다.

 

- 이제 호출된 메서드에서 hello-template, name이라는 키값에 spring이라는 값을 가진 모델 객체가 같이 리턴이 되고, 

 

- 이제 이 viewResolver에서 templates/hello-template.html을 찾아 thymeleaf 엔진에게 변환을 요청 후, 클라이언트에게 반환한다.

 

이처럼 아무것도 변환하지 않고 파일 그대로를 보여준 정적 웹 페이지와 달리, 템플릿 엔진을 사용하면 이 html을 변환 후 클라이언트에게 반환 해준다.

 

📍API

이번에도 마찬가지로 HelloController에다가 정적 클래스와 컨트롤러를 하나 추가해준다.

@GetMapping("hello-api")
    @ResponseBody
    public Hello helloApi(@RequestParam("name") String name) {
        Hello hello = new Hello();
        hello.setName(name);
        return hello;
    }

    static class Hello {
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name){
            this.name = name;
        }
    }

 

그런데 이전에 작성했던 다른 컨트롤러들과 달리 @ResponseBody라는 태그가 추가 되었다. 이는 무엇일까?

- 여기서 말하는 Body는 HTML의 body태그가 아니라, HTTP의 Header, Body를 의미한다. 여기서 이 HTTP의 Body부분에 이 함수에서 return된 "hello " + name 이라는 데이터를 직접 넣어 주겠다는 뜻이다. 이 태그의 기본 반환형은 JSON이다.

 

이제 브라우저를 열어 어떻게 생겼는지 확인 해보자.

 

어떠한 방식으로 이 JSON object가 반환 되었는지 그림으로 살펴보자.

 

- 로컬에서 hello-api로 요청을 보내면, 내장된 톰캣 서버에서 이 요청을 받아 스프링에 전달 해준다.

 

- 그럼 스프링은 hello-api 컨트롤러를 찾게 될것이다. 하지만 여기서 이 컨트롤러에 @ResponseBody라는 태그가 있는것을 확인한다.

 

- 이 @ResponseBody를 확인한 스프링은 HTTP 응답 body에 그대로 이 return : hello(name:spring)을 넘겨야한다고 판단한다.

 

- 하지만 여기서 문제가 있다. 컨트롤러를 보면 알겠지만 위의 MVC 방식과 달리 이번에는 문자열 그 자체가 아닌 객체를 리턴한다. 

 

- 위에서 언급했듯, 이 객체의 타입은 @ResponseBody 태그의 디폴트 타입인 JSON으로 리턴한다.

 

-이때, 이미지에서 보이는 HttpMessageConverter가 동작한다. 기존에는 ViewResolver가 동작했지만, @ResponseBody의 존재가 확실해지면 HttpMessageConverter가 동작한다.

 

- 우리가 넘겨준 hello객체의 모습은 name:spring이고, 키-값을 기반으로 하는 JSON객체와 비슷하다. 이를 인지한 HttpMessageConverter는 JsonConverter를 동작시켜 반환받은 hello객체를 JSON으로 변환시켜 클라이언트에 응답 해준다. 그래서 내가 방금 위에서 처럼 그냥 json객체를 결과로 받은것이다.

 

 

이처럼 스프링부트의 기본적인 3가지 웹 동작 방식을 알아보았다.

 

 

+ Recent posts