@Conditional 어노테이션이란?
컴포넌트의 Bean 등록여부에 조건을 달 수 있게하는 어노테이션이다.
스프링은 IOC컨테이너에 객체를 Bean으로 등록하여 사용한다. @Component로 선언된 클래스는 모두 Bean으로 등록되는데 여기에 조건부를 달 수가 있다. 즉, A경우에 등록을 하되, B경우에는 등록하지 않도록 설정할 수 있다.
예를 들어보자.
Spring기반의 웹사이트가 있다. 여기에 추가 기능을 넣으려고 한다.
Spring은 추가기능의 설정파일을 읽고 POJO에서 Bean을 IOC컨테이너에 등록한다. 이때 설정파일을 조건부로 읽는다면 필요할 때만 IOC컨테이너에 Bean을 등록할 수 있다. 여기서 @Conditional 어노테이션을 사용한다.
구현
웹사이트 실행시 옵션으로 -Dmemory=on인 경우만, 메모리량을 보여주는 기능을 추가하려고 한다.
간단히 SpringBoot 프로젝트를 생성한다.
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
SpringBoot 프로젝트에 생성되어 있는 DemoApplication 클래스이다. @SpringBootApplication은 컴포넌트스캔 기능을 가지고 있다. 컴포넌트 스캔은 하위 디렉토리에 위치한 @Component로 명시된 클래스를 Bean으로 등록하는 기능이다.
설정클래스는 @Configuration로 명시되어 있지만, 이것 또한 @Component이다. 그러므로 하위디렉토리에 설정클래스를 위치시킨다.
package com.example.demo.config;
import com.example.memory.MemoryController;
import com.example.memory.MemoryFinder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MemoryConfig {
@Bean
public MemoryController memoryController(){ return new MemoryController(memoryFinder()); }
@Bean
public MemoryFinder memoryFinder(){ return new MemoryFinder(); }
}
MemoryConfig는 MemoryController와 MemoryFinder를 Bean으로 등록한다.
위 코드는 조건부가 아니다. 컴포넌트 스캔시, 조건을 타지 않는다.
그러므로 웹사이트 실행시 옵션으로 -Dmemory=on인 경우에만 Bean으로 등록하도록 설정해야 한다.
package com.example.demo.config;
import com.example.memory.MemoryCondition;
import com.example.memory.MemoryController;
import com.example.memory.MemoryFinder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Configuration
@Conditional(MemoryCondition.class) // 조건 추가!
public class MemoryConfig {
@Bean
public MemoryController memoryController(){ return new MemoryController(memoryFinder()); }
@Bean
public MemoryFinder memoryFinder(){ return new MemoryFinder(); }
}
@Conditional 어노테이션이 추가되었다.
MemoryCondition.class는 Condition 인터페이스를 구현한 구현체이다. Condition 인터페이스는 Boolean을 반환하는 메소드, 한 개만 가지고 있는 함수형 인터페이스이다.
public class MemoryCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String memory = context.getEnvironment().getProperty("memory"); // 옵션값 가져오기
return "on".equals(memory); // "on"이면 스캔 O , 아니면 스캔 X
}
}
@Conditional(MemoryCondition.class)는 MemoryCondition의 matches 메소드의 return값을 받아 컴포넌트 스캔여부를 판단한다. "on"이면 스캔되어 IOC컨테이너에 Bean으로 등록되고 on이 아니면 IOC컨테이너에 Bean으로 등록되지 않는다.
그럼 옵션으로 -Dmemory=on을 추가해보자.
IntelliJ 우측 상단에 Edit Configurations을 클릭한다.
1. Modify Options를 클릭하고 Add VM options를 클린한다. 그리고 -Dmemory=on 옵션을 추가한다.
웹어플리케이션이 실행되면
DemoApplication은 MemoryConfig를 컴포넌트 스캔한다. ( memory 디렉토리는 DemoApplication의 하위디렉토리가 아니므로, 컴포넌트 스캔을 하지 않는다. 그저 POJO이다.
MemoryConfig.java
@Configuration
@Conditional(MemoryCondition.class) // 컴포넌트 스캔시 조건검사!
public class MemoryConfig {
@Bean
public MemoryController memoryController(){ return new MemoryController(memoryFinder()); }
@Bean
public MemoryFinder memoryFinder(){ return new MemoryFinder(); }
}
이때, @Conditional에서 조건을 검사한다. MemoryCondition 객체를 생성하여 boolean을 return한다.
MemoryCondition.java
public class MemoryCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String memory = context.getEnvironment().getProperty("memory"); // 옵션값 가져오기
return "on".equals(memory); // "on"이면 스캔 O , 아니면 스캔 X
}
}
ApplicationContext 즉, IOC컨테이너에서 옵션값을 가져온다. memory 속성의 값을 가져와 on 여부를 return한다. True가 반환되면 MemoryConfig.java에 정의된 @Bean은 IOC컨테이너에 등록된다.
그럼 MemoryController 가 Bean으로 등록되므로, 이제 URL로 해당 기능에 접근이 가능해진다.
localhost:8080/memory 로 접근하지
사용중인 Memory 양과 최대 사용가능한 Memory양이 출력되었다.
추가했던 VM option을 제거해보겠다.
MemoryController가 Bean으로 등록되지 않아 접근이 불가능하다.
이와같이,
@Conditional 어노테이션을 사용하면 조건부로 Bean을 등록할 수 있다.
하지만 Condition 인터페이스의 구현체를 만들어야 한다는 문제가 있다.
SpringBoot는 이를 해결할 다양한 어노테이션을 가지고 있다.
@Configuration
//@Conditional(MemoryCondition.class)
@ConditionalOnProperty(name = "memory", havingValue = "on")
public class MemoryConfig {
@Bean
public MemoryController memoryController(){ return new MemoryController(memoryFinder()); }
@Bean
public MemoryFinder memoryFinder(){ return new MemoryFinder(); }
}
@ConditionalOnProperty 어노테이션으로 속성과 속성값을 지정해주면 된다. 자동으로 Condition 구현체가 생성되어 조건을 검사한다. 그럼 이를 실행해보자.
MemoryCondition 클래스를 구현하지 않아도 이와같이, 조건검사가 이루어졌음을 확인할 수 있다.
참고자료
'SPRING > Spring Boot' 카테고리의 다른 글
[SpringBoot] SpringBoot 자동구성 원리 (4) | 2023.05.04 |
---|---|
[SpringBoot] 자동구성 라이브러리 만들기 (0) | 2023.04.29 |
[SpringBoot] 실행가능한 JAR (2) | 2023.04.27 |
[SpringBoot] SpringBoot 웹환경 구성 원리 (0) | 2023.04.27 |
[SpringBoot] 내장톰캣에서 Spring Boot클래스 만들기 (0) | 2023.04.02 |