본문으로 바로가기
Spring 기초 Part 1: IoC, Bean, DI와 주요 Annotation
LinkedInGitHub
WORKSPACE

EXPLORER

157 POSTS
BLOG
면접질문
해야하는 거
AI 시대, 개발자는 사라지는가?
Python Counter, 빈도수를 쉽게 세는 방법
정렬
1. 투 포인터
DFS와 BFS
알고리즘 논리
코딩 테스트 핵심 알고리즘 정리파이썬 딕셔너리와 코딩 테스트 활용
예시로 살펴보는 AXAX 프로젝트, 문제 정의부터 확산까지AX 시대의 가치 정의와 현장 리딩
MSA
1. Singleton PatternSOLID
Kafka의 핵심 설계 원리Kafka 메시징 시스템의 구성과 동작 방식Kafka 기본 개념과 EC2 Docker 구성
쿠버네티스 입문
Modular Monolith1. MonolithMSA, 서비스 분리와 운영의 원리
Socket이란 무엇인가
네크워크 참조 모델
전체 데이터 구조REST API의 개념과 설계 원칙2. Field/ Parameter / Argument/ this
Actuator 란?EntityManagerJPA 연관관계 매핑JPA 트랜잭션(Transaction)입력값 검증Java의 AOP(Aspect Oriented Programming)Async : AsynchronousBeanJPA(Java Persistence API)Proxy 패턴Spring MVCdocker container1. Spirng 필요컴포넌트 스캔
Spring 컨테이너
application.yamlLombok
이번 강의는 무엇을 노리고 있을까?Spring AI를 배우기 전에 정리할 것23
1. 기술 스택
개발 문서를 읽기 위한 핵심 기술 용어
Docker를 이해하기 위한 운영체제 기초
sigterm
6. 설정과 저장소, 앱을 운영할 수 있는 상태로 만들기5. Service와 Ingress, 요청은 어디로 흐를까4. 직접 실험하는 Kubernetes, Pod 복구부터 롤백까지3. kubectl과 Pod, 상태에서 원인을 찾는 법2. 클러스터는 명령을 어떻게 Pod로 바꿀까1. 쿠버네티스, 원하는 상태와 컨테이너 이미지Calico쿠버네티스 입문, 원하는 상태를 유지하는 시스템
Psql JSONB, 행 잠금, 멱등성Psql 함수,프로시져,트리거
머신러닝 입문딥러닝 학습 기본 개념데이터 시각화기초 통계와 ML 파이프라인 연결분석 자동화와 파이프라인 설계
CNN 아키텍처 발전 과정이미지 세그멘테이션 모델과 핵심 개념객체 탐지 모델과 핵심 개념
딥러닝 데이터셋 엔지니어링
딥러닝 학습 문제 진단과 디버깅
도메인 적응 방법현대 LLM 워크플로의 패턴
Transformer에서 LoRA 적용 대상 정하기LoRA (Low-Rank Adaptation)
LLM 양자화와 QLoRA
분산 학습과 MLOps딥러닝 모델 경량화와 추론 최적화딥러닝 기본 학습 테크닉딥러닝 중급 학습 테크닉
Mixture of Experts(MoE) 핵심 개념멀티모달 파운데이션 모델 핵심 개념State Space Model과 MambaTransformer와 Vision Transformer
Latent Space
1Chunking?
DevOps 기초 1편
실습에서는?Agile 개요, 왜 필요한가?
CI/CD 기초 4편: 배포 전략과 운영CI/CD 기초 3편: Jenkins와 Argo CD를 이용한 GitOps 배포CI/CD 기초 2편: Docker 이미지와 배포 파이프라인CI/CD 기초 1편: 개념과 GitHub Actions
OCI: 컨테이너 이미지와 런타임의 공통 표준Docker 기초 13편: Compose Healthcheck와 실전 구성Docker 기초 12편: Compose 네트워크와 VolumeDocker 기초 11편: Compose 명령어와 환경 변수Docker 기초 10편: Compose 기본 구조와 이미지 빌드Docker 기초 9편: Docker 및 Kubernetes 네트워크Docker 기초 8편: 컨테이너 런타임과 격리Docker 기초 7편: 이미지 Layer와 tar 내부 구조Docker 기초 6편: 이미지 Layer와 빌드 최적화Docker 기초 5편: 컨테이너 기본 명령어와 VolumeDocker 기초 4편: 가상화와 컨테이너 이미지 생명주기Docker 기초 3편: CI/CD 연결과 배포 원칙Docker 기초 2편: Layer, Registry, Volume과 NetworkDocker 기초 1편: Dockerfile, Image와 Container
NginxNginx 로드 밸런싱과 HTTPSNginx 리버스 프록시와 Spring Boot 연결Nginx 기초와 동작 구조
05. Pinia 상태 관리: store 설계와 사용법04. Vue 컴포넌트 설계: props, emit, slot과 생명주기03. Vue Composition API 정리02. Vue 기초 문법 점검: JavaScript, 템플릿01. Vue.js 입문: 핵심 구조와 렌더링
Java 심화 Part 3: 함수형 프로그래밍과 LambdaJava 심화 Part 2: AnnotationJava 심화 Part 1: Reflection
Java 기초 Part 5: Stream APIJava 기초 Part 4: 제네릭Java 기초 Part 3: 제어문Java 기초 Part 2: 주석과 JavadocJava 기초 Part 1: 백엔드 배경과 Java 실행 구조
Java 디버깅 Part 1: 자주 헷갈리는 핵심 개념Java 디버깅 Part 2: VS Code 자동 컴파일과 프로젝트 구조
Java 실행과 JVM Part 3: ClassLoader와 JVM 메모리Java 실행과 JVM Part 2: 메모리와 데이터 흐름Java 실행과 JVM Part 1: Java와 Python 컴파일 비교
Java 객체지향 Part 5: static 메서드와 중첩 클래스Java 객체지향 Part 4: 상속과 인터페이스Java 객체지향 Part 3: 좋은 설계와 OOP 4대 특성Java 객체지향 Part 2: OOP 핵심 문법Java 객체지향 Part 1: 클래스, 객체, 필드와 생성자
Spring 기초 Part 11: Actuator와 애플리케이션 모니터링Spring 기초 Part 10: 비동기 처리와 @AsyncSpring 기초 Part 9: JPA 트랜잭션과 동시성 제어Spring 기초 Part 8: AOP와 공통 관심사 분리Spring 기초 Part 7: Proxy 패턴과 Spring ProxySpring 기초 Part 6: JPA 연관관계 매핑Spring 기초 Part 5: EntityManager와 영속성 컨텍스트Spring 기초 Part 4: JPA, Entity와 RepositorySpring 기초 Part 3: REST API 요청값과 입력값 검증Spring 기초 Part 2: Spring MVC 요청 처리 흐름Spring 기초 Part 1: IoC, Bean, DI와 주요 Annotation
DNS = Domain Name System
Spring Boot, WebSocket, Vue, Docker 로 Raspberry Pi 실시간 모니터링 프로젝트 만들기 - 1편1. Spring Boot 구현
1. GitHub Project 만들기
Python 코드 품질: 디버깅부터 테스트와 자동화까지Python 01. 실행 구조와 실무 기초
sLLM 핵심 기술과 전체 구조제한된 자원에서 sLLM 구축하기
시대 단상에 대한 주저리주저리
WORKSPACE

SEARCH

제목, 카테고리와 태그로 검색하세요.

VERSION CONTROL

SOURCE CONTROL

masterGitHub Pages
저장소 열기
BUILD STATUS

RUN AND DEBUG

게시물은 GitHub Actions에서 검증하고 정적 페이지로 빌드합니다.

Actions 열기
WORKSPACE

MANAGE

홈 열기전체 게시물태그 보기블로그 소개
Spring 기초 Part 1: IoC, Bean, DI와 주요 Annotation●
workspace>posts>java>spring>spring-ioc-bean-di.md
java / spring2026.08.111 min read9 tags

Spring 기초 Part 1: IoC, Bean, DI와 주요 Annotation

Spring을 사용하는 이유와 IoC Container, Bean, DI, Component Scan, Spring MVC, Transaction의 관계를 정리한다.

Spring 기초 Part 1: IoC, Bean, DI와 주요 Annotation

1. Spring이란 무엇인가

Spring이 뭔지 일단 한 문장으로 말하면 다음과 같다.

Spring은 Java 객체를 대신 생성하고 관리하며, 서로 연결해 주고, 웹 요청 처리나 DB Transaction 같은 애플리케이션 기능까지 제공하는 Framework다.

여기서 Framework란 애플리케이션의 전체 구조와 실행 흐름을 제공하고, 정해진 시점에 개발자가 작성한 코드를 호출하는 기반을 말한다. 개발자가 필요할 때 직접 호출하는 Library와 비교하면, Framework는 애플리케이션의 흐름을 주도한다는 특징이 있다.

Spring을 처음 배우면 Annotation부터 정말 많이 보게 된다. 그런데 일단 다음 세 개념의 관계부터 이해해야 한다.

  1. IoC Container: 객체를 생성하고 관리하는 공간
  2. Bean: Spring Container가 관리하는 객체
  3. DI: Bean이 필요로 하는 다른 Bean을 넣어 주는 방식

관계를 한 줄로 나타내면 다음과 같다.

Spring Container가 Bean을 생성하고, Bean 사이의 의존 관계를 DI로 연결한다.

Spring을 왜 사용하는가

그냥 Java에서 직접 new를 사용해도 애플리케이션을 만들 수 있다. 그런데 규모가 커지면 객체 생성과 연결, 설정, Transaction, 웹 요청 처리 같은 반복 코드가 빠르게 늘어난다. Spring은 이러한 문제를 다음과 같이 줄여 준다.

  • 객체 관리 자동화: 객체 생성, 의존 관계 설정, 생명주기 관리를 Container에 맡길 수 있다.
  • 낮은 결합도: 클래스가 구체적인 구현 객체를 직접 생성하지 않으므로 구현 교체와 테스트가 쉬워진다.
  • 관심사 분리: Controller, Service, Repository처럼 역할을 나누어 코드를 관리할 수 있다.
  • 선언적 기능 사용: @Transactional 같은 애너테이션으로 반복적인 기반 코드를 직접 작성하지 않고 기능을 적용할 수 있다.
  • 일관된 생태계: Spring MVC, Spring Data 등 여러 모듈을 같은 객체 관리 방식과 설정 모델로 사용할 수 있다.

그렇다고 모든 객체를 무조건 Bean으로 만들어야 하는 것은 아니다. 값만 담는 단순 객체나 한 메서드 안에서 잠깐 사용하는 객체처럼 Container의 관리가 필요 없는 객체는 직접 new로 생성해도 된다.

2. 먼저 알아둘 용어

용어정의
객체(Object)클래스를 바탕으로 메모리에 생성된 실제 값
컴포넌트(Component)애플리케이션을 구성하는 하나의 기능 단위. Spring에서는 보통 Container가 관리하는 클래스 또는 Bean 후보를 가리킨다.
의존성(Dependency)어떤 객체가 자신의 기능을 수행하기 위해 필요로 하는 다른 객체
메타데이터(Metadata)코드 자체의 실행 내용이 아니라 코드의 역할과 처리 방법을 설명하는 부가 정보
스테레오타입(Stereotype)@Service, @Repository처럼 클래스의 계층별 역할을 나타내는 애너테이션
프록시(Proxy)원본 객체를 감싸서 메서드 호출 전후에 트랜잭션, 로깅 같은 부가 기능을 수행하는 대리 객체
트랜잭션(Transaction)여러 데이터 작업을 하나의 논리적 작업 단위로 묶어 모두 성공시키거나 모두 취소하는 처리 단위

3. Spring Container

Spring Container는 애플리케이션에 필요한 객체를 생성하고, 설정하고, 서로 연결하고, 생명주기를 관리하는 실행 환경이다. 일반적으로 ApplicationContext가 Spring IoC Container의 역할을 담당한다.

Container는 설정 정보를 읽고 다음 작업을 수행한다.

  1. 관리할 클래스를 찾거나 @Bean 설정을 읽는다.
  2. 해당 클래스의 객체를 생성한다.
  3. 객체가 필요로 하는 의존성을 찾는다.
  4. 생성자 등을 통해 의존성을 주입한다.
  5. 완성된 객체를 Bean으로 보관하고 필요한 곳에 제공한다.

예를 들어 Spring Boot 애플리케이션에서는 보통 @SpringBootApplication이 붙은 시작 클래스의 패키지와 그 하위 패키지를 대상으로 Component Scan이 이루어진다.

애플리케이션 실행
    ↓
설정 정보와 컴포넌트 탐색
    ↓
Bean 생성
    ↓
Bean 사이의 의존성 주입
    ↓
요청을 처리할 준비 완료

Container를 왜 사용하는가

개발자가 객체 생성 순서와 공유 방법을 매번 직접 관리하면 객체가 많아질수록 코드가 복잡해진다. Container에 관리를 맡기면 애플리케이션 코드는 객체를 만드는 방법보다 각 객체가 맡은 비즈니스 역할에 집중할 수 있다.

4. Bean

Bean은 Spring Container가 생성하고 관리하는 객체다. 쉽게 말하면 Spring이 관리하고 있는 Java 객체다.

@Service
public class UserService {
}

Component Scan이 UserService를 발견해 객체를 생성하고 Container에 등록했다면 그 객체는 Spring Bean이다.

반면 다음 객체는 개발자가 직접 생성했으므로 그 자체로는 Bean이 아니다.

UserService userService = new UserService();
개발자가 직접 new로 생성
→ 일반 Java 객체

Spring Container가 생성하거나 등록하여 관리
→ Spring Bean

Bean은 기본적으로 하나의 Container 안에서 하나만 생성되는 singleton scope를 사용한다. 여기서 singleton은 JVM 전체에 무조건 객체가 하나라는 뜻이 아니라, 해당 Spring Container가 같은 Bean 정의에 대해 하나의 인스턴스를 관리한다는 뜻이다. 필요하면 prototype, request, session 같은 다른 scope도 사용할 수 있다.

Bean으로 관리하면 좋은 이유

  • 동일한 객체를 여러 곳에서 일관되게 공유할 수 있다.
  • 객체 생성과 종료 같은 생명주기를 Spring에 맡길 수 있다.
  • DI, 트랜잭션, AOP 같은 Spring 기능을 적용할 수 있다.
  • 구현 교체와 테스트용 대체 객체 사용이 쉬워진다.

5. IoC: 제어의 역전

**IoC(Inversion of Control)**는 객체의 생성과 연결에 대한 제어권이 애플리케이션 코드에서 외부 Container로 이동하는 설계 원칙이다.

Spring을 사용하지 않을 때

UserService userService = new UserService();
UserController userController = new UserController(userService);

개발자가 객체의 생성 순서와 연결 방법을 직접 결정한다.

개발자 코드
    ↓ 생성
UserService
    ↓ 전달
UserController

Spring을 사용할 때

@Service
public class UserService {
}

@RestController
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }
}

개발자는 필요한 의존성을 생성자에 선언하고, 실제 객체 생성과 연결은 Spring Container가 담당한다.

Spring Container
    ├─ UserService Bean 생성
    ├─ UserController Bean 생성
    └─ UserService Bean을 UserController에 주입

여기서 중요한 점은 IoC와 DI가 완전히 같은 단어는 아니라는 것이다. IoC는 제어권이 뒤집힌 상태를 설명하는 원칙이고, DI는 그 원칙을 구현하는 대표적인 방법이다.

IoC가 필요한 이유

클래스가 사용할 구현체를 직접 생성하면 두 클래스가 강하게 결합된다. 생성 책임을 Container로 옮기면 각 클래스는 자신의 역할에 집중할 수 있고, 객체 구성 방법은 별도의 설정으로 관리할 수 있다.

6. DI: 의존성 주입

**DI(Dependency Injection)**는 객체가 필요로 하는 의존성을 객체 외부에서 전달하는 방식이다. 말 그대로 Dependency를 외부에서 Injection한다는 뜻이다.

여기서 의존성이라는 말이 조금 어렵게 느껴질 수 있다. UserController가 회원 관련 기능을 수행하기 위해 UserService를 사용한다면, UserService가 UserController의 의존성이다.

@RestController
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }
}

UserController 내부에는 new UserService()가 없다. Spring이 이미 관리 중인 UserService Bean을 생성자 매개변수로 전달한다.

[UserService Bean]
         │
         │ DI
         ▼
[UserController Bean]

생성자 주입을 권장하는 이유

Spring에는 생성자, setter, field를 이용한 여러 주입 방식이 있지만, 필수 의존성에는 생성자 주입을 주로 사용한다.

  • 객체를 만들 때 필수 의존성이 반드시 전달된다.
  • 필드를 final로 선언해 생성 후 변경을 막을 수 있다.
  • 필요한 의존성이 생성자에 명확하게 드러난다.
  • Spring 없이도 테스트에서 가짜 객체를 직접 전달하기 쉽다.

생성자가 하나뿐이라면 보통 @Autowired를 생략할 수 있다.

인터페이스와 함께 사용하면 더 유연해진다

public interface MessageSender {
    void send(String message);
}

@Service
public class EmailSender implements MessageSender {

    @Override
    public void send(String message) {
        // 이메일 발송
    }
}
@Service
public class NotificationService {

    private final MessageSender messageSender;

    public NotificationService(MessageSender messageSender) {
        this.messageSender = messageSender;
    }
}

NotificationService는 EmailSender라는 구체 클래스가 아니라 MessageSender라는 역할에 의존한다. 따라서 구현 교체와 테스트가 쉬워진다. 단, 같은 인터페이스를 구현한 Bean이 여러 개라면 Spring이 어느 Bean을 주입할지 결정할 수 있도록 @Primary나 @Qualifier 같은 추가 정보가 필요하다.

7. Bean을 등록하는 두 가지 대표 방법

7.1 Component Scan과 스테레오타입 애너테이션

Component Scan은 지정된 패키지를 탐색해 Bean 후보 클래스를 자동으로 찾고 등록하는 기능이다.

@Component
public class EmailSender {
}

@Component는 해당 클래스를 Spring이 관리할 컴포넌트 후보로 표시한다. 다음 애너테이션들은 모두 @Component를 기반으로 하며 계층별 의미를 추가한 스테레오타입 애너테이션이다.

@Component
    ├─ @Service
    ├─ @Repository
    └─ @Controller
         └─ @RestController의 기반
@Component

특정 계층으로 분류하기 어려운 일반 컴포넌트에 사용한다.

@Component
public class PasswordEncoder {
}
@Service

회원가입, 주문, 결제, 가격 계산 같은 비즈니스 로직을 담당하는 클래스에 사용한다.

@Service
public class UserService {

    public void register() {
        System.out.println("회원가입");
    }
}

@Component를 사용해도 Bean 등록은 가능하지만, @Service를 사용하면 클래스의 역할이 명확해져 계층 구조를 이해하기 쉽다.

@Repository

데이터베이스 조회, 저장, 수정, 삭제 같은 데이터 접근 책임을 맡는 클래스에 사용한다.

@Repository
public class UserRepository {

    public void save(User user) {
        // 데이터 저장
    }
}

역할을 명확히 나타낼 뿐 아니라, 지원되는 데이터 접근 기술에서 발생한 예외를 Spring의 일관된 데이터 접근 예외 계층으로 변환하는 데에도 사용된다.

@Controller

Spring MVC에서 HTTP 요청을 받고, 처리 결과를 화면인 View로 연결하는 웹 계층 클래스에 사용한다.

@Controller
public class PageController {

    @GetMapping("/hello")
    public String hello() {
        return "hello"; // View 이름
    }
}
@RestController

REST API처럼 메서드의 반환값을 HTTP 응답 본문에 직접 작성하는 Controller에 사용한다.

@RestController
public class UserController {

    @GetMapping("/users")
    public String getUsers() {
        return "users";
    }
}

@RestController는 @Controller와 @ResponseBody의 의미를 합친 **조합 애너테이션(composed annotation)**이다. 객체를 반환하면 일반적으로 등록된 메시지 변환기가 객체를 JSON 같은 응답 형식으로 직렬화한다.

7.2 @Configuration과 @Bean

외부 라이브러리의 클래스처럼 소스에 @Component를 붙일 수 없거나, 객체 생성 과정을 직접 설정해야 할 때 사용한다.

@Configuration
public class AppConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new PasswordEncoder();
    }
}
  • @Configuration: Bean 설정을 정의하는 클래스임을 나타낸다.
  • @Bean: 메서드가 반환한 객체를 Spring Bean으로 등록한다.

기본 Bean 이름은 메서드 이름인 passwordEncoder가 된다.

두 등록 방법의 선택 기준은 다음과 같다.

상황적합한 방법
직접 작성한 Controller, Service, Repository@Component 계열 + Component Scan
외부 라이브러리 객체 등록@Configuration + @Bean
생성 과정에 세부 설정이 필요한 객체@Configuration + @Bean

8. 애너테이션은 무엇이며 왜 사용하는가

Java **애너테이션(Annotation)**은 클래스, 메서드, 필드, 매개변수 등에 부가적인 의미나 처리 정보를 붙이는 메타데이터다.

@Service
public class UserService {
}

여기서 @Service를 붙였다고 Annotation이 알아서 new UserService()를 실행하는 것은 아니다. Spring의 기반 기능이 Annotation 정보를 읽고, 그 의미에 따라 Bean을 등록하거나 기능을 적용한다.

애너테이션으로 역할이나 설정 표시
    ↓
Spring이 메타데이터 확인
    ↓
Bean 등록, 요청 매핑, 트랜잭션 등의 기능 적용

애너테이션을 사용하는 이유

  • 설정 의도가 코드 가까이에 드러난다.
  • 반복적인 설정 코드를 줄일 수 있다.
  • “무엇을 적용할지”만 선언하고 세부 실행은 프레임워크에 맡길 수 있다.
  • 클래스와 메서드의 역할을 빠르게 파악할 수 있다.

Spring은 기능에 따라 클래스 메타데이터 읽기, Reflection, Bean 후처리기, AOP Proxy 등 여러 기술을 함께 사용한다. 따라서 “모든 Spring 애너테이션은 Reflection만으로 동작한다”고 이해하면 정확하지 않다. 예를 들어 @Transactional은 애너테이션이 트랜잭션 정보를 제공하고, 기본적으로 AOP Proxy가 메서드 호출을 가로채 실제 트랜잭션을 처리한다.

9. Spring MVC와 REST API

Spring MVC는 HTTP 요청을 Controller 메서드에 연결하고, 입력값 변환과 응답 생성을 지원하는 Spring의 웹 프레임워크다.

  • HTTP: 클라이언트와 서버가 요청과 응답을 주고받는 통신 규칙
  • API: 프로그램이 다른 프로그램의 기능이나 데이터를 사용하기 위한 접점
  • REST API: 자원을 URL로 표현하고 HTTP 메서드로 조회, 생성, 수정, 삭제 의도를 나타내는 API 방식
  • Mapping: 특정 HTTP 요청을 실행할 Java 메서드와 연결하는 것

일반적인 요청 처리 흐름은 다음과 같다.

클라이언트
    ↓ HTTP 요청
DispatcherServlet
    ↓ 요청에 맞는 메서드 탐색
Controller
    ↓ 비즈니스 로직 호출
Service
    ↓ 데이터 접근
Repository
    ↓
Database

DispatcherServlet은 Spring MVC의 앞단에서 요청을 받아 알맞은 Controller로 전달하는 Front Controller다.

요청 Mapping 애너테이션

@RequestMapping은 URL, HTTP 메서드, 요청 매개변수, 헤더, 미디어 타입 등을 기준으로 요청을 Controller 메서드에 연결한다. 다음 애너테이션들은 HTTP 메서드별 축약형이다.

애너테이션HTTP 메서드주된 의미
@GetMappingGET자원 조회
@PostMappingPOST자원 생성 또는 처리 요청
@PutMappingPUT자원 전체 수정
@PatchMappingPATCH자원 일부 수정
@DeleteMappingDELETE자원 삭제
@RestController
@RequestMapping("/users")
public class UserController {

    @GetMapping
    public String getUsers() {
        return "users";
    }

    @PostMapping
    public void createUser(@RequestBody CreateUserRequest request) {
    }

    @DeleteMapping("/{id}")
    public void deleteUser(@PathVariable Long id) {
    }
}

클래스의 @RequestMapping("/users")는 공통 경로를 지정한다. 따라서 각 메서드는 다음 요청을 처리한다.

GET    /users
POST   /users
DELETE /users/10

10. HTTP 요청값을 받는 방법

@PathVariable

URL 경로 자체에 포함된 값을 받는다. 특정 자원을 식별할 때 주로 사용한다.

@GetMapping("/users/{id}")
public UserResponse getUser(@PathVariable Long id) {
    // GET /users/10 요청이면 id는 10
}

@RequestParam

URL의 Query Parameter를 받는다. 검색, 필터, 정렬, 페이지 번호처럼 선택 조건을 전달할 때 주로 사용한다.

@GetMapping("/users")
public UserResponse getUser(@RequestParam String name) {
    // GET /users?name=min 요청이면 name은 "min"
}

@RequestBody

HTTP 요청 본문의 JSON 같은 데이터를 Java 객체로 변환해 받는다. 자원을 생성하거나 수정할 때 주로 사용한다.

{
  "name": "민형",
  "age": 25
}
public class CreateUserRequest {
    private String name;
    private int age;

    // getter, setter 또는 생성자
}
@PostMapping("/users")
public void createUser(@RequestBody CreateUserRequest request) {
}

Spring MVC는 등록된 메시지 변환기를 이용해 요청 본문을 CreateUserRequest 객체로 역직렬화한다.

그래서 세 방식의 차이를 정리하면 다음과 같다.

방식요청 예시용도
@PathVariable/users/10특정 자원의 식별자
@RequestParam/users?name=min검색, 필터 같은 부가 조건
@RequestBodyJSON 요청 본문생성, 수정할 데이터

11. Controller, Service, Repository를 나누는 이유

각 계층이 하나의 책임에 집중하도록 나누면 변경 이유가 다른 코드가 뒤섞이는 것을 막을 수 있다.

Controller
→ HTTP 요청과 응답을 담당

Service
→ 비즈니스 규칙과 작업 순서를 담당

Repository
→ 데이터 저장소 접근을 담당

예를 들어 회원가입 흐름은 다음처럼 구성할 수 있다.

@RestController
@RequestMapping("/users")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping
    public void createUser(@RequestBody CreateUserRequest request) {
        userService.register(request.getName(), request.getAge());
    }
}
@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Transactional
    public void register(String name, int age) {
        User user = new User(name, age);
        userRepository.save(user);
    }
}
@Repository
public class UserRepository {

    public void save(User user) {
        // DB 저장 로직
    }
}

이 구조에서는 웹 통신 방식이 바뀌어도 Service의 비즈니스 규칙에 미치는 영향을 줄일 수 있고, 데이터 저장 기술이 바뀌어도 Controller까지 수정할 가능성이 작아진다.

12. @Transactional

트랜잭션은 여러 데이터 변경을 하나의 작업 단위로 묶는 기능이다. 모든 작업이 성공하면 commit하여 결과를 확정하고, 중간에 실패하면 rollback하여 이전 상태로 되돌린다.

왜 필요한지는 계좌 이체를 생각하면 바로 이해할 수 있다. 출금만 성공하고 입금이 실패한 상태가 남아서는 안 된다.

@Transactional
public void transfer() {
    accountA.withdraw(10_000);
    accountB.deposit(10_000);
}
트랜잭션 시작
    ↓
A 계좌에서 10,000원 출금
    ↓
B 계좌에 10,000원 입금
    ↓
모두 성공 → COMMIT
하나라도 실패 → ROLLBACK

왜 @Transactional을 사용하는가

개발자가 메서드마다 트랜잭션 시작, commit, rollback, 자원 정리 코드를 반복하지 않아도 된다. 비즈니스 코드는 실제 작업에 집중하고, 트랜잭션 경계는 선언적으로 표현할 수 있다.

동작 원리

기본적인 Proxy 방식에서는 Spring이 원본 Bean을 감싸는 Proxy를 만들고 메서드 호출 전후에 트랜잭션 처리를 추가한다.

호출자
  ↓
Transaction Proxy
  ├─ 트랜잭션 시작
  ├─ 실제 메서드 호출
  └─ 성공 시 commit / 실패 시 rollback

주의할 점

  • @Transactional이 실제로 적용되려면 대상 객체가 Spring Bean으로 관리되어야 한다.
  • 기본 Proxy 방식에서는 외부에서 Proxy를 거쳐 들어오는 호출이 대상이다. 같은 클래스의 메서드가 내부에서 다른 @Transactional 메서드를 직접 호출하는 self-invocation에는 트랜잭션이 적용되지 않을 수 있다.
  • 기본 설정에서는 RuntimeException과 Error가 rollback 대상이고, checked exception은 자동 rollback 대상이 아니다. 필요하면 rollbackFor 등을 명시한다.

13. 전체 흐름 다시 보기

애플리케이션 시작 과정은 다음과 같다.

1. Spring 애플리케이션 실행
2. Container 생성
3. 설정 정보와 Component Scan으로 Bean 후보 탐색
4. Controller, Service, Repository Bean 생성
5. 생성자 주입으로 Bean 사이의 의존 관계 연결
6. 웹 요청을 받을 준비 완료

요청 처리 과정은 다음과 같다.

1. 클라이언트가 POST /users와 JSON을 전송
2. Spring MVC가 UserController.createUser()에 요청을 Mapping
3. @RequestBody가 JSON을 CreateUserRequest로 변환
4. Controller가 UserService 호출
5. @Transactional Proxy가 트랜잭션 시작
6. Service가 비즈니스 로직 수행
7. Repository가 DB에 데이터 저장
8. 성공 시 commit 후 HTTP 응답 반환

14. 핵심 애너테이션 요약

애너테이션정의사용하는 이유
@Component일반적인 Spring 관리 컴포넌트 후보Component Scan으로 Bean을 자동 등록하기 위해
@Service비즈니스 로직 계층의 컴포넌트역할을 명확히 하고 Service를 Bean으로 관리하기 위해
@Repository데이터 접근 계층의 컴포넌트데이터 접근 책임을 표시하고 예외 변환 같은 지원을 받기 위해
@ControllerView 기반 MVC ControllerHTTP 요청을 처리하고 View로 연결하기 위해
@RestController응답 본문을 직접 반환하는 ControllerREST API 응답을 JSON 등으로 반환하기 위해
@RequestMapping공통적인 요청 Mapping경로, HTTP 메서드, 헤더 등 다양한 조건으로 요청을 연결하기 위해
@GetMappingGET 요청 Mapping조회 요청을 메서드와 연결하기 위해
@PostMappingPOST 요청 Mapping생성, 처리 요청을 메서드와 연결하기 위해
@RequestBody요청 본문을 객체로 변환JSON 등의 본문 데이터를 Java 객체로 받기 위해
@PathVariableURL 경로 값을 매개변수에 연결특정 자원의 식별자를 받기 위해
@RequestParamQuery Parameter를 매개변수에 연결검색, 필터, 정렬 조건을 받기 위해
@ConfigurationJava 기반 Bean 설정 클래스객체 생성과 설정 방법을 코드로 정의하기 위해
@Bean메서드 반환 객체를 Bean으로 등록직접 생성하거나 외부 라이브러리 객체를 등록하기 위해
@Transactional트랜잭션 경계를 나타내는 메타데이터여러 데이터 작업의 원자성을 선언적으로 보장하기 위해

15. 최종 정리

Spring은 Java 객체의 생성, 설정, 생명주기와 의존 관계를 IoC Container가 관리하도록 하고, 웹 요청 처리와 데이터 접근, 트랜잭션 같은 애플리케이션 공통 기능을 제공하는 프레임워크다.

Bean은 Spring Container가 관리하는 객체이고, IoC는 객체 관리의 제어권을 Container로 옮기는 원칙이며, DI는 객체가 필요로 하는 의존성을 외부에서 주입하는 구현 방식이다.

Spring 애너테이션은 클래스와 메서드의 역할 또는 처리 방법을 표시하는 메타데이터다. Spring은 이 정보를 해석해 Bean 등록, 의존성 주입, HTTP 요청 Mapping, 트랜잭션 같은 실제 기능을 적용한다.

결국 핵심 연결 순서만 다시 보면 다음과 같다.

Annotation과 Configuration
        ↓
Bean 등록
        ↓
IoC Container가 관리
        ↓
DI로 객체 연결
        ↓
Controller → Service → Repository
        ↓
웹 요청과 비즈니스 로직, 데이터 처리 수행

참고 자료

  • Spring Framework: IoC Container
  • Spring Framework: Container Overview
  • Spring Framework: Classpath Scanning and Managed Components
  • Spring Framework: Annotated Controllers
  • Spring Framework: Mapping Requests
  • Spring Framework: Using @Transactional
TAGS#Java#Spring#Spring Framework#IoC#DI#Bean#Annotation#Spring MVC#Transaction
PREVIOUSJava 심화 Part 3: 함수형 프로그래밍과 LambdaNEXT2. Field/ Parameter / Argument/ this
DISCUSSION

COMMENTS

GitHub 계정으로 로그인하여 댓글을 남길 수 있습니다. 댓글은 GitHub Discussions에 공개 저장되며, 작성 내용과 GitHub 프로필 정보가 다른 방문자에게 보일 수 있습니다.

GitHub 로그인 후 댓글 쓰기Discussion 열기
DOCUMENT STRUCTURE

이 문서에는 목차가 없습니다.

DOCUMENT INFO
TYPE
Markdown
DATE
2026.08.11
READ
1 min read
WORDS
0
CATEGORY
java / spring
RELATED DOCUMENTS
Spring 기초 Part 9: JPA 트랜잭션과 동시성 제어Spring 기초 Part 7: Proxy 패턴과 Spring ProxySpring 기초 Part 2: Spring MVC 요청 처리 흐름Spring 기초 Part 10: 비동기 처리와 @AsyncSpring 기초 Part 8: AOP와 공통 관심사 분리Spring 기초 Part 6: JPA 연관관계 매핑
main java / spring
1 min readUTF-8Markdown
본문 글씨 크기
RECENTLY OPENED1
Spring 기초 Part 1: IoC, Bean, DI와 주요 Annotationrecently opened