2025. 6. 18. 16:49ㆍSpring Framework/Spring IoC
어떤 Bean이 다른 Bean의 의존성이라면, 일반적으로 하나의 Bean이 다른 Bean을 프로퍼티(속성)로 갖는 경우를 말한다.
🧩 @DependsOn 사용
@DependsOn은 Spring IoC 컨테이너가 Bean을 초기화할 때, 특정 Bean 보다 먼저 초기화되어야 할 Bean을 지정하는 어노테이션이다.
@DependsOn을 사용시 Bean 간의 초기화 순서를 제어할 수 있고,
데이터베이스 연결, 외부 리소스 로드 등 특정 Bean을 먼저 실행해야 할 경우 유용하다.
📃 예제
config 패키지
@Configuration
public class AppConfig {
@Bean
@DependsOn({"loggerInitializer", "userService"}) // 두 빈이 먼저 초기화된 후 실행
public MyService myService() {
return new MyService();
}
@Bean
@DependsOn("loggerInitializer") // loggerInitializer 빈이 먼저 초기화 된 후 실행
public UserService userService() {
return new UserService();
}
@Bean
public LoggerInitializer loggerInitializer() {
return new LoggerInitializer();
}
}
service 패키지
public class LoggerInitializer {
public LoggerInitializer() {
System.out.println("Logger initialized 생성!!");
}
}
public class MyService {
public MyService() {
System.out.println("MyService 생성!!");
}
}
public class UserService {
public UserService() {
System.out.println("UserService 생성!!");
}
}
Main 클래스
public class Main {
public static void main(String[] args) {
ApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
}
}
❌ @DependsOn을 적용 안하고 실행 시키면
MyService 생성!!
UserService 생성!!
Logger initialized 생성!!
순서대로 생성
⭕ @DependsOn을 적용 시키면
Logger initialized 생성!!
UserService 생성!!
MyService 생성!!
지정한 순서대로 Bean이 생성된걸 볼 수 있다!!
🧩 @Lazy 사용 : Bean의 지연 초기화 (Lazy Initialization)
Lazy Initialization는 Bean이 필요할 때 까지 초기화를 지연시키는 방식이다. Bean이 실제로 필요해질 때까지 생성하지 않도록 Spring에 지시한다.
어플리케이션 실행 속도를 높이고, 불필요한 리소스 사용을 방지한다.
Constructor 기반 DI에서 발생하는 순환 참조를 해결할때도 사용한다.
📃 예제1 개별 Bean에 적용
@Configuration
public class AppConfig {
@Bean
@Lazy // myService은 처음 사용될 때까지 초기화되지 않음
public MyService myService() {
return new MyService();
}
@Bean
public UserService userService() {
return new UserService(); // 즉시 초기화됨
}
@Bean
public LoggerInitializer loggerInitializer() {
return new LoggerInitializer(); // 즉시 초기화됨
}
}
📃 예제2 모든 Bean에 적용
@Configuration
@Lazy // 이 클래스 내의 모든 빈을 Lazy Initialized로 설정
public class AppConfig {
@Bean
public MyService myService() {
return new MyService();
}
@Bean
public UserService userService() {
return new UserService();
}
@Bean
public LoggerInitializer loggerInitializer() {
return new LoggerInitializer();
}
}
🧩 Autowiring : 자동 의존성 주입
Spring IoC 컨테이너는 협업하는 Bean 간의 관계를 자동으로 연결(Autowire)할 수 있다.
Autowiring은 생성자 아규먼트나 프로퍼티를 명시해야 하는 필요성을 크게 줄여준다. 그리고 객체가 변경되거나 확장됨에 따라 구성을 자동으로 업데이트할수 있게 도와준다
@Autowired는 기본적으로 byType 동작이다!!
⌨️ Java 기반 Autowiring (@Autowired)
현대 스프링에서는 XML 보다 Annotation 기반 Autowiring(@Autowired)가 더 일반적이다.
1. 필드 주입(권장 ❌)
@Component
public class A {
@Autowired
private B b;
}
2. 생성자 주입(권장)
@Component
public class A {
private B b;
@Autowired
public A(B b) {
this.b = b;
}
}
3. 세터 주입
@Component
public class A {
private B b;
@Autowired
public void setB(B b) {
this.b = b;
}
}
💥 Autowiring 한계와 단점
Autowiring은 프로젝트 전반에 걸겨 일관되게 사용될 때 가장 효과적이다.
프로젝트 전반에서는 Autowiring을 사용하지 않으면서, 일부 빈 정의에서만 Autowiring을 사용하면 개발자에게 혼란을 줄 수 있다.
Autowiring의 한계는 다음과 같다:
- 명시적 참조 우선
→ XML의 ref나 Java Config의 @Bean 파라미터 등 명시적 주입이 우선 - 기본형(primitive), String, Class 등의 단순 타입은 자동 주입할 수 없다.
→ Autowiring은 오직 참조 타입에만 적용된다. - Autowiring은 명시적 주입보다 정확도가 낮다.
→ Autowiring은 편리하지만, 의존성 관계가 명확하게 문서화되지 않으며, 의도하지 않은 결과가 발생할 가능성이 존재 - 다수의 Bean이 같은 타입을 가질 경우 모호성 발생
→ Autowiring 대상이 되는 타입에 대해 컨테이너 내의 여러 개의 Bean 정의가 존재할 경우, Spring은 이를 임의로 선택하지 않고 예외를 발생시킨다. - 자동 주입 정보는 외부 도구에서 인식되지 않을 수 있음
→ Spring 설정으로부터 문서를 자동 생성하는 도구가 있을 경우, Autowiring 방식으로 정의된 의존성은 문서에 명시되지 않을 수 있다
🛠️ 해결 방법
| Autowiring을 포기하고 명시적 설정 사용 | Java Config에서 직접 주입할 대상을 지정 |
| 우선 후보(Primary Bean) 지정 | bean 요소에 primary="true"를 설정하여 해당 빈을 기본 후보로 지정 |
| 애노테이션 기반의 세밀한 제어 사용 | @Autowired, @Qualifier, @Primary 등 애노테이션을 활용 |
| 특정 빈을 Autowiring 대상에서 제외 | autowireCandidate=false → 이 빈을 타입 기반 Autowiring 대상에서 제외 defaultCandidate=false → Qualifier 없이는 주입 대상이 되지 않도록 제한 |
📃 문제 예제 코드
config 패키지
@Configuration
public class AppConfig {
@Bean
public EmailNotificationService emailNotificationService() {
return new EmailNotificationService();
}
@Bean
public SmsNotificationService smsNotificationService() {
return new SmsNotificationService();
}
@Bean
public UserService userService(NotificationService notificationService ) {
return new UserService(notificationService);
}
}
service 패키지
public interface NotificationService {
void send(String message);
}
public class EmailNotificationService implements NotificationService {
@Override
public void send(String message) {
System.out.println("Email : " + message);
}
}
public class SmsNotificationService implements NotificationService {
@Override
public void send(String message) {
System.out.println("SMS : " + message);
}
}
public class UserService {
private NotificationService notificationService;
public UserService(NotificationService notificationService) {
this.notificationService = notificationService;
}
public void notifyUser() {
notificationService.send("Hello from UserService");
}
}
Main 클래스
public class Main {
public static void main(String[] args) {
ApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class);
userService.notifyUser();
}
}
‼️ 이 상태로 출력시 예외가 발생
예외 요약
No qualifying bean of type 'NotificationService' available:
expected single matching bean but found 2:
emailNotificationService, smsNotificationService
현재 NotificationService 타입의 Bean이 2개 존재 :
▶ emailNotificationService
▶ smsNotificationService
그런데 UserService 에서는 어떤 구현체를 주입해야 할지 명확하지 않음, 그래서 다음과 같은 예외를 던진다
✔️ 해결 방법 1 : @Primary 사용
@Configuration
public class AppConfig {
@Bean
@Primary // @Primary로 기본 주입 대상 지정!!
public EmailNotificationService emailNotificationService() {
return new EmailNotificationService();
}
@Bean
public SmsNotificationService smsNotificationService() {
return new SmsNotificationService();
}
@Bean
public UserService userService(NotificationService notificationService ) {
return new UserService(notificationService);
}
}
@Primary로 지정후 출력시
Email : Hello from UserService
✔️ 해결 방법 2 : @Qualifier 지정
@Configuration
public class AppConfig {
@Bean
public EmailNotificationService emailNotificationService() {
return new EmailNotificationService();
}
@Bean
public SmsNotificationService smsNotificationService() {
return new SmsNotificationService();
}
@Bean
// @Qualifier("smsNotificationService")로 주입 대상 직접 지정
// UserService 클래스 생성자에서도 지정가능!!(이럴 경우 여기서 따로 지정할 필요는 없다)
public UserService userService(@Qualifier("smsNotificationService") NotificationService notificationService ) {
return new UserService(notificationService);
}
}
출력시
SMS : Hello from UserService
✔️ 해결 방법 3 : autowireCandidate = false 지정
디폴트는 true
@Configuration
public class AppConfig {
@Bean // 디폴트로 autowireCandidate = true
public EmailNotificationService emailNotificationService() {
return new EmailNotificationService();
}
@Bean(autowireCandidate = false) // 타입 기반 autowiring 대상에서 제외됨
public SmsNotificationService smsNotificationService() {
return new SmsNotificationService();
}
@Bean
public UserService userService(NotificationService notificationService ) {
return new UserService(notificationService);
}
}
출력시
Email : Hello from UserService'Spring Framework > Spring IoC' 카테고리의 다른 글
| Spring의 Method Injection (0) | 2025.06.20 |
|---|---|
| Dependency Injection(DI, 의존성 주입) (0) | 2025.06.18 |
| Bean의 인스턴스화 방법 (0) | 2025.06.11 |
| Bean 이름 지정 (Naming Beans) (0) | 2025.06.11 |
| Bean Definition(정의) (0) | 2025.06.11 |