Spring Bean이란?
Spring 컨테이너가 생성·관리·소멸까지 책임지는 애플리케이션 핵심 객체다. 컨테이너는 설정에 따라 Bean을 만들고 의존성을 주입하며, 스코프와 생명주기를 관리한다. 핵심은 “객체의 생성과 연결의 제어권이 컨테이너에 있다”는 점이다.
예시: 가장 단순한 Bean과 주입
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;
@Component
class HelloService {
String say(String name) { return "Hello, " + name; }
}
@RestController
class HelloApi {
private final HelloService helloService;
HelloApi(HelloService helloService) { this.helloService = helloService; } // 컨테이너가 주입
@GetMapping("/hello")
String hello(String name) { return helloService.say(name); }
}
Bean 등록 방법
컴포넌트 스캔으로 자동 등록하거나, 구성 클래스에서 @Bean 메서드로 수동 등록할 수 있다. 자동 등록은 관용적 구조에 적합하고, 수동 등록은 외부 라이브러리·팩토리 초기화 등 세밀한 제어가 필요할 때 유용하다.
예시: 자동 등록(@Component 계열)
import org.springframework.stereotype.*;
@Service // @Component, @Service, @Repository, @Controller 등 계열 사용
class UserService {
/* ... */
}
예시: 수동 등록(@Configuration + @Bean)
import org.springframework.context.annotation.*;
import javax.sql.DataSource;
import com.zaxxer.hikari.HikariDataSource;
@Configuration
class AppConfig {
@Bean
DataSource dataSource() {
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl("jdbc:h2:mem:testdb");
ds.setUsername("sa");
return ds; // 컨테이너가 이 반환값을 Bean으로 등록
}
}
의존성 주입 방식
권장 방식은 생성자 주입이다. 필드 주입은 테스트와 불변성 측면에서 불리하다. 동일 타입 Bean이 여러 개면 @Qualifier나 @Primary로 주입 대상을 선택한다.
예시: 생성자 주입 + @Qualifier
import org.springframework.stereotype.*;
import org.springframework.beans.factory.annotation.*;
interface Notifier { void send(String msg); }
@Component("smsNotifier")
class SmsNotifier implements Notifier { public void send(String msg){ /*...*/ } }
@Component("emailNotifier")
class EmailNotifier implements Notifier { public void send(String msg){ /*...*/ } }
@Service
class AlarmService {
private final Notifier notifier;
public AlarmService(@Qualifier("emailNotifier") Notifier notifier) {
this.notifier = notifier; // 동일 타입 여러개 → Qualifier로 선택
}
}
Bean 스코프
기본은 singleton(컨테이너당 1개)이다. 필요 시 prototype(주입마다 새 객체), 웹 환경에서는 request, session 등의 스코프를 사용할 수 있다.
예시: prototype 스코프
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@Scope("prototype")
class TempHolder { /* 매 주입마다 새 인스턴스 */ }
생명주기(초기화/소멸)
Bean은 생성 → 의존성 주입 → 초기화 → 사용 → 소멸 순서로 관리된다. 초기화 로직은 @PostConstruct, 소멸 전에 정리는 @PreDestroy로 훅을 제공한다(싱글턴 기본, 컨테이너 종료 시 호출).
예시: @PostConstruct / @PreDestroy
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.stereotype.Component;
@Component
class CacheManager {
@PostConstruct
void init() { System.out.println("warm up cache"); }
@PreDestroy
void close() { System.out.println("flush cache"); }
}
프록시와 AOP/트랜잭션
트랜잭션이나 AOP를 적용할 때 컨테이너는 대상 Bean 대신 프록시 객체를 주입한다. 프록시는 메서드 전후에 부가 기능(트랜잭션 시작/커밋, 로깅 등)을 끼워 넣는다. 자체 내부 호출은 프록시를 거치지 않을 수 있으니 주의한다.
예시: 트랜잭션 프록시(@Transactional)
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
class OrderService {
@Transactional
public void placeOrder() {
// 여기 진입 시 프록시가 트랜잭션 시작/종료를 관리
}
}
실수하기 쉬운 포인트
- 필드 주입 남용은 테스트와 리팩터링에 불리하다 → 생성자 주입 권장.
- 동일 타입 Bean 다수일 때 주입 실패 →
@Qualifier또는@Primary로 명시. - 프록시 내부 호출은 부가 기능이 적용되지 않을 수 있다(동일 클래스 내 메서드 호출 주의).
@ConfigurationProperties로 설정 값을 타입 안정성 있게 바인딩하면 테스트가 쉬워진다.
'백엔드' 카테고리의 다른 글
| [Spring Boot] Spring IoC - 컨테이너, Bean 등록/스코프/생명주기 (0) | 2025.10.11 |
|---|---|
| [Spring Boot] Spring IoC - 개념 (0) | 2025.10.11 |
| [Spring Boot] Framework (0) | 2025.10.11 |
| MySQL - 백엔드 개발자가 알아야 할 데이터베이스 기초 (0) | 2025.02.14 |
| AWS 클라우드 서비스 (0) | 2025.02.10 |