[스프링 코어] 1-2. IoC 컨테이너와 빈 2부(부제 : ApplicationContext가 갖는 기능)

최진민·2021년 1월 28일
0

스프링 코어

목록 보기
2/8
post-thumbnail

Environment 1부. Profile

  • Profile

    • : 빈들의 묶음
    • Environment의 역할은 활성화할 Profile 확인 및 설정
  • Profile 정의(클래스)

    package me.jinmin;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Profile;
    
    @Configuration
    @Profile("test") // **이 부분**
    public class TestConfiguration {
    
        @Bean
        public BookRepository bookRepository(){
            return new TestBookRepository();
        }
    }
    • 위 클래스는 profile이 test일 경우에만 사용이 되는 빈 설정 파일이 된다.

      package me.jinmin;
      
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.boot.ApplicationArguments;
      import org.springframework.boot.ApplicationRunner;
      import org.springframework.context.ApplicationContext;
      import org.springframework.core.env.Environment;
      import org.springframework.stereotype.Component;
      
      import java.util.Arrays;
      
      @Component
      public class AppRunner implements ApplicationRunner {
      
          @Autowired
          ApplicationContext context;
      
          @Autowired
          BookRepository bookRepository;
      
          @Override
          public void run(ApplicationArguments args) throws Exception {
              Environment environment = context.getEnvironment();
              System.out.println(Arrays.toString(environment.getActiveProfiles()));
              System.out.println(Arrays.toString(environment.getDefaultProfiles()));
          }
      }
    • 💥오류가 난다. Why? ⇒ profile이 test로 설정되지 않았기 때문에. 그렇다면 Profile을 설정해보자.

  • Profile 설정(선언)

    • IntelliJ 버전 별로 상이하게 설정!

      • 공통(Ultimate, Community) : Edit Configurations... Click

      • 아래의 이미지에서 !

        • Ultimate ver. ⇒ Active profiles:칸에 test입력

        • Community ver. ⇒ VM options:칸에 -Dspring.profiles.active="test"입력

      • AppRunner.class 구동 결과

        print :
        [test]
        [default]
  • Profile 정의

    • Class
      • @Configuration @Profile("test")
      • @Component @Prfile("test")
    • Method
      • @Bean @Profile("test")
  • Profile 표현식

    • ! (not)

    • & (and)

    • | (or)

    • 예제) (Profile 설정(-Dspring.profiles.action="test")을 지우고 App. 실행)

      package me.jinmin;
      
      import org.springframework.context.annotation.Profile;
      import org.springframework.stereotype.Repository;
      
      @Repository
      @Profile("!test")
      public class TestBookRepository implements BookRepository {
      
      }
    • 결과) Profile을 test로 설정하지 않았지만 @Profile("!test")으로 정의했기때문에 오류없이 실행됨을 볼 수 있다.

      print:
      []
      [default]
  • Profile의 쓰임새?

    • 테스트 환경에서는 A라는 빈을 사용하고, 배포 환경에서는 B라는 빈을 쓰고 싶다.
    • 이 빈은 모니터링 용도니까 테스트할 때는 필요가 없고 배포할 때만 등록이 되면 좋겠다.

Environment 2부. Property

  • Property

    • 다양한 방법으로 정의할 수 있는 설정값 (key-value)
    • Environment의 역할은 Property 소스 설정 및 값 가져오기.
    • 계층형(우선순위)으로 접근
  • 우선 순위 (StandardServletEnvironment의 우선 순위)

    • ServletConfig 매개변수
    • ServletContext 매개변수
    • JNDI (java:comp\env)
    • JVM 시스템 프로퍼티 (-Dkey="value")
    • JVM 시스템 환경 변수 (os 환경 변수)
  • 기본적인 Property 설정과 접근

    • 설정

      • 이전에 설정했던 Edit Configurations... Click❗
      • VM options:-Dapp.name=spring5로 설정
    • 접근

      • (1)

        package me.jinmin;
        
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.boot.ApplicationArguments;
        import org.springframework.boot.ApplicationRunner;
        import org.springframework.context.ApplicationContext;
        import org.springframework.core.env.Environment;
        import org.springframework.stereotype.Component;
        
        @Component
        public class AppRunner implements ApplicationRunner {
        
            @Autowired
            ApplicationContext context;
        
            @Autowired
            BookRepository bookRepository;
        
            @Override
            public void run(ApplicationArguments args) throws Exception {
                Environment environment = context.getEnvironment();
                System.out.println(environment.getProperty("app.name"));
            }
        }

        print : spring5

      • (2)

        • \main\resourcesapp.properties생성(일반 file) 후 "app.about=spring"입력

          app.about=spring
        • @SpringBootApplication이 표시된 클래스에 @PropertySource("classpath:/app.properties") 추가

          package me.jinmin;
          
          import org.springframework.boot.SpringApplication;
          import org.springframework.boot.autoconfigure.SpringBootApplication;
          import org.springframework.context.annotation.PropertySource;
          
          @SpringBootApplication
          @PropertySource("classpath:/app.properties")
          public class demoIocApplication {
              public static void main(String[] args) {
                  SpringApplication.run(demoIocApplication.class, args);
              }
          }
        • AppRunner.class 실행 및 결과

          package me.jinmin;
          
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.boot.ApplicationArguments;
          import org.springframework.boot.ApplicationRunner;
          import org.springframework.context.ApplicationContext;
          import org.springframework.core.env.Environment;
          import org.springframework.stereotype.Component;
          
          @Component
          public class AppRunner implements ApplicationRunner {
          
              @Autowired
              ApplicationContext context;
          
              @Autowired
              BookRepository bookRepository;
          
              @Override
              public void run(ApplicationArguments args) throws Exception {
                  Environment environment = context.getEnvironment();
                  System.out.println(environment.getProperty("app.name"));
                  System.out.println(environment.getProperty("app.about"));
              }
          }
          print:
          spring5
          spring
      • 우선 순위?

        • app.properties의 내용을 "app.about=spring"에서 "app.name=spring"으로 변경한 후 실행하여 결과를 확인하자!

        • 결과 (JVM 시스템 프로퍼티가 더 우선 순위가 높다.)

          package me.jinmin;
          
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.boot.ApplicationArguments;
          import org.springframework.boot.ApplicationRunner;
          import org.springframework.context.ApplicationContext;
          import org.springframework.core.env.Environment;
          import org.springframework.stereotype.Component;
          
          @Component
          public class AppRunner implements ApplicationRunner {
          
              @Autowired
              ApplicationContext context;
          
              @Autowired
              BookRepository bookRepository;
          
              @Override
              public void run(ApplicationArguments args) throws Exception {
                  Environment environment = context.getEnvironment();
                  System.out.println(environment.getProperty("app.name"));
              }
          }
          print:
          spring5
        • 또 다른 방법

          package me.jinmin;
          
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.beans.factory.annotation.Value;
          import org.springframework.boot.ApplicationArguments;
          import org.springframework.boot.ApplicationRunner;
          import org.springframework.context.ApplicationContext;
          import org.springframework.core.env.Environment;
          import org.springframework.stereotype.Component;
          
          @Component
          public class AppRunner implements ApplicationRunner {
          
              @Autowired
              ApplicationContext context;
          
              @Autowired
              BookRepository bookRepository;
          
              @Value("${app.name}")
              String appName;
          
              @Override
              public void run(ApplicationArguments args) throws Exception {
                  Environment environment = context.getEnvironment();       
                  System.out.println(appName);
              }
          }
          print : 
          spring5

MessageSource

  • 국제화 기능을 제공하는 인터페이스

  • 사용법

    • main\resources에 세 가지를 추가하자

      • messages.properties (default)
        "greeting=Hello {0}"

      • messages_ko_KR.properties (KOREA)
        "greeting=안녕, {0}"

      • messages_en.properties (ENGLISH)
        "greeting=Hello {0}"

        package me.jinmin;
        
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.boot.ApplicationArguments;
        import org.springframework.boot.ApplicationRunner;
        import org.springframework.context.MessageSource;
        import org.springframework.stereotype.Component;
        
        import java.util.Locale;
        
        @Component
        public class AppRunner implements ApplicationRunner {
        
            @Autowired
            MessageSource messageSource;
        
            @Override
            public void run(ApplicationArguments args) throws Exception {
                System.out.println(messageSource.getMessage("greeting", new String[]{"jinmin"}, Locale.ENGLISH));
                System.out.println(messageSource.getMessage("greeting", new String[]{"jinmin"}, Locale.KOREA));
                System.out.println(messageSource.getMessage("greeting", new String[]{"jinmin"}, Locale.getDefault()));
            }
        }
      • 결과(default의 값이 한글로 나오는 경우는 os의 디폴트)

  • ✨(참고) 릴로딩 기능이 있는 MessageSource 활용

    package me.jinmin;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.MessageSource;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.support.ReloadableResourceBundleMessageSource;
    
    @SpringBootApplication
    public class demoIocApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(demoIocApplication.class, args);
        }
    
        @Bean
        public MessageSource messageSource(){
            var messageSource = new ReloadableResourceBundleMessageSource();
            messageSource.setBasename("classpath:/messages");
            messageSource.setDefaultEncoding("UTF-8");
            messageSource.setCacheSeconds(3);
            return messageSource;
        }
    }
    package me.jinmin;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.ApplicationArguments;
    import org.springframework.boot.ApplicationRunner;
    import org.springframework.context.MessageSource;
    import org.springframework.stereotype.Component;
    
    import java.util.Locale;
    
    @Component
    public class AppRunner implements ApplicationRunner {
    
        @Autowired
        MessageSource messageSource;
    
        @Override
        public void run(ApplicationArguments args) throws Exception {
    
            while(true){
                System.out.println(messageSource.getMessage("greeting", new String[]{"jinmin"}, Locale.ENGLISH));
                System.out.println(messageSource.getMessage("greeting", new String[]{"jinmin"}, Locale.KOREA));
                System.out.println(messageSource.getMessage("greeting", new String[]{"jinmin"}, Locale.getDefault()));
                Thread.sleep(1000L);
            }
        }
    }
    • 1초마다 매번 (1)의 결과가 나오며, messages.properties의 변경 내용을 Real-time으로 변경하고 Build할 경우, (2)의 내용이 나온다.

      • (1)

      • (2)


ApplicationEventPublisher

  • 이벤트 기반 프로그래밍에 필요한 인터페이스 제공. 옵저버 패턴 구현체

  • (1) 4.2 이전🎆

    • Event 형성

      • main\java\me\..\MyEvent

        package me.jinmin;
        
        import org.springframework.context.ApplicationEvent;
        
        public class MyEvent extends ApplicationEvent {
        
            private int data;
        
            public MyEvent(Object source) {
                super(source);
            }
        
            public MyEvent(Object source, int data) {
                super(source);
                this.data = data;
            }
        
            public int getData() {
                return data;
            }
        }
    • Event 처리

      • \main\java\me\..\MyEventHandler

        package me.jinmin;
        
        import org.springframework.context.ApplicationListener;
        import org.springframework.stereotype.Component;
        
        @Component
        public class MyEventHandler implements ApplicationListener<MyEvent> {
            @Override
            public void onApplicationEvent(MyEvent event) {
                System.out.println("이벤트를 받았다. 데이터는 : " + event.getData());
            }
        }
    • Event 출판(publish, 또는 발생) & 실행 결과

      • main\java\me\AppRunner

        package me.jinmin;
        
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.boot.ApplicationArguments;
        import org.springframework.boot.ApplicationRunner;
        import org.springframework.context.ApplicationEventPublisher;
        import org.springframework.stereotype.Component;
        
        @Component
        public class AppRunner implements ApplicationRunner {
        
            @Autowired
            ApplicationEventPublisher applicationEventPublisher; // **이 부분**
        
            @Override
            public void run(ApplicationArguments args) throws Exception {
                applicationEventPublisher.publishEvent(new MyEvent(this, 100));
            }
        }
        print:
        이벤트를 받았다. 데이터는 : 100
    • 순서

      • SpringBoot 실행 ⇒ AppRunner ⇒ Event 발생 ⇒ 등록되어 있는 Bean(MyEventHandler)이 받아서 데이터 출력
  • (2) 4.2 이후🎇 (ApplicationEvent 상속 x)

    • Event 형성

      • \main\java\me\..\MyEvent

        package me.jinmin;
        
        public class MyEvent {
        
            private int data;
        
            private Object source;
        
            public MyEvent(Object source, int data) {
                this.source = source;
                this.data = data;
            }
        
            public Object getSource() {
                return source;
            }
        
            public int getData() {
                return data;
            }
        }
        • Spring 코드 없음 ㅎㄷㄷ;;; 비침투성(중요)
    • Event 처리

      • \main\java\me\..\MyEventHandler

        package me.jinmin;
        
        import org.springframework.context.event.EventListener;
        import org.springframework.stereotype.Component;
        
        @Component
        public class MyEventHandler {
        
            @EventListener //중요
            public void handle(MyEvent event) {
                System.out.println("이벤트를 받았다. 데이터는 : " + event.getData());
            }
        }
    • AppRunner 실행 결과는 4.2 이전과 같다.

  • 복수 개의 Event(Handler) 처리

    • MyEvnetHandler

      package me.jinmin;
      
      import org.springframework.context.event.EventListener;
      import org.springframework.stereotype.Component;
      
      @Component
      public class MyEventHandler {
      
          @EventListener
          public void handle(MyEvent event) {
              System.out.println(Thread.currentThread().toString());
              System.out.println("이벤트를 받았다. 데이터는 : " + event.getData());
          }
      }
    • AnotherHandler

      package me.jinmin;
      
      import org.springframework.context.event.EventListener;
      import org.springframework.stereotype.Component;
      
      @Component
      public class AnotherHandler {
      
          @EventListener
          public void handle(MyEvent myEvent){
              System.out.println(Thread.currentThread().toString());
              System.out.println("AnotherHandler " + myEvent.getData());
          }
      }
    • AppRunner.class 구동 결과

      print:
      Thread[main,5,main]
      AnotherHandler 100
      Thread[main,5,main]
      이벤트를 받았다. 데이터는 : 100

      ⇒ 같은 쓰레드에서의 진행과 같은 값.

    • (1) 순서는 랜덤인지 정해진 순서가 있는지 모른다. ⚾But, 정해줄 수는 있다.
      @Order
      - 각 Handler에 @EventListener 아래 @Order(Ordered.HIGHEST_PRECEDENCE)@Order(Ordered.HIGHEST_PRECEDENCE + 2) 를 추가하면 +2를 한 EventHandler가 나중에 발생한다.

    • (2) 비동기로 실행하고 싶다?

      • 구동 Class의 @SpringBootApplication아래 @EnableAsync를 추가하고 + 각 Handler에 @EventListener 아래 @Async를 추가. (순서 보장 x)

      • ‼비동기로 이벤트를 발생기키면 Thread 별로 발생하기 때문에 Thread 스케줄링에 따라 순서가 정해지고 @Order는 소용없게 된다.

      • 결과

        print:
        Thread[task-2,5,main]
        이벤트를 받았다. 데이터는 : 100
        Thread[task-1,5,main]
        AnotherHandler 100
  • Spring이 제공하는 기본 이벤트

    • ContextRefreshedEvent: ApplicationContext를 초기화 했거나 리프래시 했을 때 발생.

    • ContextStartedEvent: ApplicationContext를 start()하여 라이프사이클 빈들이 시작
      신호를 받은 시점에 발생.

    • ContextStoppedEvent: ApplicationContext를 stop()하여 라이프사이클 빈들이 정지
      신호를 받은 시점에 발생.

    • ContextClosedEvent: ApplicationContext를 close()하여 싱글톤 빈 소멸되는 시점에
      발생.

    • RequestHandledEvent: HTTP 요청을 처리했을 때 발생.

    • 사용법

      package me.jinmin;
      
      import org.springframework.context.event.ContextClosedEvent;
      import org.springframework.context.event.ContextRefreshedEvent;
      import org.springframework.context.event.EventListener;
      import org.springframework.scheduling.annotation.Async;
      import org.springframework.stereotype.Component;
      
      @Component
      public class MyEventHandler {
      
          @EventListener
          @Async
          public void handle(MyEvent event) {
              System.out.println(Thread.currentThread().toString());
              System.out.println("이벤트를 받았다. 데이터는 : " + event.getData());
          }
      
          @EventListener
          @Async
          public void handle(ContextRefreshedEvent event) {
              System.out.println(Thread.currentThread().toString());
              System.out.println("ContextRefreshedEvent");
              var applicationContext = event.getApplicationContext();
          }
      
          @EventListener
          @Async
          public void handle(ContextClosedEvent event) {
              System.out.println(Thread.currentThread().toString());
              System.out.println("ContextRefreshedEvent");
              var applicationContext = event.getApplicationContext();
          }
      }

ResourceLoader

  • 리소스를 읽어오는 기능을 제공하는 인터페이스

  • \main\java\me...\AppRunner

    package me.jinmin;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.ApplicationArguments;
    import org.springframework.boot.ApplicationRunner;
    import org.springframework.core.io.Resource;
    import org.springframework.core.io.ResourceLoader;
    import org.springframework.stereotype.Component;
    
    import java.nio.file.Files;
    import java.nio.file.Path;
    
    @Component
    public class AppRunner implements ApplicationRunner {
    
        @Autowired
        ResourceLoader resourceLoader;
    
        @Override
        public void run(ApplicationArguments args) throws Exception {
            Resource resource = resourceLoader.getResource("classpath:test.txt");
            System.out.println(resource.exists());
            System.out.println(resource.getDescription());
            System.out.println(Files.readString(Path.of(resource.getURI())));
        }
    }
  • \resources\test.txt ⇒ "Hello Spring"

  • 결과

profile
열심히 해보자9999

0개의 댓글