< 스프링이 제공하는 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

@Condotional 어노테이션이란 ?

컴포넌트의 Bean 등록 여부에 조건을 달 수 있게 해주는 어노테이션

 

스프링은 IOC컨테이너에 객체를 Bean으로 등록하여 사용하는데,

@Component로 선언된 클래스는 모두 Bean으로 등록되는데 여기에 조건부를 달 수 있다

ex. A경우에 등록을 하되, B경우에는 등록하지 않도록 설정 가능

 

Spring에서 추가기능의 설정파일을 읽고 해당되는 POJO에서 bean을 IOC컨테이너에 등록한다

이때 설절파일을 조건부로 읽는다면 필요할 때만 IOC컨테이너에 Bean 등록 가능하다

@Condotional 사용해야하는데 Condition 인터페이스의 구현체를 만들어야 한다

 

ex.

@Conditional(TomcatWebServerConfig.TomcatCondition.class)
public class TomcatWebServerConfig {
    @Bean("tomcatWebServerFactory")
    public ServletWebServerFactory servletWebServerFactory(){
        return new TomcatServletWebServerFactory();
    }

    static class TomcatCondition implements Condition {
        @Override
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            return true;
        }
    }
}
@MyAutoConfiguration
@Conditional(JettyWebServerConfig.JettyCondition.class)
public class JettyWebServerConfig {
    // 톰캣과 두 개 이름이 동일할 경우 충동한다고 스프링 컨테이너가 초기화할 때 에러 발생 할 수 있으므로
    // 이름 구분해주기
    @Bean("JettyWebServerFactory")
    public ServletWebServerFactory servletWebServerFactory(){
        return new JettyServletWebServerFactory();
    }

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

@Condition은 엘리먼트를 꼭 하나 등록해줘야하는데

그 엘리먼트 타입은 무조건 condition이라는 인터페이스를 구현한 클래스여야한다.

 

@Condition(xxxCondition.class)는 xxxCondition의 matches 메소드의 return값을 받아 컴포넌트 스캔 여부 판단

Boolean으로 return 해주면 되는데 true면 bean으로 등록 false면 IOC컨테이너에 Bean으로 등록되지 않는다

 

예시를 보면 Tomcat은 true를 반납하고 Jetty는 false를 반납하고 있으므로 Tomcat 서버가 실행된다.

 

 

 

이 외.

@SpringBootApplication : Component Scan 기능을 가지고 있음

@ComponentScan : 하위 디렉토리에 위치한 @Component로 명시된 Bean으로 등록하는 기능

@Configration : 이것 또한 @Component. 하위디렉토리에 설정 클래스를 위치시킨다

 

 

 

 

 

 

 

 

 

 

+ Recent posts