출처 : 김영한님의 스프링 핵심 원리 - 기본편 인프런 강의
스프링 핵심 원리 - 기본편 - 인프런 | 강의
스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런...
www.inflearn.com
스프링의 역사
EJB(Enterprise Java Beans) 기술은 컨테이너, 설정에 의한 트랜잭션 관리, 분산 기술 ORM 등의 좋은 장점들이 있었지만 사용하기가 너무 복잡하고 느리고 라이프 사이클이 어떻게 돌아가는지도 알기 힘들었다.
그래서 로드 존슨이라는 개발자가 EJB의 문제점을 지적하면서 EJB 없이도 충분히 고품질의 확장 가능한 애플리케이션을 개발할 수 있음을 3만줄 이상의 예제 코드로 선보였다. 이 코드에 BeanFactory, ApplicationContext, POJO, 제어의 역전, 의존관계 주입 등 지금의 스프링 핵심 개념과 기반 코드가 포함되어 있다.
책 출간 직후 유겐 휠러와 얀 카로프가 로드 존슨에게 오픈소스 프로젝트를 제안하면서 전통적인 J2EE(EJB)라는 겨울을 넘어 새로운 시작이라는 뜻으로 스프링 프로젝트가 시작되었다.
스프링이란?
Spring | Projects
Spring Framework Provides core support for dependency injection, transaction management, web apps, data access, messaging, and more.
spring.io
스프링 프레임워크뿐만 아니라 정말 많은 프로젝트로 형성된 스프링 생태계를 뜻한다.

스프링 프레임워크
스프링 DI 컨테이너, AOP, 이벤트와 같은 핵심 기술과 스프링 MVC, 스프링 WebFlux 등의 웹 기술, 데이터 접근 기술인 트랜잭션, JDBC, ORM, XML도 지원한다. 그리고 스케줄링을 통한 예약기능, 이메일 전송, 원격접근 등의 기술과 이를 테스트할 수 있는 스프링 기반 테스트(JUnit)을 지원한다.
스프링 부트
스프링의 많은 기능을 편리하게 사용할 수 있도록 지원하는 기술로 스프링 프레임워크를 직접 구성하고 단독으로 실행할 수 있는 애플리케이션을 쉽게 생성해준다.
- Tomcat 값은 웹 서버를 내장해서 별도의 웹 서버 설치 불필요
- 예전에는 빌드하고 별도 설치한 톰캣 서버의 특정 위치에 빌드한 프로젝트를 넣고 띄우고... 복잡했다.
- 손쉬운 빌드 구성을 위한 starter 종속성 제공
'org.springframework.boot:spring-boot-starter-test'
- 스프링과 3rd parth(서드 파티) 외부 라이브러리 자동 구성
- 과거에는 버전에 따라 라이브러리마다 호환이 맞는 게 있고 안맞는 게 있어서 어려웠는데 스프링 부트는 이 버전을 다 관리해주고 지정해서 다운받아준다.
- 메트릭, 상태 확인, 외부 구성 같은 프로덕션 준비 기능 제공
- 운영에서 모니터링은 굉장히 중요한데 스프링 부트가 기본적으로 제공
- 관례에 의한 간결한 설정
- 스프링 프레임워크만 사용하면 설정이 매우 힘들었는데 스프링부트는 디폴트로 필수적인 설정들을 다 해준다.
- 스프링 프레임워크과 별도로 사용하는 것이 아닌 같이 사용하는 도구
스프링의 정확한 의미?
스프링이라는 단어는 문맥에 따라 다르게 사용된다. 핵심 기술인 스프링 DI 컨테이너 기술을 뜻하기도 하고 스프링 프레임워크를 뜻하기도 한다. 또는 스프링 부트, 스프링 프레임워크 등을 모두 포함한 스프링 생태계 전체를 의미하기도 한다.
스프링의 핵심 개념?
웹 애플리케이션을 만들고, DB 접근을 편리하게 해주는 기술, 웹 서버를 자동으로 띄워주는 기술, 클라우드, 마이크로 서비스 기술 등은 핵심이 아닌 결과물일 뿐이다. 정말 핵심은 무엇일까?
스프링은 핵심은 객체 지향 언어인 자바를 기반으로 객체 지향 언어가 가진 강력한 특징을 살려내 좋은 객체 지향 애플리케이션을 개발하는 것이다.
좋은 객체 지향 프로그래밍이란?
객체 지향 프로그래밍은 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러 개의 독립된 단위, 즉 "객체"들의 모임으로 파악하고자 하는 것이다. 각각의 객체는 메시지를 주고받고, 데이터를 처리할 수 있다. (협력) 따라서 객체 지향 프로그래밍은 프로그램을 유연하고 변경이 용이하게 만들기 때문에 대규모 소프트웨어 개발에 많이 사용된다.
유연하고 변경이 용이하다?
레고 블럭을 조립할 때 쉽게 바꿔 끼우듯이, 키보드와 마우스를 같은 기능을 하는 다른 모델로 바꾸듯이 객체 지향 프로그래밍을 통해 컴포넌트를 쉽고 유연하게 변경하면서 개발을 할 수 있다. 이러한 궁극의 유연함과 용이함은 바로 객체 지향의 핵심인 다형성이다.
다형성(Polymorphism)
실세계와 객체 지향은 1:1로 정확히 매칭되지는 않지만 실세계의 비유로 다형성을 이해하기에는 좋다.
역할과 구현으로 세상을 구분하는 것이 핵심인데 다음 예시로 이해해보자.

운전면허를 소지한 운전자가 K3를 타다가 아반떼를 타더라도 문제 없이 운전이 가능하다. 자동차의 종류에 관계 없이 운전자는 자동차이기만 하면 다 운전할 수 있는 것이다.
이것은 자동차란 역할에 대한 구현만 바뀌었기 때문에 가능하다. 자동차 역할의 인터페이스에 따라 자동차들을 구현(제조)했기 때문에 자동차 인터페이스에 대해서만 알고 의존하고 있어도 모든 종류의 자동차를 운전할 수 있다. 자동차가 바뀌어도 운전자에게 전혀 영향을 주지 않는 것이다. 새로운 자동차가 나와도 운전자는 두려워할 일이 없다.
운전자를 클라이언트라고 했을 때 운전자는 자동차의 내부 구조를 몰라도 되고 자동차 역할만 준수하는 구현체라면 내부적으로 바뀌어도 운전자에게 영향을 주지 않는다. 또한 자동차 역할만 구현하면 되기 때문에 자동차 세상을 무한 확장 가능하게 되고, 클라이언트에게 영향을 주지 않고 새로운 기능을 제공할 수 있게 된다.
이 모든 장점이 다 역할과 구현을 분리했기 때문에 가능한 일들이다.
또 다른 예로 공연 무대가 있다.

로미오와 줄리엣은 역할과 구현이 구분되어 있어서 배우가 누가 하던 대체 가능하다. 로미오의 역할을 하는 배우는 줄리엣 역할을 어떤 배우가 하더라도 대본대로만 하면 되기 때문에 전혀 상관없다. 로미오가 클라이언트고 줄리엣이 서버라고 가정해보면 줄리엣의 구현이 바뀐다고 해서 로미오의 역할이 바뀌지 않는 것이다.
역할과 구현을 분리하자
역할과 구현을 분리하면 세상이 단순해지고 유연해지며 변경도 편리해진다.
- 클라이언트는 대상의 역할(인터페이스만)만 알면 된다.
- 로미오 역할을 하는 사람은 대본에서의 배우 상관 없이 줄리엣 역할만 알면 된다.
- 클라이언트는 구현 대상의 내부 구조를 몰라도 된다.
- 자동차가 내부적으로 어떻게 동작하는 지 알 필요 없이 운전법만 알면 된다.
- 클라이언트는 구현 대상의 내부 구조가 변경되어도 영향을 받지 않는다.
- 기름차가 전기차로 내부 구조가 바뀌어도 영향 없다.
- 클라이언트는 구현 대상 자체를 변경해도 영향을 받지 않는다.
- K3에서 테슬라로 바꿔도 영향 없다.
JAVA 언어를 이용한 역할과 구현의 분리
자바 언어의 다형성을 이용한다.
- 역할 : 인터페이스
- 구현 : 인터페이스를 구현한 클래스, 구현 객체
객체를 설계할 때 역할(인터페이스)를 먼저 부여하고, 그 역할을 수행하는 구현 객체를 만들어서 역할과 구현을 명확하게 분리한다.
JAVA의 다형성
자바의 오버라이딩을 생각해보면 다형성으로 인터페이스를 구현한 객체를 실행 시점에 유연하게 변경할 수 있다.

public class MemberService { // private MemberRepository memberRepository = new MemoryMemberRepository(); private MemberRepository memberRepository = new JdbcMemberRepository(); }
다형성의 본질
다형성의 본질을 이해하려면 협력이라는 객체 사이의 관계에서 시작해야 한다.
- 인터페이스를 구현한 객체 인스턴스를 실행 시점에 유연하게 변경 가능
- 클라이언트를 변경하지 않고, 서버의 구현 기능을 유연하게 변경 가능
역할과 구현 분리 시 주의점
역할과 구현을 분리하면 유연하고 변경이 용이함과 동시에 클라이언트에게 영향을 주지 않고 확장 가능한 설계가 가능하다. 하지만 역할을 처음부터 잘못 설계하면 구현체 전부 다 변경되어야 하기 때문에 인터페이스를 안정적으로 잘 설계하는 것이 중요하다.
스프링과 객체 지향
객체지향의 개념 중 다형성이 가장 중요하다. 스프링은 다형성을 극대화하여 이용할 수 있게 도와준다.
- 제어의 역전(IoC)
- 의존관계 주입(DI)
좋은 객체 지향 설계의 5가지 원칙 (SOLID)
SRP 단일 책임 원칙 (Single Responsibility Principle)
한 클래스는 하나의 책임만 가져야 한다는 원칙이다. 하나의 책임이라는 것은 모호하기 때문에 변경을 기준으로 생각해야 한다. 책임의 범위를 적절하게 조절하는 것이 객체 지향 설계의 묘미이다. 변경이 있을 때 파급 효과가 적으면 단일 책임 원칙을 잘 따른 것이다.
OCP 개방-폐쇄 원칙 (Open/Closed Principle)
소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다는 원칙이다.
그런데 확장을 하려면 당연히 기존 코드를 변경해야 하는 것이 아닌가?
다형성을 이용하면 코드 변경 없이 확장이 가능하다. 인터페이스를 구현한 새로운 클래스를 하나 만들어서 새로운 기능을 구현하면 된다. 새로운 클래스를 만드는 것은 기존 코드를 변경하는 것이 아니기 때문에 원칙을 지킨 것이다.
지금까지 배운 역할과 구현의 분리를 생각해보면
public class MemberService { // private MemberRepository memberRepository = new MemoryMemberRepository(); private MemberRepository memberRepository = new JdbcMemberRepository(); }
MemoryMemberRepository(구현객체)에서 JdbcMemberRepository(구현 객체)로 바꾸려면 기존의 MemberService(클라이언트) 코드를 변경해야 했다. 분명 다형성을 사용했지만 기존 코드를 변경한 것이므로 OCP 원칙이 지켜지지 않은 경우다.
이 문제를 해결하려면 객체를 생성하고 연관 관계를 맺어주는 별도의 조립, 설정자인 스프링 컨테이너가 필요하다.
LSP 리스코프 치환 원칙 (Liskov Substitution Principle)
프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다는 원칙으로 다형성을 지원하기 위한 원칙, 인터페이스를 구현한 구현체를 믿고 사용할 수 있도록 해야 한다. 단순히 컴파일에 성공하는 것을 넘어서서 기능적으로 규약에 따라 보장을 해줘야 한다.
자동차 인터페이스의 엑셀은 앞으로 가는 기능으로 굴러가기만 하면 되는 것이 아닌 느리더라도 무조건 앞으로 가는 것이 보장되어야 한다. 뒤로 가면 LSP 위반인 것이다.
ISP 인터페이스 분리 원칙 (Interface Segregation Principle)
특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다는 원칙으로 자동차 인터페이스는 운전 인터페이스, 정비 인터페이스로 분리하는 것이 인퍼페이스가 명확해지고 대체 가능성이 높아진다.
스프링 프레임워크 코드를 보면 정말 철저하게 분리되어 있다.
DIP 의존관계 역전 원칙 (Dependency Inversion Principle)
프로그래머는 "추상화에 의존해야지, 구체화에 의존하면 안된다." 를 따르는 원칙으로 구현 클래스에 의존하지 말고 인터페이스에 의존하라는 뜻이다. 구현체에 의존하게 되면 변경이 아주 어려워지므로 역할에 의존해서 유연하고 변경이 용이하도록 설계해야 한다.
위의 MemberService는 인터페이스에 의존하고 있지만 구현 클래스를 직접 선택하고 있기 때문에 동시에 구현 클래스에도 의존하고 있다. -> DIP 위반
MemberService가 MemberRepository, 즉 인터페이스만 바라보고 MemoryMemberRepository와 JdbcMemberRepository, 즉 구현체에 대해서는 존재 자체도 몰라야 한다.
객체 지향의 핵심은 다향성인 것은 맞으나 다형성만으로는 구현 객체를 변경할 때 코드도 함꼐 변경해야 하기 때문에 OCP, DIP를 지킬 수 없었다.
뭔가가 더 필요하다...
객체 지향 설계와 스프링
이를 해결하기 위해 스프링은 다음 기술로 다형성 + OCP, DIP를 가능하게 지원한다.
- DI (Dependency Injection) : 의존관계, 의존성 주입
- DI 컨테이너 제공
스프링은 위의 기술로 자바 객체들을 컨테이너 안에 넣고 이 안에서 의존관계, 의존성을 연결하고 주입해줘서 클라이언트 코드의 변경 없이 기능을 확장할 수 있도록 해준다.
이렇게 좋은 객체 지향 개발을 위해 다형성과 OCP, DIP 지키려고 만들어진 것이 스프링 프레임워크이다.
정리
- 모든 설계에서 역할과 구현을 분리해서 유연하고 변경을 쉽게 할 수 있도록 하자
- 이상적으로는 모든 설계에 인터페이스를 부여하는 것이 좋지만 비용이 들어가는 것이기 때문에 기능을 확장할 가능성이 없는 경우 구체 클래스를 직접 사용하고 나중에 필요시 인터페이스를 도입하는 것도 방법이다.
- 어떤 DB를 사용할 지 아직 정해지지 않은 상태에서 일단 개발은 해야할 경우 일단 인터페이스로 정해두고 하면 개발이 가능하다. 이런 식으로 인터페이스를 먼저 만들어두면 구현 기술의 선택을 최대한 미룰 수 있는 장점이 있다.
- 할인 정책의 경우도 아직 안정해있어도 일단 그에 대한 간단한 구현체를 만들어 개발한 후 정해지면 그 때 기능을 확장하면 된다.
'Spring > Spring 핵심 원리 - 기본' 카테고리의 다른 글
[Spring] 컴포넌트 스캔 (0) | 2022.02.23 |
---|---|
[Spring] 싱글톤 컨테이너 (0) | 2022.02.23 |
[Spring] 스프링 컨테이너와 스프링 빈 (0) | 2022.02.22 |
[Spring] 객체 지향 원리 적용 (0) | 2022.02.22 |
[Spring] 순수 자바 코드로 설계하기 (0) | 2022.02.21 |