2026.03.08 - [Spring] - [Spring] 스프링 프레임워크의 기초 이해
[Spring] 스프링 프레임워크의 기초 이해
Framework (프레임 워크)프레임워크는 애플리케이션을 만들 때 필요한 도구와 기능을 미리 모아놓은 틀이다. 집을 지을 때 망치, 못, 설계도와 같은 도구가 필요하듯이 앱을 만들 때도 반복적으로
min-soon.tistory.com
스프링 애플리케이션의 기본 구조
일반적인 스프링 애플리케이션은 보통 Controller, Service, Repository 3개의 계층으로 나누어 설계합니다.
전체적인 구조는 다음과 같습니다.
Client 요청
↓
Controller
↓
Service
↓
Repository
↓
Database
각 계층은 서로 다른 역할을 담당하며, 이를 계층형 구조(Layered Architecture)라고 합니다.
이 구조는 애플리케이션의 역할을 명확하게 분리하여 유지보수성을 높이는 목적을 가지고 있습니다.
계층별 역할
| 계층 | 특징 | 주요 역할 |
| Controller | 사용자의 요청을 가장 먼저 받음 * 애플리케이션의 시작 지점 |
- HTTP 요청 처리 - 요청 데이터 전달 - Service 호출 - 결과 반환 |
| Service | 애플리케이션 핵심 비즈니스 로직 처리 *Controller로부터 요청을 받아 필요한 작업 후, 필요에 따라 Repository 호출 |
- 회원 가입 로직 - 주문 처리 - 결제 로직 - 데이터 검증 |
| Repository | 데이터베이스와 직접 상호작용 | - 데이터 저장 - 데이터 조회 - 데이터 수정 - 데이터 삭제 |
계층 분리의 이유
애플리케이션을 계층으로 나누는 이유는 각 클래스의 역할을 명확하게 분리하기 위해서입니다.
위와 같이 구조를 분리하면 유지보수성이 향상되고, 기능 확장이 용이해집니다.
이러한 계층 구조를 통해 역할을 분리할 수 있지만, 객체 간의 의존 관계를 잘못 설계하면 유지보수가 어려워질 수 있습니다.
이를 해결하기 위해 객체 지향 설계에서는 SOLID 원칙 이라는 중요한 설계 원칙을 제시하고 있습니다.
SOLID 원칙(객체 지향 설계 원칙)
좋은 객체 지향 설계를 위해 자주 언급되는 것이 SOLID 원칙입니다.
SOLID는 객체 지향 설계를 더 유연하고 유지보수하기 쉽게 만들기 위한 5가지 설계 원칙을 의미합니다.
| 원칙명 | 설명 | |
| SRP (Single Responsibility Principle) |
단일 책임 원칙 | 하나의 클래스는 하나의 책임만 가져야 함 -> 한 개의 클래스는 하나의 기능만 담당하도록 설계 |
| OCP( Open Closed Principle) |
개방 폐쇄 원칙 | 확장에는 열려 있어야 하고, 수정에는 닫혀 있어야 한다. ->기존 코드를 변경하지 않고 기능을 확장할 수 있도록 설계 |
| LSP (Lsikov Substitution Principle) |
리스코프 치환 법칙 | 자식 클래스는 언제나 부모 클래스를 대체할 수 있어야 한다. -> 부모 타입을 사용하는 곳에 자식 객체를 사용 가능하도록 설계 |
| ISP (Interface Segregation Principle) |
인터페이스 분리 법칙 | 하나의 큰 인터페이스보다는 작고 구체적인 인터페이스 여러 개로 나눈다. -> 자신이 사용하는 기능만 의존하도록 설계 |
| DIP (Dependency Inversion Principle) |
의존관계 역전 원칙 | 구체적인 구현 클래스가 아닌, 추상화(인터페이스)에 의존해야 한다. -> 구현이 아닌 역할에 의존하도록 설계 |
이 중에서 스프링을 이해할 때 가장 중요한 원칙은 DIP(의존 관계 역전 원칙) 입니다
DIP(Dependency Inversion Principle)
DIP(의존 관계 역전 원칙)는 특정 구현 클래스가 아닌, 추상화(인터페이스)에 의존하도록 설계해야 합니다.
다음과 같은 구조가 있다고 가정해 보겠습니다.

MemberService는 MemberRepository를 사용하여 회원 데이터를 저장하거나 조회 합니다.
일반적으로 의존 관계를 만들기 위해서는 다음과 같이 객체를 직접 생성할 것 입니다.
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
}
하지만 이 코드는 인터페이스가 아닌 구현 클래스에 직접 의존하고 있으며, 다음과 같은 문제가 발생합니다.
✔️ Repository 구현체 변경시 Service 코드도 수정 필요
↳ 기능 확장을 위해 기존 코드의 수정 필요 => DIP 위반
이 문제를 해결하기 위해 관심사의 분리(Separation of Concerns)가 필요합니다.
관심사의 분리
관심사의 분리란 하나의 객체가 여러 가지 책임을 가지지 않도록 역할을 분리하는 설계 방식입니다.
즉 각각의 객체가 자신의 역할에만 집중하도록 만드는 것 입니다.
애플리케이션을 하나의 공연이라고 생각하고, 공연은 배역(인터페이스)과 배우(구현 클래스)로 나뉜다고 생각하겠습니다.
여기서 배역에 맞는 배우는 누가 선택할까요? 이 역할은 공연 기획자가 담당할 것 입니다.
만약 배우가 직접 배우를 선택 한다면 연기도 해야하고, 배우도 직접 선택해야합니다.
즉 하나의 객체가 너무 많은 책임을 가지게 되며, 다음과 같이 역할을 분리해야 합니다.
배우 → 연기 수행
공연 기획자 → 배우 섭외 및 배치
또한 배우(구현 클래스)가 바뀐다고 해서 공연의 내용이 바뀌거나 문제가 생길까요?
배역(인터페이스)의 배우(구현 클래스)가 교체 된다고 할지라도, 배우는 동일한 역할을 수행할 수 있습니다.
여기서 중요한 점은 어떤 배우(구현 클래스) 인지가 아닌, 어떤 역할을 수행하느냐 입니다
즉, 인터페이스는 역할을 정의하는 것이고, 구현 클래스는 그 역할을 실제로 수행하는 것 입니다.
인터페이스를 기반으로 설계하면 다음과 같은 구조가 되며, 이는 구현 클래스가 변경되어도 서비스의 로직은 영향을 받지 않습니다.
Service
↓
Repository (Interface)
↓
구현체 (MemoryRepository / JdbcRepository 등)
그렇다면 이제 배우를 직접 선택하는 것이 아니라, 외부에서 배우를 정해주는 구조가 필요합니다.
스프링 설정 클래스
앞의 내용들로 우리는 객체 생성과 의존 관계를 관리하는 별도의 설정 클래스(공연 기획자)가 필요하다는 것을 깨달았습니다. 기존에는 객체를 직접 생성했지만, 이는 구현클래스를 직접 생성하고 의존하여, DIP원칙을 위반했었습니다.
이제 스프링을 이용하여 객체 생성과 의존 관계를 설정하는 설정 클래스(AppConfig)를 만들어보겠습니다.
해당 설정 클래스는 어떤 객체를 생성하고 어떤 객체와 연결할 것인지, 애플리케이션의 전체 동작 방식을 설정 합니다.
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
}
}
중복되는 부분을 제거하고, 역할과 구현 클래스가 한 눈에 들어와 애플리케이션 전체 구성을 빠르게 파악할 수 있다.
이처럼 객체의 생성과 의존 관계를 관리하는 제어권이 개발자가 작성한 비즈니스 로직이 아닌 외부로 넘어가는 것을
객체 지향 설계에서 IoC(Inversion of Control, 제어의 역전)이라고 합니다.

설정 클래스가 분리돼어 있어, 구현 클래스가 변경 되더라도 구성영역 코드만 수정하면 됩니다.
@Configuration
@Configuration은 해당 클래스가 스프링의 설정 클래스임을 의미 합니다.
즉, 이 스프링이 이 클래스를 읽어 애플리케이션의 객체 생성과 의존 관계 설정 정보로 사용 합니다.
@Bean
@Bean은 스프링 컨테이너에 객체를 등록하는 역할을 합니다. @Bean이 붙은 메서드는 실행되며, 반환된 객체가 스프링 컨테이너에 등록 됩니다. 이렇게 스프링 컨테이너에 등록된 객체를 스프링 빈(Spring Bean)이라고 합니다.
특별히 이름을 지정하지 않는 경우 메서드의 이름이 빈의 이름이 됩니다.
@Bean
public MemberService memberService()
↓
빈 이름 : memberService
스프링 컨테이너
이제 애플리케이션을 실행하는 코드도 스프링을 사용하도록 변경을 하기 위해 우선 스프링 컨테이너를 생성하겠습니다.
스프링 컨테이너(Spring Container)는 애플리케이션에서 사용할 객체를 생성하고 관리하는 역할을 합니다.
- 객체 생성, 의존 관계 주입(DI), 객체 생명주기 관리, 스프링 빈 관리
✔️컨테이너 생성
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
위 코드에서 ApplicationContext를 스프링 컨테이너(Spring Container)라고 합니다.
ApplicationContext란?
ApplicationContext는 스프링 컨테이너를 의미하는 인터페이스이며, 다음과 같은 구현체를 사용합니다.
✔️ 스프링 컨테이너 구현체
AnnotationConfigApplicationContext
즉 아래 코드는 AppConfig를 설정 정보로 사용하는 스프링 컨테이너를 생성한다는 의미입니다.
✔️컨테이너 생성
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
과거 스프링에서는 XML 기반 설정을 사용하기도 했지만, 현재는 어노테이션 기반 설정 방식이 대부분 사용되며,
스프링 부트에서도 기본적으로 이 방식을 사용 합니다.
자바 코드vs 스프링 구조 차이
기존에는 개발자가 직접 객체를 생성하고 의존 관계를 연결하여 다음과 같은 구조였습니다.
설정 클래스(AppConfig> -> 객체 생성 -> 의존 관계 연결
하지만 스프링을 사용하면 다음과 같이 구조가 변경 됩니다.
@Configuration
↓
스프링 컨테이너 생성
↓
@Bean 객체 등록
↓
스프링 빈 관리
↓
getBean()으로 조회
스프링을 사용하면 개발자가 직접 객체를 관리하는 것이 아니라, 스프링 컨테이너가 객체를 생성하고 관리하게 됩니다.
스프링 컨테이너 생성 과정



스프링 빈 조회
스피링 빈을 조회할 때 중요한 특징 중 하나는 상속 관계를 고려하여 조회가 가능하다는 점입니다.
스프링에서는 부모 타입으로 빈을 조회하면 해당 타입을 상속하거나 구현한 자식 타입의 빈들도 함께 조회 됩니다.
*자바 최상위 클래스인 Object 타입으로 조회하면 스프링 컨테이너에 등록된 모든 스프링 빈 조회 가능
이름으로 조회
✔️ ac.getBean("빈이름", 타입)
MemberService memberService = ac.getBean("memberService", MemberService.class);
타입으로 조회
✔️ ac.getBean(타입) *같은 타입의 빈이 여러개일 경우 충돌 발생 위험
MemberService memberService = ac.getBean(MemberService.class);
부모 타입으로 조회 하면 자식 타입의 빈들도 같이 조회 되기 때문에 같은 타입의 빈이 여러개일 경우 충돌 발생 위험이 있습니다.
만약 같은 타입의 빈이 여러개일 경우 빈 이름을 지정하면 된다.
@Bean(name = "이름 지정")
또한 구체적인 구현 클래스로도 조회가 가능하지만 이는 구현 클래스에 의존하게 되기 때문에 좋은 방법은 아닙니다.
따라서 인터페이스 타입으로 조회하는 것이 권장 됩니다.
모든 빈 조회
✔️ ac.getBeansOfType(타입)
Map<String, MemberService> beans = ac.getBeansOfType(MemberService.class);
주로 같은 타입의 여러 빈을 확인할 때 사용 됩니다.
BeanFactory와 ApplicationContext
앞에서 우리는 스프링 컨테이너를 생성하였으며, ApplicationContext를 스프링 컨테이너라고 설명 했습니다.
하지만 사실 스프링 컨테이너의 최상위 인터페이스는 BeanFactory입니다.
해당 인터페이스는 스프링의 핵심 기능인 빈(Bean) 관리와 조회 기능을 담당합니다.
즉, BeanFactory는 기본적인 기능만 제공하는 인터페이스입니다.
ApplicationContext
ApplicationContext는 BeanFactory를 상속받은 인터페이스입니다.
즉, BeanFactory의 기능을 모두 포함하면서 추가적인 기능을 더 제공하는 인터페이스입니다.

BeanFactory와 ApplicationContext의 차이
두 인터페이스의 핵심 차이는 부가 기능의 존재 여부입니다.
애플리케이션을 개발할 때는 단순히 빈을 생성하고 조회하는 기능만으로는 부족하기에, ApplicationContext는 다음과 같은 기능들이 존재 합니다.
| 인터페이스 명칭 | 내용 | 설명 |
| 메세지 소스 (Message Source) |
국제화 기능 제공 | 같은 메시지를 언어에 따라 다르게 출력 |
| 환경 변수 처리 (Environment Capable) |
애플리케이션의 환경 정보 관리 | 개발 환경, 운영 환경 등 관리 |
| 애플리케이션 이벤트 (Application Event Publisher) |
이벤트를 발행하고 구독하는 기능 | 어플 내부에서 이벤트 기반 구조 설계 가능 |
| 리소스 조회 (Resource Loader) |
파일, 클래스패스, 외부 등에서 리소스를 편리하게 조회 | 다양한 설정 형식 지원 (자바, XML, Groovy 기반 설정 등) |
스프링은 이처럼 정말 다양하고 편리한 기능을 제공 합니다.
그러면 스프링은 어떻게 @Bean을 보고 어떤 객체를 생성해야 하는지 알고, 다양한 설정 형식을 지원할 수 있는걸까요?
BeanDefinition(스프링 빈 설정 메타 정보)
BeanDefinition은 스프링 빈에 대한 설정 정보(메타데이터)로, 스프링 빈의 설계도 같은 역할을 합니다.
쉽게 이야기 해서 역할과 구현을 개념적으로 나눈 것으로, 스프링은 BeanDefinition이라는 설계도를 보고 객체를 생성합니다.
또한 스프링 빈을 생성하기 위한 다양한 정보가 담겨 있으며, BeanDefinition은 스프링 빈을 생성하기 위한 모든 정보를 담고 있는 객체 라고 볼 수 있습니다.
✔️ BeanDefinition의 정보
Ex))빈 이름, 빈 클래스 이름 생성 방법, 의존 관계, 초기화, 메서드, 스코프 정보 등
BeanDefinition이 필요한 이유
스프링은 Java, XML 등 여러 방식으로 설정을 작성할 수 있습니다. 스프링 내부에서는 설정 방식이 무엇인지 상관없이 동일한 방식으로 객체를 생성해야하며, 자바 코드인지 XML인지 몰라도 되며 오직 BeanDefinition만 알고 있으면 됩니다.
그래서 스프링은 다음과 같은 구조를 사용합니다.
설정 파일(Java / XML / Groovy)
↓
BeanDefinition 생성
↓
스프링 컨테이너가 Bean 생성

어떤 설정 방식이든 모두 BeanDefinition으로 변환한 뒤 사용 하며, 이것이 바로 스프링의 핵심 설계 개념입니다.
즉, BeanDefinition은 스프링 빈을 생성하기 위한 설정 정보를 담고 있는 객체이며, 스프링은 모든 설정을 BeanDefinition으로 변환한 뒤 객체를 생성합니다.
'Spring' 카테고리의 다른 글
| [Spring Boot] 테스트의 이해와 @Transactional 동작 원리 (0) | 2026.05.12 |
|---|---|
| [Spring] - AOP(관점 지향 프로그래밍)의 이해 (0) | 2026.04.28 |
| [Spring] - BCryptPasswordEncoder를 이용한 비밀번호 암호화와 보안 원리 (1) | 2026.04.16 |
| [Spring] 싱글톤 패턴과 싱글톤 컨테이너 정리 (0) | 2026.03.23 |
| [Spring] 스프링 프레임워크의 기초 이해 (0) | 2026.03.09 |