Spring Boot @Import 어노테이션을 활용한 자동 구성

1. 서론

Spring Boot에서 @Import 어노테이션은 지정한 클래스들을 Spring IoC 컨테이너에 자동으로 등록하는 강력한 기능을 제공한다. 이 어노테이션은 크게 4가지 방식으로 활용할 수 있다:

  1. 일반 Bean 클래스 직접 등록
  2. Configuration 클래스 등록
  3. ImportSelector 구현 클래스 등록
  4. ImportBeanDefinitionRegistrar 구현 클래스 등록

@Import 어노테이션 정의

@Import 어노테이션의 정의를 살펴보면, Class 타입의 배열을 파라미터로 받는 간단한 구조를 가지고 있다:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
    Class<?>[] value();
}

2. 실습 프로젝트 구성

2.1 프로젝트 의존성 설정

Spring Boot 프로젝트에 Lombok 의존성을 추가로 설정한다:

<?xml version="1.0" encoding="UTF-8"?>
<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>
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.14</version>
    </parent>
    
    <groupId>com.example</groupId>
    <artifactId>import-demo</artifactId>
    <version>1.0.0</version>
    
    <properties>
        <java.version>17</java.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

2.2 Member 엔티티 클래스 생성

package com.example.importdemo;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class Member {
    private String name;
    private Integer age;
    
    public Member() {
        this.name = "홍길동";
        this.age = 25;
    }
}

2.3 Bean 직접 등록 테스트

package com.example.importdemo;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Import;

@Slf4j
@Import(Member.class)
@SpringBootApplication
public class ImportDemoApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = 
            SpringApplication.run(ImportDemoApplication.class, args);
        
        Member member = context.getBean(Member.class);
        log.info("registered member: {}", member);
    }
}

2.4 Configuration 클래스 등록 테스트

package com.example.importdemo;

import org.springframework.context.annotation.Bean;

public class MemberConfig {
    @Bean
    public Member createMember() {
        return new Member("김철수", 30);
    }
}

Application 클래스의 @Import를 다음과 같이 변경:

@Import(MemberConfig.class)

2.5 ImportSelector 구현 테스트

package com.example.importdemo;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class MemberImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.example.importdemo.Member"};
    }
}

Application 클래스의 @Import 변경:

@Import(MemberImportSelector.class)

2.6 ImportBeanDefinitionRegistrar 구현 테스트

package com.example.importdemo;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

public class MemberBeanRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata, 
            BeanDefinitionRegistry registry) {
        
        BeanDefinition definition = 
            BeanDefinitionBuilder.rootBeanDefinition(Member.class).getBeanDefinition();
        registry.registerBeanDefinition("member", definition);
    }
}

2.7 각 방식의 장점 비교

방식 특징
Bean 직접 등록 간단하고 직관적인 사용법
Configuration 클래스 여러 Bean을集中管理 가능
ImportSelector 동적으로 클래스 로드 가능, 외부 설정 파일 활용
ImportBeanDefinitionRegistrar Bean 정의 세부 제어 가능

3. 커스텀 Enable 어노테이션 만들기

실무에서는 외부 라이브러리의 Bean을 사용할 때 어떤 설정 클래스를 import해야 하는지 알기 어렵다. 이 문제를 해결하기 위해 커스텀 Enable 어노테이션을 생성한다:

package com.example.importdemo;

import org.springframework.context.annotation.Import;
import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MemberImportSelector.class)
public @interface EnableMember {}

Application 클래스에서 사용:

@Slf4j
@EnableMember
@SpringBootApplication
public class ImportDemoApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = 
            SpringApplication.run(ImportDemoApplication.class, args);
        
        Member member = context.getBean(Member.class);
        log.info("member info: {}", member);
    }
}

4. 모듈 간 Bean 공유 실습

4.1 member-core 모듈 구성

Bean을 제공하는 독립 모듈을 생성한다:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>
    
    <groupId>com.example</groupId>
    <artifactId>member-core</artifactId>
    <version>1.0.0</version>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

4.2 Member 엔티티 정의

package com.example.member;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Member {
    private Long id;
    private String name;
    private String email;
    private String department;
    
    public Member getDefaultMember() {
        return new Member(1L, "관리자", "admin@example.com", "인사팀");
    }
}

4.3 member-core 모듈의 Enable 어노테이션 추가

package com.example.member;

import org.springframework.context.annotation.Import;
import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(Member.class)
public @interface EnableMemberConfig {}

4.4 member-core Application 클래스

package com.example.member;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@Slf4j
@SpringBootApplication
public class MemberCoreApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = 
            SpringApplication.run(MemberCoreApplication.class, args);
        
        Member member = context.getBean(Member.class);
        log.info("default member: {}", member.getDefaultMember());
    }
}

4.5 member-client 모듈에서 사용

<dependencies>
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>member-core</artifactId>
        <version>1.0.0</version>
    </dependency>
</dependencies>

member-client의 Application 클래스:

package com.example.client;

import com.example.member.EnableMemberConfig;
import com.example.member.Member;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@Slf4j
@EnableMemberConfig
@SpringBootApplication
public class MemberClientApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = 
            SpringApplication.run(MemberClientApplication.class, args);
        
        Member member = context.getBean(Member.class);
        log.info("member from core: {}", member.getDefaultMember());
    }
}

5. 정리

Spring Boot의 자동 구성 메커니즘에서 @Import 어노테이션은 핵심적인 역할을 담당한다. 특히 ImportSelector 방식은 META-INF/spring.factories 또는 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 파일에 지정된 설정 클래스를 로드하는 방식으로广泛应用된다.

외부 라이브러리의 Bean을 사용하려면 해당 라이브러리에서 제공하는 Enable 어노테이션을 활성화해야 하며, 이렇게 하면 개발자가 별도의 Bean 등록 작업을 수행할 필요 없이 자동으로 Spring 컨테이너에 등록된다.

태그: spring-boot autowire import-annotation importselector importbeandefinitionregistrar

6월 12일 00:07에 게시됨