Spring DI, AOP

8. @Import와 @Conditional - 패스트캠퍼스 백엔드 부트캠프 3기

gkss2tpt 2025. 2. 24. 15:57

1. Condition과 @Conditional

  • 조건에 따라 빈의 등록 여부를 결정. @Bean, @Conponent와 같이 사용
  • Condition의 matches()를 구현한 클래스를 @Conditional로 지정
  • 조건부 빈 등록
    • 개별 빈 : @Bean + @Component + @Conditional
    • 빈 여러개(그룹) : @Configuration + @Import + ImportSelector(조건부 결정)
  • Starter : 의존 라이브러리 자동관리
  • AutoConfiguration : 빈 등록 자동관리

  • 그 외의 애너테이션

 

2. @Import와 ImportSelector 

  • 조건에 따라 다른 Configuration(자바설정 : 빈 여러개 정의)을 적용할 때 사용
  • ImportSelector를 구현하고 이를 @Import하는 애너테이션을 작성해서 적용

@ToString
@Component
@Conditional(TrueCondition.class)
class Engine{}

@ToString
@Component
@Conditional(FalseCondition.class)
class Door{}

class TrueCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return true;
    }
}

class FalseCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return false;
    }
}


//@SpringBootApplication    // 은 아래의 3개 애너테이션을 붙인것과 동일
@SpringBootConfiguration    // @Configuration하고 동일
@EnableAutoConfiguration
@ComponentScan
public class Main {

    public static void main(String[] args) {
        ApplicationContext ac = SpringApplication.run(Main.class, args);
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();

        System.out.println("ac = " + ac);

        Arrays.sort(beanDefinitionNames);   // 빈 목록이 담긴 배열을 정렬
        Arrays.stream(beanDefinitionNames)  // 배열을 스트림으로 변환
                .filter(b->!b.startsWith("org"))
                .forEach(System.out::println);  // 스트림의 요소를 하나씩 꺼내서 출력

    }

    @Bean
    MyBean myBean(){return new MyBean();}
}

class MyBean{}

  • door는 빈등록이 안된 모습...
class OSCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment env = context.getEnvironment();
//        System.out.println("System.getProperties() = " + System.getProperties());

        return env.getProperty("os.name").equals("Windows 11");
    }
}
  • System.getProperties()로 os.name이 Windows 11인 Bean들을 뽑아보면...

  • engine과 door가 잘 등록이되었다.

  • 자바 설정 파일 추가
@ToString
class Car{}
@ToString
class SportsCar extends Car{}
@ToString
class SportsCar2 extends Car{}

@ToString
@Component
@Conditional(OSCondition.class)
class Engine{}

@ToString
@Component
@Conditional(OSCondition.class)
class Door{}

class TrueCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return true;
    }
}

class OSCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment env = context.getEnvironment();
//        System.out.println("System.getProperties() = " + System.getProperties());

        return env.getProperty("os.name").equals("Windows 11");
    }
}


class MainConfig{ @Bean Car car() {return new Car();} }
class Config1 { @Bean Car sportsCar() {return new SportsCar();} }
class Config2 { @Bean Car sportsCar() {return new SportsCar2();} }
//@SpringBootApplication    // 은 아래의 3개 애너테이션을 붙인것과 동일
@SpringBootConfiguration    // @Configuration하고 동일
@EnableAutoConfiguration
@ComponentScan
public class Main {

    public static void main(String[] args) {
//        ApplicationContext ac = SpringApplication.run(Main.class, args);
        ApplicationContext ac = new AnnotationConfigApplicationContext(MainConfig.class, Config1.class, Config2.class);   // 자바 설정을 이용하는 AC
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();

        System.out.println("ac = " + ac);

        Arrays.sort(beanDefinitionNames);   // 빈 목록이 담긴 배열을 정렬
        Arrays.stream(beanDefinitionNames)  // 배열을 스트림으로 변환
                .filter(b->!b.startsWith("org"))
                .filter(b -> !b.contains("Main"))
                .forEach(System.out::println);  // 스트림의 요소를 하나씩 꺼내서 출력
        
        System.out.println("ac.getBean(\"sportsCar\") = " + ac.getBean("sportsCar"));
    }

    @Bean
    MyBean myBean(){return new MyBean();}
}

  • SportsCar2가 나오는이유는 덮어쓰기 때문
@Import({Config1.class, Config2.class})
class MainConfig{ @Bean Car car() {return new Car();} }

class Config1 { @Bean Car sportsCar() {return new SportsCar();} }
class Config2 { @Bean Car sportsCar() {return new SportsCar2();} }
ApplicationContext ac = new AnnotationConfigApplicationContext(MainConfig.class);   // 자바 설정을 이용하는 AC
  • 위와 같음

  • MainConfig와 Config1 등록
//@Import({Config1.class, Config2.class})
@Import(MyImportSelector.class)
class MainConfig{ @Bean Car car() {return new Car();} }

class Config1 { @Bean Car sportsCar() {return new SportsCar();} }
class Config2 { @Bean Car sportsCar() {return new SportsCar2();} }

class MyImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {

        return new String[]{Config1.class.getName()};
    }
}
  • Config1만 나오는 모습...

  • 사용자 정의 애너테이션을 이용한 등록
//@Import({Config1.class, Config2.class})
//@Import(MyImportSelector.class)
@EnableMyAutoConfiguration("test")
class MainConfig{ @Bean Car car() {return new Car();} }

class Config1 { @Bean Car sportsCar() {return new SportsCar();} }
class Config2 { @Bean Car sportsCar() {return new SportsCar2();} }

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MyImportSelector.class)
@interface EnableMyAutoConfiguration {
    String value() default "";
}

class MyImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        AnnotationAttributes attr = AnnotationAttributes
                .fromMap(importingClassMetadata.getAnnotationAttributes(EnableMyAutoConfiguration.class.getName()));
//        String mode = "test";
        String mode = attr.getString("value");
        if (mode.equals("test"))
            return new String[]{Config1.class.getName()};
        else
            return new String[]{Config2.class.getName()};

    }
}

  • Config1이 잘나온다
//@Import({Config1.class, Config2.class})
//@Import(MyImportSelector.class)
@EnableMyAutoConfiguration("test2")
class MainConfig{ @Bean Car car() {return new Car();} }
  • test를 바꾸면?

  • Config2가 잘나온다