1.4 제어의 역전(IoC)
1.4.1 오브젝트 팩토리
기존의 클라이언트였던 테스트 main() 메소드는 테스트 용도였지만,
클라이언트 역할까지 맡아버려 오브젝트를 생성하고 관계를 맺어주는 역할까지 맡아버렸다.
따라서 우리는 구현 클래스 오브젝트를 만들고 두 오브젝트를 연결해주는 역할을 분리해야한다.
팩토리
- 객체의 생성 방법을 결정하고 만들어진 오브젝트를 돌려주는 것
public class DaoFactory {
public IndependentUserDao userDao() {
ConnectionMaker connectionMaker = new SimpleConnectionMaker();
IndependentUserDao userDao = new IndependentUserDao(connectionMaker);
return userDao;
}
}
이제는 팩토리를 통해 UserDao 오브젝트를 받아 테스트에 활용하면 된다.
이제 오브젝트들의 역할과 관계에 따라 나누어볼 수 있다.
1. 애플리케이션의 핵심적인 데이터 로직과 기술 로직을 담당하는 컴포넌트
2. 애플리케이션의 오브젝트를 구성하고 관계를 정의하는 설계도
DaoFactory를 분리함으로써 구조를 결정하는 오브젝트가 분리됨에 의미가 크다.
1.4.2 오브젝트 팩토리의 활용

ConnectionMaker 구현 클래스의 오브젝트를 생성하는 코드마다 메소드가 반복되고 있다.
new SimpleConnectionMaker()라는 구현 클래스의 인스턴스를 만드는 부분이 반복되고 있다.
이런 상황이라면 구현 클래스를 바꿀 때마다 모든 메소드를 수정해야하기 때문에 수정이 필요하다.

이렇게 분리된다면 DAO의 팩토리 메소드가 많아져도
connectionMaker 메소드 내부의 ConnectionMaker 구현 클래스만 변경하면 된다.
1.4.3 제어권의 이전을 통한 제어관계 역전
현재 프로그램 실행 흐름을 살펴보자.
main() -> 다음 오브젝트 결정 후 생성 -> 오브젝트 내 메소드 호출 -> 다음 사용할 오브젝트/메소드 결정 및 호출 반복
이 구조는 오브젝트 자신이 사용할 오브젝트를 결정하고 언제, 어떻게 만들지 선택하는 능동적인 참여 구조다.
제어의 역전이란 이 제어의 흐름 개념을 거꾸로 뒤집는 것이다.
제어의 역전이 이루어지면 자신이 사용할 오브젝트를 스스로 결정하지도 않고, 생성하지도 않는다.
모든 제어 권한을 다른 대상에 위임하기 때문이다.
서블릿과 앞서 배운 추상 클래스 구조에서도 제어의 역전 개념이 여실히 드러나고 있다.
서블릿을 개발해서 서버에 배포할 수는 있지만 개발자가 제어할 수는 없다.
대신 서블릿 제어 권한을 가진 컨테이너가 적절히 서블릿 클래스의 오브젝트를 만들고 내부 메소드를 호출한다.
추상 클래스에서도 서브 클래스에서는 추상 메소드를 구현하기는 하지만,
슈퍼 클래스에서 언제, 어떻게 사용될 지는 모른다.
제어권을 상위 템플릿 메소드에 넘기고 자신은 필요할 때 호출되어 사용되는 제어의 역전 개념을 발견할 수 있다.
라이브러리와 프레임워크의 대표적인 차이도 이 제어의 역전이다.
라이브러리를 사용하는 애플리케이션 코드는 애플리케이션의 흐름을 직접 제어하며
동작하는 중에 필요한 기능이 있을 때 능동적으로 라이브러리를 사용한다.
반면 프레임워크는 애플리케이션 코드가 프레임워크에 의해 사용된다.
프레임워크가 흐름을 주도하는 중에 개발자가 만든 애플리케이션 코드를 사용하는 방식이다.
앞서 자연스럽게 관심을 분리하고 책임을 나누고 유연하게 확장 가능한 구조를 만들기 위해
팩토리를 도입한것이 IoC를 적용한 과정이다.
제어의 역전에서는 프레임워크 또는 컨테이너와 같이
컴포넌트 생성과 관계 설정, 사용, 생명주기 관리 등을 관장하는 존재가 필요하다.
우리는 미니멀하게 DaoFactory를 오브젝트 수준에서 가장 단순한 IoC 컨테이너로 활용해보았다.
애플리케이션 전반에 걸쳐 본격적으로 활용하기 위해서는 스프링과 같은 IoC 프레임워크의 도움을 받는다.
스프링은 IoC를 기반 기술, 극한까지 적용하는 프레임워크다!!
1.5 스프링의 IoC
스프링의 핵심은 빈 팩토리 또는 애플리케이션 컨텍스트라고 불리는 것이다.
1.5.1 오브젝트 팩토리를 이용한 스프링 IoC
애플리케이션 컨텍스트와 설정 정보
스프링이 제어권을 갖고 직접 만들고 관계를 부여하는 오브젝트들을 Bean이라고 부른다.
스프링 컨테이너가 관계설정, 사용 등을 제어하는 제어의 역전이 적용되는 오브젝트를 가리킨다고 보면 된다.
Bean Factory
- Bean 생성, 관계 설정 등 제어를 담당하는 IoC 오브젝트
- 보통 빈팩토리를 확장한 Application Context(AC)를 주로 사용
앞으로 빈 팩토리라고 부를 때는 빈 생성, 관계 설정에 입각해 IoC 기본 기능에 초점을 두는 것이며,
AC로 부를 때는 애플리케이션 전반에 걸쳐 모든 구성요소의 제어 작업을 담당하는 IoC 엔진이라는 의미를 부각시키는 것이다.
AC는 별도의 정보를 참고해 빈 생성, 관계설정 등 제어 작업을 총괄한다.
기존 DaoFactory를 참고해 설정정보를 담고있는 무언가를 가져와 활용할 수 있다.
따라서 팩토리의 역할이 설정정보까지 담고있던 엔진이었는데, 이제는 활용되는 것이다.
건물이 설계도면을 따라 만들어지듯이 애플리케이션도 AC와 그 설정정보를 따라 만들고 구성된다.
@Configuration
- 빈 팩토리를 위한 오브젝트 설정을 담당하는 클래스
@Bean
- 오브젝트를 만들어주는 메소드
이 두 애노테이션으로 스프링 프레임워크의 AC가 IoC를 제공할 때 완벽한 설정정보를 가질 수 있다.

이 코드를 보면 그냥 daoFactory를 불러와서 생성하면 더 깔끔하지 않나?
더 얻을 수 있는 장점이 뭘까?
1.5.2 애플리케이션 컨텍스트의 동작 방식
애플리케이션 컨텍스트는 ApplicationContext 인터페이스를 구현하고,
ApplicationContext는 BeanFactory가 구현하는 BeanFactory를 상속했기 때문에
AC는 일종의 빈 팩토리로 볼 수 있다.
AC는 애플리케이션에서 IoC를 적용해 관리할 모든 오브젝트 생성과 관계설정을 담당한다.
코드가 없고 생성/연관관계 정보를 별도 설정정보를 통해 얻어낸다. 때로는 외부 오브젝트 팩토리로 위임하는 경우도 있다.
@Configuration이 붙은 DaoFactory는 AC가 활용하는 IoC 설정정보다.
@Bean이 붙은 메소드 이름을 가져와 빈 목록을 만들어내고,
클라이언트가 getBean()으로 일일이 요청해야 오브젝트가 전달되는데 장점은 무엇일까?
1. 클라이언트는 구체적인 팩토리 클래스를 알 필요가 없다.
- 오브젝트 팩토리 내에 계속 코드가 추가되어야 한다.
2. 종합 IoC 서비스를 제공한다.
- 관계 설정만이 아닌 오브젝트 생성 방식, 시점, 전략을 다르게 가져갈 수 있다.
- 오브젝트 후처리, 정보 조합, 설정 방식 다변화, 인터셉팅 등 다양한 기능이 사용가능해진다.
- 빈이 사용할 수 있는 기반 기술, 외부 시스템 연동 등 컨테이너 차원에서 제공된다.
3. 빈을 검색하는 다양한 방법을 제공한다.
1.5.3 스프링 IoC 용어 정리
- Bean
- 스프링이 IoC 방식으로 관리하는 오브젝트, 관리되는 오브젝트
- 애플리케이션에서 사용되는 모든 오브젝트가 다 빈은 아니고 스프링이 직접 생명주기를 담당하는 오브젝트
- Bean Factory
- 스프링 IoC 담당 핵심 컨테이너
- 빈 등록, 생성, 조회하고 리턴, 부가적 빈 관리
- 보통 AC 사용, 빈 팩토리가 구현하는 가장 기본적인 인터페이스 이름
- Application Context
- 빈 팩토리를 확장한 IoC 컨테이너
- 빈을 등록하고 관리하는 기본적인 기능은 빈 팩토리와 동일
- 빈팩토리는 빈 생성/제어 관점, AC는 스프링 애플리케이션 지원 기능 모두 포함
- ApplicationContext는 BeanFactory 상속
- Configuration Metadata
- AC 또는 Bean Factory가 IoC를 적용하기 위해 사용하는 메타정보
- 컨테이너에 의해 관리되는 오브젝트를 생성하고 구성할 때 사용
- 형상정보, 청사진 ..
- IoC Container
- IoC 방식으로 빈을 관리한다는 의미
- 애플리케이션 컨텍스트보다 추상적인 의미이기도 하다
- 애플리케이션 컨텍스트 오브젝트는 하나의 애플리케이션에서 여러개 만들어서 사용되기 때문에 통틀어서 스프링 컨테이너라고 부를 수 있다
- 스프링 프레임워크
- IoC 컨테인, 애플리케이션 컨텍스트를 포함해서 스프링이 제공하는 모든 기능 통틀어 부름
- 주로 스프링으로 줄여 부름
1.6 싱글톤 레지스트리와 오브젝트 스코프
아직까지는 직접 만든 Factory와 @Configuration이 적용된 컨테이너가 동일하게 작동하고 있다.
차이점을 알아보기 이전에 동일성과 동등성의 차이를 간단하게 살펴보자.
오브젝트의 동일성과 동등성
동일성(identity)의 비교는 ==, 동등성(equality)의 비교는 equals() 메소드를 통해 이뤄진다.
동등한 경우는 두 개의 각기 다른 오브젝트가 메모리상 각각 존재하지만,
동일한 경우는 오브젝트는 하나만 존재하고 래퍼런스 변수가 다른 것 뿐이다.
우리가 만든 팩토리로는 동일성이 지켜지지 않는다. 하지만 AC의 getBean은 동일성이 지켜진다.
1.6.1 싱글톤 레지스트리로서 애플리케이션 컨텍스트
AC는 우리가 만들었던 오브젝트 팩토리와 비슷한 방식으로 동작하는 IoC 컨테이너를 활용해 오브젝트를 생성한다.
여타 설정을 하지 않는다면 모두 싱글톤으로 빈 오브젝트를 생성한다.
서버 애플리케이션과 싱글톤
그렇다면 왜 스프링은 빈을 싱글톤으로 만들어낼까?
스프링은 주로 서버환경에서 활용되고, 그래야만 가치가 커진다.
대규모 엔터프라이즈 환경이라고 가정해보면 서버 하나당 최대 초당 수십, 수백번의 브라우저 요청을 받아 처리한다.
그런데 이 과정에서 다양한 기능을 담당하는 오브젝트가 모두 새롭게 생성된다면?
서블릿 또한 기본적인 서비스 오브젝트이며, 여러 스레드에서 하나의 오브젝트를 공유한다.
이렇게 제한된 수, 대개 한 개의 오브젝트만 만들어 사용하는 것이 싱글톤 패턴의 원리다.
하지만 디자인 패턴에서 싱글톤 패턴은 사용하기도 까다롭고 문제점도 있다.
간단히 싱글톤 패턴을 알아보자.
싱글톤 패턴 구현 방법
- private 접근 제어자로 외부 생성 불가
- 생성된 싱글톤 오브젝트를 저장할 수 있는 자신과 같은 스태틱 필드 정의
- 스태틱 팩토리 메소드인 getInstance()를 만들고 호출 시점에 오브젝트가 생성돼 스태틱 필드에 저장
- 생성된 이후에는 getInstance() 메소드를 통해 스태틱 필드에 저장된 오브젝트 사용
싱글톤 패턴의 한계
- private으로 생성되어 상속 불가 -> 다형성 이용 불가
- static으로 필드가 만들어져 목으로 대체하거나 주입 불가 -> 테스트 난이도 상승
- 여러 JVM으로 분산되는 경우 서버환경에서 싱글톤 보장 불가
- 전역 상태, 즉 아무 객체나 자유롭게 접근하고 수정하고 공유하는 모델은 객체지향적이지 못함
싱글톤 레지스트리
서버 환경에서 싱글톤으로 만들어져 서비스 오브젝트 방식으로 사용되는 것은 적극 지지하나,
기본적인 싱글톤 구현 방식은 앞서 본 여러 단점이 있어 스프링은 직접 싱글톤 형태의 오브젝트를 관리하고 기능을 제공한다.
이를 싱글톤 레지스트리라고 명명한다.
POJO 평범한 자바 클래스를 제어권을 컨테이너로 넘긴다면 싱글톤으로 활용할 수 있도록 보장한다.
오브젝트 생성에 관한 모든 권한은 AC에 있기 때문이다.
따라서 public으로 생성하여도 싱글톤 방식이 유지되고, 앞서 살펴본 한계가 해결된다.
가장 중요한 것은 스프링이 지지하는 객체지향적인 설계 방식과 원칙, 디자인 패턴을 적용할 수 있다.
1.6.2 싱글톤과 오브젝트의 상태
싱글톤 오브젝트로 유지되기 때문에 여러 스레드가 동시에 접근하여 사용할 수 있도록 무상태로 만들어져야 한다.
읽기 전용이면 인스턴스 번수로 공유해도 되지만, 저장공간이 하나이기 때문에 서로 덮어쓸 수 있기 때문이다.
따라서 파라미터, 로컬 변수, 리턴 값을 이용해 값을 주고받는다.
메서드 파라미터, 로컬변수는 스레드 스택 영역에 저장되기 때문에 독립적이다.
1.6.3 스프링 빈의 스코프
스프링 빈의 기본 스코프는 컨테이너 내에 한 개의 오브젝트만 만들어져 스프링 컨테이너가 존재하는 동안 유지된다.
싱글톤 스코프
- 스프링 빈의 기본 설정, 컨테이너와 생명주기 동일
프로토타입 스코프
- 컨테이너에 빈을 요청할 때마다 매번 새로운 오브젝트 생성
요청 스코프
- 웹을 통해 HTTP 요청이 생길 때마다 생성
세션 스코프
- 웹의 세션과 스코프가 유사
1.7 의존관계 주입(DI)
1.7.1 제어의 역전(IoC)과 의존관계 주입
IoC는 전통적인 소프트웨어 방법론에서 살펴보던 일반적인 개념이다.
객체지향적인 설계나 디자인 패턴, 컨테이너에서 동작하는 서버 기술을 사용하다보면
자연스럽게 IoC를 적용하거나 그 원리로 동작하는 기술을 사용하고 있다.
여기서 스프링의 의도를 더욱 명확히 나타내기 위해 의존관계 주입(DI)라는 용어를 사용하고 있다.
스프링이 여타 프레임워크와 차별화되어 제공해주는 기능은 의존관계 주입이라는 새로운 용어를 사용할 때 분명히 드러난다.
오브젝트 레퍼런스를 외부로부터 제공받고, 여타 오브젝트와 다이나믹하게 의존관계가 만들어지는 것이 핵심이다.
1.7.2 런타임 의존관계 설정
의존관계
의존한다는 것은 의존 대상이 변하면 의존하는 대상에 영향을 미친다는 뜻이다.
A -> B, A가 B를 의존하고 있을 때 B의 기능이 추가/변경 시 그 영향이 A로 전달된다.
UserDao의 의존관계
현재 UserDao가 ConnectionMaker에 의존하고 있다. 따라서 ConnectionMaker의 변경에 UserDao가 영향을 받는다.
하지만 인터페이스를 통해 설계 시점에 느슨한 의존관계를 만들어주면
UserDao의 오브젝트가 런타임 시에 사용할 오브젝트가 어떤 클래스로 만든 것인지 미리 알 수가 없다.
-> 변경에서 자유로워진다.
개발자, 운영자가 사전에 어떤 클래스의 오브젝트를 사용할 것인지 정해놓기는 하지만,
실제 설계와 코드 속에서는 드러나지 않는다.
이렇게 프로그램이 시작되고 런타임 시에 의존관계를 맺는 대상을 의존 오브젝트라고 부른다.
의존관계 주입은 이렇게 구체적인 의존 오브젝트와 그것을 사용할 주체를 런타임 시에 연결해주는 작업을 말한다.
또한, 이 주입해주는 제 3의 존재가 있다는 것이 의존관계 주입의 핵심이기도 하다.
의존관계 주입의 3가지 조건
1. 클래스 모델이나 코드에는 런타임 시점의 의존관계가 드러나지 않는다. 그러기 위해서는 인터페이스만 의존해야한다.
2. 런타임 시점의 의존관계는 컨테이너나 팩토리같은 제 3의 존재가 결정한다.
3. 의존관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공해줌으로써 만들어진다.
DI 컨테이너는 오브젝트를 만드는 시점에서 생성자 파라미터로 오브젝트의 레퍼런스가 전달되는 것이다.
최근 스프링 부트에서는 보통 생성자 주입을 통해 오브젝트의 레퍼런스가 제공된다.
public class IndependentUserDao {
private final ConnectionMaker connectionMaker;
public IndependentUserDao(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
}
...
}
이런식으로 두 개의 오브젝트 간 런타임 의존관계가 만들어졌다.
DI는 자신이 사용할 오브젝트에 대한 선택과 생성 제어권을 외부로 넘기고 자신은 수동적으로 주입받는 오브젝트를 사용한다!!
1.7.3 의존관계 검색과 주입
해당 파트는 아직 지식이 부족해서 학습을 더 진행한 후 작성하겠다.
1.7.4 의존관계 주입의 응용
DI 장점
1. 인터페이스를 통한 결합도가 낮은 코드
2. 코드에서 런타임 클래스에 대한 의존관계가 나타나지 않음
3. 다른 책임을 가진 사용 의존관계가 있는 대상이 바뀌거나 변경되어도 자신은 영향받지 않음
4. 변경을 통한 다양한 확장 방법에는 자유롭다.
-> 이러한 장점으로 인해 스프링이 제공하는 많은 기능이 DI의 혜택을 이용한다.
디테일하게 살펴보자.
기능 구현의 교환
@Bean
public ConnectionMaker connectionMaker() {
// 둘중에 필요에 따라 교체만 하면 됨
// return new ProductionDBConnectionMaker();
return new LocalDBConnectioMaker();
}
만약 로컬에서 테스트하려고 DAO에서 매번 클래스 오브젝트를 생성해서 사용했다면,
new LocalDBConnectionMaker()라는 코드로 모든 DAO에서 변경해주어야 했다.
부가기능 추가
public class CountingConnectionMaker implements ConnectionMaker{
int counter = 0;
private ConnectionMaker realConnectionMaker;
public CountingConnectionMaker(ConnectionMaker realConnectionMaker) {
this.realConnectionMaker = realConnectionMaker;
}
public Connection makeConnection throws ClassNotFoundException, SQLException{
this.counter++;
return realConnectioMaker.makerConnection();
}
public int getCounter() {
return this.counter;
}
}
DAO와 DB 커넥션을 만드는 오브젝트 사이의 연결 횟수를 카운팅하는 기능이 추가되었다.
CountingConnectionMaker 클래스는 내부에서 직접 DB 커넥션을 만들지 않는다.
count를 증가하는 기능이 추가되었고, 실제 DBConnector를 호출해 DAO에 돌려줄 뿐이다.
DAO가 계속 증가하여도 DI를 통해 관심사가 분리되었기 때문에
DAO가 직접 의존해서 사용할 ConnectionMaker 타입의 오브젝트는 @Configuration 안에서 만들어준다.
-> 관심사의 분리를 통해 얻어지는 높은 응집도
스프링을 공부하는 것은 DI를 어떻게 활용해야할지 공부하는 것이기도 하다.
'spring' 카테고리의 다른 글
| [Spring] 토비의 스프링 Vol.1 2장 - 테스트 (2) (0) | 2026.06.18 |
|---|---|
| [Spring] 토비의 스프링 Vol.1 2장 - 테스트 (1) (0) | 2026.06.17 |
| [Spring] 토비의 스프링 Vol.1 1장 - 오브젝트와 의존관계 (1) (0) | 2026.06.09 |
| [Spring] 토비의 스프링 강의 섹션 3 - 스프링 도입 (0) | 2026.05.04 |
| [Spring] 토비의 스프링 강의 섹션 3 - 스프링 이전 (0) | 2026.05.04 |