Study/Spring

스프링 핵심기술 04. ApplicationContext가 상속받는 인터페이스들

going.yoon 2022. 2. 6. 13:02

ApplicationContext는 Bean Factory의 역할 + Spring의 여러가지 기능들을 담당하고 있다. 이 ApplicationContext는 다음과 같은 인터페이스들을 상속받고 있는데

  •  ApplicationEventPublisher
  •  EnvironmentCapable
  •  HierarchicalBeanFactory
  •  ListableBeanFactory
  •  MessageSource
  •  ResourceLoader
  •  ResourcePatternResolver 

 

이들 중 가장 먼저 살펴볼 것은,  EnvionmentCapable 인터페이스이다.

이 Environment가 제공하는 기능은 크게 프로파일프로퍼티로 나눌 수 있는데, 먼저 프로파일에 대해 알아보자.

 

1. 프로파일 인터페이스

이 프로파일은 개발/ 운영서버가 분리된 환경이거나 할 때 특정 상황에서만 빈을 등록하거나 조작할 수 있는 기능을 제공해준다.

 

//test 환경에서만 사용할 Repository
@Repository
@Scope("test")
public class BookRepository {

}

// test 환경에서만 등록되어야 하는 Configuration file
@Configuration
@Profile("test")
public class TestConfiguration{

	@Bean
    public BookRepository bookRepository(){
    	return new TestBookRepository();
    }
}

 

 

2. 프로퍼티 인터페이스

: 이는 프로퍼티나 시스템 환경변수 등에 접근할 수 있는 기능을 제공해 준다. 프로퍼티를 사용하여 접근할 수 있는 대상의 범위와 우선순위는 다음과 같다.

  • ServletConfig 매개변수
  • ServletContext 매개변수
  • JNDI 
  • JVM 시스템 프로퍼티
  • JVM 시스템 환경변수
// Application.class에 properties 파일 명시
@SpringBootApplication
@PropertySource("classpath:/app.properties")
public class Application {
	public static void main(String[] args){
    	SpringApplication.run(Application.class,args);
    }
}

// AppRunner.class에서 프로퍼티 파일 내 변수 접근
@Component
public class AppRunner implements ApplicationRunner {
	@Autowired
    ApplicationContext ctx;
    
    @Override
    public void run(ApplicationArguments args) throws Exception{
    	Environment environment = ctx.getEnvironment();
        String appName = environment.getProperty("app.name");
        String appAbout = environment.getProperty("app.about");
        
    }

}

 

 

3. MessageSource 인터페이스

message.properties에 등록된 메세지들을 읽어오는 인터페이스이다.

먼저 resources 아래에 messages.properties, messages_ko_KR.properties들을 생성해주면 

 

- resources

  - Resource Bundle 'messages' 

     -messages.properties

     -messages_ko_KR.properties

 

이런 구조로 프로퍼티 파일이 생성된다.

 

 

자바에서의 사용법은 다음과 같다.

 

@Component
public class AppRunner implements ApplicationRunner {

    @Autowired
    MessageSource messageSource; // #1. ApplicationContext가 상속받는 MessageSource 객체를 주입받는다.

    @Override
    public void run(ApplicationArguments args) throws Exception{
		
        //#2. properties 파일에 접근
        System.out.println(messageSource.getMessage("greeting", new String[]{"gayoung"}, Locale.ROOT));
        System.out.println(messageSource.getMessage("greeting", new String[]{"gayoung"}, Locale.KOREA)); // #3. messages_ko_RK.properties파일에 접근
    }
}

 

 

4. ApplicationEventPublisher 이벤트

이 기능은 이벤트를 발생시키고, 발생시킨 이벤트를 핸들링 하는 기능을 제공한다.

먼저 스프링 4.2 이전에 방식을 알아보자. 이 방식은 EventClass에서 ApplicationEvent 인터페이스를 상속받고,

Bean으로 등록된 EventHandler에서 ApplicationListener<이벤트클래스>를 상속받아야 한다.

 

// #1. event class
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;
    }
}

// #2. eventHandler class : 빈으로 등록되어야 함.
@Component
public class MyEventHandler implements ApplicationListener<MyEvent> {

    @Override
    public void onApplicationEvent(MyEvent event) {
        System.out.println("이벤트 받았다."+ event.getData() );
        System.out.println("이벤트 받았다."+ event.getSource() );
        System.out.println("이벤트 받았다."+ event.getTimestamp());
        System.out.println("이벤트 받았다."+ event.getClass());
    }
}

# 3. 이벤트 발생부
@Component
public class AppRunner implements ApplicationRunner {

    @Autowired
    ApplicationEventPublisher eventPublisher;
    
    @Override
    public void run(ApplicationArguments args) throws Exception {
        eventPublisher.publishEvent(new MyEvent(this,100));
    }
}

 

이벤트 발생 결과 #1

 

 

스프링 4.2 이상에서는 이벤트와 리스너에서 어떠한 인터페이스도 상속받지 않아도 된다.

리스너에서 @EventListener 어노테이션만 붙여준다면~

 

 

// #1. event Class
public class MyEvent { //#1.1 implements 부분이 사라짐

    private int data;
    private Object source;

    public MyEvent(Object source, int data){
        this.source = source;
        this.data = data;
    }

    public int getData(){
        return data;
    }

    public Object getSource() {
        return source;
    }
}

// #2. EventHandler class
@Component
public class MyEventHandler  {

    @EventListener // #2.1 Bean등록과 EventListner 등록은 필수. 돼지꼬리 땡땡. 
    public void onApplicationEvent(MyEvent event) {
        System.out.println("이벤트 받았다."+ event.getData() );
        System.out.println("이벤트 받았다."+ event.getSource() );
        //System.out.println("이벤트 받았다."+ event.getTimestamp());  // #2.2 얘는 에러남
        System.out.println("이벤트 받았다."+ event.getClass());
    }
}

// #3. 이벤트 발생부

@Component
public class AppRunner implements ApplicationRunner {

    @Autowired
    ApplicationEventPublisher eventPublisher;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        eventPublisher.publishEvent(new MyEvent(this,100));
    }
}

이벤트 발생 결과 #2

 

 

또한 만약 동일 이벤트에 대한 핸들러가 여러개일 경우, 이 핸들러간의 순서를 정하거나 / 아니면 비동기적으로 처리할 수도 있다.

순서를 정하고 싶으면 @Order(Ordered.HIGHEST_PRECEDENCE) 옵션을 사용하면 되고,

비동기적으로 처리하고 싶으면 @Async 애노테이션 및 Application.class에서 @EnableAsync 애노테이션을 사용하면 된다.

 

// #1. 새로운 핸들러 추가
@Component
public class MyNewEventHandler {

    @EventListener
    //@Order(Ordered.HIGHEST_PRECEDENCE)
    @Async
    public void newHandle(MyEvent event){
        System.out.println(Thread.currentThread().toString());
        System.out.println("새로운 핸들러 호출");
    }

}

// #2. 기존의 핸들러
@Component
public class MyEventHandler  {

    @EventListener
    //@Order(Ordered.HIGHEST_PRECEDENCE+2)
    @Async
    public void onApplicationEvent(MyEvent event) {
        System.out.println(Thread.currentThread().toString());
        System.out.println("이벤트 받았다."+ event.getData() );
    }
}


# 3. 이벤트 호출부 
@SpringBootApplication
@EnableAsync // #3.1 비동기 처리를 위한 애노테이션 추가
public class Application {

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}

}

비동기 방식으로 이벤트 핸들러 호출 - Thread값이 다르게 찍힘 확인

 

또한 스프링에서 기본적으로 제공하는 이벤트를 처리할 수도 있다.

@Component
public class MyEventHandler  {
    @EventListener
    @Async
    public void handle(ContextRefreshedEvent event){
        System.out.println("context is refreshed");
    }
}

  위의 예시는 ApplicationContext가 초기화 될 때 발생하는 ContextRefreshedEvent를 이벤트 핸들러에서 처리한 것이다. 그 외에도 스프링에서 기본적으로 제공하는 이벤트는 아래와 같은 것들이 있다.

 

  • ContextRefreshedEvent : ApplicationContext를 초기화 했거나 리프레시 했을 때 발생
  • ContextStartedEvent : ApplicationContext를 start()하여 라이프 사이클 빈들이 시작 신호를 받은 시점에 발생
  • ContextStoppedEvent: ApplicationContext를 stop()하여 라이프 사이클 빈들이 정지 신호를 받은 시점에 발생
  • ContextClosedEvent : ApplicationContext를 close()하여 싱글톤 빈들이 소멸되는 시점에 발생
  • RequestHandledEvent: HTTP요청을 처리했을 때 발생

 

 

5. ResourceLoader 인터페이스

이 인터페이스는 다른 파일들을 읽어와야 할 때 사용할 수 있는 기능을 제공한다.

@Component
public class AppRunner implements ApplicationRunner {
    @Autowired
    ResourceLoader resourceLoader; //#1. resourceLoader 주입
    @Override
    public void run(ApplicationArguments args) throws Exception {
        Resource resource = resourceLoader.getResource("classpath:test.txt"); // #2. 가져올 resource의 경로 입력
        System.out.println("resource exits?" + resource.exists());
        System.out.println("resource : " + resource);
    }
}