2023. 5. 4. 13:35ㆍSpring Boot/Spring Boot Auto Configuration
@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"
};
}
}
ServiceConfigA.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ServiceConfigA {
@Bean
public String serviceA() {
return "Service A Bean";
}
}
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 > Spring Boot Auto Configuration' 카테고리의 다른 글
@EnableAutoConfiguration (0) | 2024.10.16 |
---|---|
@SpringBootConfiguration (0) | 2024.10.16 |
SpringApplication.run (0) | 2024.10.16 |
@SpringBootApplication (0) | 2024.10.16 |
Classpath (0) | 2024.10.16 |