Spring Framework/Spring IoC / / 2024. 8. 13. 15:50

Spring IoC 컨테이너 이해하기: 제5편

Java-based Container Configuration

이 섹션에서는 Java 코드에서 어노테이션을 사용하여 Spring 컨테이너를 구성하는 방법을 다룹니다.

 

Basic Concepts: @Bean and @Configuration

Spring의 Java 구성 지원에서 핵심 아티팩트는 @Configuration 어노테이션이 달린 클래스와 @Bean 어노테이션이 달린 메서드입니다. 

@Bean 어노테이션은 메서드가 Spring IoC 컨테이너에서 관리할 새 객체를 인스턴스화하고, 구성하고, 초기화한다는 것을 나타내는 데 사용됩니다. Spring의 <beans/> XML 구성에 익숙한 사람들에게 @Bean 어노테이션은 <bean/> 엘리먼트와 동일한 역할을 합니다. @Bean 어노테이션이 달린 메서드는 모든 Spring @Component와 함께 사용할 수 있습니다. 그러나 @Configuration 빈과 함께 가장 많이 사용됩니다. 

클래스에 @Configuration 어노테이션을 달면 해당 클래스의 주요 목적이 빈 정의의 소스라는 것을 나타냅니다. 또한 @Configuration 클래스는 동일한 클래스에서 다른 @Bean 메서드를 호출하여 빈 간 종속성을 정의할 수 있습니다. 가능한 가장 간단한 @Configuration 클래스는 다음과 같습니다.

@Configuration
public class AppConfig {

	@Bean
	public MyServiceImpl myService() {
		return new MyServiceImpl();
	}
}

 

앞의 AppConfig 클래스는 <beans/> XML과 동일합니다.

@Configuration 클래스 내에서 @Bean 메소드들 간의 로컬 호출 여부 

일반적인 시나리오에서, @Bean 메소드는 @Configuration 클래스로 선언되어야 하며, 이를 통해 전체 구성 클래스 처리가 적용되고 메소드 간 참조가 컨테이너의 라이프사이클 관리로 리디렉션됩니다. 이렇게 하면 동일한 @Bean 메소드가 일반 자바 메소드 호출을 통해 실수로 호출되는 것을 방지할 수 있으며, 추적하기 어려운 미묘한 버그를 줄이는 데 도움이 됩니다. 

@Configuration으로 어노테이션 처리되지 않은 클래스 내에서 @Bean 메소드가 선언되거나 @Configuration(proxyBeanMethods=false)로 선언된 경우, 이러한 메소드들은 "라이트" 모드로 처리된다고 합니다. 이러한 시나리오에서는 @Bean 메소드가 특별한 런타임 처리가 없는 일반적인 팩토리 메소드 메커니즘으로 동작합니다(즉, CGLIB 서브클래스를 생성하지 않음). 따라서 이러한 메소드에 대한 커스텀 자바 호출은 컨테이너에 의해 인터셉트되지 않으며, 주어진 빈에 대해 기존의 싱글톤(또는 스코프된) 인스턴스를 재사용하는 대신 매번 새로운 인스턴스를 생성하게 됩니다. 

결과적으로, 런타임 프록시가 없는 클래스에서의 @Bean 메소드는 빈 간의 의존성을 선언하는 데 사용되지 않습니다. 대신, 이들은 주로 자신이 속한 컴포넌트의 필드와 팩토리 메소드에서 선언할 수 있는 아규먼트를 통해 주입된 협력자에 의존하도록 기대됩니다. 이러한 @Bean 메소드는 다른 @Bean 메소드를 호출할 필요가 없으며, 모든 호출은 팩토리 메소드의 아규먼트를 통해 표현될 수 있습니다. 여기서 긍정적인 부수 효과는 런타임 시 CGLIB 서브클래싱이 적용될 필요가 없기 때문에 오버헤드와 메모리 사용량이 감소한다는 것입니다.

 

@Bean 및 @Configuration 어노테이션은 다음 섹션에서 자세히 설명합니다. 그러나 먼저 Java 기반 구성을 사용하여 Spring 컨테이너를 만드는 다양한 방법을 다룹니다.

 

Instantiating the Spring Container by Using AnnotationConfigApplicationContext

다음 섹션에서는 Spring 3.0에 도입된 Spring의 AnnotationConfigApplicationContext를 설명합니다. 이 다재다능한 ApplicationContext 구현은 @Configuration 클래스뿐만 아니라 일반 @Component 클래스와 JSR-330 메타데이터로 어노테이션이 달린 클래스도 입력으로 허용할 수 있습니다. 

@Configuration 클래스가 입력으로 제공되면 @Configuration 클래스 자체가 빈 정의로 등록되고 클래스 내에서 선언된 모든 @Bean 메서드도 빈 정의로 등록됩니다. 

@Component 및 JSR-330 클래스가 제공되면 빈 정의로 등록되고 필요한 경우 @Autowired 또는 @Inject와 같은 DI 메타데이터가 해당 클래스 내에서 사용된다고 가정합니다.

 

Simple Construction

ClassPathXmlApplicationContext를 인스턴스화할 때 Spring XML 파일이 입력으로 사용되는 것과 거의 같은 방식으로 AnnotationConfigApplicationContext를 인스턴스화할 때 @Configuration 클래스를 입력으로 사용할 수 있습니다. 이렇게 하면 다음 예제에서 보듯이 Spring 컨테이너를 완전히 XML 없이 사용할 수 있습니다.

public static void main(String[] args) {
	ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
	MyService myService = ctx.getBean(MyService.class);
	myService.doStuff();
}

 

앞서 언급했듯이 AnnotationConfigApplicationContext는 @Configuration 클래스에서만 작동하는 데 국한되지 않습니다. 다음 예제에서 보듯이 @Component 또는 JSR-330 주석이 달린 클래스는 생성자에 입력으로 제공될 수 있습니다.

public static void main(String[] args) {
	ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
	MyService myService = ctx.getBean(MyService.class);
	myService.doStuff();
}

 

앞의 예제에서는 MyServiceImpl, Dependency1, Dependency2가 @Autowired와 같은 Spring DI 어노테이션을 사용한다고 가정합니다.

 

Building the Container Programmatically by Using register(Class<?>…​)

아규먼트가 없는 생성자를 사용하여 AnnotationConfigApplicationContext를 인스턴스화한 다음 register() 메서드를 사용하여 구성할 수 있습니다. 이 접근 방식은 AnnotationConfigApplicationContext를 프로그래밍 방식으로 빌드할 때 특히 유용합니다. 다음 예는 그 방법을 보여줍니다.

public static void main(String[] args) {
	AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
	ctx.register(AppConfig.class, OtherConfig.class);
	ctx.register(AdditionalConfig.class);
	ctx.refresh();
	MyService myService = ctx.getBean(MyService.class);
	myService.doStuff();
}

 

Enabling Component Scanning with scan(String…​) 

컴포넌트 스캐닝을 활성화하려면 다음과 같이 @Configuration 클래스에 어노테이션을 달 수 있습니다.

@Configuration
@ComponentScan(basePackages = "com.acme")
public class AppConfig  {
	// ...
}

 

앞의 예에서 com.acme 패키지는 @Component 어노테이션이 달린 클래스를 찾기 위해 스캔되고, 해당 클래스는 컨테이너 내에서 Spring 빈 정의로 등록됩니다. AnnotationConfigApplicationContext는 다음 예에서 보여지는 것처럼 동일한 컴포넌트 스캔 기능을 허용하기 위해 scan(String…​) 메서드를 노출합니다.

public static void main(String[] args) {
	AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
	ctx.scan("com.acme");
	ctx.refresh();
	MyService myService = ctx.getBean(MyService.class);
}

 

@Configuration 클래스는 @Component로 메타 어노테이션이 달려 있으므로 컴포넌트 스캐닝의 후보입니다. 앞의 예에서 AppConfig가 com.acme 패키지(또는 그 아래의 패키지) 내에 선언되었다고 가정하면 scan()을 호출하는 동안 선택됩니다. Refresh() 시 모든 @Bean 메서드가 처리되고 컨테이너 내에서 빈 정의로 등록됩니다.

 

 

Support for Web Applications with AnnotationConfigWebApplicationContext

AnnotationConfigApplicationContext의 WebApplicationContext 변형은 AnnotationConfigWebApplicationContext와 함께 사용할 수 있습니다. 이 구현은 Spring ContextLoaderListener 서블릿 리스너, Spring MVC DispatcherServlet 등을 구성할 때 사용할 수 있습니다. 다음 web.xml 스니펫은 일반적인 Spring MVC 웹 애플리케이션을 구성합니다(contextClass context-param 및 init-param 사용에 유의하세요).

<web-app>
	<!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
		instead of the default XmlWebApplicationContext -->
	<context-param>
		<param-name>contextClass</param-name>
		<param-value>
			org.springframework.web.context.support.AnnotationConfigWebApplicationContext
		</param-value>
	</context-param>

	<!-- Configuration locations must consist of one or more comma- or space-delimited
		fully-qualified @Configuration classes. Fully-qualified packages may also be
		specified for component-scanning -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>com.acme.AppConfig</param-value>
	</context-param>

	<!-- Bootstrap the root application context as usual using ContextLoaderListener -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<!-- Declare a Spring MVC DispatcherServlet as usual -->
	<servlet>
		<servlet-name>dispatcher</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
			instead of the default XmlWebApplicationContext -->
		<init-param>
			<param-name>contextClass</param-name>
			<param-value>
				org.springframework.web.context.support.AnnotationConfigWebApplicationContext
			</param-value>
		</init-param>
		<!-- Again, config locations must consist of one or more comma- or space-delimited
			and fully-qualified @Configuration classes -->
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>com.acme.web.MvcConfig</param-value>
		</init-param>
	</servlet>

	<!-- map all requests for /app/* to the dispatcher servlet -->
	<servlet-mapping>
		<servlet-name>dispatcher</servlet-name>
		<url-pattern>/app/*</url-pattern>
	</servlet-mapping>
</web-app>

 

프로그래밍적 사용 사례의 경우 GenericWebApplicationContext를 AnnotationConfigWebApplicationContext의 대안으로 사용할 수 있습니다. 자세한 내용은 GenericWebApplicationContext javadoc을 참조하세요.

 

 

Using the @Bean Annotation

@Bean은 메서드 레벨 어노테이션이며 XML <bean/> 엘리먼트와 직접적으로 대응됩니다. 이 어노테이션은 <bean/>에서 제공하는 다음과 같은 일부 속성을 지원합니다.

  • init-method
  • destroy-method
  • autowiring
  • name

@Bean 애노테이션은 @Configuration 어노테이션이 붙은 클래스나 @Component 어노테이션이 붙은 클래스에서 사용할 수 있습니다.

 

Declaring a Bean

빈을 선언하려면 @Bean 어노테이션으로 메서드에 어노테이션을 달 수 있습니다. 이 메서드를 사용하여 메서드의 리턴 값으로 지정된 타입의 ApplicationContext 내에 빈 정의를 등록합니다. 기본적으로 빈 이름은 메서드 이름과 동일합니다. 다음 예제는 @Bean 메서드 선언을 보여줍니다. 

@Configuration
public class AppConfig {

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

 

위 선언은 다음 텍스트 이미지에서 볼 수 있듯이 TransferServiceImpl 타입의 객체 인스턴스에 바인딩된 transferService라는 이름의 빈을 ApplicationContext에서 사용할 수 있게 합니다.

transferService -> com.acme.TransferServiceImpl

 

디폴트 메서드를 사용하여 빈을 정의할 수도 있습니다. 이를 통해 디폴트 메서드에서 빈 정의가 있는 인터페이스를 구현하여 빈 구성을 구성할 수 있습니다.

public interface BaseConfig {

	@Bean
	default TransferServiceImpl transferService() {
		return new TransferServiceImpl();
	}
}

@Configuration
public class AppConfig implements BaseConfig {

}

 

다음 예제와 같이 인터페이스(또는 베이스 클래스) 리턴 유형으로 @Bean 메서드를 선언할 수도 있습니다.

@Configuration
public class AppConfig {

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

 

그러나, 이는 고급 타입 예측의 가시성을 지정된 인터페이스 타입(TransferService)으로 제한합니다. 전체 타입(TransferServiceImpl)은 해당 싱글톤 빈이 인스턴스화된 후에야 컨테이너에 알려지게 됩니다. 비지연(non-lazy) 싱글톤 빈들은 선언된 순서에 따라 인스턴스화되기 때문에, 다른 컴포넌트가 선언되지 않은 타입(예: @Autowired TransferServiceImpl)을 기준으로 매칭을 시도할 때, 이 빈이 인스턴스화된 시점에 따라 다른 타입 매칭 결과가 나타날 수 있습니다.

선언된 서비스 인터페이스로 타입을 일관되게 참조하는 경우 @Bean 리턴 타입이 해당 디자인 결정에 안전하게 결합될 수 있습니다. 그러나 여러 인터페이스를 구현하는 컴포넌트나 구현 타입으로 잠재적으로 참조되는 컴포넌트의 경우 가능한 가장 구체적인 리턴 타입을 선언하는 것이 더 안전합니다(적어도 빈을 참조하는 주입 지점에서 요구하는 만큼 구체적)

 

Bean Dependencies

@Bean 어노테이션이 붙은 메서드는 해당 빈을 빌드하는 데 필요한 종속성을 설명하는 임의의 수의 파라미터를 가질 수 있습니다. 예를 들어, TransferService에 AccountRepository가 필요한 경우 다음 예제와 같이 메서드 파라미터로 해당 종속성을 구체화할 수 있습니다.

@Configuration
public class AppConfig {

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

 

해결 메커니즘은 생성자 기반 종속성 주입과 거의 동일합니다. 자세한 내용은 관련 섹션을 참조하세요.

 

Receiving Lifecycle Callbacks

@Bean 어노테이션으로 정의된 모든 클래스는 일반 라이프사이클 콜백을 지원하고 JSR-250의 @PostConstruct 및 @PreDestroy 주석을 사용할 수 있습니다. 자세한 내용은 JSR-250 어노테이션을 참조하세요.

 

일반적인 Spring 라이프사이클 콜백도 완벽하게 지원됩니다. 빈이 InitializingBean, DisposableBean 또는 Lifecycle을 구현하는 경우 해당 메서드는 컨테이너에서 호출됩니다. 

BeanFactoryAware, BeanNameAware, MessageSourceAware, ApplicationContextAware 등과 같은 표준 *Aware 인터페이스 세트도 완벽하게 지원됩니다. 

@Bean 어노테이션은 다음 예제에서 볼 수 있듯이 빈 요소에 Spring XML의 init-method 및 destroy-method 속성과 매우 유사하게 임의의 init 및 destroy 콜백 메서드를 지정하는 것을 지원합니다.

public class BeanOne {

	public void init() {
		// initialization logic
	}
}

public class BeanTwo {

	public void cleanup() {
		// destruction logic
	}
}

@Configuration
public class AppConfig {

	@Bean(initMethod = "init")
	public BeanOne beanOne() {
		return new BeanOne();
	}

	@Bean(destroyMethod = "cleanup")
	public BeanTwo beanTwo() {
		return new BeanTwo();
	}
}

 

 

기본적으로 Java 구성으로 정의된 빈은 public close 또는 shutdown 메서드가 있고 자동으로 destroy 콜백에 등록됩니다.  public close 또는 shutdown 메서드가 있고 컨테이너가 종료될 때 호출되지 않도록 하려면 빈 정의에 @Bean(destroyMethod = "")을 추가하여 디폴트(추론된) 모드를 비활성화할 수 있습니다. 

JNDI로 획득한 리소스의 경우 기본적으로 이를 수행할 수 있습니다. 해당 리소스의 수명 주기는 애플리케이션 외부에서 관리되기 때문입니다. 특히 Jakarta EE 애플리케이션 서버에서 문제가 되는 것으로 알려져 있으므로 DataSource의 경우 항상 이를 수행해야 합니다. 

다음 예제는 DataSource에 대한 자동 destruction 콜백을 방지하는 방법을 보여줍니다.
@Bean(destroyMethod = "")
public DataSource dataSource() throws NamingException {
	return (DataSource) jndiTemplate.lookup("MyDS");
}


또한 @Bean 메서드에서는 일반적으로 Spring의 JndiTemplate 또는 JndiLocatorDelegate 헬퍼를 사용하거나 직접 JNDI InitialContext를 사용하지만 JndiObjectFactoryBean 변형은 사용하지 않는 프로그램적 JNDI 조회를 사용합니다(이 경우 실제 대상 유형 대신 FactoryBean 유형으로 반환 유형을 선언해야 하므로 여기에 제공된 리소스를 참조하려는 다른 @Bean 메서드에서 교차 참조 호출에 사용하기 어렵습니다).

 

위의 예에서 BeanOne의 경우, 다음 예와 같이 생성 중에 init() 메서드를 직접 호출하는 것도 마찬가지로 유효합니다.

@Configuration
public class AppConfig {

	@Bean
	public BeanOne beanOne() {
		BeanOne beanOne = new BeanOne();
		beanOne.init();
		return beanOne;
	}

	// ...
}

 

Java에서 직접 작업하는 경우 객체를 원하는 대로 조작할 수 있으며 항상 컨테이너 수명 주기에 의존할 필요가 없습니다.

 

Specifying Bean Scope

Spring에는 @Scope 어노테이션이 포함되어 있어 빈의 범위를 지정할 수 있습니다.

 

Using the @Scope Annotation

@Bean 어노테이션으로 정의된 빈이 특정 scope를 가져야 한다고 지정할 수 있습니다. Bean Scopes 섹션에 지정된 표준 범위를 사용할 수 있습니다. 

디폴트 스코프는 싱글톤이지만 다음 예제와 같이 @Scope 어노테이션으로 이를 재정의할 수 있습니다.

@Configuration
public class MyConfiguration {

	@Bean
	@Scope("prototype")
	public Encryptor encryptor() {
		// ...
	}
}

 

@Scope and scoped-proxy

Spring은 스코프가 지정된 프록시를 통해 스코프가 지정된 종속성을 처리하는 편리한 방법을 제공합니다. XML 구성을 사용할 때 이러한 프록시를 만드는 가장 쉬운 방법은 <aop:scoped-proxy/> 엘리먼트입니다. @Scope 어노테이션으로 Java에서 빈을 구성하면 proxyMode 특성으로 동일한 지원을 제공합니다. 기본값은 ScopedProxyMode.DEFAULT이며, 일반적으로 컴포넌트 스캔 지시 레벨에서 다른 디폴트 값이 구성되지 않는 한 스코프가 지정된 프록시를 만들지 않아야 함을 나타냅니다. ScopedProxyMode.TARGET_CLASS, ScopedProxyMode.INTERFACES 또는 ScopedProxyMode.NO를 지정할 수 있습니다.

// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
public UserPreferences userPreferences() {
	return new UserPreferences();
}

@Bean
public Service userService() {
	UserService service = new SimpleUserService();
	// a reference to the proxied userPreferences bean
	service.setUserPreferences(userPreferences());
	return service;
}

 

Customizing Bean Naming

기본적으로 구성 클래스는 @Bean 메서드의 이름을 결과 빈의 이름으로 사용합니다. 그러나 이 기능은 다음 예제에서 보여지는 것처럼 name 속성을 사용하여 재정의할 수 있습니다.

@Configuration
public class AppConfig {

	@Bean("myThing")
	public Thing thing() {
		return new Thing();
	}
}

 

Bean Aliasing

Naming Beans에서 논의했듯이, 때로는 단일 빈에 여러 이름을 부여하는 것이 바람직한데, 이를 빈 별칭이라고도 합니다. @Bean 어노테이션의 name 속성은 이 목적을 위해 문자열 배열을 허용합니다. 다음 예는 빈에 대한 여러 별칭을 설정하는 방법을 보여줍니다.

@Configuration
public class AppConfig {

	@Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
	public DataSource dataSource() {
		// instantiate, configure and return DataSource bean...
	}
}

 

Bean Description

때로는 빈에 대한 보다 자세한 텍스트 설명을 제공하는 것이 도움이 됩니다. 이는 모니터링 목적으로 빈이 노출될 때(아마도 JMX를 통해) 특히 유용할 수 있습니다. 

@Bean에 설명을 추가하려면 다음 예와 같이 @Description 어노테이션을 사용할 수 있습니다.

@Configuration
public class AppConfig {

	@Bean
	@Description("Provides a basic example of a bean")
	public Thing thing() {
		return new Thing();
	}
}

 

Using the @Configuration annotation

@Configuration은 객체가 빈 정의의 소스임을 나타내는 클래스 레벨 어노테이션입니다. @Configuration 클래스는 @Bean 어노테이션이 달린 메서드를 통해 빈을 선언합니다. @Configuration 클래스에서 @Bean 메서드에 대한 호출은 빈 간 종속성을 정의하는 데에도 사용할 수 있습니다. 일반적인 소개는 기본 개념: @Bean 및 @Configuration을 참조하세요.

 

Injecting Inter-bean Dependencies

빈이 서로 종속성을 가질 때, 그 종속성을 표현하는 것은 다음 예제에서 보듯이 한 빈 메서드가 다른 빈 메서드를 호출하는 것만큼 간단합니다.

@Configuration
public class AppConfig {

	@Bean
	public BeanOne beanOne() {
		return new BeanOne(beanTwo());
	}

	@Bean
	public BeanTwo beanTwo() {
		return new BeanTwo();
	}
}

 

앞의 예제에서 beanOne은 생성자 주입을 통해 beanTwo에 대한 참조를 받습니다.

이 빈 간 종속성 선언 방법은 @Bean 메서드가 @Configuration 클래스 내에서 선언될 때만 작동합니다. 일반 @Component 클래스를 사용하여 빈 간 종속성을 선언할 수 없습니다.

 

Lookup Method Injection

앞서 언급했듯이, lookup method injection은 드물게 사용해야 하는 고급 기능입니다. 싱글톤 스코프의 빈이 프로토타입 스코프의 빈에 종속되어 있는 경우에 유용합니다. 이러한 유형의 구성에 Java를 사용하면 이 패턴을 구현하는 자연스러운 수단이 제공됩니다. 다음 예에서는 lookup method injection을 사용하는 방법을 보여줍니다.

public abstract class CommandManager {
	public Object process(Object commandState) {
		// grab a new instance of the appropriate Command interface
		Command command = createCommand();
		// set the state on the (hopefully brand new) Command instance
		command.setState(commandState);
		return command.execute();
	}

	// okay... but where is the implementation of this method?
	protected abstract Command createCommand();
}

 

Java 구성을 사용하면 abstract createCommand() 메서드가 새(프로토타입) Command 클래스 객체를 찾는 방식으로 재정의되는 CommandManager의 하위 클래스를 만들 수 있습니다. 다음 예제는 이를 수행하는 방법을 보여줍니다.

@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
	AsyncCommand command = new AsyncCommand();
	// inject dependencies here as required
	return command;
}

@Bean
public CommandManager commandManager() {
	// return new anonymous implementation of CommandManager with createCommand()
	// overridden to return a new prototype Command object
	return new CommandManager() {
		protected Command createCommand() {
			return asyncCommand();
		}
	}
}

// SpringLookupInjection 참조

 

Further Information About How Java-based Configuration Works Internally

다음 예제에서는 @Bean 메서드가 두 번 호출되는 것을 보여줍니다.

@Configuration
public class AppConfig {

	@Bean
	public ClientService clientService1() {
		ClientServiceImpl clientService = new ClientServiceImpl();
		clientService.setClientDao(clientDao());
		return clientService;
	}

	@Bean
	public ClientService clientService2() {
		ClientServiceImpl clientService = new ClientServiceImpl();
		clientService.setClientDao(clientDao());
		return clientService;
	}

	@Bean
	public ClientDao clientDao() {
		return new ClientDaoImpl();
	}
}

 

clientDao()는 clientService1()에서 한 번, clientService2()에서 한 번 호출되었습니다. 이 메서드는 ClientDaoImpl의 새 인스턴스를 생성하여 리턴하므로 일반적으로 두 개의 인스턴스(각 서비스당 하나씩)가 있을 것으로 예상합니다. 이는 확실히 문제가 될 것입니다. Spring에서 인스턴스화된 빈은 기본적으로 싱글톤 스코프를 갖습니다. 여기서 마법이 등장합니다. 모든 @Configuration 클래스는 시작 시 CGLIB로 서브클래싱됩니다. 서브클래스에서 자식 메서드는 부모 메서드를 호출하고 새 인스턴스를 만들기 전에 먼저 컨테이너에서 캐시된(범위가 지정된) 빈을 확인합니다.

동작은 빈의 스코프에 따라 다를 수 있습니다. 여기서는 싱글톤에 대해 이야기하고 있습니다.
CGLIB 클래스는 org.springframework.cglib 패키지로 다시 패키징되어 spring-core JAR에 직접 포함되므로 클래스 경로에 CGLIB를 추가할 필요가 없습니다.

 

CGLIB이 시작 시점에 동적으로 기능을 추가하기 때문에 몇 가지 제한 사항이 있습니다. 특히, 구성 클래스는 final로 선언되어서는 안 됩니다. 그러나 구성 클래스에서 생성자는 @Autowired를 사용하거나 디폴트가 아닌 생성자를 선언하여 디폴트 주입을 위한 단일 생성자를 포함하여 모든 생성자가 허용됩니다. 

만약 CGLIB에 의해 부과되는 제한을 피하고 싶다면, @Bean 메소드를 non-@Configuration 클래스(예: 일반 @Component 클래스)에서 선언하거나, 구성 클래스에 @Configuration(proxyBeanMethods = false)로 애노테이션을 지정하는 것을 고려하십시오. 이 경우, @Bean 메소드 간의 교차 호출이 인터셉트되지 않으므로 생성자나 메소드 수준의 의존성 주입에만 의존해야 합니다.

 

 

Composing Java-based Configurations

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 {

	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. @Profile 애노테이션을 사용한 샘플 코드 
AppConfig.java

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

@Configuration
public class AppConfig {

    @Bean
    @Profile("dev")
    public DataSource devDataSource() {
        // 개발 환경용 데이터소스 설정
        return new HikariDataSource(); // 가정된 HikariCP 데이터소스 설정
    }

    @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());
    }
}


2. @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");
        }
    }
}


3. 주요 클래스 정의 
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

생략

  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유