본문 바로가기
  • 실행력이 모든걸 결정한다
Spring Series/Spring Framework

[Spring] @Enable 어노테이션을 이용한 설정 모듈화

by 김코더 김주역 2022. 10. 28.
반응형

@Enable 설정 어노테이션을 이용하면 xml 전용 태그를 만드는 것보다 훨씬 간단하게 설정 정보를 모듈화할 수 있다. 이번 포스팅에서는 bean 설정 정보를 재사용하는 방법과 이를 응용해서 설정 정보를 모듈화하는 방법을 알아볼 것이다.

 

1. @Import와 @Configuration 클래스 상속

1) @Import

- @Import를 이용해서 다른 @Configuration 클래스를 재사용하는 방법이 있다. 예를 들어, DBConfig라는 @Configuration 설정 클래스를 재사용하려면 아래 코드처럼 작성하면 된다.

@Configuration
@Import(DBConfig.class)
public class AppConfig {...}

- 매번 동일한 내용의 설정 정보만 사용할 수 있다는 한계가 있다.

 

2) @Configuration 클래스 상속

- @Configuration 클래스를 재사용하면서 일부 설정만 변경할 수 있는 방법이다. 아래와 같이 @Configuration 클래스를 상속받아서 필요한 @Bean 메소드만 오버라이딩해주면 된다.

@Configuration
public class AppConfig extends DBConfig {...}

- 상속은 한 개의 클래스만 가능하다. 따라서, 재사용할 설정 클래스가 여러 개인 경우에는 모두 적용이 불가능하다는 한계가 있다.

 

 

2. @Enable 전용 어노테이션

- @Enable로 시작하는 어노테이션은 대부분 @Import 어노테이션을 포함하고 있기 때문에 모듈화된 bean 설정 정보를 추가하는 기능이 있다. 게다가 어노테이션의 엘리먼트를 통해 옵션 정보를 제공할 수 있도록 해준다.

- 꼭 @Enable로 시작하지 않아도 되지만, 관례이기 때문에 이를 따르는 것이 좋다.

 

@Enable 생성 및 적용

- 아래 예시에서는 DBConfig 설정 클래스를 재사용하는 @EnableDB 어노테이션을 정의한다.

@Target(ElementType.TYPE) // 어노테이션의 적용 대상을 타입으로 지정
@Retention(RetentionPolicy.RUNTIME) // 어노테이션 정보의 유지 시점을 런타임으로 지정
@Import(DBConfig.class)
public @interface EnableDB {}

이 @EnableDB를 그대로 애플리케이션의 설정 클래스에 붙여주기만 하면 된다.

@Configuration
@EnableDB
public class AppConfig {...}

 

 

 

3. ImportAware

- @Configuration 클래스가 자신을 @Import 하는 어노테이션의 엘리먼트 값을 참조하기위해 구현하는 인터페이스다. 받은 엘리먼트 정보를 활용해서 필요한 설정 항목을 자유롭게 변경할 수 있다.

- ImportAware의 setImportMetadata()는 AnnotationMetadata 타입의 파라미터에 @Enable 어노테이션 메타정보를 담아서 전달한다. 

@Configuration
public class DBConfig implements ImportAware {
    ...
    
    @Override
    public void setImportMetadata(AnnotationMetadata importMetadata) {
        Map<String, Object> elements = importMetadata.getAnnotationAttributes(EnableDB.class.getName());
        String name=(String) elements.get("name");
        // name 엘리먼트 정보 활용
    }
}

필요한 설정 정보는 @Enable 어노테이션에 엘리먼트로 추가해서 사용하면 된다. 다음 예시는 어노테이션에 name 속성을 추가해서 사용하는 코드다.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(DBConfig.class)
public @interface EnableDB {
    String name();
}

 

@Configuration
@EnableDB(name="User")
public class AppConfig {...}

 

 

 

4. Bean 설정자

- 어노테이션의 엘리먼트만으로는 설정이 충분하지 않을 때 사용할 수 있다.

- @Enable 어노테이션과 함께 자바 코드를 이용한 설정 정보의 확장 포인트다. 그래서 인터페이스로 정의되는 것이며, 여러 개의 bean 설정자를 동시에 구현해서 적용할 수 있다.

- Bean 설정자를 적용하려면 bean 설정자의 구현체가 bean으로 등록되어 있어야 한다. 보통 @Configuration 설정 클래스가 bean 설정자를 구현해서 사용한다. Spring이 제공하는 bean 설정자는 WebMvcConfigurer가 대표적이다. WebMvcConfigurer는 bean 설정을 @EnableWebMvc가 제공하는 기본 bean 설정 정보에 추가해준다.

- 다음과 같이 bean 설정자를 정의할 수 있다. 

public interface HelloConfigurer {
    void configName(Hello hello);
}

그리고 bean 설정자는 다음과 같이 @Configuration 설정 클래스에서 구현할 수 있다. 설정 정보를 추가하기 위한 어노테이션도 붙여주자.

@Configuration
@EnableHello
public class AppConfig implements HelloConfigurer {
    @Override
    public void configName(Hello hello) {
        hello.setName("David");
    }
}

 

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(HelloConfig.class)
public @interface EnableHello {}

이제 HelloConfig 클래스에서는 HelloConfigurer 타입을 주입받아서 configName() 메소드를 사용할 수 있다.

@Configuration
public class HelloConfig {
    @Autowired HelloConfigurer helloConfigurer;
    
    @Bean
    Hello hello() {
        Hello hello = new Hello();
        helloConfigurer.configName(hello);
        return h;
    }
}

 

 

 

5. ImportSelector와 ImportBeanDefinitionRegistrar

- 옵션에 따라 bean의 타입이나 구성이 아예 바뀌어야 한다면 @Import 기반의 방법으로는 충분하지 않고, ImportSelector또는 ImportBeanDefinitionRegistrar을 사용하면 된다.

 

1) ImportSelector

- @Enable 어노테이션의 메타정보를 이용해서 @Import에 적용할 @Configuration 설정 클래스를 결정해주는 오브젝트다.

- ImportSelector의 구현체를 생성해서 selectImports() 메소드를 구현하면 된다. selectImports() 메소드는 파라미터로 제공받은 AnnotationMetadata 타입의 어노테이션 메타정보를 활용해서, 사용할 @Configuration 설정 클래스들을 조합하고 이들을 이름 배열로 반환해주도록 구현해야 한다.

 

예를 들어, 아래 코드의 경우를 살펴보자. HelloConfig1, HelloConfig2는 모두 @Configuration 설정 클래스라고 가정하자. @EnableHello의 mode 엘리먼트 값이 "mode1"이라면 HelloConfig1 클래스의 풀네임을 반환하고, 그 외에는 HelloConfig2 클래스의 풀네임을 반환하도록 selectImports() 메소드를 구현한 예시다. 패키지의 이름을 포함하기 때문에 필자는 풀네임이라고 표현했다.

public class HelloSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        String mode=(String)importingClassMetadata.getAnnotationAttributes(EnableHello.class.getName()).get("mode");
        if(mode.equals("mode1") return new String[] {HelloConfig1.class.getName()};
        else return new String[] {HelloConfig2.class.getName()};
    }
}

 그리고 @Enable 어노테이션에 있는 @Import에는 ImportSelect의 구현체를 넣어주면 되고, mode 엘리먼트를 활용할 수 있도록 다음과 같이 작성했다.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(HelloSelector.class)
public @interface EnableHello {
    String mode();
}

아래 코드의 경우에는 mode 엘리먼트 값이 "mode1"이기 때문에 AppConfig는 HelloConfig1 설정 클래스를 사용하게 될 것이다.

@Configuration
@EnableHello(mode="mode1")
public class AppConfig {...}

 

 

2) ImportBeanDefinitionRegistrar

- 옵션에 따라 복잡한 bean 설정 조합을 만들어야 하는 경우에 활용할 수 있다. Bean 메타정보를 생성하는 코드를 직접 작성해야 한다.

- 활용하기 어렵다는 이유로 사용을 권장하지는 않기 때문에 이 포스팅에서 자세히 다루지는 않겠다.

 

 

● 참고 자료 : 토비의 스프링 3.1 Vol.2

 

반응형

'Spring Series > Spring Framework' 카테고리의 다른 글

[Spring] MockMvc 테스트  (0) 2022.12.20
[Spring] ModelMapper  (0) 2022.12.15
[Spring] 캐시 추상화  (0) 2022.10.27
[Spring] Spring의 Task와 Scheduling  (0) 2022.10.25
[Spring] 리모팅과 EJB  (0) 2022.10.25

댓글