IT기술/MSA (with. springboot)

Spring @Primary와 @Lazy 어노테이션 완벽 가이드: MSA 환경에서의 효율적인 빈 관리

후스파 2025. 7. 6. 15:26
반응형

마이크로서비스 아키텍처(MSA)에서 스프링 빈은 의존성 주입을 통해 애플리케이션의 구성 요소를 관리하는 중요한 역할을 합니다. 이번 포스트에서는 스프링 빈의 세부 정의 중 @Primary와 @Lazy 어노테이션에 대해 자세히 살펴보겠습니다.


@Primary 어노테이션

개요

@Primary 어노테이션은 같은 타입의 빈이 여러 개 존재할 때, 어떤 빈을 우선적으로 의존성 주입할 것인지를 지정하는 데 사용됩니다. 이는 스프링이 빈을 주입할 때 혼란을 방지하고, 특정 빈을 기본적으로 선택할 수 있도록 합니다.

사용 예

예를 들어, MyService라는 클래스의 두 개의 구현체가 있을 때, 하나의 구현체에 @Primary 어노테이션을 추가하여 우선적으로 주입하도록 설정할 수 있습니다.

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;

@Service
public class MyServiceImpl1 implements MyService {
    @Override
    public String processData(String data) {
        return "Processed by Implementation 1: " + data;
    }
}

@Service
@Primary
public class MyServiceImpl2 implements MyService {
    @Override
    public String processData(String data) {
        return "Processed by Implementation 2: " + data;
    }
}

이 경우, MyService 타입의 빈이 주입될 때 MyServiceImpl2가 기본적으로 선택됩니다.

Configuration 클래스에서의 사용

@Configuration
public class AppConfig {

    @Bean
    public Employee johnEmployee() {
        return new Employee("John");
    }

    @Bean
    @Primary
    public Employee tonyEmployee() {
        return new Employee("Tony");
    }
}

중복 이름 오류 처리

같은 이름을 가진 빈이 존재할 경우 오류가 발생할 수 있습니다. 이를 방지하기 위해 application.properties 파일에서 다음과 같이 설정할 수 있습니다:

spring.main.allow-bean-definition-overriding=false

이 설정을 통해 빈 정의가 중복될 경우 오류를 체크할 수 있습니다.

@Primary vs @Qualifier 비교

// @Primary 사용 - 기본 빈 지정
@Service
@Primary
public class DatabaseEmailService implements EmailService {
    // 기본 이메일 서비스
}

// @Qualifier 사용 - 명시적 빈 선택
@Autowired
@Qualifier("smsNotificationService")
private NotificationService notificationService;

@Lazy 어노테이션

개요

@Lazy 어노테이션은 스프링 빈 컨테이너가 설정을 로딩할 때 빈 객체를 즉시 생성하는 것이 아니라, 의존성 주입이 실제로 발생하는 시점에 빈 객체를 생성하도록 지시합니다. 이를 통해 애플리케이션의 성능을 최적화하고, 불필요한 리소스 소모를 방지할 수 있습니다.

사용 예

다음은 @Lazy 어노테이션을 사용하여 빈 객체의 생성을 지연시키는 예시입니다:

import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

@Component
@Lazy
public class ExpensiveService {

    public ExpensiveService() {
        System.out.println("ExpensiveService 생성 - 리소스 집약적 작업 수행");
        // 무거운 초기화 작업
    }

    public void performTask() {
        System.out.println("작업 수행 중...");
    }
}

@Component
public class MyComponent {

    private final ExpensiveService expensiveService;

    @Autowired
    public MyComponent(@Lazy ExpensiveService expensiveService) {
        this.expensiveService = expensiveService; // 실제 사용 시점에 생성
    }

    public void useService() {
        expensiveService.performTask(); // 이 시점에 ExpensiveService 생성
    }
}

Configuration 클래스에서의 사용

@Configuration
public class AppConfig {

    @Bean
    @Lazy
    public DatabaseConnection databaseConnection() {
        System.out.println("데이터베이스 연결 생성 중...");
        return new DatabaseConnection();
    }

    @Bean
    public DataService dataService(@Lazy DatabaseConnection connection) {
        return new DataService(connection);
    }
}

전역 Lazy 초기화 설정 (Spring Boot 2.2+)

# application.yml
spring:
  main:
    lazy-initialization: true
# application.properties
spring.main.lazy-initialization=true

prototype 스코프와의 차이

@Lazy와 prototype 스코프는 비슷한 점이 있지만, 중요한 차이점이 있습니다:

  • prototype 스코프: 의존성 주입이 발생할 때마다 새로운 객체를 생성
  • @Lazy: 설정 시점에는 생성하지 않고, 실제 필요할 때만 생성하므로 메모리 사용을 줄이는 데 유리
// Prototype 스코프 - 매번 새 인스턴스
@Component
@Scope("prototype")
public class PrototypeService {
    // 매번 새로운 인스턴스 생성
}

// Lazy 초기화 - 지연 생성, 이후 재사용
@Component
@Lazy
public class LazyService {
    // 첫 사용 시에만 생성, 이후 재사용
}

실무 활용 패턴

조건부 @Primary 사용

@Configuration
public class DatabaseConfig {

    @Bean
    @Primary
    @ConditionalOnProperty(name = "database.type", havingValue = "mysql")
    public DataSource mysqlDataSource() {
        return new MySQLDataSource();
    }

    @Bean
    @ConditionalOnProperty(name = "database.type", havingValue = "postgresql")
    public DataSource postgresDataSource() {
        return new PostgreSQLDataSource();
    }
}

@Lazy와 순환 의존성 해결

@Service
public class ServiceA {

    private final ServiceB serviceB;

    public ServiceA(@Lazy ServiceB serviceB) {
        this.serviceB = serviceB; // 순환 의존성 해결
    }
}

@Service
public class ServiceB {

    private final ServiceA serviceA;

    public ServiceB(ServiceA serviceA) {
        this.serviceA = serviceA;
    }
}

성능 최적화를 위한 @Lazy 활용

@Service
public class ReportService {

    @Autowired
    @Lazy
    private EmailService emailService; // 이메일 발송이 필요할 때만 초기화

    @Autowired
    @Lazy
    private PdfGeneratorService pdfService; // PDF 생성이 필요할 때만 초기화

    public void generateReport(ReportType type) {
        if (type == ReportType.EMAIL) {
            emailService.sendReport(); // 이 시점에 EmailService 초기화
        } else if (type == ReportType.PDF) {
            pdfService.generatePdf(); // 이 시점에 PdfGeneratorService 초기화
        }
    }
}

결론

@Primary와 @Lazy 어노테이션은 MSA에서 스프링 빈을 보다 유연하고 효율적으로 관리하는 데 중요한 역할을 합니다.
@Primary는 빈의 우선순위를 설정하여 의존성 주입의 혼란을 줄이고, @Lazy는 성능 최적화를 통해 불필요한 리소스 소모를 방지합니다.
핵심 활용 가이드라인:

  • @Primary: 기본 구현체가 명확한 경우 사용, 테스트 시 @Qualifier와 함께 활용
  • @Lazy: 리소스 집약적이거나 선택적으로 사용되는 빈에 적용
  • 순환 의존성 해결: @Lazy를 활용하여 의존성 순환 문제 해결
  • 성능 최적화: 애플리케이션 시작 시간 단축을 위한 전략적 @Lazy 적용

이러한 어노테이션을 적절하게 활용하여 스프링 애플리케이션의 구조를 더욱 견고하게 만들어 보시기 바랍니다.

반응형