빈 (스프링 프레임워크)
1. 개요
1. 개요
스프링 프레임워크는 자바 플랫폼을 위한 오픈 소스 애플리케이션 프레임워크이다. 이 프레임워크는 로드 존슨에 의해 개발되었으며, 2002년 10월에 최초로 등장했다. 주로 복잡한 엔터프라이즈급 자바 애플리케이션을 효율적으로 구축하기 위한 목적으로 사용된다.
스프링의 핵심 기능은 의존성 주입(DI)과 관점 지향 프로그래밍(AOP)이다. 의존성 주입은 객체 간의 결합도를 낮추고 테스트 가능한 코드를 작성하도록 돕는 디자인 패턴을 구현한 것이며, 관점 지향 프로그래밍은 로깅이나 트랜잭션 관리와 같이 여러 모듈에 걸쳐 나타나는 공통 관심사를 효과적으로 분리하여 처리할 수 있게 해준다.
이러한 기능들을 바탕으로 스프링은 개발자에게 POJO(Plain Old Java Object) 기반의 경량화된 개발 모델을 제공한다. 이는 특정 인터페이스나 클래스를 상속받지 않는 일반 자바 객체로도 스프링의 강력한 서비스를 활용할 수 있음을 의미한다.
결과적으로 스프링 프레임워크는 모듈화가 잘 되어 있고 유연성과 확장성이 뛰어나며, 대규모 비즈니스 애플리케이션 개발에 널리 채택되어 사실상의 표준 프레임워크로 자리 잡았다.
2. 역사
2. 역사
스프링 프레임워크의 역사는 2002년 10월, 개발자 로드 존슨이 출판한 책 "Expert One-on-One J2EE Design and Development"에서 시작된다. 이 책은 당시 복잡하고 무거웠던 J2EE(EJB) 중심의 엔터프라이즈 애플리케이션 개발에 대한 비판과 더 가볍고 실용적인 대안을 제시했으며, 약 3만 줄에 달하는 예제 코드가 그 핵심을 이루었다. 이 예제 코드가 바로 스프링 프레임워크의 시초가 되었다.
커뮤니티의 강력한 요청에 따라 로드 존슨은 2003년 2월 이 예제 코드를 오픈 소스 프로젝트로 공개했고, 이 프로젝트는 책의 주요 예제 중 하나였던 호텔 예약 시스템에서 유래한 "스프링"이라는 이름을 갖게 되었다. 초기 버전은 아파치 2.0 라이선스 하에 소스포지(SourceForge)에서 호스팅되었다. 스프링 1.0은 2004년 3월에 정식으로 릴리스되었으며, 제어의 역전 컨테이너, 관점 지향 프로그래밍 지원, JDBC 추상화 계층 등 핵심 기능을 제공했다.
이후 스프링은 빠르게 자바 엔터프라이즈 개발의 사실상 표준 프레임워크로 자리 잡았다. 2009년 12월에 출시된 스프링 3.0은 자바 5 이상을 기반으로 완전한 어노테이션 기반 설정과 SpEL을 도입하는 등 큰 변화를 가져왔다. 2013년 12월의 스프링 4.0은 자바 8과 자바 EE 7을 완벽하게 지원하기 시작했다. 스프링 5.0(2017년 9월)은 리액티브 프로그래밍 모델을 본격적으로 지원하고, 스프링 6.0(2022년 11월)은 자바 17 이상과 자카르타 EE 9+를 기반으로 하는 등 지속적으로 현대적인 자바 생태계의 흐름을 반영하며 발전해오고 있다.
3. 핵심 개념
3. 핵심 개념
3.1. 제어의 역전 (IoC)
3.1. 제어의 역전 (IoC)
제어의 역전(IoC, Inversion of Control)은 스프링 프레임워크의 근본적인 설계 원칙이다. 이는 전통적인 애플리케이션 개발 방식에서 객체가 자신이 필요로 하는 의존 객체를 직접 생성하거나 조회하는 흐름을 뒤집는다. 즉, 객체의 생성과 의존 관계 설정, 생명주기 관리에 대한 제어권이 애플리케이션 코드에서 프레임워크로 넘어간다.
이 원칙의 구체적인 구현 방식이 의존성 주입(DI)이다. 개발자는 객체 간의 의존 관계를 코드 내부에서 명시적으로 구성하지 않고, 외부 설정(XML이나 자바 기반 설정, 어노테이션)을 통해 정의한다. 그러면 스프링 컨테이너가 설정 정보를 읽고, 필요한 객체(빈)를 생성한 후, 의존 관계를 자동으로 연결해 준다.
이러한 방식은 코드의 결합도를 낮추고 유연성과 테스트 용이성을 크게 향상시킨다. 비즈니스 로직을 담당하는 클래스는 구체적인 구현 클래스에 의존하지 않고 인터페이스에만 의존하게 되어, 구현체를 쉽게 교체할 수 있다. 또한 단위 테스트 시 실제 의존 객체 대신 목 객체(Mock Object)를 주입하는 것이 간편해진다.
결국 제어의 역전은 객체 지향 설계 원칙 중 의존 관계 역전 원칙(DIP)을 프레임워크 수준에서 실현한 패턴이다. 이를 통해 개발자는 복잡한 객체 그래프의 관리에서 벗어나 핵심 비즈니스 로직 개발에 집중할 수 있게 된다.
3.2. 의존성 주입 (DI)
3.2. 의존성 주입 (DI)
의존성 주입은 스프링 프레임워크의 핵심 원리 중 하나로, 객체 간의 의존 관계를 외부에서 설정하고 주입하는 디자인 패턴이다. 이는 제어의 역전 원칙을 구체적으로 구현한 방식에 해당한다. 전통적인 프로그래밍에서는 객체가 필요로 하는 의존 객체를 직접 생성하거나, 정적 메소드를 통해 가져왔다. 반면 의존성 주입을 사용하면 객체는 자신이 사용할 의존 객체를 외부(주로 스프링 컨테이너)로부터 받아 사용한다. 이로 인해 객체는 구체적인 의존 객체 생성 로직에서 자유로워지고, 결합도가 낮아져 유연하고 테스트하기 쉬운 코드를 작성할 수 있다.
의존성 주입은 주로 세 가지 방법으로 구현된다. 첫 번째는 생성자 주입으로, 의존 객체를 필드에 저장하기 위해 객체 생성 시점에 필수적인 의존성을 주입하는 방식이다. 두 번째는 세터 주입으로, 객체 생성 후 세터 메소드를 통해 의존성을 설정하는 방식이다. 세 번째는 필드 주입으로, 리플렉션을 이용해 직접 필드에 의존성을 주입하는 방식이지만, 일반적으로 생성자 주입을 권장한다.
이 패턴의 주요 장점은 모듈성과 테스트 용이성의 향상이다. 객체가 구체적인 클래스가 아닌 인터페이스에 의존하도록 설계할 수 있어, 구현체를 쉽게 교체할 수 있다. 예를 들어, 데이터 접근 계층에서 실제 데이터베이스 DAO와 메모리 기반 테스트용 DAO를 런타임에 교체하여 사용할 수 있다. 또한 단위 테스트 시 실제 의존 객체 대신 목 객체나 스텁을 쉽게 주입할 수 있어 테스트가 간편해진다.
스프링 프레임워크에서는 XML 기반 설정, 자바 기반 설정(@Configuration), 어노테이션 기반 설정(@Autowired, @Resource 등)을 통해 의존성 주입을 구성한다. 스프링 컨테이너는 설정 정보를 읽어 빈들 간의 의존 관계를 구성하고, 애플리케이션 실행 시 필요한 객체를 생성하고 연결하는 역할을 수행한다. 이를 통해 개발자는 복잡한 객체 그래프의 생성과 관리를 프레임워크에 위임하고, 비즈니스 로직 개발에 집중할 수 있게 된다.
3.3. 관점 지향 프로그래밍 (AOP)
3.3. 관점 지향 프로그래밍 (AOP)
관점 지향 프로그래밍은 애플리케이션의 핵심 비즈니스 로직과 공통적으로 반복되는 부가 기능을 분리하여 모듈화하는 프로그래밍 패러다임이다. 로깅, 트랜잭션 관리, 보안 처리와 같은 코드는 여러 클래스와 메서드에 걸쳐 중복되어 나타나는 경우가 많다. 이러한 코드를 핵심 로직에서 분리하여 별도의 모듈로 관리함으로써 코드의 재사용성을 높이고 유지보수성을 개선하는 것이 주요 목적이다.
스프링 프레임워크는 프록시 기반의 AOP를 지원하여 이 개념을 구현한다. 공통 관심 사항을 정의한 모듈을 '어드바이스'라고 하며, 이 어드바이스가 적용될 지점을 지정하는 '포인트컷'과 결합하여 '어드바이저'를 형성한다. 스프링 컨테이너는 런타임에 대상 객체에 대한 프록시를 생성하고, 지정된 메서드 호출 시점에 어드바이스 로직을 위빙한다.
이를 통해 개발자는 트랜잭션 관리나 성능 모니터링과 같은 횡단 관심사에 대한 코드를 비즈니스 서비스 클래스에 직접 작성하지 않고도 적용할 수 있다. 예를 들어, @Transactional 어노테이션 하나로 메서드 단위의 트랜잭션 경계를 선언적으로 설정할 수 있는 것이 스프링 AOP의 대표적인 활용 사례이다. 이는 코드의 간결성과 선언적 프로그래밍의 장점을 제공한다.
3.4. 빈 (Bean)
3.4. 빈 (Bean)
빈은 스프링 프레임워크가 관리하는 객체이다. 스프링 애플리케이션의 기본 구성 요소로, 스프링 컨테이너에 의해 생성, 조립, 관리된다. 일반적인 자바 객체와 달리, 빈은 컨테이너에 의해 그 생명주기가 관리되며, 의존성 주입을 통해 다른 객체와의 관계가 설정된다.
빈을 정의하는 방법은 크게 세 가지가 있다. 전통적인 방식은 XML 파일을 사용하여 빈의 클래스와 의존 관계를 명시하는 것이다. 두 번째는 자바 기반 설정으로, @Configuration 어노테이션이 붙은 클래스 내부에서 @Bean 어노테이션을 사용해 빈을 정의한다. 세 번째이자 가장 일반적인 방식은 어노테이션 기반 설정으로, 클래스에 @Component나 그 하위 어노테이션(@Service, @Repository, @Controller 등)을 붙여 자동으로 빈으로 등록하는 것이다.
빈은 다양한 스코프를 가질 수 있다. 기본 스코프는 싱글톤으로, 컨테이너 내에 단 하나의 인스턴스만 생성되어 재사용된다. 반면 프로토타입 스코프는 빈을 요청할 때마다 새로운 인스턴스를 생성한다. 또한 웹 애플리케이션을 위해 요청(request)이나 세션(session)과 같은 웹 관련 스코프도 제공된다.
빈의 의존성은 주로 생성자 주입이나 세터 주입을 통해 주입된다. 최근에는 생성자 주입이 필수적인 의존성을 명확히 하고 불변 객체를 보장하는 데 유리하여 권장되는 방식이다. 빈이 생성되고 소멸되는 생명주기 동안에는 @PostConstruct와 @PreDestroy 같은 어노테이션을 사용해 초기화와 정리 작업을 수행할 수 있다.
3.5. 스프링 컨테이너
3.5. 스프링 컨테이너
스프링 컨테이너는 스프링 프레임워크의 핵심이며, 빈의 생명주기를 관리하는 컨테이너이다. 이 컨테이너는 빈 객체의 생성, 구성, 조립, 그리고 최종 소멸까지의 전 과정을 책임진다. 스프링 컨테이너는 제어의 역전 원칙을 구현하는 주체로, 애플리케이션 코드가 직접 객체를 생성하고 관리하는 대신, 컨테이너가 설정 정보를 바탕으로 객체를 생성하고 의존 관계를 연결해 준다.
스프링 컨테이너의 주요 구현체로는 BeanFactory와 ApplicationContext가 있다. BeanFactory는 빈을 관리하는 가장 기본적인 컨테이너 인터페이스를 제공한다. 반면, ApplicationContext는 BeanFactory의 모든 기능을 포함하면서도 국제화, 이벤트 처리, AOP 통합 등 엔터프라이즈 애플리케이션 개발에 필요한 다양한 부가 기능을 추가로 제공한다. 따라서 대부분의 스프링 애플리케이션은 ApplicationContext를 사용한다.
컨테이너는 XML 기반 설정, 자바 기반 설정, 또는 어노테이션 기반 설정과 같은 다양한 방법으로 제공된 설정 메타데이터를 읽어들인다. 이 메타데이터에는 어떤 빈을 생성할지, 그리고 그들 간의 의존성 주입 관계는 어떻게 되는지에 대한 정보가 담겨 있다. 컨테이너는 이 정보를 해석하여 실제 객체 인스턴스를 만들고, 필요한 의존 객체를 주입하여 완전히 구성된 애플리케이션을 준비시킨다.
결국, 스프링 컨테이너는 개발자가 비즈니스 로직에 집중할 수 있도록 객체의 생성과 의존 관계 설정이라는 복잡한 저수준 작업을 대신 처리해 주는 프레임워크의 핵심 엔진 역할을 한다. 이를 통해 느슨한 결합도와 높은 테스트 용이성을 갖는 애플리케이션을 구성할 수 있다.
4. 빈의 정의와 등록
4. 빈의 정의와 등록
4.1. XML 기반 설정
4.1. XML 기반 설정
XML 기반 설정은 스프링 프레임워크 초기부터 제공된 전통적인 빈 정의 방식이다. 애플리케이션의 구성 요소인 빈과 그들 간의 의존 관계를 XML 파일에 명시적으로 선언하는 방법이다. 주로 applicationContext.xml 또는 beans.xml과 같은 이름의 파일을 사용하며, <beans>를 루트 엘리먼트로 하여 그 안에 개별 <bean> 정의를 작성한다. 각 빈은 고유한 id나 name 속성으로 식별되며, class 속성에 해당 빈의 완전한 클래스 경로를 지정한다. 이 방식은 설정 정보가 자바 소스 코드와 분리되어 있어, 애플리케이션의 구성 변경 시 코드 재컴파일 없이 XML 파일만 수정하면 된다는 장점이 있다.
의존성 주입은 XML 설정에서 핵심적으로 다루어진다. 생성자 주입은 <constructor-arg> 엘리먼트를 사용하여 구현한다. 이 엘리먼트의 ref 속성으로 다른 빈을 참조하거나 value 속성으로 기본형이나 문자열 값을 전달할 수 있다. 세터 주입은 <property> 엘리먼트를 통해 이루어지며, 빈의 세터 메서드에 대응하는 이름의 프로퍼티를 설정한다. 또한, 컬렉션 타입(리스트, 맵, 셋 등)이나 내장 타입의 값을 주입하기 위한 전용 엘리먼트도 제공되어 복잡한 객체 그래프 구성이 가능하다.
XML 설정은 명시적이고 중앙 집중화된 구성 관리가 가능하지만, 파일이 커지면 가독성이 떨어지고 오타 등의 실수가 발생하기 쉬운 단점도 있다. 또한, 리팩토링 시 클래스명이나 프로퍼티명이 변경되면 XML 파일을 수동으로 업데이트해야 하는 부담이 있다. 이러한 단점을 보완하기 위해 이후 어노테이션 기반 설정과 자바 기반 설정이 등장하게 되었다. 그러나 레거시 시스템 유지보수나 외부 라이브러리의 빈을 명시적으로 등록해야 하는 경우 등에서는 여전히 유용하게 사용된다.
4.2. 자바 기반 설정 (@Configuration)
4.2. 자바 기반 설정 (@Configuration)
자바 기반 설정은 XML 파일 대신 자바 코드를 사용하여 스프링 컨테이너에 빈을 등록하고 구성하는 방식을 말한다. 이 방식의 중심에는 @Configuration 어노테이션이 있으며, 이 어노테이션이 붙은 클래스는 하나 이상의 @Bean 메서드를 정의하여 빈 설정 메타데이터를 제공한다. 이 접근법은 자바의 강력한 타입 안정성과 리팩토링 지원을 활용할 수 있어, 복잡한 의존성 주입 설정을 보다 안전하고 명확하게 관리할 수 있게 해준다.
@Configuration 클래스 내부에서는 @Bean 어노테이션이 붙은 메서드를 통해 빈을 정의한다. 이 메서드는 빈 객체를 생성하고 필요한 의존성을 설정한 후 반환하는 역할을 한다. 스프링 컨테이너는 이 메서드들을 호출하여 반환된 객체들을 빈으로 관리한다. 이때 메서드 간의 의존성은 단순히 다른 @Bean 메서드를 호출함으로써 해결되며, 스프링은 이를 싱글톤 빈으로 적절히 관리한다.
자바 기반 설정은 어노테이션 기반 설정(@Component 등)과 함께 사용될 수 있으며, 두 방식을 혼합하는 것이 일반적이다. 예를 들어, 컴포넌트 스캔으로 대부분의 빈을 자동 등록하면서, 특정 라이브러리에서 제공하는 클래스나 복잡한 설정이 필요한 빈은 @Configuration 클래스 내에서 명시적으로 정의할 수 있다. 이는 설정의 유연성과 명시성을 동시에 확보하는 데 유리하다.
이 방식은 특히 스프링 부트의 등장과 함께 더욱 널리 채택되었다. 스프링 부트의 자동 구성 기능도 내부적으로 수많은 @Configuration 클래스를 기반으로 구현되어 있으며, 개발자는 필요에 따라 이러한 구성을 쉽게 재정의할 수 있다. 결과적으로, 자바 기반 설정은 현대적인 스프링 애플리케이션 개발에서 표준적인 설정 방법으로 자리 잡았다.
4.3. 어노테이션 기반 설정 (@Component)
4.3. 어노테이션 기반 설정 (@Component)
어노테이션 기반 설정은 XML이나 자바 설정 클래스 없이도 스프링 프레임워크의 빈을 정의하고 등록할 수 있는 방식을 말한다. 이 방식의 중심에는 @Component 어노테이션이 있으며, 이는 클래스가 스프링 컨테이너에 의해 관리되는 컴포넌트임을 표시한다. 개발자는 빈으로 등록하고자 하는 클래스에 @Component를 붙이기만 하면 되며, 스프링 컨테이너는 클래스패스 스캔을 통해 이 어노테이션이 붙은 클래스를 자동으로 탐지하여 빈으로 등록한다.
@Component는 범용적인 스테레오타입 어노테이션으로, 보다 구체적인 계층을 표현하는 @Repository, @Service, @Controller 어노테이션들의 메타 어노테이션으로 사용된다. 예를 들어, 데이터 접근 계층의 클래스에는 @Repository를, 비즈니스 로직 계층에는 @Service를, 웹 애플리케이션의 컨트롤러 계층에는 @Controller를 사용하는 것이 일반적이다. 이들은 모두 @Component를 내포하고 있어 동일하게 컴포넌트 스캔의 대상이 되며, 각 계층에 맞는 추가적인 의미를 부여한다.
컴포넌트 스캔을 활성화하려면 자바 설정 클래스에 @ComponentScan 어노테이션을 적용하거나 XML 설정 파일에 <context:component-scan> 요소를 선언해야 한다. 이를 통해 스프링은 지정된 기본 패키지 및 그 하위 패키지를 탐색하며 @Component가 달린 클래스를 찾아낸다. 발견된 각 클래스는 고유한 빈 이름으로 등록되며, 기본적으로 클래스 이름의 첫 글자를 소문자로 한 값을 사용한다.
이 방식의 가장 큰 장점은 설정의 간결함과 개발 생산성 향상이다. 수많은 빈을 XML 파일에 일일이 선언하거나 @Bean 메서드를 작성할 필요 없이, 해당 클래스에 어노테이션 하나를 추가하는 것만으로 빈 등록이 완료된다. 이로 인해 현대적인 스프링 부트 애플리케이션에서는 어노테이션 기반 설정이 사실상 표준으로 자리 잡았다.
5. 빈의 생명주기
5. 빈의 생명주기
스프링 프레임워크에서 빈의 생명주기는 스프링 컨테이너에 의해 관리된다. 컨테이너는 빈 객체를 생성하고, 의존 관계를 주입하며, 초기화를 수행한 후 애플리케이션에서 사용할 수 있도록 준비한다. 애플리케이션이 종료되면 컨테이너는 빈을 소멸시키고 자원을 정리하는 과정을 거친다. 이렇게 빈의 생성부터 소멸까지의 전 과정을 생명주기라고 한다.
빈의 생명주기 콜백을 관리하는 주요 방법은 세 가지이다. 첫째, 초기화 콜백과 소멸 콜백 인터페이스를 구현하는 방법이다. InitializingBean 인터페이스의 afterPropertiesSet() 메서드는 모든 프로퍼티 설정이 완료된 후 호출되며, DisposableBean 인터페이스의 destroy() 메서드는 컨테이너 종료 시 호출된다. 둘째, 빈 설정 메타데이터에서 init-method와 destroy-method 속성을 지정하는 방법이다. 이는 설정 파일이나 어노테이션을 통해 외부에서 메서드 이름을 정의할 수 있어 인터페이스에 대한 결합을 피할 수 있다. 셋째, 가장 일반적으로 사용되는 @PostConstruct와 @PreDestroy 어노테이션을 사용하는 방법이다. 이 JSR-250 표준 어노테이션들은 각각 빈 초기화 후와 컨테이너 종료 직전에 실행될 메서드에 부여한다.
콜백 유형 | 인터페이스/어노테이션 | 실행 시점 | 비고 |
|---|---|---|---|
초기화 |
| 의존성 주입 완료 후 | 스프링 전용 인터페이스 |
초기화 |
| 의존성 주입 완료 후 | JSR-250 표준 어노테이션 |
초기화 |
| 의존성 주입 완료 후 | XML 또는 자바 설정에서 지정 |
소멸 |
| 컨테이너 종료 시 | 스프링 전용 인터페이스 |
소멸 |
| 컨테이너 종료 시 | JSR-250 표준 어노테이션 |
소멸 |
| 컨테이너 종료 시 | XML 또는 자바 설정에서 지정 |
이러한 생명주기 관리 메커니즘을 통해 개발자는 데이터베이스 연결 풀의 초기화, 캐시 데이터 로딩, 네트워크 소켓 오픈, 또는 사용 후 자원의 안전한 해제와 같은 중요한 초기화 및 정리 작업을 빈에 위임할 수 있다. 이는 애플리케이션의 객체 지향 프로그래밍 설계 원칙을 강화하고, 자원 누수를 방지하는 데 기여한다.
6. 빈 스코프
6. 빈 스코프
6.1. 싱글톤 (Singleton)
6.1. 싱글톤 (Singleton)
스프링 프레임워크에서 싱글톤 스코프는 빈의 기본 스코프이다. 이 스코프로 정의된 빈은 스프링 컨테이너당 하나의 인스턴스만 생성된다. 컨테이너는 이 단일 인스턴스를 캐시에 저장하며, 해당 빈에 대한 모든 요청과 참조는 동일한 객체를 반환하게 된다.
싱글톤 스코프는 상태를 유지하지 않는(Stateless) 객체에 가장 적합하다. 대표적인 예로 서비스 계층의 비즈니스 로직 객체, 데이터 접근 객체(DAO), 인프라스트럭처 객체(커넥션 풀, 캐시 매니저) 등이 있다. 이러한 객체는 여러 클라이언트가 공유해도 문제가 발생하지 않으며, 인스턴스를 하나만 생성함으로써 메모리 사용량을 줄이고 성능을 향상시킬 수 있다.
싱글톤 빈의 생명주기는 컨테이너의 생명주기와 밀접하게 연결된다. 빈 인스턴스는 컨테이너가 시작될 때 생성되고, 컨테이너가 종료될 때 소멸된다. 이는 프로토타입 스코프와 대비되는 특징이다. 싱글톤 빈이 프로토타입 빈을 참조할 경우 주의가 필요하다. 의존성 주입 시점에만 프로토타입 빈이 생성되므로, 싱글톤 빈 내에서 매번 새로운 프로토타입 인스턴스를 얻기 위해서는 추가적인 방법이 필요하다.
싱글톤 패턴의 구현 방식은 디자인 패턴에서의 전통적인 싱글톤과 다르다. 스프링의 싱글톤은 각 애플리케이션 컨텍스트 내에서 보장되며, 자바 가상 머신 수준의 싱글톤이 아니다. 따라서 서로 다른 스프링 컨테이너는 각각 자신만의 싱글톤 빈 인스턴스를 가질 수 있다.
6.2. 프로토타입 (Prototype)
6.2. 프로토타입 (Prototype)
스프링 프레임워크에서 프로토타입 스코프는 싱글톤 스코프와 대비되는 개념이다. 프로토타입 스코프로 정의된 빈은 스프링 컨테이너로부터 요청될 때마다 새로운 인스턴스가 생성되어 반환된다. 즉, 컨테이너는 빈의 생성과 의존성 주입, 초기화 콜백까지 수행한 후, 완성된 객체를 클라이언트에 넘기고 그 이후의 생명주기 관리를 하지 않는다. 이는 하나의 인스턴스를 공유하는 싱글톤과 근본적으로 다른 동작 방식이다.
프로토타입 빈은 상태를 가지거나 상태가 변경될 수 있는 객체에 적합하다. 예를 들어, 각기 다른 데이터를 캡슐화해야 하는 사용자 세션 객체나 특정 요청마다 독립적인 계산이 필요한 서비스 객체를 구현할 때 사용될 수 있다. 반면, 무상태성을 유지하는 서비스나 리포지토리와 같은 객체는 일반적으로 싱글톤 스코프로 정의하여 메모리 사용을 최적화한다.
프로타입 빈의 생명주기는 싱글톤과 다르게 관리된다. 스프링 컨테이너는 빈 인스턴스를 생성하고 의존성 주입을 완료한 후, 필요한 경우 초기화 콜백(예: @PostConstruct 메서드)을 호출한다. 그러나 컨테이너는 반환된 인스턴스에 대한 참조를 유지하지 않으므로 소멸 전 콜백(예: @PreDestroy 메서드)은 호출되지 않는다. 인스턴스의 소멸과 자원 정리는 클라이언트 코드의 책임이 된다.
프로토타입 스코프를 사용하려면 @Scope("prototype") 어노테이션을 빈 정의에 명시하면 된다. 자바 기반 설정에서는 @Bean 메서드와 함께, 어노테이션 기반 설정에서는 @Component가 붙은 클래스와 함께 사용할 수 있다. 이 스코프는 특히 멀티스레드 환경에서 상태 충돌을 방지해야 할 때 유용하지만, 빈 생성 비용이 증가할 수 있으므로 신중하게 적용해야 한다.
6.3. 웹 관련 스코프
6.3. 웹 관련 스코프
웹 관련 스코프는 스프링 프레임워크가 웹 애플리케이션 환경을 위해 제공하는 특별한 빈의 생존 범위이다. 이 스코프들은 HTTP 요청이나 HTTP 세션과 같은 웹 특정 생명주기에 빈의 존재를 바인딩하여, 웹 애플리케이션 개발 시 상태를 관리하는 데 유용하다. 주로 스프링 MVC나 스프링 웹플럭스를 사용하는 웹 프로젝트에서 활용된다.
가장 일반적인 웹 관련 스코프로는 request와 session이 있다. request 스코프는 하나의 HTTP 요청이 시작될 때 빈이 생성되고, 요청이 완료되면 소멸된다. 이는 요청 단위로 상태를 유지해야 하는 객체에 적합하다. session 스코프는 사용자의 HTTP 세션이 시작될 때 생성되어 해당 세션이 무효화될 때까지 유지된다. 사용자별로 데이터를 유지하는 로그인 정보나 장바구니와 같은 객체에 사용된다. 이 외에도 글로벌 세션 스코프나 애플리케이션 스코프 등이 있지만, 현대 웹 개발에서는 덜 사용된다.
이러한 스코프를 사용하려면 빈을 정의할 때 @Scope 어노테이션으로 명시적으로 지정해야 한다. 예를 들어, @Scope("request") 또는 @Scope("session")과 같이 사용한다. 웹 관련 스코프는 기본적으로 비활성화되어 있으므로, XML 설정에서는 <aop:scoped-proxy/>를, 자바 설정에서는 ScopedProxyMode를 사용하여 프록시를 설정해야 할 수 있다. 이는 짧은 생명주기를 가진 빈을 더 긴 생명주기의 빈(예: 싱글톤)에 주입할 때 발생할 수 있는 문제를 해결하기 위함이다.
웹 관련 스코프를 사용할 때는 주의가 필요하다. 특히 session 스코프는 사용자마다 빈 인스턴스가 생성되어 메모리 사용량이 증가할 수 있으며, 세션 클러스터링 환경에서는 추가적인 고려 사항이 발생한다. 또한, 비동기 처리나 스레드 환경에서 스코프 빈에 접근할 때는 동시성 문제가 발생하지 않도록 설계해야 한다.
7. 의존성 주입 방법
7. 의존성 주입 방법
7.1. 생성자 주입
7.1. 생성자 주입
생성자 주입은 스프링 프레임워크에서 의존성 주입을 수행하는 주요 방법 중 하나이다. 이 방식은 클래스의 생성자를 통해 필요한 의존성을 주입받는다. 생성자 주입은 객체가 생성되는 시점에 모든 의존성이 확보되어야 하므로, 객체의 불변성을 보장하고 필수적인 의존 관계를 명확하게 표현할 수 있다는 장점이 있다. 이는 NullPointerException과 같은 런타임 오류를 방지하고, 테스트 가능한 코드를 작성하는 데 유리하다.
구현 방식은 간단하다. 주입받을 대상 클래스에 필요한 의존 객체들을 매개변수로 가지는 생성자를 정의하고, 스프링 컨테이너가 해당 빈을 생성할 때 이 생성자를 호출하여 의존성을 전달한다. 자바 기반 설정에서는 @Configuration 클래스 내에서 생성자를 직접 호출하거나, 어노테이션 기반 설정에서는 생성자 위에 @Autowired 어노테이션을 생략하거나 명시적으로 붙여서 사용한다. 스프링 4.3 버전 이후로는 단일 생성자인 경우 @Autowired를 생략할 수 있다.
생성자 주입은 세터 주입이나 필드 주입과 비교했을 때 몇 가지 뚜렷한 차이점을 가진다. 첫째, 의존성이 final 필드로 선언될 수 있어 객체 생성 후 의존 관계의 변경을 방지한다. 둘째, 순환 의존성 문제가 발생하는 경우, 생성자 주입은 애플리케이션 구동 시점에 즉시 오류를 발생시켜 문제를 조기에 발견할 수 있게 한다. 이러한 특성들로 인해 최근 스프링 공식 문서와 많은 커뮤니티에서 생성자 주입을 권장하는 방식으로 제시하고 있다.
7.2. 세터 주입
7.2. 세터 주입
세터 주입은 의존성 주입을 구현하는 방법 중 하나로, 빈 클래스에 의존성을 설정하는 전용 세터 메서드를 정의하고, 스프링 컨테이너가 이 메서드를 호출하여 의존 객체를 주입하는 방식이다. 생성자 주입이 객체 생성 시점에 모든 의존성을 확정하는 반면, 세터 주입은 객체 생성 이후에도 의존성을 변경하거나 설정할 수 있는 유연성을 제공한다.
이 방식은 전통적으로 XML 기반 설정에서 널리 사용되었다. 설정 파일에서 <property> 요소를 사용하여 주입할 빈의 이름과 해당 값을 명시하면, 컨테이너는 리플렉션을 통해 해당 이름과 매핑되는 세터 메서드를 찾아 호출한다. 자바 기반 설정이나 어노테이션을 사용할 경우에는 세터 메서드 위에 @Autowired 어노테이션을 붙여 사용할 수 있다.
세터 주입의 주요 단점은 객체가 완전히 초기화된 상태, 즉 모든 필수 의존성이 주입된 상태를 보장하기 어렵다는 점이다. 객체가 일부 의존성 없이도 생성될 수 있으므로, 런타임에 NullPointerException이 발생할 위험이 있다. 따라서 필수적인 의존성에는 생성자 주입을, 선택적이거나 변경 가능성이 있는 의존성에는 세터 주입을 사용하는 것이 권장되는 패턴이다.
7.3. 필드 주입
7.3. 필드 주입
필드 주입은 의존성 주입을 수행하는 방법 중 하나로, 스프링 컨테이너가 관리하는 빈의 필드에 직접 의존 객체를 주입하는 방식이다. @Autowired 또는 @Inject 어노테이션을 필드 선언부에 명시함으로써 사용한다. 이 방식은 코드가 매우 간결해지고 설정이 편리하다는 장점이 있어 빠른 프로토타이핑이나 간단한 테스트 코드에서 종종 사용된다.
그러나 필드 주입은 몇 가지 심각한 단점을 가지고 있다. 가장 큰 문제는 의존 관계가 외부에서 명시적으로 드러나지 않는다는 점이다. 생성자나 세터 메서드를 통한 주입과 달리, 필드 주입을 사용한 클래스는 순수 자바 객체로서는 해당 의존성을 필요로 하는지 알 수 없으며, 스프링 프레임워크 없이는 인스턴스화와 테스트가 어렵다. 또한 final 필드에 사용할 수 없어 불변 객체를 만들 수 없다는 제약도 있다.
이러한 이유로 현대적인 스프링 애플리케이션 개발에서는 생성자 주입을 권장하는 경향이 강하다. 생성자 주입은 의존성을 명확하게 요구하며, 불변성을 보장하고 단위 테스트를 용이하게 한다. 필드 주입은 레거시 코드에서 발견되거나, 프레임워크 자체의 내부 구성 요소처럼 특수한 경우에 제한적으로 사용된다.
8. 빈과 관련된 주요 어노테이션
8. 빈과 관련된 주요 어노테이션
빈과 관련된 주요 어노테이션은 스프링 프레임워크가 컴포넌트를 식별하고, 의존성을 관리하며, 생명주기를 제어하는 데 사용하는 메타데이터 표시자이다. 이 어노테이션들은 주로 자바 기반 설정과 어노테이션 기반 설정 방식에서 활용되며, XML 설정 파일의 복잡성을 줄이고 직관적인 코드 작성을 가능하게 한다.
빈을 정의하고 등록하는 핵심 어노테이션으로는 @Component가 있다. 이는 해당 클래스를 스프링이 관리하는 빈으로 등록하도록 지시하는 일반적인 표시자 역할을 한다. 보다 구체적인 계층을 나타내기 위해 @Repository, @Service, @Controller 어노테이션이 사용되며, 이들은 모두 @Component의 특수한 형태이다. @Configuration 어노테이션은 해당 클래스가 하나 이상의 @Bean 메서드를 포함하는 설정 클래스임을 선언한다. @Bean 어노테이션은 메서드 수준에서 사용되어 해당 메서드가 반환하는 객체를 스프링 빈으로 등록하도록 한다.
의존성을 자동으로 연결하는 데 사용되는 주요 어노테이션은 @Autowired이다. 이는 생성자, 세터 메서드, 필드에 적용되어 스프링 컨테이너가 해당 타입에 맞는 빈을 찾아 주입하도록 한다. 특정 빈을 명시적으로 지정해야 할 경우 @Qualifier 어노테이션을 함께 사용한다. @Primary 어노테이션은 동일한 타입의 빈이 여러 개 존재할 때 기본으로 주입될 후보를 지정한다. @Value 어노테이션은 외부 프로퍼티 파일이나 환경 변수의 값을 빈의 필드에 주입하는 데 사용된다.
빈의 생명주기와 관련하여 @PostConstruct와 @PreDestroy 어노테이션이 있다. 이들은 각각 빈의 초기화와 소멸 시점에 실행될 메서드를 표시한다. @Scope 어노테이션은 빈의 인스턴스 생성 범위를 지정하는 데 사용되며, 기본값은 싱글톤이다. @Lazy 어노테이션은 빈의 지연 초기화를 지시하여 애플리케이션 시작 시점이 아닌, 해당 빈이 최초로 요청될 때 생성되도록 한다.
9. 여담
9. 여담
스프링 프레임워크의 핵심 구성 요소인 빈이라는 용어는 자바 객체를 지칭하는 일반적인 의미와는 구분된다. 스프링 컨테이너가 관리하는 객체를 빈이라고 부르며, 이는 제어의 역전 원칙을 구현한 결과물이다. 개발자가 직접 new 키워드로 객체를 생성하는 대신, 스프링 컨테이너가 객체의 생성과 의존 관계 설정, 생명주기 관리를 담당하게 된다.
빈이라는 이름의 유래는 자바빈즈 규약에서 비롯되었다. 자바빈즈는 재사용 가능한 소프트웨어 컴포넌트를 만들기 위한 표준으로, 기본 생성자와 게터, 세터 메서드를 갖춘 자바 클래스를 의미한다. 스프링 프레임워크는 초기 설계 당시 이 자바빈즈 모델을 애플리케이션의 핵심 구성 요소 모델로 채택했으며, 이에 따라 관리 대상 객체를 빈이라고 부르게 되었다.
시간이 지나면서 스프링의 빈은 반드시 자바빈즈 규약을 따를 필요는 없게 되었다. 생성자 주입이 권장되는 방식으로 자리 잡으면서, 세터 메서드 없이도 빈으로 등록되고 의존성이 주입되는 것이 일반적이 되었다. 그러나 빈이라는 용어와 기본적인 개념은 프레임워크의 근간으로 남아, 스프링 기반 애플리케이션 개발의 기본 단위가 되었다.
