스프링 공부하면서 많이 마주치는 키워드들이 있었다. DI, Ioc, 서블릿.. 등등

이런 것들을 짚고 넘어가야 할 것 같다는 생각이 들었다. 야생으로 학습 중인 만큼 지금 당장 이해 안 돼도 넘어가야 하는 부분들이 있겠지만, 그렇다고 다 넘기는 건 아닌 것 같다... 일단 한 번 짚어보고, 지금 당장 다 짚일 것 같지 않으면 넘기고 오 좀 된다 싶으면 짚고 가는게 좋을 듯 함.

그래서 서블릿이란 놈을 한 번 패보기로 했다.


배경

초창기 웹 프로그램은 정적 데이터만 전달 가능했다. 클라이언트가 어떤 걸 요청하면 웹서버가 정적데이터를 응답하는 식. 이게 끝

근데 이제 사용자 요청에 따라 다른 처리, 즉 동적인 처리를 해주고 싶었던 거다. 그걸 위해서 '웹 어플리케이션 프로그램'을 만들어 기존에 존재하던 웹 서버에 붙이고 싶은 거라고 보면 된다.

 

이걸 위해서 CGI가 등장했다. Common Gateway Interface약자로, 웹서버와 앞서 말한 웹 어플리케이션 프로그램 사이의 규약(인터풰이스)이다. C, PHP등으로 요놈의 구현체를 만든다. 이 구현체들은 결국 쉽게 말해서 동적 데이터를 처리해주는 놈, 즉 웹 어플리케이션 프로그램이다.

 

 

그래서 예전과 달리 '동적인 처리'를 해줄 수 있게 됐다. 사람들이 CGI를 많이 활용하게 됐으니까.

근데 문제가 많았던 거다. CGI가 많은 사용자를 처리하기엔 힘들었던 것.

 

  • 클라이언트로부터 request가 들어올 때마다 그 놈들 하나하나마다 웹서버에서 프로세스를 만들어 처리. 프로세스니까 당연히 비용이 비쌌음
  • request들에 대해 같은 CGI 구현체를 써도 프로세스들이 다르면 여러 개의 구현체를 사용해야 됐음. 당연히 비효율적이었음

 

이를 해결하기 위해서 프로세스가 아니라 쓰레드를 만들었다. 그리고 같은 종류의 여러 CGI구현체를 만드는 몹쓸 상황을 막기 위해 CGI구현체를 싱글턴으로 만들었고.

 

이 싱글턴이 바로 서블릿!! 클라이언트로부터 request가 들어올 때마다 쓰레드가 생기고, 이 쓰레드를 통해 싱글턴 CGI구현체에게 동적인 처리를 해달라하는데 이걸 해주는 그 놈을 서블릿이라 부르는 것. 즉 서블릿은 자바로 구현된 CGI기도 한 거다.

 

결국 서블릿은

 

= 클라이언트의 요청을 동적으로 처리할 때 쓰이는 자바 기반의 웹 애플리케이션 프로그래밍 기술(인터페이스임)

= 동적 컨텐츠를 만드는데 사용되는 놈!

 

이라고 말할 수 있다


좀 더 뜯어보기 - 동작 방식

서블릿이 그래 동적인 컨텐츠를 만드는 데 쓰이는 놈이란 건 알겠다. 웹서버가 이 놈한테 말을 건네서 이 서블릿이란 놈이 동적인 처리를 해주는 거구나. 

 

그 과정을 좀 더 뜯어본다.

 

HTTP request, response를 서블릿(얘 자체는 역시나 인터페이스)의 메서드들을 통해 편하게 다룰 수 있다고 한다.

 

 

httpServletRequest = 서블릿 컨테이너가 서블릿에게 전달하는 때 담는 봉투

httpServletResponse = 서블릿이 서블릿 컨테이너에게 돌려줄 때 담아보내라고 지정하는 봉투

 

 

  1. 사용자가 url입력 
  2. HTTP request가 웹서버로 전달됨
  3. 웹서버는 이 요청이 정적 자원을 요청하는지 판단(정적 자원이면 그대로 정적 자원 주면 됨)
  4. 동적인 처리가 필요하면 그 요청을 그대로 was한테 짬때림. 
  5. was의 웹 컨테이너(= 서블릿 컨테이너)가 이를 받고, 처리하기 위한 쓰레드를 만듦
  6. 그리고 컨테이너가 HttpServletRequest, HttpServletResponse객체를 만듦. HttpServletRequest객체로는 사용자가 요청한 내용을 편하게 다루고, HttpServletResponse객체에는 응답할 내용을 편하게 작성 가능
  7. 컨테이너가 사용자가 입력했던 url이 어느 서블릿에 대한 요청인지 찾고(by web.xml ), 걔를 호출. 이 때 아까만든 두 객체를 서블릿에게 선물로 줌
  8. 그 서블릿의 service메서드를 통해 요청이 처리됨! 즉 service메서드에 작성한 코드들이 실행되는 것.
  9. 이 때 아까 받은 request객체를 사용하고, 응답할 내용은 아까 받은 response객체에 저장하는 것.
  10. 또한 service메서드를 호출한 후 클라이언트가 보낸 요청이 GET인지 POST인지에 따라 doGet() 또는 doPost() 호출
  11. 이를 다시 클라이언트에게 최종 결과 응답 후, HttpServletRequest, HttpServletResponse는 삭제

 

※ service메서드를 호출할 때 HttpServletRequest, HttpServletResponse객체를 넘기는 것임! 즉 서블릿이 만들어질때 이 두 놈을 넘기는게 아니라 service메서드를 호출할 때 두 놈을 선물로 주는 것임에 유의

 

※ 톰캣은 was면서 서블릿 컨테이너의 기능도 제공한다고 함!


나아가기 - 스프링 web MVC와 서블릿

그러나..서블릿 역시 문제가 있었던 것이었다. 

 

앞서 설명했듯 사용자가 입력한 url별로 서블릿이 매핑된다. 10개의 각기 다른 url들이 들어오면 10개의 서블릿들이 매핑되는 것! 그럴 때마다 서블릿들이 가지는 "공통된 로직"이 반복돼서 실행된다는 문제점이 있었다. 즉 개발 측면에서 상당히 비효율적이었음.

 

이런 점을 해결하기 위해, 클라이언트로부터의 요청을 받는 서버의 앞쪽에 모든 요청을 받는 하나의 서블릿을 두기로 했다. 그 컨트롤러가 "공통된 로직"을 수행하게 하고, 핵심 비즈니스 로직을 다른 핸들러들에게 위임하는 구조로 바꾼 거다!

 

원래는 이렇게 했는데
이렇게 바꿔줬다는 거!

 

이런 방식을 Front Controller Pattern이라고 한다. 하나의 서블릿(Dispatcher Servlet)으로 모든 요청을 받게 했으니, 요청의 진입점이 같아져 관리가 보다 더 수월해진다는 장점이 있다. 또한 각 서블릿마다 가지는 공통로직을 한 곳에서만 처리함으로써 중복되는 로직의 작성도 방지하게 된다.

 

(디테일하게 디스패처 서블릿이 요청을 처리하는 과정은 본 글에선 다루지 않음)

 

결국엔 이런 방식(디스패처 서블릿이 모든 요청을 받고 공통로직들을 처리하고..)을 스프링이 사용하는 덕분에, 개발자는 핸들러(즉 컨트롤러)에만 집중하면 되도록 발전해왔다..라고 이해하면 될 듯 하다. 

스프링 웹 개발은 다음과 같은 3가지 방법이 있다.

 

  1. 정적 컨텐츠 : 서버에서 특별히 뭔가 하는 거 없이 단순히 html파일을 클라이언트 쪽으로 넘기는 방식이다. 
  2. MVC & 템플릿 엔진 : 서버에서 특정한 작업을 통해 html파일을 동적으로 만들어 넘기는 방식이다. JSP, PHP 등의 템플릿 엔진이 사용되며 이걸 하기 위해 MVC패턴이 도입된다.
  3. API : JSON 포맷 등으로 '데이터'를 클라이언트에 전달하는 방식이다. 서버 간의 통신에도 활용되며, 요즘은 React같은 애들한테 API로 데이터를 넘기고 그걸 통해 화면을 클라이언트가 그리는 식으로도 활용된다.

 

정적 컨텐츠

resources폴더의 static폴더에 있는 파일을 찾아 넘긴다. 다음 순서로 동작한다고 이해할 수 있다.

  1. url를 입력
  2. 내장된 톰캣이 요청을 받고 스프링 컨테이너에게 요청을 넘김
  3. 컨트롤러가 url에 매핑되는 메서드가 있는지 먼저 찾음
  4. 없으면 내부의 resources/static에서 찾아서 넘김

 

MVC & 템플릿 엔진

MVC는 Model, View, Controller로 각자의 역할을 구분해 개발하는 방식을 일컫는다. Model과 Controller는 비즈니스 로직과 내부 로직을 처리하는 역할을 맡고, View는 화면을 그리는 역할을 맡는다. 옛날엔 JSP를 통해서 View에서 Controller의 역할도 수행했는데 이를 model 1방식이라 부르기도 함.

 

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

 

  • GetMapping : GET POST 할 때의 get이며, /hello로 get요청이 들어왔을 때 hello라는 메서드를 실행한다는 거다. 
  • Model : View를 그리기 위해 사용되는 데이터들의 박스(?)로, key-value형태로 데이터들을 저장한다. 내부적으론 Map이 사용된다고 함.
  • return "hello" : 데이터들을 보낼 View이름을 지정하는 거라고 보면 된다.

 

 

이 방식의 동작순서는 다음과 같다.

 

  1. url를 입력
  2. 내장된 톰캣이 요청을 받고 스프링 컨테이너에게 요청을 넘김
  3. 컨트롤러가 url에 매핑되는 메서드가 있는지 먼저 찾음
  4. 있으면 해당 메서드를 호출
  5. viewResolver가 지정된 이름의 View를 찾아주고 템플릿 엔진과 연결시켜줌
  6. 템플릿엔진이 렌더링한 html파일을 반환

 

API

이 방식은 html파일이 아니라 데이터를 넘기는 방식이라고 볼 수 있다. 결국 정적 컨텐츠를 넘기는게 아니라면

 

  • html로 내려주든가
  • api로 (즉 데이터로) 주든가

 

둘 중에 하나인 거다.

 

@Controller
public class HelloController {
    @GetMapping("hello-string")
    @ResponseBody
    public String helloString(@RequestParam("name") String name) {
        return "hello" + name;
    }
}
  • GetMapping : GET POST 할 때의 get이며, /hello-string로 get요청이 들어왔을 때 helloString라는 메서드를 실행한다는 거다. 
  • @ResponseBody : 이 어노테이션을 쓰면 viewResolver를 쓰지 않고, 대신에 Http Response의 BODY에 데이터를 담게 된다.
  • @RequestParam("name") String name : 쿼리에서 "name"에 해당하는 값을 name이란 파라미터로 받아온다는 거다.
  • return "hello"  + name : 이 문자열 자체를 데이터로 준다는거다.

 

참고로 객체를 반환하면 객체가 JSON으로 변환되서 보내진다!

@Controller
public class 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;
        }
    }
}

이 방식의 동작순서는 다음과 같다.

 

  1. url를 입력
  2. 내장된 톰캣이 요청을 받고 스프링 컨테이너에게 요청을 넘김
  3. 컨트롤러가 url에 매핑되는 메서드가 있는지 먼저 찾음
  4. 있으면 해당 메서드를 호출
  5. 근데 @ResponseBody가 있으면 viewResolver에게 전달하지 않고 httpMessageConverter에게 전달함
  6. 문자는 그대로 보내고, 객체는 JSON으로 바꿔서 전달한다.

 

 

용어 정리

Gradle : 라이브러리들을 버전 설정하고 가져와주는 빌드 도구. 은 의존관계가 있는 라이브러리들도 함께 다운해준다(A라이브러리가 B에 의존하면 자동으로 B도 땡겨줌).

 

resources폴더 : 자바 파일(*.java)를 제외한 나머지 파일들을 위한 폴더

 

템플릿 엔진 : 지정된 템플릿 양식과 데이터를 합쳐 HTML 문서를 출력하는 SW. 예를 들어  그냥 a.html을 작성하기만 하면 서버에선 그냥 걔를 클라이언트쪽으로 넘기기만 하는데, 템플릿 엔진을 쓰면 html에서 루프를 쓴다던가 어떤 데이터값을 넣는다든가 할 수 있는 것이다. 

 

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

 

html태그에 이렇게 작성해두면 해당 html파일에서 타임리프(템플릿 엔진) 문법을 사용 가능하다. 이를 활용해 서버에서 받아온 특정 데이터 값을 넣고 싶으면

 

<p th:text="'안녕하세요' + ${data}">안녕하세요.손님</p>

 

이런 식으로 하면 된다. 참고로 태그 사이엔 딱히 뭔가를 쓸 필욘 없음. 근데 템플릿 엔진을 거치지 않고 html파일을 열거나 할 때 태그 사이의 값이 나온다. 이미지 태그의 alt값과 비슷한 역할이라고 생각할 수 있겠다.

 

 

+ Recent posts