본문 바로가기

Spring Framework

@Import

@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