DI에서 Depend on, Lazy, Autowiring 사용

2025. 6. 18. 16:49Spring 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