2024. 11. 15. 09:20ㆍSpring Framework/Spring IoC
서론에서 논의한 바와 같이, org.springframework.beans.factory
패키지는 프로그램 방식으로도 빈을 관리하고 조작할 수 있는 기본 기능을 제공합니다. org.springframework.context
패키지는 애플리케이션 프레임워크 지향 스타일로 추가 기능을 제공하기 위해 다른 인터페이스를 확장하는 것 외에도, BeanFactory
인터페이스를 확장한 ApplicationContext
인터페이스를 추가합니다. 많은 사람들이 ApplicationContext
를 완전히 선언적으로 사용하여 프로그램 방식으로 생성하지 않고, ContextLoader
같은 지원 클래스를 사용하여 Jakarta EE 웹 애플리케이션의 일반적인 시작 프로세스의 일부로 자동으로 ApplicationContext
를 인스턴스화하는 방식에 의존합니다.
프레임워크 지향 스타일로 BeanFactory
기능을 확장하기 위해 context
패키지는 다음과 같은 기능도 제공합니다:
MessageSource
인터페이스를 통한 i18n 스타일의 메시지 접근.ResourceLoader
인터페이스를 통한 URL과 파일과 같은 리소스 접근.ApplicationEventPublisher
인터페이스를 사용하여ApplicationListener
인터페이스를 구현하는 빈에게 이벤트 발행.HierarchicalBeanFactory
인터페이스를 통해 다중(계층적) 컨텍스트 로딩을 지원하여 각 컨텍스트가 애플리케이션의 웹 계층과 같은 특정 계층에 집중할 수 있도록 함.
Internationalization using MessageSource
ApplicationContext
인터페이스는 MessageSource
라는 인터페이스를 확장하여 국제화(i18n) 기능을 제공합니다. Spring은 또한 메시지를 계층적으로 해결할 수 있는 HierarchicalMessageSource
인터페이스도 제공합니다. 이 인터페이스들은 Spring이 메시지 해석을 수행할 수 있는 기반을 제공합니다. 이 인터페이스에서 정의된 메서드는 다음과 같습니다:
String getMessage(String code, Object[] args, String default, Locale loc)
:MessageSource
에서 메시지를 검색하는 기본 메서드입니다. 지정된 로케일에 대한 메시지가 없으면 기본 메시지가 사용됩니다. 전달된 아규먼트는 표준 라이브러리의MessageFormat
기능을 사용하여 치환 값이 됩니다.String getMessage(String code, Object[] args, Locale loc)
: 이전 메서드와 거의 동일하지만, 기본 메시지를 지정할 수 없습니다. 메시지를 찾을 수 없으면NoSuchMessageException
이 발생합니다.String getMessage(MessageSourceResolvable resolvable, Locale locale)
: 앞의 메서드들에서 사용된 모든 속성은MessageSourceResolvable
라는 클래스에 포함되어 있으며, 이 메서드와 함께 사용할 수 있습니다.
ApplicationContext
가 로드될 때, 컨텍스트에 정의된 MessageSource
빈을 자동으로 검색합니다. 이 빈은 messageSource
라는 이름을 가져야 합니다. 이와 같은 빈이 발견되면, 앞서 설명한 메서드들에 대한 모든 호출이 해당 메시지 소스로 위임됩니다. 메시지 소스를 찾을 수 없는 경우, ApplicationContext
는 동일한 이름을 가진 빈을 포함하는 상위 컨텍스트를 찾으려 시도하고, 해당 빈이 있으면 이를 MessageSource
로 사용합니다. ApplicationContext
가 메시지 소스를 찾을 수 없으면 위 메서드 호출을 수용할 수 있도록 빈 DelegatingMessageSource
가 인스턴스화됩니다.
Spring은 ResourceBundleMessageSource
, ReloadableResourceBundleMessageSource
, StaticMessageSource
라는 세 가지 MessageSource
구현을 제공합니다. 이들은 모두 계층적 메시징을 위해 HierarchicalMessageSource
를 구현합니다. StaticMessageSource
는 거의 사용되지 않지만, 소스에 메시지를 프로그램 방식으로 추가할 수 있는 방법을 제공합니다. 다음 예제는 ResourceBundleMessageSource
를 보여줍니다.
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
@Configuration
public class AppConfig {
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasenames("format", "exceptions", "windows");
return messageSource;
}
}
이 예제는 클래스패스에 format, exceptions, windows라는 세 개의 리소스 번들이 정의되어 있다고 가정합니다. 메시지를 해석하려는 모든 요청은 JDK 표준 방식에 따라 ResourceBundle 객체를 통해 처리됩니다. 예제를 위해 위의 리소스 번들 파일 중 두 개의 내용은 다음과 같다고 가정합니다:
# in format.properties
message=Alligators rock!
# in exceptions.properties
argument.required=The {0} argument is required.
다음 예제는 MessageSource 기능을 실행하는 프로그램을 보여줍니다. 모든 ApplicationContext 구현은 또한 MessageSource 구현이므로 MessageSource 인터페이스로 캐스팅할 수 있다는 점을 기억하세요.
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ResourceBundleMessageSource;
import java.util.Locale;
public class MessageSourceExample {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// Retrieve MessageSource from ApplicationContext
ResourceBundleMessageSource messageSource = (ResourceBundleMessageSource) context.getBean("messageSource");
// Fetch messages using different locales
String message = messageSource.getMessage("message", null, Locale.ENGLISH);
System.out.println(message);
}
}
위 프로그램의 실행 결과 출력은 다음과 같습니다:
Alligators rock!
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import java.util.Locale;
@Configuration
public class AppConfig {
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("exceptions");
return messageSource;
}
@Bean
public Example example(MessageSource messageSource) {
Example example = new Example();
example.setMessages(messageSource);
return example;
}
}
class Example {
private MessageSource messages;
public void setMessages(MessageSource messages) {
this.messages = messages;
}
public void execute() {
String message = this.messages.getMessage("argument.required",
new Object[]{"userDao"}, "Required", Locale.ENGLISH);
System.out.println(message);
}
}
영국 로케일을 위한 execute
메서드 호출 예제
다음은 영국 로케일(Locale.UK
)을 사용하여 메시지를 출력하는 예제입니다. exceptions_en_GB.properties
파일이 클래스패스에 포함되어 있다고 가정합니다.
public class MainApp {
public static void main(String[] args) {
MessageSource messageSource = new AnnotationConfigApplicationContext(AppConfig.class);
String message = messageSource.getMessage("argument.required",
new Object[]{"userDao"}, "Required", Locale.UK);
System.out.println(message);
}
}
추가 참고사항
- MessageSourceAware 사용:
MessageSourceAware
인터페이스를 구현한 빈은 애플리케이션 컨텍스트가 빈을 생성할 때 자동으로MessageSource
를 주입받을 수 있습니다. - ReloadableResourceBundleMessageSource 사용:
ResourceBundleMessageSource
대신ReloadableResourceBundleMessageSource
를 사용하면, 클래스패스 외부의 리소스 위치에서 번들 파일을 읽을 수 있으며, 번들 프로퍼티 파일을 핫 리로딩할 수 있습니다. 이를 통해 더 유연한 메시지 로딩과 캐싱 기능을 제공합니다.
Standard and Custom Events
ApplicationContext에서의 이벤트 처리는 ApplicationEvent 클래스와 ApplicationListener 인터페이스를 통해 제공됩니다. 만약 ApplicationListener 인터페이스를 구현한 빈이 컨텍스트에 배포되면, ApplicationEvent가 ApplicationContext에 발행될 때마다 해당 빈은 알림을 받습니다. 디폴트로 이는 표준 옵저버(Observer) 디자인 패턴입니다.
Spring 4.2부터 이벤트 인프라가 크게 개선되어 애노테이션 기반 모델과 임의의 이벤트(즉, `ApplicationEvent`를 반드시 확장하지 않는 객체)를 발행할 수 있는 기능을 제공합니다. 이러한 객체가 발행되면, Spring이 이를 이벤트로 감싸 처리합니다.
다음 테이블은 스프링이 제공하는 standard event들을 설명합니다.
Table 1. Built-in Events
Event | Explanation |
---|---|
ContextRefreshedEvent | ApplicationContext가 초기화되거나 새로 고침될 때 발행됩니다(예: ConfigurableApplicationContext 인터페이스의 refresh() 메서드를 사용하여). 여기서 " initialized "는 모든 빈이 로드되고, 후처리 빈이 감지 및 활성화되며, 싱글톤이 사전 인스턴스화되고, ApplicationContext 객체가 사용 준비된 상태를 의미합니다. 컨텍스트가 닫히지 않은 한, 선택한 ApplicationContext가 이러한 " hot" 새로 고침을 실제로 지원한다면 새로 고침이 여러 번 트리거될 수 있습니다. 예를 들어, `XmlWebApplicationContext`는 핫 새로 고침을 지원하지만, `GenericApplicationContext`는 지원하지 않습니다. |
ContextStartedEvent | ConfigurableApplicationContext 인터페이스의 start() 메서드를 사용하여 ApplicationContext가 시작될 때 게시됩니다. 여기서 "started"은 모든 Lifecycle 빈이 명시적인 시작 신호를 받았음을 의미합니다. 일반적으로 이 신호는 명시적으로 중지된 후 빈을 다시 시작하는 데 사용되지만, 초기화 시 이미 시작되지 않은 구성 요소(예: 자동 시작으로 설정되지 않은 구성 요소)를 시작하는 데도 사용할 수 있습니다. |
ContextStoppedEvent | ConfigurableApplicationContext 인터페이스의 stop() 메서드를 사용하여 ApplicationContext가 중지될 때 게시됩니다. 여기서 "stopped"은 모든 Lifecycle 빈이 명시적인 중지 신호를 받았음을 의미합니다. 중지된 컨텍스트는 start() 호출을 통해 다시 시작될 수 있습니다. |
ContextClosedEvent | ConfigurableApplicationContext 인터페이스의 close() 메서드를 사용하거나 JVM 종료 훅을 통해 ApplicationContext가 닫힐 때 게시됩니다. 여기서 "closed"은 모든 싱글톤 빈이 소멸된 상태를 의미합니다. 컨텍스트가 닫히면 수명이 종료되며, 다시 새로 고치거나 재시작할 수 없습니다. |
RequestHandledEvent | HTTP request가 처리되었음을 모든 빈에게 알리는 웹 전용 이벤트입니다. 이 이벤트는 요청이 완료된 후에 게시됩니다. 이 이벤트는 Spring의 DispatcherServlet을 사용하는 웹 애플리케이션에만 적용됩니다. |
ServletRequestHandledEvent | RequestHandledEvent의 하위 클래스이며, Servlet에 특화된 컨텍스트 정보를 추가로 제공합니다. |
다음은 Spring의 ApplicationEvent 베이스 클래스를 확장하여 사용자 정의 이벤트를 생성하고 발해하는 간단한 클래스 예제입니다:
public class BlockedListEvent extends ApplicationEvent {
private final String address;
private final String content;
public BlockedListEvent(Object source, String address, String content) {
super(source);
this.address = address;
this.content = content;
}
// accessor and other methods...
}
사용자 정의 ApplicationEvent를 발행하려면 ApplicationEventPublisher의 publishEvent() 메서드를 호출하면 됩니다. 일반적으로 이는 ApplicationEventPublisherAware를 구현하고 Spring 빈으로 등록된 클래스를 생성하여 수행됩니다. 다음은 이러한 클래스를 보여주는 예제입니다.
public class EmailService implements ApplicationEventPublisherAware {
private List<String> blockedList;
private ApplicationEventPublisher publisher;
public void setBlockedList(List<String> blockedList) {
this.blockedList = blockedList;
}
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void sendEmail(String address, String content) {
if (blockedList.contains(address)) {
publisher.publishEvent(new BlockedListEvent(this, address, content));
return;
}
// send email...
}
}
구성 시, Spring 컨테이너는 EmailService가 ApplicationEventPublisherAware를 구현했음을 감지하고 자동으로 setApplicationEventPublisher()를 호출합니다. 실제로 전달되는 파라미터는 Spring 컨테이너 자체입니다. 이는 ApplicationEventPublisher 인터페이스를 통해 애플리케이션 컨텍스트와 상호작용하는 것입니다.
사용자 정의 ApplicationEvent를 수신하려면 ApplicationListener를 구현하는 클래스를 생성하고 이를 Spring 빈으로 등록하면 됩니다. 다음은 이러한 클래스를 보여주는 예제입니다.
public class BlockedListNotifier implements ApplicationListener<BlockedListEvent> {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
public void onApplicationEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
ApplicationListener는 사용자 정의 이벤트 타입(위 예제에서는 BlockedListEvent)으로 제네릭하게 파라미터화됩니다. 이는 onApplicationEvent() 메서드가 타입 안전성을 유지할 수 있도록 하여 다운캐스팅이 필요 없게 만듭니다. 원하는 만큼 이벤트 리스너를 등록할 수 있지만, 디폴트로 이벤트 리스너는 이벤트를 동기적으로 수신한다는 점에 유의해야 합니다. 이는 publishEvent() 메서드가 모든 리스너가 이벤트 처리를 완료할 때까지 블록된다는 것을 의미합니다.
이 동기적이고 단일 스레드 방식의 한 가지 이점은, 리스너가 이벤트를 수신할 때 트랜잭션 컨텍스트가 사용 가능하다면 게시자의 트랜잭션 컨텍스트 내에서 동작한다는 점입니다. 기본적으로 비동기 이벤트 처리가 필요한 경우와 같은 다른 이벤트 발행 전략이 필요하다면, Spring의 ApplicationEventMulticaster 인터페이스와 SimpleApplicationEventMulticaster 구현에 대한 javadoc을 참조하여 사용자 정의 "applicationEventMulticaster" 빈 정의에 적용할 수 있는 구성 옵션을 확인하세요. 이러한 경우, 이벤트 처리에서는 ThreadLocals 및 로깅 컨텍스트가 전파되지 않습니다. 관찰 가능성(Observability) 문제에 대한 자세한 내용은 @EventListener 관찰 가능성 섹션을 참조하세요.
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.List;
@Configuration
public class AppConfig {
@Bean
public EmailService emailService() {
EmailService emailService = new EmailService();
emailService.setBlockedList(List.of(
"known.spammer@example.org",
"known.hacker@example.org",
"john.doe@example.org"
));
return emailService;
}
@Bean
public BlockedListNotifier blockedListNotifier() {
BlockedListNotifier notifier = new BlockedListNotifier();
notifier.setNotificationAddress("blockedlist@example.org");
return notifier;
}
@Bean(name = "applicationEventMulticaster")
public SimpleApplicationEventMulticaster applicationEventMulticaster() {
SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(5);
taskExecutor.initialize();
multicaster.setTaskExecutor(taskExecutor);
multicaster.setErrorHandler(throwable -> System.err.println("Error in event processing: " + throwable.getMessage()));
return multicaster;
}
}
class EmailService implements ApplicationEventPublisherAware {
private List<String> blockedList;
private ApplicationEventPublisher eventPublisher;
public void setBlockedList(List<String> blockedList) {
this.blockedList = blockedList;
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void sendEmail(String email, String content) {
if (blockedList.contains(email)) {
eventPublisher.publishEvent(new BlockedListEvent(this, email));
} else {
System.out.println("Email sent to: " + email);
}
}
}
class BlockedListNotifier implements ApplicationListener<BlockedListEvent> {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
@Override
public void onApplicationEvent(BlockedListEvent event) {
System.out.println("Notification sent to: " + notificationAddress + " about blocked email: " + event.getBlockedEmail());
}
}
class BlockedListEvent extends ApplicationEvent {
private final String blockedEmail;
public BlockedListEvent(Object source, String blockedEmail) {
super(source);
this.blockedEmail = blockedEmail;
}
public String getBlockedEmail() {
return blockedEmail;
}
}
설명:
- EmailService:
ApplicationEventPublisherAware
를 구현하여 커스텀 이벤트를 게시할 수 있습니다.sendEmail
메서드에서 이메일이 차단 목록에 있으면BlockedListEvent
를 게시합니다.
- BlockedListNotifier:
ApplicationListener<BlockedListEvent>
를 구현하여BlockedListEvent
를 처리합니다.- 차단된 이메일 정보를 기반으로 알림을 보냅니다.
- applicationEventMulticaster:
SimpleApplicationEventMulticaster
를 통해 비동기 이벤트 처리를 지원하도록 구성합니다.taskExecutor
를 설정하여 이벤트 처리를 병렬로 수행할 수 있습니다.
Annotation-based Event Listeners
@EventListener 애노테이션을 사용하여 관리되는 빈의 모든 메서드에 이벤트 리스너를 등록할 수 있습니다. BlockedListNotifier를 다음과 같이 재작성할 수 있습니다:
public class BlockedListNotifier {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
@EventListener
public void processBlockedListEvent(BlockedListEvent event) {
// notificationAddress를 통해 적절한 대상에게 알림을 보냄...
}
}
메서드 시그니처는 다시 이벤트 타입을 선언하지만, 이번에는 유연한 이름을 사용하고 특정 리스너 인터페이스를 구현하지 않습니다. 제네릭을 통해 이벤트 타입을 좁힐 수도 있으며, 실제 이벤트 타입이 제네릭 파라미터를 구현 계층에서 해결할 수 있는 경우에 가능합니다.
메서드가 여러 이벤트를 수신하도록 하거나 파라미터 없이 정의하려면, 애노테이션 자체에 이벤트 타입을 지정할 수도 있습니다. 다음 예제는 그 방법을 보여줍니다:
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
// ...
}
또한 SpEL 표현식을 정의하는 애노테이션의 condition
속성을 사용하여 추가 런타임 필터링을 추가할 수도 있습니다. 특정 이벤트에 대해 메서드를 호출할 조건을 정의할 수 있습니다.
다음 예제는 이벤트의 content
속성이 my-event
와 같을 때만 호출되도록 notifier
를 재작성한 방법을 보여줍니다:
@EventListener(condition = "#blEvent.content == 'my-event'")
public void processBlockedListEvent(BlockedListEvent blEvent) {
// notificationAddress를 통해 적절한 대상에게 알림을 보냄...
}
각 SpEL 표현식은 전용 컨텍스트에서 평가됩니다. 다음 테이블은 조건부 이벤트 처리를 위해 컨텍스트에서 사용할 수 있는 항목들을 보여줍니다:
Table 2. Event metadata available in SpEL expressions
Name | Location | Description | Example |
---|---|---|---|
Event | root object | The actual ApplicationEvent. | #root.event or event |
Arguments array | root object | The arguments (as an object array) used to invoke the method. | #root.args or args; args[0] to access the first argument, etc. |
Argument name | evaluation context | The name of a particular method argument. If the names are not available (for example, because the code was compiled without the -parameters flag), individual arguments are also available using the #a<#arg> syntax where <#arg> stands for the argument index (starting from 0). | #blEvent or #a0 (you can also use #p0 or #p<#arg> parameter notation as an alias) |
#root.event를 사용하면 메서드 시그니처가 실제로 발행된 임의의 객체를 참조하더라도 기본 이벤트에 접근할 수 있습니다.
다른 이벤트를 처리한 결과로 이벤트를 발행해야 하는 경우, 메서드 시그니처를 변경하여 발행할 이벤트를 리하도록 설정할 수 있습니다. 다음 예제가 이를 보여줍니다:
@EventListener
public ListUpdateEvent handleBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
이 기능은 asynchronous listeners 를 지원하지 않습니다.
handleBlockedListEvent() 메서드는 처리하는 각 BlockedListEvent에 대해 새로운 ListUpdateEvent를 게시합니다. 여러 이벤트를 게시해야 하는 경우, 이벤트의 Collection이나 배열을 반환할 수 있습니다.
Asynchronous Listeners
특정 리스너가 이벤트를 비동기적으로 처리하도록 설정하려면, 일반적인 @Async
지원을 재사용할 수 있습니다. 다음 예제는 이를 구현하는 방법을 보여줍니다:
@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {
// BlockedListEvent는 별도의 스레드에서 처리됩니다.
}
비동기 이벤트를 사용할 때는 다음과 같은 제한 사항에 유의해야 합니다:
- 비동기 이벤트 리스너가
Exception
을 발생시키는 경우, 이는 호출자에게 전파되지 않습니다. 자세한 내용은AsyncUncaughtExceptionHandler
를 참조하세요. - 비동기 이벤트 리스너 메서드는 값을 반환하여 후속 이벤트를 게시할 수 없습니다. 처리가 완료된 후 다른 이벤트를 게시해야 하는 경우,
ApplicationEventPublisher
를 주입하여 이벤트를 수동으로 게시해야 합니다. ThreadLocals
와 로깅 컨텍스트는 기본적으로 이벤트 처리에 대해 전파되지 않습니다. 관찰 가능성(Observability) 문제에 대한 자세한 내용은@EventListener
관찰 가능성 섹션을 참조하세요.
Ordering Listeners
임의의 리스너가 다른 리스너보다 먼저 호출되도록 설정하려면, 메서드 선언에 @Order 애노테이션을 추가할 수 있습니다. 다음 예제는 이를 보여줍니다:
@EventListener
@Order(42)
public void processBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress...
}
Generic Events
제네릭을 사용하여 이벤트의 구조를 더 구체적으로 정의할 수도 있습니다. 예를 들어, EntityCreatedEvent를 사용하여 T가 실제로 생성된 엔터티의 타입이 되도록 설정할 수 있습니다. 다음과 같이 리스너 정의를 작성하여 Person에 대한 EntityCreatedEvent만 수신하도록 할 수 있습니다:
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
// ...
}
타입 소거(Type Erasure)로 인해, 이벤트 리스너가 필터링하는 제네릭 파라미터를 이벤트가 해결하는 경우에만 작동합니다(예: class PersonCreatedEvent extends EntityCreatedEvent { … }와 같은 경우).
모든 이벤트가 동일한 구조를 따르는 경우(앞의 예에서처럼) 이를 처리하는 것이 다소 번거로울 수 있습니다. 이러한 경우, ResolvableTypeProvider를 구현하여 런타임 환경에서 제공하는 것을 넘어 프레임워크를 안내할 수 있습니다. 다음 이벤트는 이를 구현하는 방법을 보여줍니다:
public class EntityCreatedEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {
public EntityCreatedEvent(T entity) {
super(entity);
}
@Override
public ResolvableType getResolvableType() {
return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getSource()));
}
}
이는 ApplicationEvent 뿐만 아니라 이벤트로 전송하는 임의의 객체에도 작동합니다.
마지막으로, 기존의 ApplicationListener 구현과 마찬가지로, 실제 멀티캐스팅은 런타임에 컨텍스트 전역의 ApplicationEventMulticaster를 통해 이루어집니다. 기본적으로 이는 호출자의 스레드에서 동기적으로 이벤트를 게시하는 SimpleApplicationEventMulticaster입니다. 이를 "applicationEventMulticaster" 빈 정의를 통해 대체하거나 사용자 정의할 수 있습니다. 예를 들어, 모든 이벤트를 비동기적으로 처리하거나 리스너 예외를 처리할 수 있도록 설정할 수 있습니다:
@Bean
ApplicationEventMulticaster applicationEventMulticaster() {
SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
multicaster.setTaskExecutor(...);
multicaster.setErrorHandler(...);
return multicaster;
}
Convenient Access to Low-level Resources
애플리케이션 컨텍스트를 최적화하여 사용하고 이해하기 위해서는 Spring의 Resource 추상화에 익숙해져야 합니다(자세한 내용은 Resources 섹션 참조).
애플리케이션 컨텍스트는 ResourceLoader로, 이를 사용하여 Resource 객체를 로드할 수 있습니다. Resource는 기본적으로 JDK의 java.net.URL 클래스보다 더 많은 기능을 제공하는 버전입니다. 실제로 Resource의 구현은 적절한 경우 java.net.URL 인스턴스를 감쌉니다. Resource는 클래스패스, 파일 시스템 위치, 표준 URL로 설명할 수 있는 위치, 기타 변형된 위치를 포함하여 거의 모든 위치에서 low 레벨 리소스를 투명하게 가져올 수 있습니다. 리소스 위치 문자열이 특별한 접두사가 없는 단순 경로일 경우, 해당 리소스가 어디에서 오는지는 실제 애플리케이션 컨텍스트 유형에 따라 다릅니다.
애플리케이션 컨텍스트에 배포된 빈이 ResourceLoaderAware라는 특수 콜백 인터페이스를 구현하도록 구성할 수 있으며, 초기화 시점에 애플리케이션 컨텍스트 자체가 ResourceLoader로 전달되어 자동으로 호출됩니다. 또한, 정적 리소스에 접근하기 위해 Resource 타입의 프로퍼티를 노출할 수 있으며, 이는 다른 프로퍼티와 마찬가지로 주입됩니다. 이러한 Resource 프로퍼티를 단순한 문자열 경로로 지정하고, 빈이 배포될 때 해당 텍스트 문자열에서 실제 Resource 객체로 자동 변환되도록 설정할 수 있습니다.
ApplicationContext 생성자에 제공된 위치 경로나 경로들은 실제로 리소스 문자열이며, 단순한 형태로 특정 컨텍스트 구현에 따라 적절히 처리됩니다. 예를 들어, ClassPathXmlApplicationContext는 단순한 위치 경로를 클래스패스 위치로 처리합니다. 또한, 실제 컨텍스트 유형에 상관없이 클래스패스나 URL에서 정의를 강제로 로드하도록 특정 접두사가 포함된 위치 경로(리소스 문자열)를 사용할 수도 있습니다.
Application Startup Tracking
ApplicationContext
는 Spring 애플리케이션의 라이프사이클를 관리하며, 컴포넌트를 중심으로 풍부한 프로그래밍 모델을 제공합니다. 그 결과, 복잡한 애플리케이션은 동일하게 복잡한 컴포넌트 그래프와 시작 단계를 가질 수 있습니다.
시작 단계에서 특정 메트릭(예:성능지표)를 사용해 애플리케이션 시작 단계를 추적하면, 시작 단계에서 시간이 소비되는 위치를 이해하는 데 도움을 줄 수 있으며, 컨텍스트 라이프사이클을 전체적으로 더 잘 이해하는 데에도 사용할 수 있습니다.
AbstractApplicationContext
(및 해당 서브클래스)는 ApplicationStartup
으로 계측되어 다양한 시작 단계에 대한 StartupStep
데이터를 수집합니다:
- 애플리케이션 컨텍스트 라이프사이클(기본 패키지 스캐닝, 설정 클래스 관리)
- 빈 라이프사이(인스턴스화, 스마트 초기화, 후처리)
- 애플리케이션 이벤트 처리
다음은 AnnotationConfigApplicationContext
에서 계측이 이루어지는 예제입니다:
// create a startup step and start recording
StartupStep scanPackages = getApplicationStartup().start("spring.context.base-packages.scan");
// add tagging information to the current step
scanPackages.tag("packages", () -> Arrays.toString(basePackages));
// perform the actual phase we're instrumenting
this.scanner.scan(basePackages);
// end the current step
scanPackages.end();
애플리케이션 컨텍스트는 이미 여러 단계로 계측(instrument)되어 있습니다. 기록된 시작 단계는 특정 도구를 사용하여 수집, 표시 및 분석할 수 있습니다. 기존 시작 단계의 전체 목록은 전용 부록 섹션에서 확인할 수 있습니다.
디폴트 ApplicationStartup 구현은 최소한의 오버헤드를 위해 아무 작업도 하지 않는(no-op) 변형입니다. 이는 디폴트로 애플리케이션 시작 시 메트릭이 수집되지 않음을 의미합니다. Spring Framework는 Java Flight Recorder를 사용하여 시작 단계를 추적하는 구현(FlightRecorderApplicationStartup)을 제공합니다. 이 변형을 사용하려면 ApplicationContext가 생성되자마자 이를 인스턴스로 구성해야 합니다.
개발자는 자체적으로 AbstractApplicationContext 서브클래스를 제공하거나 더 정밀한 데이터를 수집하고자 할 경우, ApplicationStartup 인프라를 사용할 수도 있습니다.
ApplicationStartup은 애플리케이션 시작 단계와 코어 컨테이너에만 사용하도록 설계되었으며, 이는 Java 프로파일러나 Micrometer와 같은 메트릭 라이브러리를 대체하려는 것이 아닙니다.
사용자 정의 StartupStep을 수집하려면, 컴포넌트는 애플리케이션 컨텍스트에서 직접 ApplicationStartup 인스턴스를 가져오거나, 컴포넌트가 ApplicationStartupAware를 구현하도록 만들거나, 주입 지점에서 ApplicationStartup 타입을 요청할 수 있습니다.
개발자는 사용자 정의 startup 단계를 생성할 때 "spring.*" 네임스페이스를 사용해서는 안 됩니다. 이 네임스페이스는 Spring 내부 용도로 예약되어 있으며 변경될 수 있습니다.
Convenient ApplicationContext Instantiation for Web Applications
다음 예제와 같이 ContextLoaderListener를 사용하여 ApplicationContext를 등록할 수 있습니다:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
리스너는 contextConfigLocation 파라미터를 검사합니다. 해당 파라미터가 존재하지 않으면, 리스너는 디폴트로로 /WEB-INF/applicationContext.xml을 사용합니다. 파라미터가 존재하는 경우, 리스너는 미리 정의된 구분자(쉼표, 세미콜론, 공백)를 사용하여 문자열을 분리하고, 해당 값을 애플리케이션 컨텍스트를 검색할 위치로 사용합니다. 또한 Ant 스타일 경로 패턴도 지원합니다. 예를 들어, /WEB-INF/*Context.xml은 WEB-INF 디렉터리에 있는 이름이 Context.xml로 끝나는 모든 파일을, /WEB-INF/**/*Context.xml은 WEB-INF의 모든 하위 디렉터리에 있는 그러한 파일들을 검색합니다.
Deploying a Spring ApplicationContext as a Jakarta EE RAR File
'Spring Framework > Spring IoC' 카테고리의 다른 글
Spring IoC (0) | 2024.11.15 |
---|---|
The BeanFactory API (0) | 2024.11.15 |
Registering a LoadTimeWeaver (0) | 2024.11.15 |
Environment Abstraction (1) | 2024.11.14 |
Using @PostConstruct and @PreDestory (0) | 2024.11.14 |