오늘 퇴근전에 팀장님이 @Configuration 관련해서 물어봤다.
일단 될거라고는 했지만 그 보다 더 생각난게 @Import라는 어노테이션이다. 물론 그때는 있다는걸 알고만 있었지 확실하게 아는게 아니여서...
Import를 알아보기전에 상속에 대해 보자.
public class SuperConfig {
@Bean
public String hello(){
return "hello";
}
}
@Configuration
public class ChildConfig extends SuperConfig {
@Bean
public String world() {
return "world";
}
}
위와 같은 설정 정보가 있다고 가정하자. SuperConfig에는 @Configuration이 없지만 어차피 인스턴스는 ChildConfig로 만들어도 SuperConfig를 만들수 있기에 가능하다.
public static void main(String[] args) {
ApplicationContext applicationContext = SpringApplication.run(SpringBootApplication.class, args);
String hello = applicationContext.getBean("hello", String.class);
String world = applicationContext.getBean("world", String.class);
System.out.println(hello);
System.out.println(world);
}
위와 같이 찍어보면 hello와 world 두개모두 출력된다. 물론 재정의도 가능하다.
@Configuration
public class ChildConfig extends SuperConfig {
@Bean
public String world() {
return "world";
}
@Override
public String hello(){
return "hello1";
}
}
ChildConfig 에 hello를 @Override만 해도 된다. 다시 출력해보면 hello1 과 world가 출력된다.
그건 그렇고 자바는 다중상속이 안된다. 아무튼 다중상속이 안되니 다른 클래스를 상속받지 못한다. 그리고 설정 정보 같은 경우에는 Override 하면 안되는 정보도 있어 (변경시 설정 정보가 꼬일수가 있기에) final을 메서드에 붙이면 아래와 에러가난다.
@Bean method hello must not be private or final; change the methods modifiers to continue
그래서 상속보다는 좀 더 유연하고 확장성이 있는 @Import를 알아보자.
@Import(Config2.class)
@Configuration
public class Config1 {
@Bean
public String hello1() {
return "import Hello";
}
}
public class Config2 {
@Bean
public String world1() {
return "import World";
}
}
상속만큼 간편하게 사용가능하다. @Import의 설정 클래스정보만 입력해주면된다. 실제 실행을 해보면 import Hello 와 import World 둘다 보다 출력된다. @Import의 좋은 점은 어노테이션에도 사용가능하다. 예를들어보자.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(value = Config1.class)
public @interface EnableConfig {
}
@Import(Config2.class)
public class Config1 {
@Bean
public String hello1() {
return "enable import Hello";
}
}
public class Config2 {
@Bean
public String world1() {
return "enable import World";
}
}
또한 배열로 되어있어 여러개도 가능하다. 여러개의 설정 정보를 넣어도 된다.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({Config1.class, Config2.class})
public @interface EnableConfig {
}
public class Config1 {
@Bean
public String hello1() {
return "enable import Hello";
}
}
public class Config2 {
@Bean
public String world1() {
return "enable import World";
}
}
@Enable* 어노테이션은 Spring에서 권장하는 네이밍이지 굳이 Enable을 사용하지 않아도 괜찮다. 우리가 흔히 보던 @EnableWebMvc 도 위와 같이 되어있다.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
또한 @Import 어노테이션의 메타 설정을 넣어서 그때 그때마다 설정 정보를 바꿀수도 있다. 예를들어보자.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({Config1.class, Config2.class})
public @interface EnableConfig {
boolean show() default true;
}
show 라는 메타정보를 넣어보자. 하는일은 딱히 없다. 그냥 true이면 show를 리턴하고 false이면 hide를 리턴하는 그런 간단한 코드를 만들예정이다.
//...
@EnableConfig(show = false)
//...
@Configuration
public class Config2 implements ImportAware {
boolean show;
@Bean
public String world1() {
if(show){
return "show";
}
return "hide";
}
@Override
public void setImportMetadata(AnnotationMetadata annotationMetadata) {
Map<String, Object> metaData = annotationMetadata.getAnnotationAttributes(EnableConfig.class.getName());
this.show = (boolean)metaData.get("show");
}
}
ImportAware 인터페이스를 구현하면 원하는 설정에 따라 show를 보여줄지 hide를 보여줄지 선택하면 된다.
ImportAware 구현하기 위해서는 @Configuration 달아줘야 작동한다. 이유는 잘모르겠다....흠
setImportMetadata() 메서드가 world1() 메서드 보다 먼저 호출 되니 걱정 하지 않아도 된다.
우리는 @Import를 사용해서 좀 더 유연하고 확장성있게 사용해봤다.
만약 사내에서 공통적으로 쓰는 설정이 있다면 위와 같이 만들어 놓고 사용하면 좀 더 편리하게 개발 할 수 있을 듯 하다.
물론 상속을 쓰지 말라는건 아니다. 써도 상관없지만 좀 더 확장성있게 @Import도 사용해보자.