티스토리 뷰
Spring innitializr로 스프링부트 프로젝트 셋업시 웹 프로젝트에서 사용할만한 기본적인 자동 구성 클래스들이 내장되어 있고 자동 구성으로 설정되어 있다.
이 수많은 클래스들이 모두 자동구성되어 등록되는게 아니라 필요하지 않은 클래스라면 조건적으로 등록하지 않게 할수도 있다.
Tomcat 서블릿 컨테이너가 기본 내장되어 있는데 이는 Gradle에 dependencies 라이브러리들 확인해 보면
org.springframework.boot:spring-boot-starter-web <- 스프링 부트를 돌아가게 만드는 모듈 내장된 라이브러리에 서블릿컨테이너로 Tomcat이 내장되어 있다.
인텔리제이 터미널 창에
./gradlew dependencies --configuration compileClasspath
적으면
spring-boot-starter-web 의 트리구조를 확인할 수 있다.
core, aop, logging, tomcat 등등.. 버전 호환을 맞춘 라이브러리들이 기본 구성 되어있다.
jetty 서블릿 컨테이너 사용하기
Tomcat 서블릿 컨테이너 말고 다른 서블릿 컨테이너를 설정할 수 있다.
@Conditional
SpringBoot App을 빌드할 때 어떤 조건이 충족되는 경우에만 Application Context에 로드하고 싶다.
테스트 중일 때는 비활성화되어 있거나 런타임 환경에 특정 조건일 때만 충족해야할 경우에 사용한다.
Spring은 특정한 경우에만 사용자가 커스텀하게 정의하여 사용할 수 있도록 Conditional 어노테이션을 도입했다.
(도입한지는 꽤 됐다)
Spring4부터 사용이 가능하다.
프로젝트 조건에 따라 Configuration에 설정된 Bean을 다르게 불러와야 한다면?
@Conditional을 이용하여 설정을 개선해본다.
@Configuration 애노테이션이 붙은 클래스에 @Conditional 애노테이션을 추가하고 Condition 인터페이스를 구현한 클래스를 넣고 Condition 인터페이스의 matches 메소드를 오버라이딩 하여 리턴값으로 받는 boolean 값으로 해당 Bean을 조건에 따라 스프링 부트 실행시 자동 구성으로 등록되는 Bean을 등록하거나 안할 수 있다.
Configuration class 말고도 Bean 팩터리 메소드에서도 @Conditional 애노테이션을 사용할 수 있다.
메소드에 선언된 Conditional 클래스 리턴값이 true여야 팩터리 메서드로 실행되는 Bean이 등록이 된다.
https://live-everyday.tistory.com/235
Spring의 @Conditional과 Spring Boot의 AutoConfiguration
Contents 1. Introduction 2. @Conditional 모든 Spring 프로젝트의 Configuration에서 공통되는 부분이 있다면? Configuration이 공유될 때 발생할 수 있는 문제 @Conditional @Conditional을 이용한 개선 3. AutoConfiguration Sprin
live-everyday.tistory.com
@Conditional 테스트
package tobyspring.study;
import static org.assertj.core.api.Assertions.*;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Map;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class ConditionalTest {
@Test
@DisplayName("true 컨디셔널")
void conditional(){
// 스프링 부트에서 제공하는 테스트 전용 ApplicationContext
// 테스트 AssertJ 라이브러리 제공하는 유틸리티 클래스
ApplicationContextRunner contextRunner = new ApplicationContextRunner();
// 적용할 Configuration 넘기고 실행
contextRunner.withUserConfiguration(Config1.class)
.run(context -> {
// Bean 존재 여부 체크
assertThat(context).hasSingleBean(MyBean.class);
assertThat(context).hasSingleBean(Config1.class);
});
}
@Test
@DisplayName("false 컨디셔널")
void conditional2(){
new ApplicationContextRunner().withUserConfiguration(Config2.class)
.run(context -> {
// Bean 존재하지 않는지 여부 체크
assertThat(context).doesNotHaveBean(MyBean.class);
assertThat(context).doesNotHaveBean(Config2.class);
});
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Conditional(BooleanCondition.class)
@interface BooleanConditional{
boolean value();
}
// 조건에 따라 Bean을 등록하는 설정 클래스
@Configuration
@BooleanConditional(true)
static class Config1 {
@Bean
MyBean myBean() {
return new MyBean();
}
}
// 조건에 따라 Bean을 등록하는 설정 클래스
@Configuration
@BooleanConditional(false)
static class Config2 {
@Bean
MyBean myBean() {
return new MyBean();
}
}
// 테스트용 생성할 Bean
static class MyBean {}
static class BooleanCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// Annotation에 붙은 속성값을 활용해서 컨디션의 조건을 결정하는 로직 구현
Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(BooleanConditional.class.getName());
assert annotationAttributes != null;
return (Boolean)annotationAttributes.get("value");
}
}
}
ApplicationContextRunner
테스트를 위해 ApplicationContext를 실행하고 AssertJ 라이브러리를 제공하는 객체이다.
Configuration에 적용된 Condition 조건에 따른 상황이 잘 동작하는지 확인할 수 있다.
Assertions 클래스의 static 메서드 assertThat 기능은 클래스로 접근해서 사용하는데
Assertions.assertThat(context).hasSingleBean(MyBean.class);가 아닌
assertThat(context).hasSingleBean(MyBean.class); 로 쓸 수 있는 이유는
https://offbyone.tistory.com/283
자바(Java) - static import 사용하기
자바 클래스의 static 메소드는 클래스에 대한 인스턴스의 생성없이 메소드를 사용할 수 있습니다. 예로 절대값을 구하는 java.lang.Math 클래스의 abs() 메소드는 다음과 같이 클래스명.메소드로 바로
offbyone.tistory.com
잘못 사용될 수 있을 때
https://velog.io/@kasania/Java-Static-import%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B4%80%EC%B0%B0
중복된 코드 제거
AutoConfiguration 으로 스프링 부트에서 설정된 Bean을 사용하지 않고
개발자가 커스텀한 Bean을 사용하기 위해 사용되는 애노테이션 @ConditionalOnMissingBean
스프링 시작될 때 사용자 구성정보인 Bean들이 먼저 생성되게 되는데 자동구성 정보에 선언된 Bean과 사용자가 정의한 Bean 중 사용자 구성정보에서 등록하지 않았으면 등록되게 하는 애노테이션.
예제에서는 서블릿 컨테이너를 커스텀하여 내가 만든 서블릿 컨테이너를 실행 시키기 위해 ComponentScan으로 등록되는 Bean으로 구현해 놓았다면 AutoConfiguration에서 등록되는 서블릿 컨테이너가 충돌되어 실행되지 않기 때문에 Configuration 설정에서 서블릿 컨테이너 Bean 등록하는 메서드에 @ConditionalOnMissingBean을 붙여서 사용자가 만든 서블릿 컨테이너가 있다면 그 객체를 등록시키고 만약 없다면 자동 구성정보에 있는 서블릿컨 테이너를 등록하게 만드는 역할을 한다.
스프링 부트의 @Conditional
Class Conditions
지정한 클래스의 프로젝트 내 존재를 확인해서 포함 여부를 결정한다.
주로 @Configuration 클래스 레벨에서 사용하지만 @Bean 메서드에도 적용 가능하다. 단, 클래스 레벨의 검증 없이 @Bean 메서드에만 적용하면 불필요하게 @Configuration 클래스가 Bean으로 등록되기 때문에, 클래스 레벨 사용을 우선해야 한다.
- @ConditionalOnClass: 클래스의 풀네임 지정하면 그 클래스가 프로젝트 클래스 패스에 포함되어 있는지, 라이브러리로 등록했는지 확인 후 존재하면 클래스(메서드) 내부 로직 사용 (Bean 등록)
- @ConditionalOnMissingClass: 지정한 클래스가 존재하지 않으면 클래스(메서드) 내부 로직 사용 (Bean 등록)
???@ConditionalOnClass 애노테이션으로 일단 클래스 검증 통과하고 내부 메서드 검증해야 한다는 뜻? 메서드 검증에서 통과가 모두 안되면 클래스 자체가 등록될 필요가 없어서?
Bean Conditions
빈의 존재 여부를 기준으로 포함 여부를 결정한다. 빈의 타입 또는 이름을 지정할 수 있다. 지정된 빈 정보가 없으면 메서드 리턴타입을 기준으로 빈의 존재여부를 체크한다.
컨테이너에 등록된 빈 정보를 기준으로 체크하기 때문에 자동 구성 사이에 적용하려면 @Cofiguration 클래스의 적용 순서가 중요하다. 개발자가 직접 정의한 커스텀 빈 구성 정보가 자동 구성 정보 처리보다 우선하기 때문에 이 관계에 적용하는 것은 안전하다. 반대로 커스텀 빈 구성정보에 적용하는건 피해야 한다. 어떤 시점에 어떤 빈이 먼저 생성이되는지 정확하게 파악하지 않으면 검증 후에 검증했던 빈이 등록이 되는 로직이라면 실행되지 않아야할 로직이 먼저 실행될 수 있다.
- @ConditionalOnBean: 특정 Bean Class나 이름이 Bean Factory에 포함되면 내부 로직 사용
- @ConditionalOnMissingBean: 특정 Bean Class나 이름이 Bean Factory에 없으면 내부 로직 사용
@Configuration 클래스 레벌의 @ConditionalOnClass와 @Bean 메서드 레벨의 @ComditionalOnMissingBean 조합은 가장 대표적으로 사용되는 방식이다.
클래스의 존재로 해당 기술의 사용 여부를 확인하고, 직접 추가한 커스텀 빈 구성의 존재를 확인해서 자동 구성의 빈 오브젝트를 이용할지 최종 결정한다.
Property Conditions
@ConditionalOnProperty는 스프링 환경 프로퍼티 정보를 이용한다. 지정된 프로퍼티가 존재하고 값이 false가 아니면 포함 대상이된다. 특정 값을 가진 경우를 확인하거나 프로퍼티가 존재하지 않을 때 조건을 만족하게 할 수도 있다.
프로퍼티의 존재를 확인해서 빈 오브젝트를 추가하고, 해당 빈 오브젝트에서 프로퍼티 값을 이용해서 세밀하게 빈 구성을 할수도 있다.
Resource Conditions
@ConditionalOnResource는 지정된 리소스(파일)의 존재를 확인하는 조건이다.
Web Application Conditions
웹 애플리케이션 여부를 확인한다. 모든 스프링 부트 프로젝트가 웹 기술을 사용해야 하는 것은 아니다.
- @ConditionalOnWebApplication: 웹 애플리케이션이면 로직 수행
- @ConditionalOnNotWebApplication: 웹 애플리케이션이 아니면 로직 수행
SpEL Expression Conditions
@ConditionalOnExpression은 스프링 SpEL(스프링표현식)의 처리 결과를 기준으로 판단한다. 매우 상세한 조건설정이 가능하다.
토비의 스프링 부트 - 이해와 원리 - 인프런 | 강의
스프링 부트의 핵심 기능을 직접 만들어보면서 스프링 부트의 동작 원리를 이해하고, 이를 통해 스프링 부트를 잘 학습하고 사용하는 방법을 배우는 강의입니다. 스프링 부트가 사용하는 스프
www.inflearn.com
'자바 > 스프링 부트' 카테고리의 다른 글
섹션 9. Spring JDBC 자동 구성 개발 (0) | 2023.02.28 |
---|---|
섹션8. 외부 설정을 이용한 자동 구성 (0) | 2023.02.19 |
섹션6.자동 구성 기반 애플리케이션 (0) | 2023.02.12 |
섹션5. DI와 테스트, 디자인 패턴 (0) | 2023.02.09 |
섹션4. 독립 실행형 스프링 애플리케이션 (0) | 2023.02.06 |