Composing Java-based Configurations

2024. 11. 14. 14:19Spring Framework/Spring IoC

Spring의 Java 기반 구성 기능을 사용하면 어노테이션을 작성할 수 있어 구성의 복잡성을 줄일 수 있습니다.

 

Using the @Import Annotation

<import/> 엘리먼트가 Spring XML 파일 내에서 구성의 모듈화를 돕기 위해 사용되는 것처럼, @Import 어노테이션을 사용하면 다음 예제와 같이 다른 구성 클래스에서 @Bean 정의를 로드할 수 있습니다.

@Configuration
public class ConfigA {

	@Bean
	public A a() {
		return new A();
	}
}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

	@Bean
	public B b() {
		return new B();
	}
}
이제 컨텍스트를 인스턴스화할 때 ConfigA.class와 ConfigB.class를 모두 지정할 필요 없이 다음 예제와 같이 ConfigB만 명시적으로 제공하면 됩니다.
public static void main(String[] args) {
	ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

	// now both beans A and B will be available...
	A a = ctx.getBean(A.class);
	B b = ctx.getBean(B.class);
}

 

이 접근 방식은 컨테이너 인스턴스화를 간소화합니다. 컨테이너를 생성하는 동안 잠재적으로 많은 수의 @Configuration 클래스를 기억할 필요가 없고, 하나의 클래스만 처리하면 되기 때문입니다.

Spring Framework 4.2부터 @Import 애노테이션은 일반 컴포넌트 클래스에 대한 참조도 지원하게 되었습니다. 이는 AnnotationConfigApplicationContext.register 메서드와 유사한 방식으로 작동하며, 컴포넌트 스캐닝을 피하고 몇 개의 구성 클래스를 사용하여 모든 컴포넌트를 명시적으로 정의하고자 할 때 특히 유용합니다.

기본 개념 설명
@Import는 구성 클래스에 다른 구성 클래스나 컴포넌트 클래스를 포함시킬 때 사용됩니다. 이를 통해 여러 구성 클래스를 하나의 중앙 구성 클래스로 묶을 수 있으며, 이로 인해 구성의 모듈화가 가능해집니다.

Spring 4.2 이전에는 @Import 애노테이션이 다른 @Configuration 클래스만 참조할 수 있었지만, Spring 4.2부터는 일반적인 컴포넌트 클래스 (@Component, @Service, @Repository, @Controller 등)도 참조할 수 있게 되었습니다. 이를 통해 명시적으로 관리하고 싶은 컴포넌트만 등록하여 구성할 수 있습니다.

코드 예제
아래 예제는 @Import를 사용하여 컴포넌트 클래스를 명시적으로 등록하는 방법을 보여줍니다.
1. 일반 컴포넌트 클래스 정의
import org.springframework.stereotype.Component;

@Component
public class MyService {
    public void performService() {
        System.out.println("Service is being performed.");
    }
}

@Component
public class MyRepository {
    public void performRepositoryAction() {
        System.out.println("Repository action performed.");
    }
}​
2. 설정 클래스 정의 및 @Import 사용
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import({MyService.class, MyRepository.class})
public class AppConfig {
    // 다른 설정이 있을 경우 여기에 추가
}​
3. 메인 메서드에서 Spring 컨텍스트 사용
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        // ApplicationContext 초기화 및 설정 클래스 로드
        ApplicationContext context = 
                new AnnotationConfigApplicationContext(AppConfig.class);

        // MyService와 MyRepository 빈을 가져와서 사용
        MyService myService = context.getBean(MyService.class);
        myService.performService();

        MyRepository myRepository = context.getBean(MyRepository.class);
        myRepository.performRepositoryAction();
    }
}​

코드 설명
⦁ MyService와 MyRepository: 각각 @Component` 애노테이션이 붙은 일반 컴포넌트 클래스입니다. 이 클래스들은 서비스 로직과 리포지토리 동작을 담당합니다.
⦁ AppConfig 클래스: Spring 구성 클래스입니다. @Import 애노테이션을 사용하여 MyService와 MyRepository 컴포넌트 클래스를 명시적으로 등록합니다. 이 방식으로 컴포넌트 스캐닝 없이도 필요한 컴포넌트만 설정에 포함할 수 있습니다.
⦁ MainApp 클래스: AnnotationConfigApplicationContext를 사용하여 AppConfig 설정 클래스를 로드하고, 등록된 빈을 가져와서 사용합니다. MyService와 MyRepository 빈이 정상적으로 인스턴스화되고, 메서드를 호출하여 각각의 동작을 수행합니다.

이 접근 방식은 특히 대규모 프로젝트에서 명시적인 구성 관리가 필요할 때 유용합니다. @Import를 사용하면 구성 클래스 간의 의존성을 명확히 할 수 있고, 컴포넌트 스캐닝에 의존하지 않고도 필요한 컴포넌트를 명시적으로 구성할 수 있습니다. 이는 구성의 명시성과 예측 가능성을 높이며, 컴포넌트 스캐닝에서 발생할 수 있는 잠재적인 문제를 회피하는 데 도움을 줍니다.

 

Injecting Dependencies on Imported @Bean Definitions

앞의 예제는 작동하지만 단순합니다. 대부분의 실제 시나리오에서 빈은 구성 클래스 간에 서로 종속성이 있습니다. XML을 사용하는 경우 컴파일러가 관여하지 않기 때문에 문제가 되지 않으며 ref="someBean"을 선언하고 컨테이너 초기화 중에 Spring이 이를 해결하도록 신뢰할 수 있습니다. @Configuration 클래스를 사용하는 경우 Java 컴파일러는 구성 모델에 제약 조건을 두므로 다른 빈에 대한 참조는 유효한 Java 구문이어야 합니다.

다행히도 이 문제를 해결하는 것은 간단합니다. 이미 논의했듯이 @Bean 메서드는 빈 종속성을 설명하는 임의의 수의 파라미터를 가질 수 있습니다. 각각 다른 빈에 선언된 빈에 따라 달라지는 여러 @Configuration 클래스가 있는 다음과 같은 보다 현실적인 시나리오를 고려하세요.

@Configuration
public class ServiceConfig {

	@Bean
	public TransferService transferService(AccountRepository accountRepository) {
		return new TransferServiceImpl(accountRepository);
	}
}

@Configuration
public class RepositoryConfig {

	@Bean
	public AccountRepository accountRepository(DataSource dataSource) {
		return new JdbcAccountRepository(dataSource);
	}
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

	@Bean
	public DataSource dataSource() {
		// return new DataSource
	}
}

public static void main(String[] args) {
	ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
	// everything wires up across configuration classes...
	TransferService transferService = ctx.getBean(TransferService.class);
	transferService.transfer(100.00, "A123", "C456");
}

// SpringTransactionManager 참조

 

같은 결과를 얻는 또 다른 방법이 있습니다. @Configuration 클래스는 궁극적으로 컨테이너의 또 다른 빈일 뿐이라는 점을 기억하세요. 즉, 다른 빈과 마찬가지로 @Autowired 및 @Value 주입과 다른 기능을 활용할 수 있습니다.

 

※ 구성 클래스내에 @Autowired를 사용한 의존성 주입

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

import javax.sql.DataSource;

@Configuration
public class ServiceConfig {

    @Autowired
    private AccountRepository accountRepository; // 자동 주입

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl(accountRepository); // 필드에서 가져온 빈 사용
    }
}

@Configuration
public class RepositoryConfig {

    @Autowired
    private DataSource dataSource; // 자동 주입

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource); // 필드에서 가져온 빈 사용
    }
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // DataSource 구현체 반환
        // 예를 들어, HikariDataSource 등
    }
}

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
        // 모든 설정 클래스가 적절하게 연결됨
        TransferService transferService = ctx.getBean(TransferService.class);
        transferService.transfer(100.00, "A123", "C456");
    }
}
이런 방식으로 주입하는 종속성이 가장 단순한 종류인지 확인하세요. @Configuration 클래스는 컨텍스트 초기화 중에 매우 일찍 처리되며, 이런 방식으로 종속성을 강제로 주입하면 예상치 못한 조기 초기화가 발생할 수 있습니다. 가능하면 앞의 예와 같이 매개변수 기반 주입을 사용하세요.
동일한 구성 클래스의 @PostConstruct 메서드 내에서 로컬로 정의된 빈에 액세스하지 마세요. 비정적 @Bean 메서드는 의미적으로 완전히 초기화된 구성 클래스 인스턴스가 호출되어야 하기 때문에 순환 참조가 발생합니다. 순환 참조가 허용되지 않으면(예: Spring Boot 2.6+) BeanCurrentlyInCreationException이 발생할 수 있습니다.
또한 @Bean을 통한 BeanPostProcessor 및 BeanFactoryPostProcessor 정의에 특히 주의하세요. 이러한 정의는 일반적으로 정적 @Bean 메서드로 선언해야 하며, 포함된 구성 클래스의 인스턴스화를 트리거하지 않아야 합니다. 그렇지 않으면 @Autowired와 @Value가 구성 클래스 자체에서 작동하지 않을 수 있습니다. AutowiredAnnotationBeanPostProcessor보다 일찍 빈 인스턴스로 생성할 수 있기 때문입니다.

위 설명에 해당하는 샘플 코드 예시
1. 파라미터 기반 주입 사용: @Autowired를 사용한 필드 주입 대신, 파라미터 기반 주입을 권장합니다.
2. @PostConstruct 주의: @PostConstruct 메서드 내에서 동일한 구성 클래스의 non-static @Bean 메서드에 접근하지 않도록 합니다.
3. BeanPostProcessor와 BeanFactoryPostProcessor 정의: 이러한 클래스들은 일반적으로 static @Bean 메서드로 정의해야 하며, 그렇지 않으면 예상치 못한 초기화 문제가 발생할 수 있습니다.

샘플 코드
1. 기본 구성 및 주입 예제
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;

@Configuration
public class AppConfig {

    private final TransferService transferService;

    // 매개변수 기반 주입을 사용하여 TransferService를 초기화
    public AppConfig(TransferService transferService) {
        this.transferService = transferService;
    }

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }

    @Bean
    public AccountRepository accountRepository(DataSource dataSource) {
        return new JdbcAccountRepository(dataSource);
    }

    @Bean
    public DataSource dataSource() {
        // 데이터베이스 연결을 위한 DataSource 설정
        // 예를 들어, HikariDataSource 또는 다른 구현체를 반환
    }

    @PostConstruct
    public void init() {
        // 이 메서드 내에서 동일한 구성 클래스의 비정적 @Bean 메서드에 접근하지 마세요
        // 예: transferService(); (비정적 메서드 접근 금지)
        System.out.println("AppConfig initialized");
    }
}


2. BeanPostProcessor 및 BeanFactoryPostProcessor 정의 예제

import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component;

@Configuration
public class ProcessorConfig {

    // 정적 @Bean 메서드로 BeanPostProcessor 정의
    @Bean
    public static BeanPostProcessor customBeanPostProcessor() {
        return new CustomBeanPostProcessor();
    }

    // 정적 @Bean 메서드로 BeanFactoryPostProcessor 정의
    @Bean
    public static BeanFactoryPostProcessor customBeanFactoryPostProcessor() {
        return new CustomBeanFactoryPostProcessor();
    }
}

@Component
class CustomBeanPostProcessor implements BeanPostProcessor {
    // BeanPostProcessor 구현
}

@Component
class CustomBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    // BeanFactoryPostProcessor 구현
}

설명
1. 파라미터 기반 주입:
   AppConfig 클래스에서는 TransferService를 생성자 주입 방식으로 받아 초기화합니다. 이렇게 하면 Spring 컨텍스트 초기화 중에 발생할 수 있는 조기 초기화 문제를 피할 수 있습니다.
2. @PostConstruct 주의사항:
   @PostConstruct 메서드에서 non-static @Bean 메서드를 호출하지 않도록 주의해야 합니다. 순환 참조가 발생할 수 있으며, Spring Boot 2.6+에서는 BeanCurrentlyInCreationException이 발생할 수 있습니다.
3. static @Bean 메서드를 통한 BeanPostProcessor 및 BeanFactoryPostProcessor 정의:
   ProcessorConfig 클래스에서는 BeanPostProcessor와 BeanFactoryPostProcessor를 static 메서드로 정의하여 Spring이 해당 클래스의 인스턴스화를 조기에 트리거하지 않도록 합니다. 이렇게 하면 @Autowired와 @Value가 예상대로 동작합니다.

이 코드는 Spring에서 의존성 주입을 처리하는 올바른 방법을 보여줍니다. 특히, Spring 컨텍스트 초기화 중 발생할 수 있는 조기 초기화 문제를 피하기 위해 파라미터 기반 주입과 static @Bean 메서드를 사용하여 BeanPostProcessor 및 BeanFactoryPostProcessor를 정의하는 방법을 강조합니다. 이러한 패턴을 따르면 예상치 못한 초기화 문제를 방지할 수 있습니다.

 

다음 예제는 어떻게 한 빈이 다른 빈에 자동으로 연결될 수 있는지 보여줍니다.

@Configuration
public class ServiceConfig {

	@Autowired
	private AccountRepository accountRepository;

	@Bean
	public TransferService transferService() {
		return new TransferServiceImpl(accountRepository);
	}
}

@Configuration
public class RepositoryConfig {

    // @Configuration 클래스의 생성자 주입은 Spring Framework 4.3부터만 지원됩니다. 
    // 또한 대상 빈이 생성자를 하나만 정의하는 경우 @Autowired를 지정할 필요가 없다는 점에 유의하세요.
	private final DataSource dataSource;

	public RepositoryConfig(DataSource dataSource) {
		this.dataSource = dataSource;
	}

	@Bean
	public AccountRepository accountRepository() {
		return new JdbcAccountRepository(dataSource);
	}
}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

	@Bean
	public DataSource dataSource() {
		// return new DataSource
	}
}

public static void main(String[] args) {
	ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
	// everything wires up across configuration classes...
	TransferService transferService = ctx.getBean(TransferService.class);
	transferService.transfer(100.00, "A123", "C456");
}

 

@Configuration 클래스의 생성자 주입은 Spring Framework 4.3부터만 지원됩니다. 또한 대상 빈이 생성자를 하나만 정의하는 경우 @Autowired를 지정할 필요가 없다는 점에 유의하세요.
Fully-qualifying imported beans for ease of navigation

이전 시나리오에서 @Autowired를 사용하면 잘 작동하고 원하는 모듈성을 제공하지만 자동 와이어링된 빈 정의가 정확히 어디에 선언되는지 확인하는 것은 여전히 ​​다소 모호합니다. 예를 들어 ServiceConfig를 살펴보는 개발자로서 @Autowired AccountRepository 빈이 정확히 어디에 선언되는지 어떻게 알 수 있을까요? 코드에 명시적으로 나와 있지 않지만, 이것으로 충분할 수 있습니다. Eclipse용 Spring Tools는 모든 것이 어떻게 와이어링되는지 보여주는 그래프를 렌더링할 수 있는 도구를 제공(스프링 부트 프로젝트에서만 지원)하며, 이것만으로도 충분할 수 있습니다. 또한 Java IDE는 AccountRepository 타입의 모든 선언과 사용을 쉽게 찾고 해당 타입을 리턴하는 @Bean 메서드의 위치를 ​​빠르게 보여줄 수 있습니다.

이러한 모호성이 허용되지 않고 IDE 내에서 한 @Configuration 클래스에서 다른 @Configuration 클래스로 직접 이동하려는 경우 구성 클래스 자체를 자동 와이어링하는 것을 고려하세요. 다음 예는 그 방법을 보여줍니다.

@Configuration
public class ServiceConfig {

	@Autowired
	private RepositoryConfig repositoryConfig;

	@Bean
	public TransferService transferService() {
		// navigate 'through' the config class to the @Bean method!
		return new TransferServiceImpl(repositoryConfig.accountRepository());
	}
}

 

 이전 상황에서 AccountRepository가 정의된 위치는 완전히 명시적입니다. 그러나 ServiceConfig는 이제 RepositoryConfig에 밀접하게 결합되었습니다. 이것이 트레이드오프입니다. 이러한 밀접 결합은 인터페이스 기반 또는 추상 클래스 기반 @Configuration 클래스를 사용하여 다소 완화할 수 있습니다. 다음 예를 고려하세요.

@Configuration
public class ServiceConfig {

	@Autowired
	private RepositoryConfig repositoryConfig;

	@Bean
	public TransferService transferService() {
		return new TransferServiceImpl(repositoryConfig.accountRepository());
	}
}

@Configuration
public interface RepositoryConfig {

	@Bean
	AccountRepository accountRepository();
}

@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {

	@Bean
	public AccountRepository accountRepository() {
		return new JdbcAccountRepository(...);
	}
}

@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class})  // import the concrete config!
public class SystemTestConfig {

	@Bean
	public DataSource dataSource() {
		// return DataSource
	}

}

public static void main(String[] args) {
	ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
	TransferService transferService = ctx.getBean(TransferService.class);
	transferService.transfer(100.00, "A123", "C456");
}

이제 ServiceConfig는 구체적인 DefaultRepositoryConfig와 관련하여 느슨하게 결합되었으며, 내장된 IDE 툴링은 여전히 ​​유용합니다. RepositoryConfig 구현의 타입 계층을 쉽게 얻을 수 있습니다. 이런 식으로 @Configuration 클래스와 해당 의존성을 탐색하는 것은 인터페이스 기반 코드를 탐색하는 일반적인 프로세스와 다르지 않습니다.

특정 빈의 시작 시 생성 순서에 영향을 주고 싶다면, 일부 빈을 @Lazy(시작 시가 아닌 첫 번째 액세스 시 생성)로 선언하거나, 다른 특정 빈에 @DependsOn(현재 빈의 직접적인 의존성이 의미하는 것 이상으로, 다른 특정 빈이 현재 빈보다 먼저 생성되도록 함)으로 선언하는 것을 고려하세요.

 

Conditionally Include @Configuration Classes or @Bean Methods

임의의 시스템 상태에 따라 전체 @Configuration 클래스 또는 개별 @Bean 메서드를 조건부로 활성화하거나 비활성화하는 것이 종종 유용합니다. 이에 대한 일반적인 예 중 하나는 @Profile을 사용하여 Spring 환경에서 특정 프로필이 활성화된 경우에만 Bean을 활성화하는 것입니다(자세한 내용은 Bean 정의 프로필 참조).

@Profile은 실제로 @Conditional이라는 훨씬 더 유연한 어노테이션을 사용하여 구현됩니다. @Conditional은 @Bean이 등록되기 전에 참조해야 하는 특정 org.springframework.context.annotation.Condition 구현을 나타냅니다.

Condition 인터페이스의 구현은 true 또는 false를 반환하는 matches(…​) 메서드를 제공합니다. 예를 들어, 다음 목록은 @Profile에 사용된 실제 Condition 구현을 보여줍니다.

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
	// Read the @Profile annotation attributes
	MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
	if (attrs != null) {
		for (Object value : attrs.get("value")) {
			if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
				return true;
			}
		}
		return false;
	}
	return true;
}

See the @Conditional javadoc for more detail.

 

위 설명에 기반하여, @Conditional 애노테이션을 사용해 특정 조건에 따라 @Configuration 클래스나 @Bean 메서드를 포함하거나 제외하는 방법을 보여주는 샘플 코드를 만들어보겠습니다. 이 코드는 @Profile 애노테이션을 사용해 특정 프로파일이 활성화된 경우에만 빈을 등록하는 방법을 예시로 들고, 나아가 사용자 정의 Condition 인터페이스를 구현하여 @Conditional을 사용하는 방법도 포함합니다.

 

1. HikariCP 디펜던시 추가

pom.xml

	<dependency>
	    <groupId>com.zaxxer</groupId>
	    <artifactId>HikariCP</artifactId>
	    <version>5.0.1</version>
	 </dependency>


2. @Profile 애노테이션을 사용한 샘플 코드
AppConfig.java

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

import com.zaxxer.hikari.HikariDataSource;

@Configuration
public class AppConfig {

    @Bean
    @Profile("dev")
    public DataSource devDataSource() {
        // 개발 환경용 데이터소스 설정
    	SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
		
		dataSource.setDriverClass(com.mysql.cj.jdbc.Driver.class);
		dataSource.setUrl("jdbc:mysql://localhost:3306/sbdt_db?characterEncoding=UTF-8");
		dataSource.setUsername("root");
		dataSource.setPassword("1234");

		return dataSource;
    }

    @Bean
    @Profile("prod")
    public DataSource prodDataSource() {
        // 프로덕션 환경용 데이터소스 설정
        return new HikariDataSource(); // 다른 데이터베이스 설정
    }
}

 

MainApp.java

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        // 프로파일 설정: "dev" 또는 "prod"
        System.setProperty("spring.profiles.active", "dev");

        ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);

        DataSource dataSource = ctx.getBean(DataSource.class);
        System.out.println("Using DataSource: " + dataSource.getClass().getName());
    }
}


3. @Conditional 애노테이션을 사용한 사용자 정의 Condition 구현
CustomCondition.java

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class CustomCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 예를 들어 특정 시스템 속성이 존재하는 경우에만 true 반환
        String expectedProperty = "my.custom.property";
        String propertyValue = context.getEnvironment().getProperty(expectedProperty);
        return propertyValue != null && propertyValue.equals("enabled");
    }
}


AppConfig.java

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean
    @Conditional(CustomCondition.class)
    public MyService myService() {
        return new MyServiceImpl();
    }
}


MainApp.java

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        // 조건에 맞는 시스템 속성 설정
        System.setProperty("my.custom.property", "enabled");

        ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);

        if (ctx.containsBean("myService")) {
            MyService myService = ctx.getBean(MyService.class);
            System.out.println("MyService Bean is available: " + myService.getClass().getName());
        } else {
            System.out.println("MyService Bean is not available");
        }
    }
}


4. 주요 클래스 정의
MyService.java

public interface MyService {
    void performService();
}


MyServiceImpl.java

public class MyServiceImpl implements MyService {

    @Override
    public void performService() {
        System.out.println("Service is being performed.");
    }
}


설명
1. @Profile 사용:
   AppConfig 클래스는 @Profile 애노테이션을 사용하여 특정 프로파일이 활성화된 경우에만 빈을 등록합니다.
   MainApp에서는 spring.profiles.active 속성을 설정하여 "dev" 또는 "prod" 프로파일을 활성화할 수 있습니다.

2. @Conditional 사용:
   CustomCondition 클래스는 Condition 인터페이스를 구현하여, 특정 시스템 속성이 설정된 경우에만 빈을 등록하도록 합니다.
   AppConfig 클래스의 myService 빈은 @Conditional을 사용하여 CustomCondition이 참일 경우에만 등록됩니다.
   MainApp에서 시스템 속성을 설정하고, 해당 조건에 맞는 빈이 등록되었는지 확인합니다.

이 샘플 코드는 @Profile과 @Conditional 애노테이션을 사용하여 Spring에서 조건부로 @Configuration 클래스나 @Bean 메서드를 포함하거나 제외하는 방법을 보여줍니다. 이러한 기능을 사용하면 특정 환경이나 조건에 맞게 애플리케이션 구성을 유연하게 제어할 수 있습니다.

 

 

Combining Java and XML Configuration

생략