# 스프링이란?

스프링(Spring)은 자바 기반의 오픈소스 애플리케이션 프레임워크로, 엔터프라이즈급 애플리케이션 개발을 단순화하기 위해 만들어졌다.

  • 경량 컨테이너: 객체(Bean)의 생명주기를 직접 관리
  • IoC(Inversion of Control): 객체 생성 및 의존관계 제어권을 프레임워크가 담당
  • DI(Dependency Injection): 의존성을 외부에서 주입하여 결합도를 낮춤
  • AOP(Aspect Oriented Programming): 공통 관심사를 핵심 로직과 분리

# 스프링 생태계

스프링은 단일 프레임워크가 아닌 다양한 모듈로 구성된 생태계다.

  • Spring Framework: 핵심 DI/IoC 컨테이너
  • Spring Boot: 설정 자동화, 내장 서버 포함 — 실무에서 가장 많이 사용
  • Spring MVC: 웹 애플리케이션을 위한 MVC 패턴 구현
  • Spring Data: JPA, MongoDB 등 데이터 접근 추상화
  • Spring Security: 인증/인가 처리

# 스프링 부트(Spring Boot)

스프링 부트는 스프링을 더 쉽게 사용할 수 있도록 만든 도구다. 복잡한 XML 설정 없이 빠르게 프로젝트를 시작할 수 있다.

  • 자동 설정(Auto Configuration): @SpringBootApplication 어노테이션 하나로 필요한 빈을 자동 등록
  • 내장 서버: Tomcat이 내장되어 있어 별도 서버 설치 없이 실행 가능
  • 스타터 의존성(Starter): spring-boot-starter-web 등 묶음 의존성으로 설정 간소화
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

# 웹 요청 처리 흐름

스프링 부트 애플리케이션이 웹 요청을 처리하는 방식은 콘텐츠 종류에 따라 달라진다.

# 정적 컨텐츠 (Static Content)

정적 컨텐츠는 서버에서 별도 가공 없이 파일 그대로 응답하는 방식이다. HTML, CSS, JS, 이미지 파일 등이 해당된다.

웹 브라우저
    │
    │  GET /hello.html
    ▼
내장 톰캣 서버 (Embedded Tomcat)
    │
    │  스프링 컨테이너에서 hello 관련 컨트롤러 탐색
    ▼
스프링 컨테이너
    │
    │  매핑된 컨트롤러 없음 → 정적 리소스 탐색
    ▼
resources/static/hello.html 파일 반환
    │
    ▼
웹 브라우저

핵심 흐름:

  1. 브라우저가 톰캣 서버로 HTTP 요청을 보낸다
  2. 톰캣이 스프링 컨테이너에 해당 URL을 처리할 컨트롤러가 있는지 먼저 확인한다
  3. 매핑된 컨트롤러가 없으면 resources/static/ 또는 resources/public/ 폴더에서 동일한 이름의 파일을 찾는다
  4. 파일이 있으면 그대로 응답한다

정적 컨텐츠는 스프링 컨테이너의 비즈니스 로직을 거치지 않고 파일을 직접 서빙한다.

# DispatcherServlet

DispatcherServlet은 스프링 MVC의 **프론트 컨트롤러(Front Controller)**다. 톰캣으로 들어오는 모든 HTTP 요청을 가장 먼저 받아 적절한 컨트롤러에게 위임하는 역할을 한다.

스프링 부트를 사용하면 DispatcherServlet이 자동으로 등록되며, 기본적으로 /* 경로의 모든 요청을 처리한다.

DispatcherServlet의 책임:

  • 요청 URL을 분석해 처리할 컨트롤러를 핸들러 매핑으로 탐색

핸들러 매핑(Handler Mapping)이란?

요청 URL과 처리할 컨트롤러 메서드를 연결해주는 일종의 지도다. 스프링 부트에서는 @GetMapping, @PostMapping 등의 어노테이션을 기준으로 자동으로 매핑 정보가 등록된다. DispatcherServlet이 요청을 받으면 핸들러 매핑에 "이 URL 담당자가 누구야?"라고 물어보고, 반환된 컨트롤러를 호출한다.

  • 컨트롤러 호출 후 반환된 뷰 이름을 ViewResolver에 전달

ViewResolver란?

컨트롤러가 반환한 뷰 이름(문자열)을 실제 템플릿 파일 경로로 변환해주는 컴포넌트다. 예를 들어 컨트롤러가 "hello"를 반환하면, ViewResolver가 resources/templates/hello.html로 해석해 템플릿 엔진에 전달한다. 스프링 부트에서 Thymeleaf를 사용하면 ThymeleafViewResolver가 자동으로 등록되어 resources/templates/ 경로와 .html 확장자를 자동으로 붙여준다.

  • 예외 발생 시 HandlerExceptionResolver에 위임

프론트 컨트롤러 패턴을 사용하기 전에는 각 URL마다 별도의 서블릿을 등록해야 했다. DispatcherServlet이 창구를 하나로 통일함으로써 공통 처리(인증, 로깅 등)를 중앙에서 관리할 수 있게 된다.

요청 1: GET /hello  ─┐
요청 2: GET /bye    ─┼──▶ DispatcherServlet ──▶ 각 컨트롤러로 분기
요청 3: POST /login ─┘

# MVC와 템플릿 엔진

MVC(Model-View-Controller) 패턴은 서버에서 동적으로 HTML을 생성하여 응답하는 방식이다. Thymeleaf 같은 템플릿 엔진이 View 역할을 담당한다.

웹 브라우저
    │
    │  GET /hello?name=spring
    ▼
내장 톰캣 서버 (Embedded Tomcat)
    │
    │  HTTP 요청을 스프링에 전달
    ▼
스프링 컨테이너
    ├─ DispatcherServlet (프론트 컨트롤러)
    │       │
    │       │  핸들러 매핑 조회 → @GetMapping("/hello") 탐색
    │       ▼
    │   Controller (@Controller)
    │       │
    │       │  비즈니스 로직 수행, Model에 데이터 저장
    │       │  return "hello";  (뷰 이름 반환)
    │       ▼
    │   ViewResolver
    │       │
    │       │  "hello" → resources/templates/hello.html 매핑
    │       ▼
    │   템플릿 엔진 (Thymeleaf)
    │       │
    │       │  Model 데이터를 HTML에 삽입하여 렌더링
    │       ▼
    │   완성된 HTML
    │
    ▼
웹 브라우저

핵심 흐름:

  1. 브라우저가 톰캣 서버로 HTTP 요청을 보낸다
  2. 톰캣이 요청을 스프링의 DispatcherServlet으로 전달한다
  3. DispatcherServlet이 핸들러 매핑을 통해 URL에 맞는 컨트롤러를 찾는다
  4. 컨트롤러가 로직을 수행하고 Model에 데이터를 담아 뷰 이름을 반환한다
  5. ViewResolver가 뷰 이름을 실제 템플릿 파일 경로로 변환한다 (resources/templates/{뷰이름}.html)
  6. 템플릿 엔진이 템플릿에 Model 데이터를 채워 최종 HTML을 생성한다
  7. 완성된 HTML이 브라우저에 응답된다
@Controller
public class HelloController {

    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("name", "spring");
        return "hello"; // ViewResolver가 resources/templates/hello.html을 찾음
    }
}
<!-- resources/templates/hello.html -->
<html xmlns:th="http://www.thymeleaf.org">
    <body>
        <p th:text="'안녕하세요, ' + ${name}">기본 텍스트</p>
    </body>
</html>

# @ResponseBody를 활용한 API 방식

@ResponseBody는 컨트롤러의 반환값을 뷰가 아닌 HTTP 응답 바디에 직접 쓰는 방식이다. ViewResolver를 거치지 않고 데이터(주로 JSON)를 그대로 응답한다.

웹 브라우저
    │
    │  GET /api/hello?name=spring
    ▼
내장 톰캣 서버 (Embedded Tomcat)
    │
    │  HTTP 요청을 스프링에 전달
    ▼
스프링 컨테이너
    ├─ DispatcherServlet
    │       │
    │       │  핸들러 매핑 조회 → @GetMapping("/api/hello") 탐색
    │       ▼
    │   Controller (@ResponseBody 포함)
    │       │
    │       │  비즈니스 로직 수행, 객체 반환
    │       ▼
    │   HttpMessageConverter
    │       │
    │       │  객체 → JSON 변환 (MappingJackson2HttpMessageConverter)
    │       │  문자열 → 그대로 (StringHttpMessageConverter)
    │       ▼
    │   HTTP 응답 바디에 직접 삽입
    │
    ▼
웹 브라우저 (JSON 데이터 수신)

핵심 흐름:

  1. 브라우저가 톰캣 서버로 HTTP 요청을 보낸다
  2. DispatcherServlet이 핸들러 매핑으로 컨트롤러를 찾는다
  3. @ResponseBody가 붙은 메서드는 반환값을 뷰 이름이 아닌 데이터로 취급한다
  4. HttpMessageConverter가 반환값의 타입에 따라 변환 방식을 결정한다
  5. 변환된 데이터가 HTTP 응답 바디에 직접 작성된다

HttpMessageConverter란?

@ResponseBody 사용 시 객체를 HTTP 응답으로 변환해주는 컴포넌트다. 반환 타입이 String이면 StringHttpMessageConverter가, 객체(클래스)이면 MappingJackson2HttpMessageConverter가 JSON으로 직렬화한다. 스프링 부트는 Jackson 라이브러리를 기본으로 포함하므로 별도 설정 없이 객체를 JSON으로 변환할 수 있다.

@Controller
public class HelloApiController {

    @GetMapping("/api/hello")
    @ResponseBody
    public Hello helloApi(@RequestParam("name") String name) {
        Hello hello = new Hello();
        hello.setName(name);
        return hello; // 객체를 반환 → JSON으로 변환되어 응답
    }

    static class Hello {
        private String name;
        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
    }
}

응답 결과:

{ "name": "spring" }

@RestController@Controller + @ResponseBody를 합친 어노테이션으로, REST API 컨트롤러를 만들 때 주로 사용한다.

# 정적 컨텐츠 vs MVC 비교

구분 정적 컨텐츠 MVC + 템플릿 엔진
처리 주체 톰캣이 파일 직접 서빙 스프링 컨테이너 전체 관여
컨트롤러 없음 필수
데이터 가공 불가 가능
응답 형태 파일 그대로 동적 생성 HTML
파일 위치 resources/static/ resources/templates/

# 스프링 빈과 의존관계

  • 스프링 빈은 스프링 IoC 컨테이너가 생성하고 관리하는 객체이다.
    • IoC(Inversion of Control): 제어의 역전
    • 객체 생성/소멸/의존성 연결을 내가 아니라 Spring이 책임
  • 기존에는 개발자가 직접 의존 관계를 코드로 정의했지만 스프링 빈, @Autowired와 같은 구문들을 사용하면 스프링에 의해 이러한 의존 관계들이 관리된다.
  • 스프링 등록 방법
    • 어노테이션을 붙여 컴포넌트 스캔을 수행하고 Bean으로 등록한 뒤, 자동 의존관계 설정(Autowiring)을 통해 Bean들 사이의 의존관계가 확립
    • @Component 애노테이션이 있으면 스프링 빈으로 자동 등록됨.
    • 위 애노테이션을 포함하는 @Controller, @Service 등의 애노테이션들도 빈으로 자동 등록
@Autowired
public MemberService(MemberRepository memberRepository) {
    this.memberRepository = memberRepository;
}
  • 위와 같은 코드의 경우 MemberRepository 타입으로 등록된 스프링 빈을 찾아 서비스 생성자 호출 시점에 의존성을 주입해준다.

# AOP (Aspect-Oriented Programming)

AOP는 핵심 비즈니스 로직과 부가 기능(로깅, 트랜잭션, 보안 등)을 분리하는 프로그래밍 패러다임이다.

여러 곳에 반복되는 공통 기능을 **횡단 관심사(Cross-cutting Concerns)**라고 하는데, AOP 없이는 이를 매 메서드마다 직접 작성해야 한다.

// AOP 없이 모든 메서드에 반복 작성
public void createOrder() {
    log.info("start");       // 반복
    startTransaction();      // 반복
    // 핵심 비즈니스 로직
    commitTransaction();     // 반복
    log.info("end");         // 반복
}

AOP를 사용하면 공통 로직을 한 곳에 모아 관리할 수 있다.

@Aspect
@Component
public class LoggingAspect {

    @Around("execution(* com.example.service.*.*(..))")
    public Object logging(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("start: {}", joinPoint.getSignature());
        Object result = joinPoint.proceed(); // 핵심 로직 실행
        log.info("end");
        return result;
    }
}

주요 용어:

용어 설명
Aspect 부가 기능을 모아둔 모듈
Advice 실제 부가 기능 코드 (@Before, @After, @Around)
Pointcut Advice를 적용할 메서드 지정
JoinPoint Advice가 끼어들 수 있는 실행 지점

스프링 AOP는 프록시 패턴 기반으로 동작한다. 런타임에 프록시 객체를 생성해 실제 메서드 호출 전후에 Advice를 끼워 넣는다.