📚 의존성 주입 (Dependency Injection)이란?
DI
는 객체 지향 프로그래밍에서 객체가 필요로 하는 의존성을 외부에서 주입해주는 설계 패턴이다.
→ 즉, 객체를 직접 생성하지 않고, 외부에서 주입받아서 사용함
스프링에서 의존성 주입이 필요한 이유는 결합도를 낮추고, 테스트 용이성을 높이기 위해서이다.
Autowired
@Autowired
: 스프링이 해당 타입의 Bean을 찾아서 자동 주입
스프링에서 의존성 주입을 수행하기 위해서 사용하는 어노테이션이다. 필요한 객체를 선언하면 스프링이 자동으로 찾아서 의존관계를 주입한다.
@Autowired
는 기본적으로 타입(Type) 기준으로 스프링 컨테이너에 있는 Bean
중 일치하는 객체를 찾아서 주입한다.
@Autowired
는 사용하는 위치에 따라서 유연성, 테스트 용이성, 유지보수성이 달라질 수 있다.
필드, 생성자, Set메서드에서 사용할 수 있지만, 일반적으로 생성자에 사용하는 것을 권장한다.
1. 필드 주입
@Component
public class Zoo {
@Autowired
private Parrot parrot;
}
간단해서 에전에는 많이 사용하던 방식이지만, final
을 사용할 수 없어 객체 불변성을 보장할 수 없다는 단점이 있다.
또한, 테스트에서 직접 parrot
을 넣어줄 수 없고, 의존성이 숨어있어 의존 관계를 쉽게 파악하기가 어려워 테스트에 어려움이 존재한다.
2. 생성자 주입
@Component
public class Zoo {
private final Parrot parrot;
@Autowired
public Zoo(Parrot parrot) {
this.parrot = parrot;
}
}
가장 권장하는 방식으로 객체가 생성되는 동시에 의존성을 주입 받기 때문에 final
을 사용할 수 있고, 객체 불변성을 보장할 수 있다.
또한, 의존 관계 명확하기 때문에 테스트 역시 쉽게 진행할 수 있다는 장점이 있다. 주입이 안 되면 컴파일러 에러/런타임 에러로 바로 확인할 수 있음.
- 생성자가 하나뿐이라면
@Autowired
생략할 수 있다.
Set메서드 주입
@Component
public class Zoo {
private Parrot parrot;
@Autowired
public void setParrot(Parrot parrot) {
this.parrot = parrot;
}
}
선택적 의존성 또는 동적으로 바꿀 필요 있을 때 사용한다. 그래서 가끔 사용하는 주입 방식이다.
객체 생성 후에도 의존성을 변경할 수 있는 유연성을 가졌지만, 변경될 수 있어 불변성을 확보하기는 어렵다.
요약
항목 | 필드 주입 | 생성자 주입 | Setter 주입 |
사용 위치 | private 필드 위 | 생성자 파라미터 위 | Setter 메서드 위 |
코드 간결성 | ★★★★★ | ★★★ | ★★★★ |
테스트 용이성 | ★ (리플렉션 써야함) | ★★★★★ (생성자로 직접 주입 가능) | ★★★★ (Setter로 쉽게 변경 가능) |
불변성 보장 | ❌ final 사용 불가 | ✅ final 가능 → 불변 객체 | ❌ 변경 가능성 있음 |
선택적 주입 | ❌ 불가능 (null 방지 어려움) | ❌ 불가능 (생성자 인자는 필수) | ✅ nullable 대응 가능 |
의존성 명시성 | ❌ 숨어 있음 | ✅ 명확히 드러남 | ⭕ 반쯤 드러남 |
권장 여부 | ❌ 지양 | ✅ Spring 공식 권장 | ⭕ 조건부 사용 가능 |
PostConstruct
@PostConstruct
: 의존성 주입이 완료된 뒤 자동으로 호출되는 초기화 메서드에 붙이는 어노테이션
스프링이 @Autowired
등으로 필요한 의존성을 주입한 후 초기화 작업이 필요할 때 자동으로 호출하여 작동한다.
예) 초기 설정, 리소스 준비, 초기화
@Component
public class Parrot {
private String name;
@PostConstruct
public void init() {
this.name = "Kiki";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
사용 예시
상황 | 설명 |
초기 데이터 로딩 | DB에서 기본 설정값 불러오기, 캐시 세팅 등 |
외부 API 연동 | 시작 시점에 API 키 인증이나 연결 시도 |
로깅 및 검증 | 빈 초기화 로그 찍기, 환경 검증 등 |
주의할 점
항목 | 내용 |
리턴값 없음 | void만 가능 |
예외 처리 | 예외 발생 시 애플리케이션 시작 자체가 실패할 수 있음 |
비동기 초기화 불가 | 블로킹 작업이나 오래 걸리는 작업은 피하는 게 좋음 |
스프링 외부에서는 동작 X | 스프링 컨테이너가 관리할 때만 호출됨 |
Autowired와 Bean, Component
@Autowired
는 스프링이 스프링 컨테이너 안에 있는 Bean 중에서" 해당 타입과 맞는 걸 찾아서 자동으로 주입해주는 어노테이션이다.
그래서 @Bean
으로 등록한 객체거나, @Component
로 자동 등록을 해서 생성된 객체든 등록되어 있기만 하면 사용이 가능하다!
다만, 서로 의존 관계를 주입하는 클래스끼리 모두 스프링에 등록이 되어있어야 한다!
빈으로 등록을 했지만 주입 받는 클래스가 등록이 안되어있을 경우에는 @Autowired
로 주입이 되지 않는다는 것이다!
1. 둘 다 Bean으로 수동 등록 후 주입
a. Parrot
과 Zoo
클래스를 생성
public class Parrot {
private String name;
public void setName(String name) {
this.name = name;
}
public void speak() {
System.out.println("짹짹! " + name);
}
}
public class Zoo {
private Parrot parrot;
public Zoo(Parrot parrot) {
this.parrot = parrot;
}
public void open() {
parrot.speak();
}
}
b. config에서 @Bean 으로 등록 후 의존성 주입
@Configuration
public class AppConfig {
@Bean
public Parrot parrot() {
Parrot p = new Parrot();
p.setName("Koko");
return p;
}
@Bean
public Zoo zoo(Parrot parrot) {
return new Zoo(parrot); // 의존성 주입됨!
}
}
2. 둘 다 Component로 자동 등록 후 주입
a. @Component
로 자동 등록
@Component
public class Parrot {
private String name;
public void setName(String name) {
this.name = name;
}
public void speak() {
System.out.println("짹짹! " + name);
}
}
b. @Component
+ @Autowired
로 주입
@Component
public class Zoo {
private final Parrot parrot;
@Autowired
public Zoo(Parrot parrot) {
this.parrot = parrot;
}
}
3. 하나는 수동, 하나는 자동 등록 후 주입
a. 빈으로 등록 후, 주입 받는 객체를 컴포넌트로 등록해서 자동 주입
@Configuration
public class AppConfig {
@Bean
public Parrot parrot() {
Parrot p = new Parrot();
p.setName("Koko");
return p;
}
}
@Component
public class Zoo {
@Autowired
private Parrot parrot; // 자동 주입
public void open() {
parrot.speak();
}
}
b. 주입할 객체를 컴포넌트로 등록하고, 주입 받는 객체를 빈으로 수동 주입
@Component
public class Parrot {
private String name;
public void setName(String name) {
this.name = name;
}
public void speak() {
System.out.println("짹짹! " + name);
}
}
@Configuration
public class AppConfig {
@Bean
public Zoo zoo(Parrot parrot) {
return new Zoo(parrot); // 여기서도 자동 주입 가능!
}
}
@Autowired
를 쓰냐 안쓰냐의 차이인데, 결국 의존성을 주입하기 위해서는 객체가 스프링에 등록이 되어있어야 한다는 점이다. 주입 받는 객체가 @Component
로 자동 등록되어 있을 경우에만 @Autowired
를 사용할 수 있다!
'Back-end > Spring' 카테고리의 다른 글
[Spring] 스프링 컨트롤러 (Spring Controller) (3) | 2025.06.19 |
---|---|
[Spring] 스프링 MVC (Spring MVC) (0) | 2025.06.18 |
[Spring] DTO와 VO (0) | 2025.06.17 |
[Spring] 스프링 컨텍스트(Context)와 스프링 빈(Bean) (2) | 2025.06.11 |
[Spring] 스프링 프레임워크 (Spring Framwork) (1) | 2025.06.10 |