본문 바로가기
Tech Stack/Backend

🥾 레거시와 Spring Boot 웹 서버 구동 방식 비교 (+ REST API)

by 박수무당벌레 2026. 6. 25.

1. 웹 서버 구동 방식 비교: 레거시 vs Spring Boot

1) 레거시 방식 (외장형 동적 웹 서버 구동)

  • 서버와 코드가 분리됨: 아파치 톰캣(Tomcat) 같은 외장 WAS(여기에 서블릿 컨테이너 엔진인 카탈리나가 포함되어 있음)를 서버 컴퓨터에 먼저 독립적으로 설치하고 실행해야 함
  • 배포 방식: 개발자는 작성한 코드를 WAR이라는 압축 파일로 빌드(패키징)한 뒤, 실행 중인 톰캣의 특정 폴더(webapps/)에 집어넣는 방식. 그러면 톰캣이 이를 감시하다가 동적으로 인식하여 구동. (WAR 파일의 압축을 자동으로 풀고 내부 구조를 인식)
    • "서버 위에 앱을 올려서 웹 서비스를 고객에게 호스팅하는 방식"
    • 서블릿을 처리할 수 있는 서블릿 컨테이너 엔진(Catalina)이 웹 애플리케이션의 생명주기를 관리하며, 요청이 들어오면 매핑된 특정 폴더의 서블릿 코드(Jakarta Servlet)를 찾아 동적으로 실행한다.
  • 단점: 톰캣을 따로 설치하고 관리해야 해서 서버 설정(톰캣 버전 맞추기, 포트 충돌 해결 등)에 많은 에너지 소비

2) Spring Boot 방식 (내장형 웹 서버 + RESTful API)

  • 코드 안에 서버: Spring Web 디펜던시(spring-boot-starter-web)를 추가하면 톰캣 서버가 내장(Embedded Tomcat)된 상태로 프로젝트가 빌드됨.
    • "서버 위에 앱(WAR)을 올리는 게 아니라, 앱(JAR) 안에 톰캣 서버가 내장되어 있음"
  • 배포 방식: 별도의 외장 톰캣 설치나 복잡한 WAS 설정/배포 과정 없이 JAR 파일 하나만 실행하면 어디서든 웹 서버가 구동 가능.
    • 운영체제(OS)에 자바(JVM)만 깔끔하게 설치되어 있다면 명령어 한 줄로 서버가 바로 켜짐.
    • 앱(코드)을 실행하면 그 안에서 톰캣 서버가 함께 독립적으로 인스턴스화되어 구동.
  • 장점:
    • Setup-free (설정의 해방): 개발자는 그저 자바 코드만 실행하면(main() 메서드 클릭) 앱 내부에서 톰캣이 알아서 인스턴스로 켜짐.
    • 클라우드 및 마이크로서비스(MSA) 최적화: 서버 환경에 구애받지 않고 빌드된 JAR 파일 하나만 던지면 어디서든(AWS, 도커 등) 웹 서버가 즉시 구동되므로 배포와 확장(Scaling)이 극적으로 쉬워짐. (JAR 특징 때문에)

💡 톰캣과 서블릿의 역할 분담

  • 톰캣 서버 (카탈리나 엔진): HTTP Request를 받아서 그에 적합한 Response 패킷의 전체적인 틀(바디, 헤더 등)을 인스턴스화하여 만듦.
  • 서블릿 (스프링 부트 코드): 톰캣이 최종적으로 만들 Response 패킷의 바디와 헤더에 들어갈 실제 내용물(데이터)을 채워 넣는 역할.
🔍 WAR (Web Application Archive)
웹 브라우저에 보여줄 HTML, CSS, JS, JSP 같은 웹 정적 자원과 서블릿 코드가 모두 포함된, 단일로 실행할 수 없고 외장 WAS에 얹혀사는 웹 애플리케이션 전용 압축 파일. 하나의 외장형 톰캣 서버 위에 여러 개의 WAR 파일(여러 개의 웹 서비스)을 올려서 공유할 수 있다는 장점이 있지만, 설정(WEB-INF, META-INF 등 정해진 웹 표준 구조 필수)이 무겁고 복잡하여 모던 웹 개발에서는 점차 밀려나는 추세.
🔍 JAR (Java Archive)
기존 의미: 자바 프로그램이 실행되는 데 필요한 클래스(.class) 파일들과 설정 파일들을 하나로 묶어놓은 압축 파일. (단독 구동 가능)
스프링 부트에서의 역할: 스프링 부트는 이 JAR 파일 안에 웹 서버(내장형 톰캣)까지 통째로 집어넣음.
ℹ️ 현재 게시글에 언급한 어노테이션 전부 Spring Framework의 Web 모듈(라이브러리)의 웹 서버 관련 어노테이션이며, spring-boot-starter-web를 추가로 사용 가능

2. 웹 아키텍처의 변화: 레거시(MPA) vs 모던 웹(SPA)

과거의 웹 개발 방식과 현재 리액트(React) 등을 활용한 최신 웹 개발 방식은 서버의 역할부터 완전히 다르다.

1) 레거시 웹 서버 (MPA , Multiple Page Application)

  • 디자인 패턴: Spring MVC (Model-View-Controller) 패턴 중심.
  • 구동 방식: 서버가 요청을 받으면 동적인 HTML/JSP 화면(View) 자체를 그려서 브라우저에 던져주는 SSR(Server Side Rendering) 방식.
  • 특징: SI나 은행권 등 보수적인 시스템에서 여전히 많이 사용됨.

2) 모던 웹 서버 (SPA , Single Page Application)

  • 구동 방식: 화면을 그리는 프론트엔드 서버와 데이터를 제공하는 백엔드 서버가 완전히 분리.
    • 화면 그리는 주체(클라이언트쪽): 웹 브라우저, 프론트엔드(React, Vue)나 모바일 앱(iOS, Android) 등 다양해짐.
    • 백엔드 서버는 데이터만 주는 스탠스를 취하고, 이때 사용하는 소통 양식이 바로 RESTful APIJSON.
  • 작업 흐름:
    1. 최초 요청: 유저가 브라우저에 도메인(https://waydd.tistory.com)을 입력하면, 프론트엔드 서버(NginX 등)가 HTML/CSS/JS로 이루어진 대용량 SPA 파일(리액트 빌드본)을 통째로 브라우저에 내려줌. (최초 로딩 시만 시간이 다소 걸림)
    2. 화면 렌더링: 브라우저가 정적 자원을 받아 스스로 화면을 그림. 이후에는 프론트엔드 서버와 통신하지 않음.
    3. 데이터 요청 (API 통신): 유저가 새로운 페이지로 이동하거나 데이터가 필요해지면, 브라우저는 백엔드 서버(api.hello.com)에 HTTP 요청을 보냄.
    4. 데이터 응답: 백엔드 서버는 화면(HTML)을 절대 주지 않고, 순수한 데이터(JSON 등)만 응답한다.

📊 비교 요약

  레거시 웹 서버 (MVC/SSR) 모던 웹 서버 (RESTful API)
주요 역할 화면(HTML/JSP) + 데이터 결합 후 반환 순수한 데이터(JSON)만 반환
클라이언트 오직 웹 브라우저 웹 브라우저(React), 모바일 앱, 타 서버 등
스프링 핵심 @Controller (기본 리턴값이 뷰 이름) @RestController (기본 리턴값이 데이터)
더보기

ℹ️ Postman에 대하여

  • 개념: 프론트엔드 서버와 백엔드 서버를 분리하게 되면서,  백엔드 개발 시 프론트엔드(React, 앱 등) 사이드가 아직 구현되지 않았을 때 웹 브라우저를 대신해 줄 필수 테스트 도구.
  • POST 요청 테스트 방법(예시):
    1. 메서드를 POST로 변경하고 URL 입력.
    2. Body 탭 ➔ raw 선택 ➔ JSON 포맷으로 변경 후 Key-Value 쌍 데이터 작성 (예: {"id": "12345", "name": "hong-gil-dong"}).
    3. Send를 누르면 백엔드의 @PostMapping과 @RequestBody가 선언된 엔드포인트로 데이터가 전송

3. 어떻게 데이터를 주고 받는가? : RESTful API

1) RESTful API 방식 특징

  • 화면 대신 데이터만 송수신: 서버는 HTML 같은 화면을 만들지 않음. 대신 클라이언트가 요청을 보내면 Pure한 데이터(주로 JSON 형식)만 깔끔하게 응답한다.
  • 자원(Resource) 중심의 설계: URL은 철저하게 '조회할 데이터나 자원(예: /users, /products)'을 가리키는 주소로만 사용한다.
  • HTTP 메서드 활용: 해당 자원을 어떻게 처리할지는 HTTP 표준 메서드를 통해 명확하게 구분.
    • GET /products ➔ 상품 목록 조회
    • POST /products ➔ 상품 등록
    • PUT /products ➔ 상품 수정
    • DELETE /products ➔ 상품 삭제
  • 결론: 화면을 그리는 역할은 프론트엔드(React, Vue 등)나 앱에 완전히 넘기고, Spring Boot는 뒷단에서 데이터의 CRUD(생성·조회·수정·삭제)를 처리하는 순수한 데이터 창구 역할만 수행하는 아키텍처 스타일을 말함.

2) Spring Boot에서 이를 구현하는 핵심 메커니즘

@RestController의 마법 (합성 어노테이션)

@RestController // @Controller + @ResponseBody
public class UserController { ... }
  • 합성 어노테이션: 내부에 @Controller와 @ResponseBody이 포함된 합성 어노테이션. 또다른 합성 어노테이션 자체에 대해선 아래 추가 설명
  • 응답 데이터 처리(변환): 원래 스프링에서 메서드가 무언가를 리턴하면, 스프링은 그걸 보여줄 'HTML 파일 이름'으로 인식함. 하지만 @RestController를 붙이면 내부의 @ResponseBody 덕분에 이 리턴값은 파일 이름이 아니라, 클라이언트에게 바로 보낼 순수한 데이터라고 인식하게 됨.
    • MVC 패턴에서 V(View)가 필요 없기 때문에, Controller(C)의 역할만 수행하며 리턴하는 값(String, DTO, 클래스 등)을 웹페이지가 아닌 순수한 데이터(JSON) 상태로 응답 바디(Body)에 직접 실어 보냄.
🔍 DTO (Data Transfer Object): 계층 간 데이터 교환을 위해 사용하는 객체.

② Jackson 라이브러리와 직렬화/역직렬화

자바 내부에서는 클래스 객체(메모리 주소)로 데이터를 다루지만, HTTP 네트워크 선로를 타고 나갈 때는 텍스트 포맷인 JSON이어야 한다. 즉, 네트워크를 통해 데이터를 주고받을 때, 자바 객체와 JSON(텍스트) 간의 변환 과정이 필수적.

[Client / Postman]  --- (JSON 텍스트) --->  [Spring Boot] (User 객체로 변환: 역직렬화)
[Client / Postman]  <--- (JSON 텍스트) ---  [Spring Boot] (User 객체를 변환: 직렬화)
  • JSON 처리 (Jackson): Spring Boot가 spring-boot-starter-web에 기본 내장된 Jackson 라이브러리를 통해 아래 작업을 알아서 처리해 줌. 개발자가 신경 쓰지 않아도 컨트롤러 단에서 자동으로 처리해 줌.
  • GET/POST 둘다 @RequestBody로 바디에 실어서 데이터를 보내게 된다.
  • 들어올 때 (역직렬화 또는 언마샬링, @RequestBody 연관 설명): 
    • 클라이언트가 HTTP Request Body에 실어 보낸, 자바스크립트 객체 형태의 JSON 데이터(텍스트) ➔ 자바 객체(인스턴스)로 변환하여 응답하는 과정
    • 구현: 엔드포인트 메서드의 파라미터 앞에 @RequestBody를 붙여주면 스프링이 자동으로 JSON 매핑 클래스를 찾아 인스턴스를 주입해 줌.
  • 나갈 때 (*직렬화 또는 마샬링): 
    • 리턴한 자바 객체 ➔ JSON 텍스트 포맷으로 자동 변환하여 응답 하는 과정
    • JSON 텍스트는 네트워크 전송이나 저장(하드웨어)이 가능하다
🔍 용어 정리
컨트롤러 계층: 엔드포인트 메서드들을 가지고 있는 클래스.
엔드포인트(Endpoint) 메서드: HTTP 특정 URL 요청을 처리하는 최종 종착지 메서드.
더보기

✨ 직렬화와 마샬링(역직렬화와 언마샬링) 구분

 

이론적으로 두 개념은 다르나, 위와 같은 서로 데이터 구조나 메모리 적재 방식이 다른 두 세상 사이에서 데이터를 주고 받는 상황에서 통용되어 사용되어짐. (스프링 부트에서 @RequestBody나 @ResponseBody가 작동할 때 뒷단에서는 Jackson 라이브러리가 움직이는 데, Java 객체와 JSON(텍스트 기반)이라는 서로 다른 생태계 간의 변환이기 때문)

 

1) 직렬화와 마샬링 비교

  직렬화 (Serialization) 마샬링 (Marshalling)
핵심 목적 객체의 상태(데이터)를 그대로 저장하거나 네트워크 전송 가능한 형태로 일렬로 늘어뜨리는 것 (Byte 형태) 서로 다른 시스템/프로세스 간에 원격 호출(RPC) 및 통신을 하기 위해 변환하는 것
초점 (Focus) 데이터 구조를 바이트 형태로 나열하는 기술적인 행위 자체 통신 규약(프로토콜), 메모리 구조(메타데이터), 코드(유형 정보)까지 고려한 범용적 변환
생태계 관점 주로 동일한 언어/플랫폼 환경을 전제함 Java(독점 규격) 세상에서 JSON(범용 표준) 세상으로 넘어가는 변환 전반을 포괄함
결과물 형태 주로 바이트 스트림 (Byte Stream) JSON, XML, 바이트 등 프로토콜에 따라 다양함
포함 관계 마샬링을 구현하는 메커니즘 중 하나로 직렬화가 사용됨 (직렬화 ⊂ 마샬링) 직렬화보다 훨씬 더 넓은 개념을 포괄하는 상위 개념
ℹ️ 마샬링 핵심 목적 보충
한 컴퓨터나 프로세스에 있는 객체를, 메모리 구조나 통신 방식이 전혀 다른 곳으로 전달하기 위해 변환하는 일련의 과정을 뜻함.
ℹ️ 마샬링 초첨 보충 설명
단순히 데이터를 바이트로 바꾸는 것을 넘어, 전송받는 쪽(원격지)에서 이 데이터를 완벽하게 이해하고 사용할 수 있도록 
객체의 타입 정보, 메타데이터, 통신 프로토콜 규약(RMI, RPC, REST API 등)까지 함께 묶어서 처리.
ℹ️ Java 객체를 자바 내장 기능을 통해 바이트 배열로 바꾸는 행위가 대표적인 직렬화.

 

2) 결정적인 차이점: 원격지(받는 쪽)에 클래스(코드) 정보가 없을 때

  • 예시 상황: 받는 쪽에 해당 객체의 클래스 파일(.class)이 이미 존재한다고 가정.
    • 두 개념의 가장 엄밀한 차이는 데이터를 전송받는 상대방 시스템에 해당 객체의 클래스 설계도 파일이 존재하지 않을 때 어떻게 작동하는지 보면 명확해짐.
  • 직렬화: 만약 받는 쪽에 클래스 정보가 없다면, 데이터를 풀어서 객체로 복원하는 '역직렬화' 과정 중에 ClassNotFoundException 같은 에러 발생 & 변환 실패.
  • 마샬링: 통신 규약을 타고 넘어간 덕분에, 받는 쪽에서 해당 객체를 아예 몰랐던 상태였어도 마샬링된 데이터를 풀어서(언마샬링) 에러 없이 완벽하게 객체로 복원하고 실행해 낼 수 있음.
    • 단순히 데이터만 보내는 것을 넘어, 필요하다면 객체의 데이터뿐만 아니라 이를 해석할 수 있는 객체의 타입 정보, 메타데이터, 코드(유형 정보)까지 가방에 함께 묶어서(패키징) 전송하기 때문

3) 웹 개발(Spring Boot) 환경에서의 작동 정리 (Jackson 라이브러리)

  1. 직렬화/역직렬화 관점 (기술적 행위)
    • 직렬화: 자바 객체를 네트워크로 보낼 수 있는 텍스트(JSON 문자열)로 일렬로 늘어뜨림
    • 역직렬화: 반대로 들어온 JSON 텍스트를 다시 구조화된 자바 객체로 바꾸는 것
  2. 마샬링/언마샬링 관점 (목적과 환경)
    • 자바 객체 생태계와 웹 브라우저(자바스크립트)의 JSON 생태계는 메모리 구조나 데이터 타입이 완전히 다름
    • 서로 다른 두 환경 체계에 맞춰 데이터를 알맞게 변환하고 패키징해서 넘겨주는 과정 (서로 다른 시스템/프로세스 간 데이터 전달을 위해 통신 규약(프로토콜), 메모리 구조, 코드(유형 정보) 등을 고려한 범용적 변환)
    • 결론: 위의 관점보다 훨씬 더 넓은 통신 환경과 프로토콜, 메타데이터 처리를 포함하는 상위 개념

4. 스프링 어노테이션의 마법: @RequestMapping과 그 단축형들

1) 클래스 레벨과 메서드 레벨의 경로 결합

  • @RequestMapping 어노테이션을 사용해서 URL 매핑과 HTTP 메서드 매핑 역할을 맡는다
  • 클래스 레벨의 공통 경로: 클래스 위에 @RequestMapping("/user")를 선언하면 하위 메서드들이 이 경로를 공유함.
    • 클래스 레벨에선 value(path) 속성을 "/"로만으로 자주 사용하는 느낌 (또한 속성 값이 하나 들어올때만 자바가 value로 자동 매핑해줌. 2개 이상은 속성 이름을 적고 대입하는 형식이 필수)
  • 메서드 레벨의 상대 경로: 클래스 레벨의 부모 경로나 결합할 대상이 있든 없든, 굳이 슬래시를 붙이지 않고 "profile"이라고만 적어도 자동으로 슬래시가 추가되서 결합됨. (클래스도 마찬가지)
  • Ant-style 패턴 지원: /profile/처럼 와일드카드를 사용해 하위 경로를 한 번에 매핑할 수 있음.
@RestController
@RequestMapping("/user") // 클래스 레벨 경로 설정 (하위 메서드들이 이 경로를 공유)
public class UserController { 

    @RequestMapping(value = "profile", method = RequestMethod.GET) // 메서드 단위에 지정 (/user/profile) 
    public String getUserProfile() {
        return "User Profile Data";
    }
}

2) 합성 어노테이션의 도입 이유

  • 자바 표준 어노테이션은 일반 클래스처럼 extends를 통한 상속이 불가능.
  • 이를 해결하기 위해 스프링 개발자들은 메타 어노테이션 기법을 도입하게 됨. 특정 어노테이션 위에 다른 어노테이션을 붙여 상속과 유사한 효과를 내는 것.
  • 효과: 코드의 명확성, 가독성 향상 및 제약 조건의 재사용(조립) + 가동성과 코드 다이어트(단축형 관점)
@GetMapping("profile") // URL: GET /user/profile
public String getUserProfile() {
    return "User Profile Data";
}

3) @GetMapping, @PostMapping의 원리

  • 과거 @RequestMapping만 존재하던 시절에서 발전한 단축형(Short-cut)이자 합성 어노테이션.
  • 내부적으로 @RequestMapping(method = RequestMethod.GET) 등을 품고 있으며, @AliasFor 속성을 통해 부모 격인 @RequestMapping의 path나 value 속성 기능을 안전하게 이어받아(상속받아) 사용함.
  • value 속성에 경로를 지정하면 자동으로 path 값에 대입되며, 슬래시(/)가 누락되어도 자동으로 붙여 처리해 줌.
@Target(ElementType.METHOD) // 메서드에만 붙일 수 있도록 제한
@Retention(RetentionPolicy.RUNTIME) // 프로그램 실행 중(Runtime)에도 유지됨
@Documented
@RequestMapping(method = RequestMethod.POST) // 💡 메타 어노테이션 기법: @RequestMapping을 품고(합성하고) 있음!
public @annotation PostMapping {

    /**
     * 💡 @AliasFor의 마법: 
     * @PostMapping("profile")이라고 적은 'value' 값이
     * 부모 격인 @RequestMapping의 'value' 속성으로 안전하게 대입(상속).
     */
    @AliasFor(annotation = RequestMapping.class)
    String[] value() default {};

    /**
     * 💡 value와 path의 상호 교환:
     * value에 값을 넣으면 path에도 자동으로 똑같이 대입되도록 조립.
     */
    @AliasFor(annotation = RequestMapping.class)
    String[] path() default {};

    // ... headers, params, consumes, produces 등 다른 속성들도 
    // 전부 부모인 @RequestMapping의 속성들과 @AliasFor로 연결되어 있습니다.
}