profile image

L o a d i n g . . .

728x90

해당 포스팅은 [Spring Boot] 대체 어떻게 실행되는걸까 ? 포스팅의 일부입니다.

2022.05.01 - [Java] - [Spring Boot] 대체 어떻게 실행되는걸까 ?

 

[Spring Boot] 대체 어떻게 실행되는걸까 ?

beanFactory.registerSingleton("springApplicationArguments", applicationArguments); Spring boot를 사용하다 보면 이 마법 같은 프레임워크가 어떻게 동작하는지 궁금할 때가 있습니다. Spring boot는 어떻게..

code-run.tistory.com

ApplicationContext의 기능 

ApplicationContext는 Spring의 advanced container입니다. ApplicationContext는 다음과 같은 기능을 가집니다. 

  1. ListableBeanFactory를 상속함으로써 ApplicationContext에 등록된 Bean에 접근할 수 있습니다.
  2. ResourceLoader를 상속함으로써 다양한(file, url, etc) resources를 읽을 수 있습니다. 
  3. ApplicationEventPublisher을 상속함으로써 event를 발생시킬 수 있습니다. 
  4. MessageSource을 상속함으로서 message의 internationalization 기능을 제공합니다. 
  5. Parent context에 저장된 값을 상속받을 수 있습니다. 만약 동일한 값이 존재한다면 Child context의 값이 우선순위가 높습니다. 이러한 방식의 장점은 공통으로 사용할 수 있는 Parent context를 설정할 수 있다는 점입니다. 
  6. BeanFactory의 lifecycle 기능 외에도 ApplicationContext는 ApplicationContextAware, ResourceLoaderAware, ApplicationEventPublisherAware, MessageSourceAware bean을 감지하고 호출할 수 있습니다. 

 

ApplicationContext가 상속하는 인터페이스는 모두 Spring의 주요 기능을 제공하기 때문에 중요합니다. 각 클래스에 대해 상세히 살펴보겠습니다. 

 

ListableBeanFactory 

BeanFactory를 상속한 인터페이스입니다. 이름 그대로 등록된 bean들을 열거할 수 있는 기능이 존재합니다(기존에는 bean의 이름을 통해서 하나하나 검색해야 합니다). 주요 메서드는 다음과 같습니다. 

  • getBeanDefinitionNames() : 등록된 모든 bean 이름을 Sring[] 형태로 반환합니다. 
  • getBeanNamesForType(Class<?> type): 등록된 bean들 중 인자로 전달된 타입인 bean들을 String[] 형태로 반환합니다.
  • getBeansWithAnnotation(Class<? extends Annotation> annotationType): 특정 Annotation을 사용중인 모든 bean을 Map<String, Object> 형태로 반환합니다. 

getBeanDefinitionNames()를 통해 등록된 bean의 이름을 나열

 

ResourceLoader 

Classpath 또는 file system으로부터 resource를 읽어들이기 위한 인터페이스입니다. ApplicationContext는 ResourceLoader을 통해서 Resource 객체를 읽어들입니다. ResourceLoader은 ApplicationContext의 유형에 맞게 Resource를 다음과 같이 읽어들입니다. 

  • ClassPathXmlApplicationContext의 경우 ClassPathResource
  • FileSystemXmlApplicationContext의 경우 FileSystemResource 
  • WebApplicationContext의 경우 ServletContextResource 

ApplicationContext의 형태와 무관하게 특정 Resource 타입을 사용하도록 강제할 수도 있습니다. 

  • context.getResource("classpath:some/resource/path/example.txt") 의 경우 ClassPathResource를 반환
  • context.getResource("file:/some/resource/path/example.txt")의 경우 FileSystemResource를 반환 
  • context.getResource("http://example.com/resource/path/example.txt")의 경우 UrlResource를 반환 
  • context.getResource("/path/example.txt")의 경우 ApplicationContext형태에 의해 결정 

 

ApplicationEventPublishers 

Event publishing 기능을 제공하는 인터페이스입니다. ApplicationContext의 publishEvent 메서드를 활용하면 쉽게 이벤트를 발생시킬 수 있습니다. 주의할 점은 event를 처리하는게 동기적으로 동작하기 때문에 event의 비동기 처리를 위해서는 추가적인 설정이 필요합니다. 다음 코드는 간단한 방법으로 ApplicationEventPublisher을 활용하는 예제입니다. CustomCommandLineRunner가 실행되면서 applicationContext.publishEvent(new CustomEvent()) 를 통해서 event를 발생시킵니다. 발생한 Event는 CustomEventListener을 통해서 처리됩니다. 

class CustomEvent {

}

@Component
class CustomCommandLineRunner implements CommandLineRunner {

    @Autowired
    ApplicationContext applicationContext;

    @Override
    public void run(String... args) throws Exception {
        applicationContext.publishEvent(new CustomEvent());
    }
}

@Component
class CustomEventListener {

    @EventListener
    public void handle(CustomEvent customEvent) {
        System.out.println("custom event fired");
    }
}

 

MessageSource 

ApplicationContext의 MessageSource 기능을 활용해서 특정 message가 어떻게 표현될지 쉽게 설정할 수 있습니다. 해당 기능을 활용하면 쉽게 message를 여러 언어로 변환이 가능합니다. 단, 인텔리제이에서 messages_ko.properties(한국어) messageSource를 설정해서 사용하실 때 "??? ???"의 문제가 발생할 수 있습니다. 이는 인텔리제의 인코딩 설정과 관련된 문제입니다. 다음 포스팅에서 자세한 해결방법을 설명하고 있습니다

Spring MessageSource를 사용하기 위한 기본 설정은 다음과 같습니다. messages 접두사로 시작하는 파일을 resources 디렉터리 아래에 생성합니다.

messages 파일 위치

각 파일은 messages로 시작하고 접미사로 각 국가 코드를 사용함으로써 java의 Locale 설정과 연동할 수 있습니다(물론 Spring 설정을 통해 messages 파일명과 파일 위치를 바꿀 수 있습니다). 예를 들면 messages_ko.properties 파일은 MessageSource의 Locale설정이 Locale.KOREAN으로 설정됐을 때 사용됩니다. 아무런 접미사가 없는 messages.properties 파일은 Spring의 MessageSource가 매칭되는 Locale 설정을 찾을 수 없을 때 사용하는 파일입니다. 각 파일에는 사용하고자 하는 message를 key=value 형태로 기입합니다.

message 설정

각각의 name이 Locale 설정에 따라 어떻게 출력되는지 확인해보겠습니다. Locale.getDefault()는 JVM host의 Locale값을 사용하는데, 현재 제 JVM은 Locale.KOREA로 설정이 돼있기 때문에 messages_ko.properties를 사용하는 것을 확인할 수 있습니다.

Locale에 따른 출력 결과

 

ParentContext, ChildContext 

Spring의 ApplicationContext는 ParentContext와 ChildContext의 개념을 통해서 bean을 재활용하는 기능을 제공합니다. 만약 ChildContext에서 찾고자 하는 bean이 없다면 ParentContext에서 bean을 탐색합니다. 가장 대표적인 활용의 예시로는 Spring MVC의 DispatcherServlet의 WebApplicationContext입니다. ParentContext로 Root WebApplicationContext가 존재하며, Servlet의 WebApplicationContext는 각자 필요한 Bean을 등록해서 사용하고 Servlet(s)이 공통으로 사용하는 Bean은 Root WebApplicationContext에서 가져와 사용합니다.

[출처]https://docs.spring.io/spring-framework/docs/5.0.0.RC3/spring-framework-reference/web.html#spring-web

 

ApplicationContextAware, ResourceLoaderAware ...

위와같이 Aware로 끝나는 interface를 구현하면 다양한 시점에 bean에서 구현한 메서드를 호출할 수 있습니다.

private void invokeAwareInterfaces(Object bean) {
    if (bean instanceof EnvironmentAware) {
        ((EnvironmentAware)bean).setEnvironment(this.applicationContext.getEnvironment());
    }

    if (bean instanceof EmbeddedValueResolverAware) {
        ((EmbeddedValueResolverAware)bean).setEmbeddedValueResolver(this.embeddedValueResolver);
    }

    if (bean instanceof ResourceLoaderAware) {
        ((ResourceLoaderAware)bean).setResourceLoader(this.applicationContext);
    }

    if (bean instanceof ApplicationEventPublisherAware) {
        ((ApplicationEventPublisherAware)bean).setApplicationEventPublisher(this.applicationContext);
    }

    if (bean instanceof MessageSourceAware) {
        ((MessageSourceAware)bean).setMessageSource(this.applicationContext);
    }

    if (bean instanceof ApplicationStartupAware) {
        ((ApplicationStartupAware)bean).setApplicationStartup(this.applicationContext.getApplicationStartup());
    }

    if (bean instanceof ApplicationContextAware) {
        ((ApplicationContextAware)bean).setApplicationContext(this.applicationContext);
    }

}

위의 코드는 ApplicationContextAwareProcessor의 invokeAwareInterfaces 메서드입니다. Bean이 OOOAware 인터페이스를 구현했다면 위의 메서드가 실행되면서 Bean이 구현한 메서드가 실행됩니다. 예를 들면 

@Component
class Example implements ApplicationContextAware {

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("Some logic in here");
    }
}

ApplicationContextAwareProcessor의 invokeAwareInterfaces메서드가 호출되면 Example bean의 setApplicationContext의 메서드가 실행됩니다. 

결론 

ApplicationContext는 단순히 bean을 등록하고 찾는 기능뿐 아니라 다양한 기능을 제공합니다. 각 기능을 이해하고 잘 활용한다면 더욱 깔끔한 어플리케이션을 짤 수 있을것 같습니다. 

Reference 

https://docs.spring.io/spring-framework/docs/3.0.0.M3/reference/html/ch05s04.html

https://kururu.tistory.com/192

https://docs.spring.io/spring-framework/docs/5.0.0.RC3/spring-framework-reference/web.html#spring-web

728x90
복사했습니다!