1. Object and relation
오브젝트와 의존관계
1.1. 초기 구현
UserDao는 JDBC API를 직접 사용하여 사용자 정보를 등록(add)하고 조회(get)합니다.
문제점:
DB 연결 생성 코드가 add, get 메서드에 중복됨
DB 종류, 접속 방식, 드라이버 변경 시 여러 메서드를 동시에 수정해야 함
데이터 접근 로직과 DB 연결 로직이 하나의 클래스에 섞여 있음
변경에 취약한 구조
1.2. 관심사 분리
관심사를 분리할 수 있는 구조이고,
사용자 데이터를 SQL로 다루는 책임
DB 커넥션을 생성하는 책임
DB 커넥션 생성부분을 별도의 메서드로 추출.
1.3. 상속을 통한 확장
getConnection()을 추상 메서드로 만들고, UserDao를 상속한 NUserDao, DUserDao에서 DB 커넥션 생성 방식을 따로 구현.
이로 인해:
UserDao는 수정 불필요
DB 연결 방식만 서브클래스로 확장이 가능
But,
동일한 DB 커넥션 로직을 재사용하기 어려움
상속이 아닌 다른 확장 방식이 필요.
클래스 분리
별도의 클래스 SimpleConnectionMaker로 DB 커넥션 생성 책임을 분리하여 UserDao가 해당 클래스를 필드로 사용.
But,
UserDao가 특정한 구현 클래스에 의존하게 됨
DB 연결 방식을 바꾸려면 UserDao를 수정해야함.
결국, 독립성은 확보되지 않음
인터페이스 도입
ConnectionMaker 인터페이스를 도입하여 독립성을 확보
UserDao는 ConnectionMaker 인터페이스에만 의존
NConnectionMaker, DConnectionMaker에서 구현
UserDao 입장에서는 DB 연결이 어떻게 되는지 모르게 됨.
1.4. 제어의 역전(IoC)
객체 생성과 사용에 대한 제어권을 애플리케이션 코드에서 분리해 외부로 이동시키는 설계 개념. 객체 팩토리를 도입하면 생성 책임과 사용 책임이 분리되어, 결합도가 낮아집니다.
1.4.1. 오브젝트 팩토리
문제점: 테스트 코드가 테스트 목적 외에 객체 생성 책임까지 갖고 있는 문제
기존구조에서는 UserDaoTest가 UserDao 생성과 동시에 어떤 ConnectionMaker를 사용할지 직접 결정하고 있었습니다.
UserDao는 데이터 접근 로직이고, ConnectionMaker 선택 / 객체 생성은 관심사가 아님.
해결책: 객체 생성을 전담하는(오브젝트팩토리)를 도입하여, 객체 생성은 오브젝트 팩토리가 결정하고, 만들어진 객체를 제공만 하게됩니다.
오브젝트 팩토리의 역할:
객체 생성 책임
구현 클래스 선택 로직 제거
변경 지점을 한 곳으로 모음

UserDaoTest는 ConnectionMaker 구현 클래스에 대해 알 필요가 없어지고, 어떻게 만들어졌는지 관심이 없어집니다.
1.4.2. 오브젝트 팩토리의 활용
DaoFactory가 여러 DAO를 생성하게 되면, 중복 코드가 발생.
각 DAO 생성 메서드마다 new ConnectionMaker()가 반복.

중복 문제는 분리를 하는것이 가장 좋은 방법이기 떄문에, ConnectionMaker의 구현 클래스를 결정하고 오브젝트를 만드는 코드를 별도의 메서드로 분리.
변경된 구조에서는 DB 연결 방식 변경시 connectionMaker 메서드만 수정하면 됩니다. DAO 갯수가 늘어나도 변경 범위는 최소화됩니다.
UserDao, ConnectionMaker: 애플리케이션의 핵심 로직을 담당하는 컴포넌트
DaoFactory: 위 컴포넌트들을 어떻게 조합할지 결정하는 설계 요소. 객체들의 관계와 의존 방향을 쉽게 파악할 수 있는 위치가 됩니다.
1.4.3. 제어권 이전을 통한 제어관계 역전
기존 구조에서는 UserDao가 어떤 ConnectionMaker 구현을 사용할지 스스로 결정헀지만, 팩토리를 도입한 후에는 결정권이 DaoFactory로 이동됩니다.
객체 생성 시점
객체 간 의존 관계
어떤 구현체를 사용할지에 대한 결정
IoC와 프레임워크
IoC는 계 원칙이고, 이를 자동화하고 확장한 것이 스프링과 같은 IoC 컨테이너입니다. 라이브러리: 애플리케이션 코드라 흐름을 제어 프레임워크: 흐름을 주도하고 애플리케이션 코드는 그 안에서 호출.
정리하면, 스프링은
객체 생성
의존 관계 설정
생명 주기 관리
를 모두 담당하는 IoC 컨테이너를 제공합니다.
1.5. 스프링의 IoC
@Configuration, @Bean은 자바 코드 기반 설정정보 역할을 수행.
스프링 IoC는 객체 생성, 관계 설정, 다양한 IoC 서비스를 제공
1.5.1. 오브젝트 팩토리를 이용한 스프링 IoC
Bean 이란, IoC 방식으로 관리되는 객체를 의미. Bean Factory는 Bean을 생성하고 관계를 설정하는 핵심 IoC 객체. Application Context는 Bean Factory를 확장한 IoC 엔진으로 이해하면 됩니다.
애플리케이션 컨텍스트의 역할:
Bean 생성
Bean 간 의존 관계 설정
Bean 사용 시점 제어
설정정보 기반으로 전체 애플리케이션 구조 관리
스프링 설정 정보로 변환한 DaoFactory
@Configuration은 해당 클래스가 설정정보라는 것을 나타냅니다.@Bean은 메서드의 반환 객체를 스프링 빈으로 등록합니다.
Application Context 사용
getBean은 애플리케이션 컨텍스트에 빈을 요청하는 메서드입니다.
UserDao는 @Bean 메서드 이름이 빈 이름이 되었고, 타입을 함께 지정하면 캐스팅 부담을 제거할 수 있습니다.
1.5.2. 애플리케이션 컨텍스트의 동작방식
애플리케이션 컨텍스트는 설정정보를 분석해 빈 목록을 구성합니다. 이후, 클라이언트가 getBean을 호출하면 아래 흐름으로 동작합니다.
설정 정보에서 Bean 메타정보 로딩
Bean 이름과 타입으로 Bean 검색
Bean 생성 메서드 호출
생성된 객체를 클라이언트에 반환
DaoFactory를 직접 사용하는 방식과 비교하면 제어권이 완전히 Application Context로 이동했고, 클라이언트는 어떤 팩토리를 사용하는지 알 필요가 없어집니다. 오로지 Application Context만 의존합니다.
Application Context 사용시 장점
클라이언트는 구체적인 팩토리 클래스를 알 필요가 없게 되며, 팩토리 구현이 바뀌어도 클라이언트 코드는 변경되지 않습니다.
IoC 서비스의 확장
객체 생성뿐만 아니라,
자동 의존성 주입
Bean 후처리
라이프사이클 관리
같은 기능들을 함께 제공합니다.
다양한 Bean 검색 방식
이름 기반
타입 기반
특정 어노테이션 기반 검색이 가능
1.5.3. 스프링 IoC 용어 정리
Bean: 스프링 IoC 컨테이너가 생성하고 관리하는 객체.
Bean Factory: 빈을 생성하고 관리하는 IoC 컨테이너의 핵심 인터페이스. getBean 메서드 제공
Application Context: BeanFactory를 확장한 IoC 컨테이너. Bean 관리 외에 애플리케이션 전반을 지원하는 기능을 포함
Configuration Metadata: IoC 컨테이너가 Bean을 생성하고 관계를 설정하는 데 사용하는 정보. 자바 코드, XML, 어노테이션 형태로 제공.
컨테이너 / IoC 컨테이너: Bean을 관리하는 주체. 스프링에서는 Application Context를 의미합니다.
Spring Framework: IoC 컨테이너와 애플리케이션 컨텍스트를 중심으로 다양한 기능을 제공하는 종합 프레임워크.
1.6. 싱글톤 레지스트리와 오브젝트 스코프
스프링 Application Context는 단순히 오브젝트 팩토리가 아니라, 싱글톤을 관리하는 싱글톤 레지스트리의 역할을 수행합니다. 스프링 빈은 기본적으로 싱글톤 스코프로 생성됩니다.
DaoFactory를 직접 사용하든, 스프링 애플리케이션 컨텍스트를 사용하든 테스트 결과만 보면 동일해보이지만, 차이가 있습니다. DaoFactory에서 userDao()를 여러 번 호출해 얻은 객체와, Application Context에서 getBean("userDao")로 얻은 객체가 동일한 객체인가?
동일성: Identity. 두 변수가 완전히 같은 객체 인스턴스를 가치킴
동등성: Equality. 서로 다른 객체이지만 내부 값이 같음.
(DaoFactory에서 userDao()를 두번 호출하면 new 키워드로 인해 매번 새로운 객체가 생성됩니다.)
1.6.1. 싱글톤 레지스트리로서의 애플리케이션 컨텍스트
ApplicationContext를 사용해 getBean("userDao")를 두 번 호출하면 항상 동일한 UserDao 인스턴스가 반환됩니다. 이는 스프링 애플리케이션 컨텍스트가 빈을 싱글톤으로 관리하는 싱글톤 레지스트리이기 때문입니다(기본 설정).
서버 환경에서 다수의 클라이언트의 요청을 동시에 처리해야할때, 객체를 매번 새로 생성하게 되면:
객체 생성 비용 증가
GC 부하 증가
메모리 사용 급증
과 같은 문제점 발생
전통적인 싱글톤 패턴의 한계
문제점:
private 생성자로 인해 상속 불가
테스트 시 대체 객체 주입이 어려움
전역 상태에 의존하게 되어 설계가 경직됨
멀티 JVM 환경에서는 싱글톤 보장이 불완전함.
이로 인해 싱글톤 패턴은 안티 패턴으로 불리기도 합니다.
싱글톤 레지스트리의 장점
스프링은 싱글톤 패턴을 객체 내부가 아닌 IoC 컨테이너 차원에서 관리합니다. 이를 통해:
public 생성자 사용 가능
상속과 다형성 유지
테스트 시 자유로운 객체 대체
객체 생성과 생명주기 제어를 컨테이너에 위임
이 간으해집니다. 객체는 싱글톤인지 여부를 전혀 알 필요가 없어집니다.
1.6.2. 싱글톤고다 오브젝트의 상태
싱글톤 빈은 여러 스레드가 동시에 접근하기 때문에 상태 관리에 주의해야합니다. 싱글톤 빈이 인스턴스 변수를 통해 상태를 저장하면 동시성 문제가 발생. 그렇기에 항상 stateless 방식으로 설계해야합니다.
변경 가능한 값은 메서드 로컬 변수로 사용
요청별 데이터는 파라미터로 전달
인스턴스 변수는 공유되어도 안전한 읽기 전용 데이터만 허용
ConnectionMaker와 같이, 변경되지 않는 의존 객체는 인스턴스 변수로 보관해도 문제가 없습니다.
1.6.3. 스프링 빈의 스코프
Singleton: 컨테이너 내에서 하나의 인스턴스만 생성. 기본 스코프
Prototype: 빈 요청 시마다 새로운 객체 생성
request: Http 요청 하나당 하나의 빈 생성
session: Http 세션당 하나의 빈 생성
1.7. 의존관계 주입(DI)
의존관계 주입은 IoC 개념을 구체적인 설계와 구현 방식으로 명확히 드러낸 핵심 메커니즘. 객체는 자신이 사용할 의존 객체를 직접 생성하지 않고, 외부에서 주입받아서 사용. 의존 관계를 인터페이스에 의존하도록 설계하면 결합도가 낮아지고 변경에 유연해집니다. 스프링 IoC컨테이너는 의존 관계 설정과 주입을 전당하는 DI 컨테이너 역할을 수행함.
1.7.1. 제어의 역전(IoC)과 의존관계 주입
IoC는 제어권이 객체 자신에게 있지 않고 외부로 넘어간 상태를 의미하지만, 이 개념만으로는 스프링이 제공하는 핵심 기능을 구체적으로 설명하기 어렵습니다. 그래서 스프링에서는 객체간 관계를 어떻게 맺고 전달하는지를 강조하기 위해 의존 관계 주입(DI) 용어를 사용.
DI는:
어떤 객체가
어떤 다른 객체에 의존하고
그 의존 객체를 누가, 언제, 어떻게 전달하는가
를 명확히 드러내는 개념점
1.7.2. 런타임 의존관계 설정
의존관계는 항상 방향성을 가지고, A가 B를 사용하면 A는 B에 의존한다고 표현합니다. 여기서 중요한 점은, 설계 시점의 의존 관계와 런타임 시점의 의존 관계가 다를 수 있습니다.
UserDao는 ConnectionMaker 인터페이스에만 의존하고, 구체적인 구현 클래스가 무엇인지는 코드에서 드러나지 않습니다.
런타임에 실제 어떤 구현 객체를 사용할지는 제3의 존재가 결정하고, 이 역할을 수행하는 것이 DaoFactory, 그리고 스프링 IoC 컨테이너입니다.
DI가 성립하기 위한 조건:
클래스 코드에는 인터페이스에만 의존해야 합니다.
구체적인 의존 객체 선택은 외부에서 이루어져야 합니다.
의존 객체는 생성자나 메서드를 통해 전달되어야 합니다.
UserDao의 의존관계 주입 예제 (생성자 주입)
UserDao는 ConnectionMaker 구현체를 알 필요가 없어지고, 오직 인터페이스 규약만 알고 사용합니다.
1.7.3. 의존관계 검색과 주입
DI와 유사하지만 다른 방식으로 의존관계 검색(Dependency Lookup)이라는 방식도 존재합니다. 의존관계 검색은 객체가 스스로 컨테이너에게 필요한 의존 객체를 요청하는 방식입니다.
전체 흐름:
AnnotationConfigApplicationContext 생성
DaoFactory 클래스 스캔
@Configuration확인@Bean메서드 실행반환된 객체를 Bean으로 등록
이후 getBean()으로 꺼내 쓸 수 있음음
AnnotationConfigApplicationContext
Spring Framework의 IoC 컨테이너 구현체
@Configuration, @Bean같은 어노테이션 기반 설정을 읽는 컨테이너객체 생성, 의존성 주입, 생명주기 관리 담당
DaoFactory::class.java
설정 클래스
이 클래스 안의 @Bean 메서드들을 읽어서 객체를 생성하고 컨테이너에 등록합니다.
위 방식은 객체가 스프링 API에 의존하게 된다는 단점이 있습니다. 따라서, 일반적인 애플리케이션 코드에서는 의존관계 주입 방식을 사용하는 것이 바람직합니다.
1.7.4. 의존관계 주입의 응용
DI를 적용하면 구현 교체가 쉬워집니다. 예를 들어, 개발 환경에서는 로컬 DB를, 운영환경에서는 운영 DB를 사용해야하는 경우 DAO 코드는 전혀 수정하지 않고 설정정보만 변경해 해결할 수 있습니다. (부가 기능을 가진 객체를 중간에 끼워 넣는것도 가능)
ConnectionMaker를 감싸는 CountingConnectionMaker를 주입하면 DB 커넥션 사용 횟수 측정 같은 기능을 손쉽게 추가할 수 있습니다.
1.7.5. 메서드를 이용한 의존관계 주입
의존관계 주입은 생성자만으로 이루어지지 않습니다. 수정자 메서드(setter)를 통한 주입도 가능합니다.
수정자 메서드 DI 예시
이 방식은:
선택적인 의존관계
변경 가능성이 있는 의존 관계
에 적합합니다.
팩토리 설정 코드에서는 아래와 같이 사용합니다.
생성자 주입과 수정자 주입은 주입 시점과 의도를 기준으로 선택.
1.8. XML을 이용한 설정
코드 기반 설정(@Configuration, @Bean)은 DI 구조를 명확히 표현할 수 있지만 설정 변경 시 컴파일이 필요.
XML 설정은 DI 정보를 순수 설정 파일로 분리해 코드 변경 없이 의존관계를 교체할 수 있게 해줍니다.
스프링 애플리케이션 컨텍스트는 자바 설정과 XML 설정을 동일한 IoC 메타데이터로 취급.
따라서, XML 설정은 빈 정의, 의존관계 주입, 값 주입까지 모두 표현이 가능합니다
1.8.1. XML 설정
XML 설정의 루트 엘리먼트는 <Beans> 이며 각 <Bean> 태그 하나가 하나의 빈 정의에 해당. 자바 설정과 XML 설정의 핵심 정보는 동일합니다.
빈 정의에 포함되는 정보:
빈 이름
빈 클래스
의존 객체 정보
자바 설정에서의 대응 관계는 아래와 같습니다.
자바 설정 <--> XML 설정 개념 대응
@Configuration <-->
<beans>@Bean 메서드 이름 <-->
<bean id="">return new Xxx() <-->
<bean class="">
ConnectionMaker() 빈의 XML 전환
XML 설정으로 변환
XML에서는
메서드 개념이 없고
클래스 이름을 직접 지정
따라서 패키지를 포함한 클래스 전체 이름을 정확히 작성해야 합니다.
UserDao() 빈의 XML 전환
UserDao는 ConnectionMaker에 의존함. XML에서는 <property> 태그를 사용해 의존관계를 표현.
name: 주입 대상 프로퍼티
이름. setConnectionMaker()에 대응ref: 주입할 다른 빈의 이름
XML 기반 DI 설정 전체 예시
1.8.2. XML을 이용하는 애플리케이션 컨텍스트
XML 설정을 사용하는 애플리케이션 컨텍스트는 GenericXmlApplicationContext 또는 ClassPathXmlApplicationContext를 사용합니다.
XML 파일은 보통 applicationContext.xml 이름으로 만들고, 클래스패스 루트 또는 지정한 패키지에 위치합니다.
1.8.3. DataSource 인터페이스로 전환
ConnectionMaker는 DB 커넥션을 하나 만들어주는 단순한 인터페이스. 실제 스프링 환경에서는 표준 JDBC 추상화 인터페이스인 DataSource를 사용합니다.
UserDao는 DataSource에 의존하도록 변경
XML에서 DataSource 설정
ref는 다른 빈을 주입할 때 사용
value는 문자열, 숫자 같은 단순 값을 주입할 때 사용
스프링은 문자열 값을 자동으로 적절한 타입으로 변환합니다.
XML 설정의 장점
자바 코드 수정 없이 설정 변경 가능.
환경별 설정 분리 용이
컴파일 없이 DI 구조 변경 가능
운영 환경에서 설정 관리 유리
But,
클래스 이름을 문자열로 관리해야 하고,
리팩토링 시 IDE 지원이 제한적입니다.
정리 XML 설정은 DI 정보를 코드에서 완전히 분리하는 방식. 스프링은 자바 설정과 XML 설정을 동일한 IoC 메타데이터로 처리. XML을 사용하면 환경 변화와 설정 변경에 더 유연하게 대응 가능. 실무에서는 자바 설정과 XML 설정을 상황에 맞게 혼용해 사용하면 좋습니다.
Last updated