스프링 부트 - 자동구성 2
- Spring/Spring
- 2023. 9. 17.
자동 구성 직접 만들기 - 기반 예제
자동 구성에 대해서 자세히 알아보기 위해 간단한 예제를 만들어보자. 실시간으로 자바의 메모리 사용량을 웹으로 확인할 수 있는 기능을 제공함.
@Getter
public class Memory {
private Long used;
private Long max;
public Memory(Long used, Long max) {
this.used = used;
this.max = max;
}
@Override
public String toString() {
return "Memory{" +
"used=" + used +
", max=" + max +
'}';
}
}
@Slf4j
public class MemoryFinder {
public Memory get() {
long max = Runtime.getRuntime().maxMemory();
long total = Runtime.getRuntime().totalMemory();
long free = Runtime.getRuntime().freeMemory();
long used = total - free;
return new Memory(used, max);
}
// 빈으로 등록할 것인데, 잘 등록되었는지 확인하기 위해 로그를 남김
@PostConstruct
public void init() {
log.info("init memory Finder");
}
}
@Slf4j
@RestController
@RequiredArgsConstructor
public class MemoryController {
private final MemoryFinder memoryFinder;
@GetMapping("/memory")
public Memory system() {
final Memory memory = memoryFinder.get();
log.info("memory = {}", memory);
return memory;
}
}
예제 코드는 다음과 같이 작성한다.
- Memory
- MemoryFinder
- MemoryController
@Configuration
public class MemoryConfig {
@Bean
public MemoryController memoryController() {
return new MemoryController(memoryFinder());
}
@Bean
public MemoryFinder memoryFinder() {
return new MemoryFinder();
}
}
- 현재 Memory 패키지는 분리되어있음. 분리된 Memory 패키지는 분리해서 라이브러리로 사용하기도 편리함.
- Memory 패키지에 있는 기능을 가져다 쓰고 싶다면, 스프링 컨테이너에 스프링빈으로 등록해야한다.
현재의 패키지 구조는 다음과 같다.
패키지 위치
- 패키지를 memory / hello로 나눈 이유는 두 개가 완전히 별도의 모듈이라는 것을 의미한다.
- 결론적으로 hello에서 memory 패키지의 기능을 불러다 사용한다고 이해하면 된다.
@Conditional
- 앞서 만든 메모리 조회 기능을 항상 사용하는 것이 아니라 특정 조건일 때만 해당 기능이 활성화 되도록 해보자.
- 개발 서버에서 확인 용도로만 해당 기능을 사용, 운영 서버에서는 해당 기능을 사용하지 않는 것.
- 여기서 핵심은 소스코드를 고치지 않고 이런 것이 가능해야 한다는 점이다. (프로젝트를 빌드해서 나온 빌드 파일을 개발 서버에도 배포, 운영서버에도 배포해야함)
- 같은 소스 코드인데 특정 상황일 때만 특정 빈들을 등록해서 사용하도록 도와주는 기능이 @Conditional임.
package org.springframework.context.annotation;
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
@Conditional 기능을 사용하려면 먼저 Condition 인터페이스를 구현해야한다. Condition 인터페이스는 다음과 같다.
- matches() 메서드 : 판단함.
- true 반환 → 조건 만족으로 판단. 동작함.
- false 반환 → 조건 만족하지 않음으로 판단. 동작하지 않음.
- 매개변수
- ConditionContext : 스프링 컨테이너, 환경 정보등을 담고 있음.
- AnnotatedTypeMetadata: 어노테이션 메타 종브를 담고 있음.
구현 목표
$ java -Dmemory=on -jar project.jar
여기서 구현할 컨디셔널 빈등록 목표는 자바 실행 옵션에 memory=on이 들어오는 경우에 스프링 빈을 등록하도록 한다.
@Slf4j
public class MemoryCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// -Dmemory=on
final String memory = context.getEnvironment().getProperty("memory");
log.info("memory = {}", memory);
return "on".equals(memory);
}
}
이것을 위한 Condition 인터페이스를 구현했다.
@Configuration
@Conditional(MemoryCondition.class)
public class MemoryConfig {
...
}
구현한 Condition 인터페이스를 사용하기 위해서 @Conditional(MemoryCondition.class)를 MemoryConfig에 붙였다. 이것은 다음과 같이 동작한다.
- MemoryCondition.class가 True를 반환 → MemoryConfig가 설정 정보로 등록됨.
- MemoryCondition.class가 False를 반환 → MemoryConfig가 설정 정보로 등록되지 않음.
실행해보기
memory.MemoryCondition : memory = null
- IDE로 실행해보면, 스프링이 시작될 때 다음과 같은 로그가 나온다.
- MemoryCondition 클래스에서 스프링부트가 시작할 때, matchs() 메서드가 호출된다. 이 때, 실행변수로 주입된 -Dmemory의 값이 on인지 확인하는 절차를 진행할 때 찍힌 로그다.
- memory = null이면, MemoryCondition은 false를 반환하고 MemoryConfig에 있는 설정 정보는 스프링 컨테이너에 등록되지 않는다. 즉, MemoryController는 자동구성으로 등록되지 않는다.
- IDE로 자동 구성을 사용하려면 add VM Option에서 -Dmemory=on을 추가해줘야한다.
추가하면 스프링이 시작될 때, MemoryCondition의 matchs() 메서드가 평가된다.
@Conditional - 다양한 기능
- 앞에서는 Condition 인터페이스의 구현체를 직접 구현했다.
- 스프링은 이미 필요한 대부분의 Condition 구현체를 만들어 두었고, 이것을 가져다 쓰기만 하면 된다.
@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(name = "memory", havingValue = "on")을 추가하자.
- 앞서 Condition 인터페이스를 직접 구현해서 ConditionOn에 넣어준 부분이 모두 포함된다.
...
@Conditional(OnPropertyCondition.class) // OnPropertyCondition 클래스 바라봄.
public @interface ConditionalOnProperty {
...
}
@Order(Ordered.HIGHEST_PRECEDENCE + 40) // OnPropertyCondition은 SpringBootCondition과 관련
class OnPropertyCondition extends SpringBootCondition {
...
}
// SpringBootCondition은 Condition 인터페이스 구현.
public abstract class SpringBootCondition implements Condition {
위의 코드의 동작을 살펴보기 위해서 @ConditionalOnProperty 어노테이션을 살펴보면 @ConditionOn + Condition 인터페이스 구현체의 조합으로 동작하는 것을 볼 수 있다.
@ConditionalOnXxx
스프링은 @Conditional과 관련해서 개발자가 편리하게 사용할 수 있도록 많은 @ConditionalOnXxx를 제공함.
- @ConditionalOnClass, @ConditionalOnMissingClass : 클래스가 있는 경우 동작함. 나머지는 반대.
- @ConditionalOnBean, @ConditionalOnMissingBean : 빈이 등록되어 있는 경우 동작함. 나머지는 그 반대
- @ConditionalOnProperty : 환경정보가 있는 경우 동작함.
- @ConditionalOnResource : 리소스가 있는 경우 동작함.
- @ConditionalOnWebApplication, @ConditionalOnNotWebApplication : 웹 어플리케이션인 경우 동작함.
- @ConditionalOnExpression : SpEL 표현식에 만족하는 경우 동작함.
@ConditionalOnXxx는 주로 스프링부트의 자동 구성에 사용되고 있다.
참고
@Conditional 자체는 스프링부트가 아니라 스프링 프레임워크가 제공하는 기능이다.
'Spring > Spring' 카테고리의 다른 글
스프링 부트 - 자동 구성 관련 요약 (0) | 2023.09.17 |
---|---|
스프링 부트 - 자동구성 4 (0) | 2023.09.17 |
스프링 부트 - 자동구성 3 (2) | 2023.09.17 |
스프링 부트 - 자동구성 1 (0) | 2023.09.14 |
Spring : EventPublishing을 통한 객체 간 결합 약화 (0) | 2023.08.27 |