dependencies 추가시 필수로 reload 해줘야 적용됨 

dependencies {
    implementation('org.springframework.boot:spring-boot-starter-web')
    implementation('org.springframework:spring-jdbc')
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

 

implementation 추가 후 reload 하지 않으면 적용되지 않음

 

< 스프링이 제공하는 Environment 총 3가지 >

1. System Properties

VM 옵션 추가 ( -D{이름}={값} 형식)

2. System Environment Variables

언더바(_)  조

3. application.properties(xml, yml)

우선순위가 가장 낮기 때문에 기본이 되는 값을 넣고

System Properties에 환경 변수 값 조절하는게 대표적인 사용패턴

 

< Environment test code >

@Bean
ApplicationRunner applicationRunner(Environment env){
    return args -> {
       String name = env.getProperty("my.name");
       System.out.println("my.name:" + name);
    };

 

아무런 프로토타입을 설정해 주지 않아 my.name 정보를 읽을려고 해도 없기 때문에 null 출력

 

 

 

<우선선위가 낮은 것부터 정보 설정 해보기>

1.property 추가해주는 Application.properties에 my.name 정보 설정 ( 우선순위 3 )

 

 

출력 값 : ApplicationProperties

 

2. Application.properties 보다 우선 환경변수 설정 ( 우선순위 2 )

추가로 edit Configurations - Dradle - Environment variable에 정보 설정

 

Environment variable 안보일 경우 Modify optios - Environment variable 클릭

 

Application.properties에 추가한 정보가 아닌 환경변수에 설정해준 값 출력

 

3. System Property ( 우선순위 1)

 edit Configurations - Application - Modify options - AD VM 클릭

java 명령으로 프로그램을 실행할때 -D 옵션 주고 프러퍼티 이름과 벨류값 설정

 

 

우선수위가 제일 높기 때문에 출력값

System Property 에서설정해준 SystemProperty가 출력됨

<현재 톰캣 서버 포트 번호 8080 으로 자동으로 실행되는데

사용자 구성정보에 포트 번호 9090 으로 변경하기>

 

 

 

1. WebServerConfiguration class 만들기

 

@Configuration(proxyBeanMethods = false)
public class WebServerConfiguration {
    @Bean
    ServletWebServerFactory customerWebServerfactory(){
        TomcatServletWebServerFactory serverFactory = new TomcatServletWebServerFactory();
        serverFactory.setPort(9090);
        return serverFactory;
    }
}

setPort 사용하여 포트 9090으로 변경

proxyBeanMethods = false

특별하게 bin 사이에 상호 메소드 호출을 통해 의존관계 주입을 넣을게 아니라면
proxyBeanMethods를 fasle로 설정

 

 

2. 테스트 해보면 오류

 

아래와 같이 멀티풀 오류가 뜸

Unable to start web server; nested exception is org.springframework.context.ApplicationContextException: Unable to start ServletWebServerApplicationContext due to multiple ServletWebServerFactory beans : customerWebServerfactory,tomcatWebServerFactory

 

* 사용자 구성정보와 자동구성정보에 똑같은 Bean이 있다면

사용자 구성정보에 있는 Bean이 우선적 그 후 자동구성정보가 실행되는데

 

사용자 구성정보에 적힌 Bean에 이미 Tomcat이 있어 자동구성정보도 실행되면서 에러 발생

 

 

 

3. 해결방안

 

자동정보인 tomcatWebServerFactory에 Bean에 아래 어노테이션 추가

 

@ConditionalOnMissingBean

 

사용자 구성 정보에 개발자가 이거랑 같은 타입의 빈을 구성정보로 만들었는지 확인 후

없을 경우에 해당 빈 생성해주는 어노테이션

 

 

 

4. 다시 테스트 해보면 9090 포트로 Tomcat 서버 확인 가능

 

 

1. 먼저 true일 경우와 false 일 경우를 테스트

 

 

2. 성공,실패할 경우 클래스

 

-성공할 경우 클래스 Config1

@Configuration
@Conditional(TrueConditon.class)
static class Config1{
    @Bean
    MyBean myBean(){
        return new MyBean();
    }
}

@Conditional에 TrueCondition.class엘리먼트 구현

true일 경우 등록시킬 MyBean 생성 - 등록되는지만 확인할 것으로 bean에 내용은 없다

static class MyBean{}

 

-실패할 경우 클래스 Config2

@Configuration
@Conditional(FalseConditon.class)
static class Config2{
    @Bean
    MyBean myBean(){
        return new MyBean();
    }
}

@Conditional에 FalseCondition.class엘리먼트 구현

 

 

3. 각각 condition이라는 인터페이스를 구현한 클래스 코딩

static class TrueConditon implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return true;
    }
}

static class FalseConditon implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return false;
    }
}

TrueCondition은 true 반환,  FalseCondition은 false 반환

 

4. 테스트

 

- true일 경우 테스트 성공

@Test
void conditional() {
    //true
    AnnotationConfigApplicationContext ac1 = new AnnotationConfigApplicationContext();
    ac1.register(Config1.class);
    ac1.refresh();

    MyBean bean = ac1.getBean(MyBean.class);
}

 

 

-false 일 경우 테스트 실패

//false
AnnotationConfigApplicationContext ac2 = new AnnotationConfigApplicationContext();
ac2.register(Config2.class);
ac2.refresh();

MyBean bean2 = ac2.getBean(MyBean.class);

 

 

true 랑 똑같이 코딩하여도 아래와 같은 bean 찾을 수 없다는 에러 발생

org.springframework.beans.factory.NoSuchBeanDefinitionException

 

bean이 생성되지 않았으므로 당연한 결과지만 예외처리 될 수 있게 코딩 변경

 

//false
ApplicationContextRunner contextRunner = new ApplicationContextRunner();
contextRunner.withUserConfiguration(Config2.class)
        .run(context -> {
            //Assertions.assertThat(context).hasSingleBean(MyBean.class); 갖고있어야 성공
            Assertions.assertThat(context).doesNotHaveBean(MyBean.class);   // 갖고있지 않아야 성공
            Assertions.assertThat(context).doesNotHaveBean(Config2.class);
        });

 

ApplicationContextRunner : 예외처리

 

 

 

 

+ 더 나아가 condition을 메타 어노테이션으로 사용하기

아예 어노테이션으로 만들어보기

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Conditional(TrueConditon.class)
@interface TrueConditonal{}

@TrueConditonal
@Configuration
//@Conditional(TrueConditon.class)
static class Config1{
    @Bean
    MyBean myBean(){
        return new MyBean();
    }
}

 

기존에 사용하던 @Coditonal 지우고 성공할 경우 어노테이션인 @TrueConditional을 만들어줌

 

 

+ Condition의 조건을 결정한 로직 만들기 - @BooleanConditional

AnnotatedTypeMetadata을 사용하여 true, false 두가지 다 적용할 수 있고

그 조건을 어노테이션 엘리먼트로 지정하는 방법

 

Boolen으로 true,false 값 전달

 

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Conditional(BoolenConditon.class)
@interface BoolenConditonal{
    boolean value();
}

@Configuration
@BoolenConditonal(true)
//@Conditional(TrueConditon.class)
static class Config1{
    @Bean
    MyBean myBean(){
        return new MyBean();
    }
}

@Configuration
@BoolenConditonal(false)
static class Config2{
    @Bean
    MyBean myBean(){
        return new MyBean();
    }
}

 

 

static class BoolenConditon implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
       Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(BoolenConditonal.class.getName());
        Boolean value = (Boolean)annotationAttributes.get("value");
        return value;
    }
}

 

어노테이션 메타 정보(@BoolenConditonal(true)에 있는 true, false)를 다 가져와 어떤 값을 넣었는지 읽어여함

metadata.getAnnotationAttributes(BoolenConditonal.class.getName());

가져온 후 Boolean값으로 value 값 읽어야함

Boolean value = (Boolean)annotationAttributes.get("value");

 

 

최종적으로 코딩이 짧아지고 간결해짐

 

 

'SpringBoot > 프로젝트' 카테고리의 다른 글

Environment properties 적용  (2) 2023.12.06
자동 구성 정보 대체하기  (0) 2023.11.30
[Spring Boot] jetty 서버 구성 추가하기  (0) 2023.11.29
Test Code 작성  (0) 2023.11.27
DI를 이용한 Decorator  (2) 2023.11.27

+ Recent posts