@Import 어노테이션을 사용하는 것은 Spring Framework에서 애플리케이션의 구성(Configuration) 메타데이터를 추가하는 방법 중 하나입니다. 이 어노테이션을 통해 다양한 방식으로 구성 클래스를 가져와 애플리케이션 컨텍스트에 등록할 수 있습니다. @Import를 사용하는 방법은 크게 정적 방법과 동적 방법으로 나눌 수 있습니다.
정적 방법(Static Method)
정적 방법은 가장 기본적인 @Import 사용 방식으로, 한 개 또는 여러 개의 구성 클래스를 직접 명시합니다. 이 방식은 컴파일 시점에 결정되므로 정적이라고 합니다. 예를 들어, @Configuration 어노테이션이 붙은 Java 클래스에 @Import 어노테이션을 사용하여 다른 구성 클래스들을 명시적으로 지정할 수 있습니다.
@Configuration
@Import({ConfigA.class, ConfigB.class})
public class MainConfig {
// ...
}
이렇게 하면 ConfigA와 ConfigB에 정의된 빈들이 MainConfig와 함께 애플리케이션 컨텍스트에 등록됩니다.
동적 방법(Dynamic Method)
동적 방법은 @Import 어노테이션과 함께 ImportSelector 또는 ImportBeanDefinitionRegistrar 인터페이스를 구현하는 방식을 말합니다. 이 방법은 구성 클래스를 프로그래밍 방식으로 결정하고 등록할 수 있으므로 동적이라고 합니다.
ImportSelector 사용
ImportSelector는 selectImports 메소드를 구현해야 하며, 이 메소드는 동적으로 등록할 구성 클래스의 이름을 반환합니다.
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 조건에 따라 동적으로 구성 클래스 결정
return new String[] {"com.example.ConfigC"};
}
}
그리고 이 ImportSelector를 @Import 어노테이션에 사용합니다.
@Configuration
@Import(MyImportSelector.class)
public class MainConfig {
// ...
}
다음 코드는 간략하게 ImportSelector 인터페이스를 사용하는 샘플입니다.
package com.coga.is;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import com.coga.*;
public class EnvironmentImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
AnnotationAttributes attr =
AnnotationAttributes.fromMap(
importingClassMetadata.getAnnotationAttributes(
EnableEnvironmentConfig.class.getName(),
false));
String env = attr.getString("value");
if ("dev".equals(env)) {
return new String[] {DevConfig.class.getName()};
}
else if ("prod".equals(env)) {
return new String[] {ProdConfig.class.getName()};
}
else {
throw new IllegalArgumentException("Invalid environment: " + env);
}
}
}
package com.coga.is;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DevConfig {
@Bean
MyBean myBean() {
return new MyBean("Development Environment Bean");
}
}
package com.coga.is;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ProdConfig {
@Bean
MyBean myBean() {
return new MyBean("Production Environment Bean");
}
}
package com.coga.is;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(EnvironmentImportSelector.class)
public @interface EnableEnvironmentConfig {
String value() default "dev";
}
package com.coga.is;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableEnvironmentConfig("dev")
public class MainConfig {
@Bean
public UseMyBean useMyBean() {
return new UseMyBean();
}
}
package com.coga.is;
public class MyBean {
private String msg;
public MyBean(String msg) {
this.msg = msg;
}
public String getMsg() {
return this.msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
package com.coga.is;
import org.springframework.beans.factory.annotation.Autowired;
public class UseMyBean {
@Autowired
private MyBean myBean;
public void printMsg() {
System.out.println(myBean.getMsg());
}
}
package com.coga.is;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Program {
public static void main(String[] args) {
ApplicationContext context =
new AnnotationConfigApplicationContext(MainConfig.class);
UseMyBean bean = context.getBean(UseMyBean.class);
bean.printMsg();
}
}
실행 결과는 다음과 같습니다.
이 예제는 개발 환경과 운영 환경을 위한 두 가지 구성 클래스(DevConfig와 ProdConfig)를 정의하고, 이를 조건부로 선택하여 애플리케이션 컨텍스트에 등록하는 방식을 사용합니다.
주요 구성 요소는 다음과 같습니다:
1. DevConfig와 ProdConfig 클래스: 각 환경에 맞는 MyBean 인스턴스를 생성하고 반환하는 @Bean 메소드를 정의합니다. DevConfig는 개발 환경을 위한 빈을, ProdConfig는 운영 환경을 위한 빈을 제공합니다.
2. EnvironmentImportSelector 클래스: ImportSelector 인터페이스를 구현하여, 주어진 환경(dev 또는 prod)에 따라 적절한 구성 클래스를 선택하고 반환합니다. selectImports 메소드 내에서는 @EnableEnvironmentConfig 어노테이션으로부터 환경 값을 읽고, 해당하는 구성 클래스의 이름을 문자열 배열로 반환합니다.
3. EnableEnvironmentConfig 어노테이션: 구성 클래스를 선택하는 데 사용될 환경 값을 지정하기 위한 커스텀 어노테이션입니다. value 속성을 통해 환경(dev 또는 prod)을 지정할 수 있으며, Import 어노테이션을 사용하여 `EnvironmentImportSelector`를 연결합니다.
4. AppConfig 클래스: 애플리케이션의 메인 구성 클래스로, @EnableEnvironmentConfig 어노테이션을 사용하여 실행 환경을 지정합니다. 이 예제에서는 dev를 값으로 사용하여 개발 환경 구성을 활성화합니다. 또한, 사용자 정의 빈 `UseMyBean`을 등록하는 @Bean 메소드를 포함합니다.
5. MyBean 클래스: 예제에서 사용되는 간단한 빈 클래스로, 문자열 메시지를 저장하고 반환하는 기능을 가집니다.
6. UseMyBean 클래스: MyBean 인스턴스에 의존하는 클래스로, @Autowired를 사용하여 MyBean을 주입받고, 이를 사용하는 printMsg 메소드를 포함합니다.
이 구조를 사용함으로써, 애플리케이션을 시작할 때 @EnableEnvironmentConfig 어노테이션의 값에 따라 자동으로 적절한 환경 설정을 로드하고, 해당 환경에 맞는 빈을 사용할 수 있게 됩니다. 이런 방식은 환경별로 다른 설정이 필요한 경우에 유용하며, 코드의 변경 없이 애플리케이션의 동작을 변경할 수 있는 유연성을 제공합니다.
ImportBeanDefinitionRegistrar 사용
ImportBeanDefinitionRegistrar 설명
ImportBeanDefinitionRegistrar는 Spring Framework에서 제공하는 인터페이스로, 개발자가 프로그래밍 방식으로 빈(Bean) 정의를 Spring 애플리케이션 컨텍스트에 등록할 수 있게 해줍니다. 이 인터페이스는 @Configuration 클래스나 @Import 어노테이션을 사용하여 애플리케이션 컨텍스트에 동적으로 빈을 추가할 때 유용하게 사용됩니다.
ImportBeanDefinitionRegistrar를 사용하면, 애플리케이션의 구성 단계에서 더 세밀한 제어를 할 수 있으며, 조건부 빈 등록, 프로파일 기반 빈 등록, 런타임에 결정되는 빈 속성 설정 등 복잡한 빈 설정 시나리오를 구현할 수 있습니다.
주요 메소드
- registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry): 이 메소드는 ImportBeanDefinitionRegistrar 인터페이스를 구현할 때 오버라이드해야 하는 유일한 메소드입니다. AnnotationMetadata 객체를 통해 현재 @Import 어노테이션이 선언된 클래스의 메타데이터에 접근할 수 있고, BeanDefinitionRegistry를 사용하여 새로운 빈 정의를 등록할 수 있습니다.
사용 사례
ImportBeanDefinitionRegistrar는 다음과 같은 경우에 유용하게 사용될 수 있습니다:
- 애플리케이션의 구성이나 환경에 기반하여 조건부로 빈을 등록하고 싶을 때
- 외부 라이브러리에서 제공하는 클래스에 대한 빈 정의를 등록하고 싶을 때
- 빈의 설정이 복잡하거나 런타임에 결정되어야 할 때
샘플 코드
아래 샘플 코드는 ImportBeanDefinitionRegistrar를 사용하여 조건부로 빈을 등록하는 간단한 예제를 보여줍니다.
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.GenericBeanDefinition;
@Configuration
@Import(MyBeanDefinitionRegistrar.class)
public class AppConfig {
}
public class MyBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 조건을 확인하여 빈을 등록할지 결정 (여기서는 단순화를 위해 항상 등록)
boolean condition = true; // 실제 애플리케이션에서는 이 조건을 동적으로 결정
if (condition) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(MyBean.class);
// 빈에 대한 추가적인 설정 (생성자 인자, 프로퍼티 값 등)
beanDefinition.getPropertyValues().add("message", "This is a dynamically registered bean!");
// 빈 정의를 레지스트리에 등록
registry.registerBeanDefinition("myDynamicBean", beanDefinition);
}
}
}
public class MyBean {
private String message;
public MyBean() {
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public void printMessage() {
System.out.println(getMessage());
}
}
샘플 코드 설명
- AppConfig: Spring의 @Configuration 어노테이션이 붙은 클래스로, 애플리케이션의 주 구성 클래스 역할을 합니다. @Import 어노테이션을 사용하여 MyBeanDefinitionRegistrar를 등록합니다.
- MyBeanDefinitionRegistrar: ImportBeanDefinitionRegistrar 인터페이스를 구현하는 클래스로, registerBeanDefinitions 메소드를 오버라이드하여 조건에 따라 MyBean 클래스의 빈 정의를 동적으로 등록합니다. 이 예제에서는 조건을 항상 참으로 설정하여 빈이 항상 등록되도록 하고 있습니다.
- MyBean: 간단한 POJO 클래스로, 메시지를 저장하고 출력하는 기능을 가집니다. MyBeanDefinitionRegistrar에 의해 빈으로 등록됩니다.
이 예제에서 MyBeanDefinitionRegistrar는 애플리케이션 구성 시 MyBean의 빈 정의를 조건에 따라 동적으로 등록합니다. 실제 애플리케이션에서는 빈 등록 조건을 애플리케이션의 구성, 환경 변수, 시스템 속성 등에 따라 결정할 수 있습니다.
다음은 Spring의 인스트락쳐 빈의 필드를 동적으로 설정하여 해당 빈을 생성하고 등록하는 코드입니다.
package com.coga.ibdr;
import java.util.Map;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
public class ImportRestTemplateRegistrar implements ImportBeanDefinitionRegistrar {
private final static String BEAN_NAME = "restTemplate";
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
Map<String, Object> metaData =
importingClassMetadata.getAnnotationAttributes(EnableRestTemplate.class.getName());
Class<? extends ClientHttpRequestFactory> factoryClass =
(Class<? extends ClientHttpRequestFactory>) metaData.get("value");
// ClientHttpRequestFactory 구현체 인스턴스 생성
ClientHttpRequestFactory factoryInstance;
try {
factoryInstance =
factoryClass.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new IllegalStateException("Failed to instantiate ClientHttpRequestFactory", e);
}
// RestTemplate BeanDefinitionBuilder 생성 및 구성
BeanDefinitionBuilder bdb =
BeanDefinitionBuilder.rootBeanDefinition(RestTemplate.class);
bdb.addConstructorArgValue(factoryInstance);
registry.registerBeanDefinition(BEAN_NAME, bdb.getBeanDefinition());
}
}
package com.coga.ibdr;
public enum Mode {
NONE,
ASYNC,
ALL
}
package com.coga.ibdr;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import com.intheeast.is2.ImportTemplateSelector;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ImportRestTemplateRegistrar.class)
public @interface EnableRestTemplate {
Class<? extends ClientHttpRequestFactory> value() default SimpleClientHttpRequestFactory.class;
}
package com.coga.ibdr;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
package com.coga.ibdr;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient() {
return WebClient.create();
}
}
package com.coga.ibdr;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
@Configuration
//@EnableRestTemplate(HttpComponentsClientHttpRequestFactory.class)
@EnableRestTemplate
@Import({RestTemplateConfig.class, WebClientConfig.class}) // RestTemplateConfig와 WebClientConfig를 가져옵니다.
public class AppConfig {
}
package com.coga.ibdr;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {AppConfig.class})
public class RestTest {
@Autowired
private RestTemplate restTemplate;
@Test
public void restTest() {
System.out.println(restTemplate.getRequestFactory());
}
}
'Spring Framework' 카테고리의 다른 글
템플릿 콜백 패턴(Template Callback Pattern) (0) | 2024.04.09 |
---|---|
전략 패턴 (0) | 2024.04.09 |
SOLID(객체 지향 설계 원칙) (0) | 2024.04.09 |
OCP(Open-Closed Principle) (0) | 2024.04.09 |
Spring Resource (0) | 2024.04.09 |