https://sundaland.tistory.com/69
[ ▶ @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 interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
ImportSelector 인터페이스의 selectImports 메서드의 리턴값은 동적으로 로드할 스프링 설정 클래스(Configuration 클래스)의 전체 클래스 이름(fully qualified class name) 배열을 의미한다. 즉, selectImports 메서드는 해당 애플리케이션 컨텍스트에 추가할 설정 클래스들을 명시적으로 리턴한다.
[ ▷ 리턴값의 의미 ]
리턴된 클래스 이름들은 모두 스프링이 @Configuration이나 @Component와 같은 애노테이션이 적용된 설정 클래스로 간주하여, 해당 클래스들에서 정의된 빈(bean)을 스프링 컨텍스트에 등록한다. 이를 통해 스프링 애플리케이션은 해당 설정 클래스들에서 정의한 빈을 로드하고 사용할 수 있게 된다.
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[] {
"com.example.config.ServiceConfigA",
"com.example.config.ServiceConfigB"
};
}
이 예에서 selectImports 메서드는 ServiceConfigA와 ServiceConfigB라는 두 개의 클래스의 전체 클래스 이름을 리턴한다. 이 값들은 SpringApplication이 시작될 때 자동으로 스프링 컨텍스트에 로드되며, 해당 설정 클래스 내에서 정의된 빈들이 스프링 컨텍스트에 추가된다.
[ ▷ 리턴되는 배열의 역할 ]
- 스프링 설정 클래스 로딩: 리턴된 각 클래스 이름에 해당하는 설정 클래스가 스프링 컨텍스트에 추가된다.
- 동적 빈 등록: 해당 설정 클래스가 스프링 컨텍스트에 추가되면, 그 클래스에서 정의된 모든 빈(bean)도 컨텍스트에 등록된.
- 다양한 조건 적용: selectImports 메서드에서 특정 조건에 따라 다르게 설정 클래스를 리턴함으로써, 특정 환경이나 상황에 맞춰 애플리케이션의 설정을 동적으로 변경할 수 있다.
[ ▷ 중요한 점 ]
- 리턴 값에 포함된 클래스들은 반드시 스프링에서 지원하는 설정 클래스여야 하며, 주로 @Configuration이나 @Component 등의 애노테이션이 적용된 클래스여야 한다.
- 리턴되는 클래스 이름은 반드시 fully qualified name이어야 합니다. 즉, 패키지 경로를 포함한 클래스의 전체 경로를 문자열로 제공해야 한다.
▼ pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.intheeast</groupId>
<artifactId>ComposingConfigurations</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>3.2.3</version> <!-- 최신 버전으로 대체 -->
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<release>17</release>
</configuration>
</plugin>
</plugins>
</build>
</project>
▼ MyImportSelector.java
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 원하는 설정 클래스를 선택하여 반환
// 조건에 따라 다른 클래스를 로드할 수 있음
return new String[] {
"com.intheeast.springframe.ServiceConfigA",
"com.intheeast.springframe.ServiceConfigB"
};
}
}
▼ ServiceConfigB.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
public class ServiceConfigB {
@Bean
public String serviceB() {
return "Service B Bean";
}
}
▼ ImportSelectorApplication.java
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Import;
@SpringBootApplication
@Import(MyImportSelector.class)
public class ImportSelectorApplication implements CommandLineRunner{
private final ApplicationContext context;
public ImportSelectorApplication(ApplicationContext context) {
this.context = context;
}
public static void main(String[] args) {
SpringApplication.run(ImportSelectorApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
System.out.println("Bean serviceA: " + context.getBean("serviceA"));
System.out.println("Bean serviceB: " + context.getBean("serviceB"));
}
}
▼ 실행 결과
Bean serviceA: Service A Bean
Bean serviceB: Service B Bean
[ ▷ ImportBeanDefinitionRegistrar 사용 ]
△ ImportBeanDefinitionRegistrar 설명
ImportBeanDefinitionRegistrar는 Spring Framework에서 제공하는 인터페이스로, 개발자가 프로그래밍 방식으로 빈(Bean) 정의를 Spring 애플리케이션 컨텍스트에 등록할 수 있게 해준다. 이 인터페이스는 @Configuration 클래스나 @Import 어노테이션을 사용하여 애플리케이션 컨텍스트에 동적으로 빈을 추가할 때 유용하게 사용된다.
ImportBeanDefinitionRegistrar를 사용하면, 애플리케이션의 구성 단계에서 더 세밀한 제어를 할 수 있으며, 조건부 빈 등록, 프로파일 기반 빈 등록, 런타임에 결정되는 빈 속성 설정 등 복잡한 빈 설정 시나리오를 구현할 수 있다.
- registerBeanDefinitions(AnnotationMetadata, BeanDefinitionRegistry): 이 메소드는 ImportBeanDefinitionRegistrar 인터페이스를 구현할 때 오버라이드해야 하는 유일한 메소드이다. AnnotationMetadata 객체를 통해 현재 @Import 어노테이션이 선언된 클래스의 메타데이터에 접근할 수 있고, BeanDefinitionRegistry를 사용하여 새로운 빈 정의를 등록할 수 있다.
ImportBeanDefinitionRegistrar는 다음과 같은 경우에 유용하게 사용될 수 있다.
- 애플리케이션의 구성이나 환경에 기반하여 조건부로 빈을 등록하고 싶을 때
- 외부 라이브러리에서 제공하는 클래스에 대한 빈 정의를 등록하고 싶을 때
- 빈의 설정이 복잡하거나 런타임에 결정되어야 할 때
△ 샘플 코드
아래 샘플 코드는 ImportBeanDefinitionRegistrar를 사용하여 조건부로 빈을 등록하는 간단한 예제를 보여준다.
▼ MyImportBeanDefinitonRegistrar.java
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
// 특정 조건에 따라 빈 정의를 등록
if (!registry.containsBeanDefinition("customService")) {
// BeanDefinition을 직접 등록
RootBeanDefinition beanDefinition = new RootBeanDefinition(CustomService.class);
registry.registerBeanDefinition("customService", beanDefinition);
System.out.println("customService 빈 등록 완료");
}
}
}
▼ EnableMyFeature.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MyImportBeanDefinitionRegistrar.class)
public @interface EnableMyFeature {
}
▼ CustomService.java
public class CustomService {
public void performAction() {
System.out.println("Performing custom service action");
}
}
▼ ImportBeanDefinitionRegistrarApplication.java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
@EnableMyFeature // 이 애노테이션을 통해 빈 등록이 이루어짐
public class ImportBeanDefinitionRegistrarApplication {
public static void main(String[] args) {
ApplicationContext context =
SpringApplication.run(ImportBeanDefinitionRegistrarApplication.class, args);
// CustomService가 빈으로 등록되었는지 확인
CustomService customService = context.getBean(CustomService.class);
customService.performAction();
}
}
- @EnableMyFeature 애노테이션은 MyImportBeanDefinitionRegistrar를 사용하도록 설정한다.
- MyImportBeanDefinitionRegistrar는 registerBeanDefinitions 메서드를 통해 CustomService 빈을 동적으로 스프링 컨텍스트에 등록한다.
- CustomService가 정상적으로 등록되었는지 확인하기 위해 ApplicationContext에서 해당 빈을 가져와 실행한다.
customService 빈 등록 완료
Performing custom service action
[ ▷ 고급 사용 사례 ]
- 조건부 빈 등록: 특정 환경 변수, 시스템 속성, 프로파일 등에 따라 빈을 조건부로 등록할 수 있다. 예를 들어, importingClassMetadata를 사용하여 클래스에 선언된 애노테이션을 기반으로 빈을 다르게 등록할 수 있다.
- 복잡한 빈 구성: 단순한 빈 정의만 등록하는 것이 아니라, 다른 빈의 의존성을 설정하거나 빈의 속성을 세밀하게 조정할 수 있다.
- 프레임워크 레벨 확장: 스프링 자체의 기능을 확장하는 라이브러리나 프레임워크에서는 특정 기능을 활성화할 때 필요한 모든 빈을 ImportBeanDefinitionRegistrar를 통해 한 번에 등록할 수 있다.
[ ▷ ImportBeanDefinitionRegistrar vs. ImportSelector ]
- ImportSelector는 설정 클래스(@Configuration) 자체를 동적으로 로드하는 데 사용된다.
- ImportBeanDefinitionRegistrar는 설정 클래스 외에도 구체적인 빈 정의를 등록하는 데 사용되며, 더 세밀한 제어가 가능하다.
[ ▷ 결론 ]
ImportBeanDefinitionRegistrar는 스프링에서 빈을 동적으로 정의하고 등록하는 강력한 기능을 제공한다. 이를 통해 정적인 설정만이 아닌, 애플리케이션의 상태나 조건에 따라 유연하게 빈을 정의할 수 있으며, 주로 프레임워크 개발자나 고급 사용자가 스프링 컨텍스트의 빈 관리를 확장할 때 사용한다.
'Spring Boot > Auto-Configuration' 카테고리의 다른 글
AutoConfigurationImportSelector (0) | 2024.10.21 |
---|---|
DeferredImportSelector (0) | 2024.10.21 |
@AutoConfigurationPackage (0) | 2024.10.21 |
Project Classpath (0) | 2024.10.21 |
@EnableAutoConfiguration (0) | 2024.10.21 |