@Conditional Annotation

떡ol·2023년 4월 6일
0

아마 개발언어를 막 배우기 시작하면 변수 선언하는 걸 제외하고는 if문이 가장 먼저가 아닐까합니다. 조건을 통해 다양한 논리 회로를 구성할 수 있죠.
@Conditional Annotation도 같은 맥락입니다 조건에따라 @Bean을 컨트롤 가능합니다.

1. @Conditional, Condition class 만들기

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를 통해 클래서 이름이 있는지 없는지를 참 거짓으로 넘겨줄 겁니다.

2. @Conditional 사용하기

위에서 만들 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

3. Spring boot에서 상속된 @Conditional Annotation

위에서 문제를 해결하기 위해서는 하나를 지우면 됩니다. 근데 이러면 의미가 없죠?
그래서 옵션을 줄 수있는 @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 : 설정한 프로퍼티가 존재하면 자동설정 등록

4. 결론

@Conditional을 사용하는 목적은 조건을 위한 것만은 아닙니다. 자동으로 구성되는 Spring boot의 시스템을 커스텀 하는것에도 목적이 있습니다. 사용자구성 빈이 자동구성보다 우선 순위에 있기 때문에 위 처럼 코드를 짜면 Spring boot의 tomcat서버는 실행되지 않습니다. 이런 이유로 경우에 따라선 사용자 Annotation과 @Conditional을 사용하는 것은 필요없는 @AutoConfiguration을 정리하여 메모리를 아낄 수 있지만, 잘못건드리면 Application이 작동이 안될 수도 있습니다.




참고자료들___
(참고) @Conditional, @ConditionalOnXXX

profile
하이

0개의 댓글