Spring

@Conditional

Domae_ 2023. 12. 21. 23:25
SMALL
@Conditional

특정 조건일 때만 해당 기능을 활성화 할수 있고, 소스코드를 고치지 않고 가능하다. 특정상황일때만 특정 빈들을 등록해서 사용하도록 도와주는 기능이다.

 

package org.springframework.context.annotation;

public interface Condition {
	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

matches() 메서드가 true 를 반환하면 조건에 만족해서 동작하고, false 를 반환하면 동작하지 않는다.

ConditionContext : 스프링 컨테이너, 환경 정보등을 담고 있다.

AnnotatedTypeMetadata : 애노테이션 메타 정보를 담고 있다.

 

이것을 implements 하고 구현체를 만들고 @Conditional 어노테이션을 사용해 구현체를 등록 시키면 해당 클래스의 기능을

matches() 매서드에 따라 활성화 또는 비활성화 할 수 있다.

 

아래에 예시를 적어 놓았다.

 

@Conditional 예제
@Configuration
public class MemoryConfig {

    @Bean
    public MemoryController memoryController() {
        return new MemoryController(memoryFinder());
    }
    @Bean
    public MemoryFinder memoryFinder() {
        return new MemoryFinder();
    }

}

 

여기서 @Configuration 으로 서버가 실행될때 객체를 IoC 컨테이너에 Bean 으로 등록하여 사용할려고 하였다.

 

@Slf4j
public class MemoryCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String memory = context.getEnvironment().getProperty("memory");
        log.info("memory={}", memory);
        return "on".equals(memory);
    }

}

 

Condition 을 구현체로 새로 하나 만들고 환경설정값의 memory 값을 가져와 on 인지 아닌지 확인하는 matches() 만들었다.

 

@Configuration
@Conditional(MemoryCondition.class)
public class MemoryConfig {

    @Bean
    public MemoryController memoryController() {
        return new MemoryController(memoryFinder());
    }
    @Bean
    public MemoryFinder memoryFinder() {
        return new MemoryFinder();
    }

}

 

그리고 추가해서 실행해보니 기능 비활성화 되어서 객체가 등록이 안되는것을 확인할 수 있었다.

실제로 활성화하는 방법은 아래에 적어 놓겠다.

 

Environment 

 

스프링은 외부 설정을 추상화해서 Environment 로 통합했다. 그래서 다음과 같은 다양한 외부 환경 설정을 Environment 하나로 읽어들일 수 있다. 

#VM Options
#java -Dmemory=on -jar project.jar -Dmemory=on #Program arguments

# -- 가 있으면 스프링이 환경 정보로 사용
#java -jar project.jar --memory=on --memory=on

#application.properties
#application.properties에 있으면 환경 정보로 사용

 

위와 같이 사용할 수 있지만 application.properties 에 직접 추가해서 활성화 하겠다.

이런식으로 사용하게 되고, 이렇게 된 경우 로그가 on 이 되면 기능이 활성화가 된다.

 

@Conditional 다양한 기능들

 

@Configuration
//@Conditional(MemoryCondition.class)
@ConditionalOnProperty(name = "memory", havingValue = "on") //MemoryCondition.class 내용과 똑같다.
public class MemoryConfig {

    @Bean
    public MemoryController memoryController() {
        return new MemoryController(memoryFinder());
    }
    @Bean
    public MemoryFinder memoryFinder() {
        return new MemoryFinder();
    }

}

@ConditionalOnProperty(name = "memory", havingValue = "on") 은 우리가 MemoryCondition.class 에 만들어 놨던 기능을 @ConditionalOnProperty 이 어노테이션으로 쉽게 사용 할 수 있다.

 

왜 그럴까? 어노테이션 안으로 들어가 보면 알 수 있다.

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty

위와 같이 Conditional 이 되어 있고, 아래의 코드를 마저 보자

	@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
		List<AnnotationAttributes> allAnnotationAttributes = metadata.getAnnotations()
				.stream(ConditionalOnProperty.class.getName())
				.filter(MergedAnnotationPredicates.unique(MergedAnnotation::getMetaTypes))
				.map(MergedAnnotation::asAnnotationAttributes).toList();
		List<ConditionMessage> noMatch = new ArrayList<>();
		List<ConditionMessage> match = new ArrayList<>();
		for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
			ConditionOutcome outcome = determineOutcome(annotationAttributes, context.getEnvironment());
			(outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());
		}
		if (!noMatch.isEmpty()) {
			return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));
		}
		return ConditionOutcome.match(ConditionMessage.of(match));
	}

OnPropertyCondition class 이다. 구현체를 보게 되면 outcome 변수에 환결설정값을 가져와 for 문으로 확인하는 구문이 존재한다. 이러므로 우리가 구현했던 기능을 @ConditionalOnProperty 으로 쉽게 구현이 가능하다.

 


대표적인 편리한 기능들이다.

@ConditionalOnClass , @ConditionalOnMissingClass 클래스가 있는 경우 동작한다. 나머지는 그 반대 @ConditionalOnBean , @ConditionalOnMissingBean 빈이 등록되어 있는 경우 동작한다. 나머지는 그 반대 @ConditionalOnProperty 환경 정보가 있는 경우 동작한다. @ConditionalOnResource 리소스가 있는 경우 동작한다. @ConditionalOnWebApplication , @ConditionalOnNotWebApplication 웹 애플리케이션인 경우 동작한다. @ConditionalOnExpression SpEL 표현식에 만족하는 경우 동작한다.

 

공식 사이트 : https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-auto-configuration.condition-annotations 

 

 

반응형
LIST