자바 진영에서 스프링 프레임워크는 이제 선택이 아닌 필수다. 하지만 단순히 기술 스택 중 하나로만 이해하기엔 스프링이 담고 있는 철학이 매우 깊다.
스프링은 자바라는 객체 지향 언어의 특징을 극대화하여, 개발자가 비즈니스 로직에만 집중하면서도 유연하고 변경에 강한 애플리케이션을 만들 수 있도록 돕는다.
1. 스프링 생태계와 스프링 부트
스프링은 방대한 기술의 집합체이며, 이를 더 쉽고 빠르게 사용할 수 있게 해주는 것이 스프링 부트다.
🔍 스프링 프레임워크의 주요 기술
- 핵심 기술: 스프링 DI 컨테이너, AOP, 이벤트, 기타 기반 기술
- 웹 기술: 스프링 MVC, 스프링 WebFlux
- 데이터 접근: 트랜잭션, JDBC, ORM 지원, XML 지원
- 기술 통합: 캐시, 이메일, 원격 접근, 스케줄링
- 테스트: 스프링 기반의 통합 테스트 환경 지원
- 지원 언어: 자바뿐만 아니라 코틀린, 그루비 활용 가능
🚀 스프링 부트(Spring Boot)의 가치
스프링 부트는 "스프링을 편리하게 사용할 수 있게 해준다"는 명확한 목적을 가진다.
- 단독 실행: 내장 서버(Tomcat 등)를 포함하여 별도의 웹 서버 설치 없이 즉시 실행 가능한 앱을 생성한다.
- 손쉬운 빌드: starter 종속성을 제공하여 라이브러리 관리를 단순화한다.
- 자동 구성: 스프링과 외부(3rd party) 라이브러리를 자동으로 연결하고 설정해준다.
2. 핵심 개념: 객체 지향과 다형성
스프링의 가장 큰 특징은 좋은 객체 지향 애플리케이션을 개발할 수 있게 도와준다는 것이다. 여기서 객체 지향 프로그래밍이란 프로그램을 독립된 단위인 '객체'들의 모임으로 파악하고, 이들이 메시지를 주고받으며 협력하는 것을 의미한다.
2-1 . 객체지향 프로그래밍(OOP)이란?
OOP는 프로그램을 단순히 명령어의 나열이 아닌, '객체(Object)'들의 모임으로 파악하는 패러다임이다.
🏗️ 핵심 구성 요소
- 클래스(Class): 객체를 만들기 위한 설계도 (예: 자동차 설계도)
- 객체(Object): 설계도를 바탕으로 구현된 실체 (예: 도로 위를 달리는 소나타, 아반떼)
- 속성(Attribute): 객체가 가진 데이터 (예: 색상, 모델명)
- 메서드(Method): 객체가 할 수 있는 행동 (예: 주행하기, 멈추기)
🏛️ OOP를 지탱하는 4대 기둥
- 캡슐화 (Encapsulation): 데이터와 처리 메서드를 하나로 묶고 내부 구현을 감추는 것. 보안성과 유지보수성을 높인다.
- 상속 (Inheritance): 부모 클래스의 특성을 물려받아 재사용하는 것. '자동차'에서 '전기차'를 파생시키는 식이다.
- 추상화 (Abstraction): 복잡한 내부 로직 대신 핵심 인터페이스만 노출하는 것. 운전자가 엔진 원리를 몰라도 핸들만 알면 운전할 수 있는 것과 같다.
- 다형성 (Polymorphism): 하나의 명령이 상황에 따라 다르게 동작하는 것.
2-2. 역할과 구현을 분리하라
객체 설계 시 가장 중요한 것은 역할(인터페이스)과 구현(구현 객체)을 구분하는 것이다.

이렇게 설계하면 클라이언트는 대상의 내부 구조를 몰라도 되며, 내부 구조나 구현 대상 자체가 변경되어도 영향을 받지 않는다. 이것이 바로 다형성의 본질이다.
3. 좋은 객체 지향 설계의 5가지 원칙 (SOLID)
다형성만으로는 완벽한 설계를 보장할 수 없다. 로버트 마틴이 정리한 SOLID 원칙이 필요한 이유다.
- SRP (단일 책임 원칙): 클래스는 하나의 책임만 가져야 한다. 변경 시 파급 효과가 적으면 잘 지킨 것이다.
- OCP (개방-폐쇄 원칙): 확장에는 열려 있으나 변경에는 닫혀야 한다. (인터페이스를 통한 확장)
- LSP (리스코프 치환 원칙): 하위 타입은 인터페이스의 규약을 깨뜨리지 않고 언제든 바꿀 수 있어야 한다.
- ISP (인터페이스 분리 원칙): 범용 인터페이스 하나보다 구체적인 여러 개의 인터페이스가 낫다.
- DIP (의존관계 역전 원칙): 구현 클래스가 아닌 인터페이스(역할)에 의존해야 한다.
⚠️ 현실적인 문제점
순수 자바 코드에서는 다형성을 활용하더라도 원칙을 어기게 되는 경우가 많다.
// MemberRepository m = new MemoryMemberRepository(); // 기존
MemberRepository m = new JdbcMemberRepository(); // 변경 시 클라이언트 코드를 직접 수정해야 함 -> OCP 위반!
구현 객체를 변경할 때 클라이언트 코드도 함께 고쳐야 한다면, 이는 OCP와 DIP를 제대로 지키지 못한 것이다. 이를 해결하기 위해 객체를 생성하고 연관관계를 맺어주는 별도의 '설정자'가 필요한데, 그것이 바로 스프링이다.
4. 스프링과 객체 지향 설계의 완성
스프링은 다형성을 극대화해서 이용할 수 있게 도와주는 기술적 기반을 제공한다.
- 제어의 역전(IoC) & 의존관계 주입(DI): 다형성을 활용해 역할과 구현을 편리하게 다룰 수 있도록 지원한다.
- 확장성: 클라이언트 코드의 변경 없이 기능을 확장할 수 있게 해준다.
정리하자면, 객체 지향의 핵심은 다형성이지만, 다형성만으로는 OCP와 DIP를 완벽히 지킬 수 없다. 스프링은 이 한계를 DI 컨테이너 기술로 해결함으로써 진정한 객체 지향 설계를 가능하게 만든다.
다만, 모든 곳에 인터페이스를 도입하면 '추상화'라는 비용이 발생하므로, 기능 확장 가능성이 없는 경우 구체 클래스를 먼저 사용하다가 향후 리팩터링을 통해 인터페이스를 도입하는 전략도 유효하다.
5. 마무리
결국 스프링을 배운다는 것은 라이브러리의 API를 외우는 것이 아니라, "어떻게 하면 더 좋은 객체 지향 코드를 작성할 것인가"에 대한 해답을 찾아가는 과정이다.
'Spring > Common' 카테고리의 다른 글
| [Spring] 빈 생명주기 콜백: 애플리케이션의 시작과 종료 관리 (0) | 2026.05.10 |
|---|---|
| [Spring] 스프링 싱글톤 컨테이너: 왜 모든 빈은 '하나'여야 할까? (0) | 2026.05.09 |
| [Spring] 엔티티 메타데이터 자동화: JPA Auditing을 활용한 일관된 데이터 관리 (0) | 2026.04.25 |
| [Spring] TraceId 로깅 시스템 실무 구축 : MDC와 Filter를 이용한 로그 데이터화 (0) | 2026.04.24 |
| [Spring] 효율적인 예외 처리: GlobalExceptionHandler로 사용자 경험과 운영 효율성 잡기 (0) | 2026.04.20 |