@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 |
댓글