아마 개발언어를 막 배우기 시작하면 변수 선언하는 걸 제외하고는 if문이 가장 먼저가 아닐까합니다. 조건을 통해 다양한 논리 회로를 구성할 수 있죠.
@Conditional Annotation도 같은 맥락입니다 조건에따라 @Bean을 컨트롤 가능합니다.
Conditional Annotation 을 사용하기 위해서는 Condition class를 상속받아야합니다.
때문에 이름 명명 규칙도 MyonCondition은 class로 작성, 'nal'이 붙은 ConditionalMyOnClass는 Annotation으로 이름을 만들어 사용합니다. (Conditional은 앞에 붙고, Condition은 뒤에 붙네요.)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Conditional(ConditionalMyOnClass.MyOnclassCondition.class)
public @interface ConditionalMyOnClass {
String value();
static class MyOnclassCondition implements Condition{
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalMyOnClass.class.getName());
return ClassUtils.isPresent((String)attributes.get("value"), context.getClassLoader());
}
}
}
저는 그냥 inner class 로 만들었는데 따로 파일 만들어서 class를 빼도 됩니다.
여기서 보실 것은 Condition은 matches를 override하며 리턴값은 boolean입니다.
ClassUtils.isPresent를 통해 클래서 이름이 있는지 없는지를 참 거짓으로 넘겨줄 겁니다.
위에서 만들 Annotaion을 사용합니다. 저는 다음과 같이 서버를 Tomcat, jetty를 선택하여 로딩할 수 있게 했습니다.
@ConditionalMyOnClass("org.apache.catalina.startup.Tomcat")
public class TomcatWebServerConfig {
@Bean("tomcatServer")
public ServletWebServerFactory servletWebServerFactory(){
return new TomcatServletWebServerFactory();
}
}
@ConditionalMyOnClass("org.eclipse.jetty.server.Server")
public class JettyWebServerConfig {
@Bean("jettyServer")
public ServletWebServerFactory servletWebServerFactory(){
return new JettyServletWebServerFactory();
}
}
ClassUtils.isPresent에 의해서 해당 값이 있으면 Bean을 등록하게 됩니다. 만약 둘 다 존재하면 당연히 에러가 나겠죠?
.NoUniqueBeanDefinitionException: No qualifying bean of type
'org.springframework.boot.web.servlet.server.ServletWebServerFactory'
available: expected single matching bean but found 2: tomcatServer,jettyServer
위에서 문제를 해결하기 위해서는 하나를 지우면 됩니다. 근데 이러면 의미가 없죠?
그래서 옵션을 줄 수있는 @ConditionalOnMissingBean 이 있습니다.
@ConditionalMyOnClass("org.eclipse.jetty.server.Server")
public class JettyWebServerConfig {
@Bean("jettyServer")
@ConditionalOnMissingBean
public ServletWebServerFactory servletWebServerFactory(){
return new JettyServletWebServerFactory();
}
}
다음과 같이 Method위에 @ConditionalOnMissingBean를 붙히면 ServletWebServerFactory bean이 사전에 등록되어있는게 없을때 이 매서드를 실행하고 Bean으로 등록되게 됩니다. 해당 글에서는 Tomcat이 실행 되겠네요.
이 밖에 비슷한 Annotaion이 많습니다.
@ConditionalOnWebApplication : 프로젝트가 웹 애플리케이션이면 Bean 등록
@ConditionalOnBean : 해당 Bean이 존재하면 자동 설정 등록
@ConditionalOnMissingBean : 해당 Bean이 존재하지 않으면 자동설정 등록
@ConditionalOnClass : 해당 클래스가 존재하면 자동설정 등록
@ConditionalOnMissingClass: 해당 클래스가 클래스 패스에 존재하지 않으면 Bean 등록
@ConditionalOnResource : 해당 자원(file 등)이 존재하면 자동설정 등록
@ConditionalOnProperty : 설정한 프로퍼티가 존재하면 자동설정 등록
@Conditional을 사용하는 목적은 조건을 위한 것만은 아닙니다. 자동으로 구성되는 Spring boot의 시스템을 커스텀 하는것에도 목적이 있습니다. 사용자구성 빈이 자동구성보다 우선 순위에 있기 때문에 위 처럼 코드를 짜면 Spring boot의 tomcat서버는 실행되지 않습니다. 이런 이유로 경우에 따라선 사용자 Annotation과 @Conditional을 사용하는 것은 필요없는 @AutoConfiguration을 정리하여 메모리를 아낄 수 있지만, 잘못건드리면 Application이 작동이 안될 수도 있습니다.
참고자료들___
(참고) @Conditional, @ConditionalOnXXX