Loading...
Spring Framework Reference Documentation 7.0.2의 Additional Capabilities of the ApplicationContext의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
ApplicationContextchapter introduction에서 논의된 것처럼, org.springframework.beans.factory
package는 프로그래매틱한 방식도 포함하여 bean을 관리하고 조작하기 위한 기본 기능을 제공합니다. org.springframework.context package는
ApplicationContext
인터페이스를 추가로 제공하며, 이 인터페이스는 다른 인터페이스들을 확장하는 것에 더해 BeanFactory 인터페이스를 확장하여 보다 애플리케이션
프레임워크 지향적인 스타일로 추가 기능을 제공합니다.
많은 사람들은 ApplicationContext를 완전히 선언적인 방식으로 사용하며, 프로그래매틱하게 생성하지도 않고,
대신 ContextLoader와 같은 support 클래스에 의존하여 Jakarta EE 웹 애플리케이션의 일반적인 시작 프로세스의 일부로
ApplicationContext를 자동으로 인스턴스화합니다.
보다 프레임워크 지향적인 스타일로 BeanFactory 기능을 향상시키기 위해, context
package는 다음과 같은 기능도 제공합니다:
MessageSource 인터페이스를 통한 i18n 스타일의 메시지에 대한 액세스.
ResourceLoader 인터페이스를 통한 URL과 파일과 같은 리소스에 대한 액세스.
ApplicationEventPublisher 인터페이스의 사용을 통해 ApplicationListener 인터페이스를 구현하는
bean에 대한 이벤트 발행.
HierarchicalBeanFactory 인터페이스를 통해 여러 (계층적인) context를 로딩하여,
각각이 애플리케이션의 웹 레이어와 같은 특정 레이어에 집중할 수 있도록 함.
MessageSourceApplicationContext 인터페이스는 MessageSource라는 인터페이스를 확장하며,
따라서 국제화(“i18n”) 기능을 제공합니다. Spring은 또한 메시지를 계층적으로
resolve할 수 있는 HierarchicalMessageSource 인터페이스를 제공합니다.
이들 인터페이스는 함께 Spring이 메시지 resolution을 수행하는 기반을 제공합니다. 이들 인터페이스에 정의된 메서드는 다음과 같습니다:
String getMessage(String code, Object[] args, String default, Locale loc): MessageSource에서
메시지를 검색하는 데 사용되는 기본 메서드입니다. 지정된 locale에 대한 메시지를 찾을 수 없을 때는
기본 메시지가 사용됩니다. 전달된 argument는 표준 라이브러리에서 제공하는 MessageFormat 기능을 사용하여
replacement value가 됩니다.
String getMessage(String code, Object[] args, Locale loc): 기본적으로 이전 메서드와 동일하지만
한 가지 차이점이 있습니다. 기본 메시지를 지정할 수 없습니다. 메시지를 찾을 수 없으면
NoSuchMessageException이 throw됩니다.
String getMessage(MessageSourceResolvable resolvable, Locale locale): 앞의 메서드에서 사용된
모든 property는 MessageSourceResolvable이라는 클래스에 wrapping되어 있으며,
이 메서드에서 사용할 수 있습니다.
ApplicationContext가 로드될 때, context에 정의된 MessageSource
bean을 자동으로 검색합니다. bean의 이름은 반드시 messageSource여야 합니다. 이러한 bean이
발견되면, 앞의 메서드에 대한 모든 호출은 해당 메시지 소스에 위임됩니다.
메시지 소스를 찾을 수 없으면,
ApplicationContext는 동일한 이름을 가진 bean을 포함하는 parent를 찾으려고 시도합니다. 만약 찾으면,
그 bean을 MessageSource로 사용합니다. ApplicationContext가 메시지에 대한 어떠한 소스도 찾을 수 없으면,
위에서 정의된 메서드에 대한 호출을 수락할 수 있도록 빈 DelegatingMessageSource가 인스턴스화됩니다.
Spring은 MessageSource 구현체로 ResourceBundleMessageSource, ReloadableResourceBundleMessageSource
및 StaticMessageSource를 제공합니다. 이들 모두는 nested messaging을 수행하기 위해
HierarchicalMessageSource를 구현합니다. StaticMessageSource는 거의 사용되지 않지만,
소스에 메시지를 프로그래매틱하게 추가하는 방법을 제공합니다.
다음 예시는 ResourceBundleMessageSource를 보여줍니다:
1<beans> 2 <bean id="messageSource" 3 class="org.springframework.context.support.ResourceBundleMessageSource"> 4 <property name="basenames"> 5 <list> 6 <value>format</value> 7 <value>exceptions</value> 8 <value>windows</value> 9 </list> 10 </property> 11 </bean> 12</beans>
이 예시는 classpath에 format, exceptions, windows라는 세 개의 리소스 번들이
정의되어 있다고 가정합니다. 메시지를 resolve하기 위한 모든 요청은
ResourceBundle 객체를 통한 메시지 resolving의 JDK 표준 방식으로 처리됩니다.
예제를 위해, 위의 리소스 번들 파일 중 두 개의 내용이 다음과 같다고 가정합니다:
1# in format.properties 2message=Alligators rock!
1# in exceptions.properties 2argument.required=The {0} argument is required.
다음 예시는 MessageSource 기능을 실행하는 프로그램을 보여줍니다.
모든 ApplicationContext 구현체는 또한 MessageSource
구현체이기도 하므로 MessageSource 인터페이스로 캐스팅할 수 있음을 기억하십시오.
1public static void main(String[] args) { 2 MessageSource resources = new ClassPathXmlApplicationContext("beans.xml"); 3 String message = resources.getMessage("message", null, "Default", Locale.ENGLISH); 4 System.out.println(message); 5}
1fun main() { 2 val resources = ClassPathXmlApplicationContext("beans.xml") 3 val message = resources.getMessage("message", null, "Default", Locale.ENGLISH) 4 println(message) 5}
위 프로그램의 결과 출력은 다음과 같습니다:
1Alligators rock!
정리하면, MessageSource는 classpath의 root에 존재하는 beans.xml이라는 파일에 정의됩니다.
messageSource bean definition은 basenames property를 통해 여러 리소스 번들을 참조합니다.
basenames property에 list로 전달되는 세 개의 파일은 classpath의 root에 파일로 존재하며,
각각 format.properties, exceptions.properties, windows.properties라고 불립니다.
다음 예시는 메시지 lookup에 전달되는 argument를 보여줍니다. 이 argument는
String 객체로 변환되어 lookup 메시지의 placeholder에 삽입됩니다.
1<beans> 2 3 <!-- this MessageSource is being used in a web application --> 4 <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> 5 <property name="basename" value="exceptions"/> 6 </bean> 7 8 <!-- lets inject the above MessageSource into this POJO --> 9 <bean id="example" class="com.something.Example"> 10 <property name="messages" ref="messageSource"/> 11 </bean> 12 13</beans>
1public class Example { 2 3 private MessageSource messages; 4 5 public void setMessages(MessageSource messages) { 6 this.messages = messages; 7 } 8 9 public void execute() { 10 String message = this.messages.getMessage("argument.required", 11 new Object [] {"userDao"}, "Required", Locale.ENGLISH); 12 System.out.println(message); 13 } 14}
1class Example { 2 3 lateinit var messages: MessageSource 4 5 fun execute() { 6 val message = messages.getMessage("argument.required", 7 arrayOf("userDao"), "Required", Locale.ENGLISH) 8 println(message) 9 } 10}
execute() 메서드 호출의 결과 출력은 다음과 같습니다:
1The userDao argument is required.
국제화(“i18n”)와 관련하여, Spring의 다양한 MessageSource
구현체는 표준 JDK ResourceBundle과 동일한 locale resolution 및 fallback 규칙을 따릅니다.
간단히 말해, 그리고 이전에 정의된 예제 messageSource를 계속 사용하면,
British(en-GB) locale에 대해 메시지를 resolve하려면
각각 format_en_GB.properties, exceptions_en_GB.properties, windows_en_GB.properties라는 파일을
생성해야 합니다.
일반적으로 locale resolution은 애플리케이션의 주변 환경에 의해 관리됩니다. 다음 예제에서, (British) 메시지가 resolve되는 locale은 수동으로 지정됩니다:
1# in exceptions_en_GB.properties 2argument.required=Ebagum lad, the ''{0}'' argument is required, I say, required.
1public static void main(final String[] args) { 2 MessageSource resources = new ClassPathXmlApplicationContext("beans.xml"); 3 String message = resources.getMessage("argument.required", 4 new Object [] {"userDao"}, "Required", Locale.UK); 5 System.out.println(message); 6}
1fun main() { 2 val resources = ClassPathXmlApplicationContext("beans.xml") 3 val message = resources.getMessage("argument.required", 4 arrayOf("userDao"), "Required", Locale.UK) 5 println(message) 6}
위 프로그램 실행의 결과 출력은 다음과 같습니다:
1Ebagum lad, the 'userDao' argument is required, I say, required.
또한 MessageSourceAware 인터페이스를 사용하여 정의된 어떤
MessageSource에 대한 reference를 획득할 수도 있습니다. ApplicationContext에 정의된
어떤 bean이든 MessageSourceAware 인터페이스를 구현하면, bean이 생성 및 구성될 때
애플리케이션 context의 MessageSource가 주입됩니다.
| 참고 | Spring의 MessageSource는 Java의 ResourceBundle을 기반으로 하기 때문에,<br>동일한 base name을 가진 번들을 merge하지 않고, 발견된 첫 번째 번들만 사용합니다.<br>동일한 base name을 가진 이후의 메시지 번들은 무시됩니다. |
| 참고 | ResourceBundleMessageSource의 대안으로, Spring은<br>ReloadableResourceBundleMessageSource 클래스를 제공합니다. 이 variant는 동일한 번들<br>파일 포맷을 지원하지만, 표준 JDK 기반<br>ResourceBundleMessageSource 구현체보다 더 유연합니다. 특히, classpath뿐 아니라<br>어떤 Spring 리소스 위치에서도 파일을 읽을 수 있고, 번들 property 파일의 hot<br>reloading을 지원합니다(사이사이에 효율적으로 캐싱하면서).<br>자세한 내용은 ReloadableResourceBundleMessageSource<br>javadoc을 참조하십시오. |
ApplicationContext에서의 이벤트 handling은 ApplicationEvent
클래스와 ApplicationListener 인터페이스를 통해 제공됩니다. ApplicationListener
인터페이스를 구현하는 bean이 context에 배포되면, ApplicationEvent가
ApplicationContext에 publish될 때마다 해당 bean은 통지를 받습니다.
본질적으로, 이는 표준 Observer 디자인 패턴입니다.
| 참고 | Spring 4.2부터 이벤트 infrastructure가 크게 개선되었으며,<br>annotation 기반 model과<br>ApplicationEvent를 반드시 상속하지는 않는 임의의 이벤트(즉, 객체)를 publish하는 기능을<br>제공합니다. 이러한 객체가 publish될 때, 우리는 이를 이벤트로 wrapping합니다. |
다음 table은 Spring이 제공하는 standard 이벤트를 설명합니다:
| Event | Explanation |
|---|---|
ContextRefreshedEvent | ApplicationContext가 초기화되거나 refresh될 때 publish됩니다(예를 들어,<br>ConfigurableApplicationContext 인터페이스의 refresh() 메서드를 사용할 때). 여기서<br>“initialized”란 모든 bean이 로드되고, post-processor bean이 탐지 및 활성화되며,<br>singleton이 pre-instantiated되고, ApplicationContext 객체가 사용 준비가 되었음을<br>의미합니다. context가 close되지 않는 한, 선택한 ApplicationContext가 그러한 “hot”<br>refresh를 실제로 지원하는 경우 여러 번 refresh를 트리거할 수 있습니다. 예를 들어,<br>XmlWebApplicationContext는 hot refresh를 지원하지만, GenericApplicationContext는<br>지원하지 않습니다. |
ContextStartedEvent | ConfigurableApplicationContext 인터페이스의 start() 메서드를 사용하여<br>ApplicationContext가 시작될 때 publish됩니다. 여기서 “started”란 모든 Lifecycle<br>bean이 명시적인 start signal을 받았음을 의미합니다. 일반적으로 이 signal은 명시적인<br>stop 이후 bean을 다시 시작하는 데 사용되지만, initialization 시에 이미 시작되지 않은(예를 들어,<br>autostart로 구성되지 않은) 컴포넌트를 시작하는 데도 사용될 수 있습니다. |
ContextStoppedEvent | ConfigurableApplicationContext 인터페이스의 stop() 메서드를 사용하여<br>ApplicationContext가 중지될 때 publish됩니다. 여기서 “stopped”란 모든 Lifecycle<br>bean이 명시적인 stop signal을 받았음을 의미합니다. 중지된 context는 start() 호출을 통해<br>다시 시작될 수 있습니다. |
ContextClosedEvent | ConfigurableApplicationContext 인터페이스의 close() 메서드 또는 JVM shutdown hook을<br>통해 ApplicationContext가 close될 때 publish됩니다. 여기서 "closed"란 모든 singleton<br>bean이 destroy됨을 의미합니다. context가 close되면 수명이 끝나며, refresh되거나 다시<br>시작될 수 없습니다. |
RequestHandledEvent | HTTP 요청이 service되었음을 모든 bean에 알리는 웹-specific 이벤트입니다. 이<br>이벤트는 요청이 완료된 후에 publish됩니다. 이 이벤트는 Spring의 DispatcherServlet을<br>사용하는 웹 애플리케이션에만 적용됩니다. |
ServletRequestHandledEvent | Servlet-specific context 정보를 추가하는 RequestHandledEvent의 subclass입니다. |
Table 1. Built-in Events
또한 자신만의 custom 이벤트를 생성하고 publish할 수도 있습니다. 다음 예시는
Spring의 ApplicationEvent base 클래스를 확장하는 간단한 클래스를 보여줍니다:
1public class BlockedListEvent extends ApplicationEvent { 2 3 private final String address; 4 private final String content; 5 6 public BlockedListEvent(Object source, String address, String content) { 7 super(source); 8 this.address = address; 9 this.content = content; 10 } 11 12 // accessor and other methods... 13}
1class BlockedListEvent(source: Any, 2 val address: String, 3 val content: String) : ApplicationEvent(source)
custom ApplicationEvent를 publish하려면,
ApplicationEventPublisher의 publishEvent() 메서드를 호출합니다. 일반적으로 이는
ApplicationEventPublisherAware를 구현하는 클래스를 생성하고 이를 Spring bean으로 등록함으로써 수행됩니다.
다음 예시는 그러한 클래스를 보여줍니다:
1public class EmailService implements ApplicationEventPublisherAware { 2 3 private List<String> blockedList; 4 private ApplicationEventPublisher publisher; 5 6 public void setBlockedList(List<String> blockedList) { 7 this.blockedList = blockedList; 8 } 9 10 public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { 11 this.publisher = publisher; 12 } 13 14 public void sendEmail(String address, String content) { 15 if (blockedList.contains(address)) { 16 publisher.publishEvent(new BlockedListEvent(this, address, content)); 17 return; 18 } 19 // send email... 20 } 21}
1class EmailService : ApplicationEventPublisherAware { 2 3 private lateinit var blockedList: List<String> 4 private lateinit var publisher: ApplicationEventPublisher 5 6 fun setBlockedList(blockedList: List<String>) { 7 this.blockedList = blockedList 8 } 9 10 override fun setApplicationEventPublisher(publisher: ApplicationEventPublisher) { 11 this.publisher = publisher 12 } 13 14 fun sendEmail(address: String, content: String) { 15 if (blockedList!!.contains(address)) { 16 publisher!!.publishEvent(BlockedListEvent(this, address, content)) 17 return 18 } 19 // send email... 20 } 21}
configuration 시점에, Spring 컨테이너는 EmailService가
ApplicationEventPublisherAware를 구현한다는 것을 감지하고 자동으로
setApplicationEventPublisher()를 호출합니다. 실제로 전달되는 parameter는 Spring
컨테이너 자체입니다. 애플리케이션 context와의 상호 작용은
ApplicationEventPublisher 인터페이스를 통해 이루어집니다.
custom ApplicationEvent를 수신하려면,
ApplicationListener를 구현하는 클래스를 생성하고 이를 Spring bean으로 등록할 수 있습니다.
다음 예시는 그러한 클래스를 보여줍니다:
1public class BlockedListNotifier implements ApplicationListener<BlockedListEvent> { 2 3 private String notificationAddress; 4 5 public void setNotificationAddress(String notificationAddress) { 6 this.notificationAddress = notificationAddress; 7 } 8 9 public void onApplicationEvent(BlockedListEvent event) { 10 // notify appropriate parties via notificationAddress... 11 } 12}
1class BlockedListNotifier : ApplicationListener<BlockedListEvent> { 2 3 lateinit var notificationAddress: String 4 5 override fun onApplicationEvent(event: BlockedListEvent) { 6 // notify appropriate parties via notificationAddress... 7 } 8}
ApplicationListener는 custom 이벤트 type(앞의 예제에서의 BlockedListEvent)으로
generic parameter가 지정되어 있음을 주목하십시오.
이는 onApplicationEvent() 메서드가 type-safe 상태를 유지할 수 있음을 의미하며,
downcasting이 필요하지 않습니다.
원하는 만큼 많은 이벤트 listener를 등록할 수 있지만, 기본적으로 이벤트 listener는 이벤트를 동기적으로 수신한다는 점에
유의해야 합니다.
이는 publishEvent() 메서드가 모든 listener가 이벤트 처리를 완료할 때까지 block됨을 의미합니다.
이러한 동기적이고 single-threaded 접근 방식의 한 가지 장점은 listener가 이벤트를 수신할 때,
트랜잭션 context가 사용 가능한 경우 publisher의 트랜잭션 context 내부에서 동작한다는 점입니다.
이벤트 publication에 대해 다른 전략이 필요해지는 경우(예를 들어, 기본적으로 비동기 이벤트 processing),
custom applicationEventMulticaster bean definition에 적용할 수 있는 configuration option에 대해서는
Spring의 ApplicationEventMulticaster 인터페이스
및 SimpleApplicationEventMulticaster 구현체의
javadoc을 참조하십시오.
이러한 경우, 이벤트 processing에 대해 ThreadLocal과 로깅 context는 전파되지 않습니다.
Observability 관련 정보는 the @EventListener Observability section을
참조하십시오.
다음 예시는 위의 각 클래스를 등록하고 구성하는 데 사용되는 bean definition을 보여줍니다:
1<bean id="emailService" class="example.EmailService"> 2 <property name="blockedList"> 3 <list> 4 <value>[email protected]</value> 5 <value>[email protected]</value> 6 <value>[email protected]</value> 7 </list> 8 </property> 9</bean> 10 11<bean id="blockedListNotifier" class="example.BlockedListNotifier"> 12 <property name="notificationAddress" value="[email protected]"/> 13</bean> 14 15<!-- optional: a custom ApplicationEventMulticaster definition --> 16<bean id="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster"> 17 <property name="taskExecutor" ref="..."/> 18 <property name="errorHandler" ref="..."/> 19</bean>
모두 종합하면, emailService bean의 sendEmail() 메서드가
호출될 때, block되어야 하는 email 메시지가 있으면
BlockedListEvent type의 custom 이벤트가 publish됩니다.
blockedListNotifier bean은 ApplicationListener로 등록되어 있으며
BlockedListEvent를 수신하고, 적절한 대상에게 통지할 수 있습니다.
| 참고 | Spring의 eventing mechanism은 동일한 애플리케이션 context 내에서 Spring bean 간의 간단한 통신을 위해 설계되었습니다.<br>그러나 보다 정교한 enterprise integration이 필요한 경우, 별도로 관리되는<br>Spring Integration project는 잘 알려진 Spring programming model을 기반으로<br>lightweight하고 pattern-oriented이며 event-driven인<br>architecture를 구축하기 위한 완전한 지원을 제공합니다. |
@EventListener annotation을 사용하여 관리되는 bean의 어떤 메서드에도
이벤트 listener를 등록할 수 있습니다. BlockedListNotifier는 다음과 같이 다시 작성할 수 있습니다:
1public class BlockedListNotifier { 2 3 private String notificationAddress; 4 5 public void setNotificationAddress(String notificationAddress) { 6 this.notificationAddress = notificationAddress; 7 } 8 9 @EventListener 10 public void processBlockedListEvent(BlockedListEvent event) { 11 // notify appropriate parties via notificationAddress... 12 } 13}
1class BlockedListNotifier { 2 3 lateinit var notificationAddress: String 4 5 @EventListener 6 fun processBlockedListEvent(event: BlockedListEvent) { 7 // notify appropriate parties via notificationAddress... 8 } 9}
| 주의 | 이러한 bean을 lazy로 정의하지 마십시오. ApplicationContext가 이를 존중하며, 메서드를 이벤트를 listen하도록 등록하지 않을 것입니다. |
메서드 signature는 다시 한 번 자신이 listen하는 이벤트 type을 선언하지만, 이번에는 유연한 이름을 사용하고 특정 listener 인터페이스를 구현하지 않습니다. 이벤트 type은 구현체 hierarchy에서 실제 이벤트 type이 generic parameter를 resolve하는 한, generic을 통해서도 좁힐 수 있습니다.
메서드가 여러 이벤트를 listen해야 하거나 아예 parameter 없이 정의되기를 원한다면, 이벤트 type을 annotation 자체에 지정할 수도 있습니다. 다음 예제는 이를 수행하는 방법을 보여줍니다:
1@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class}) 2public void handleContextStart() { 3 // ... 4}
1@EventListener(ContextStartedEvent::class, ContextRefreshedEvent::class) 2fun handleContextStart() { 3 // ... 4}
annotation의 condition attribute를 사용하여,
특정 이벤트에 대해 메서드를 실제로 호출하기 위해 일치해야 하는
SpEL expression을 정의함으로써
추가 runtime filtering을 추가하는 것도 가능합니다.
다음 예제는 이벤트의 content attribute가 my-event와 동일한 경우에만
notifier가 호출되도록 다시 작성하는 방법을 보여줍니다:
1@EventListener(condition = "#blEvent.content == 'my-event'") 2public void processBlockedListEvent(BlockedListEvent blEvent) { 3 // notify appropriate parties via notificationAddress... 4}
1@EventListener(condition = "#blEvent.content == 'my-event'") 2fun processBlockedListEvent(blEvent: BlockedListEvent) { 3 // notify appropriate parties via notificationAddress... 4}
각 SpEL expression은 전용 context에 대해 평가됩니다. 다음 table은
conditional 이벤트 processing을 위해 context에서 사용할 수 있도록 제공되는 항목을 나열합니다:
| Name | Location | Description | Example |
|---|---|---|---|
| Event | root object | 실제 ApplicationEvent. | #root.event 또는 event |
| Arguments array | root object | 메서드를 호출하는 데 사용된 argument(object array). | #root.args 또는 args; 첫 번째 argument에 access하려면 args[0] 등 |
| Argument name | evaluation context | 특정 메서드 argument의 이름. 이름이 사용 불가능한 경우<br>(예를 들어, code가 -parameters flag 없이 compile된 경우), 개별<br>argument는 #a<#arg> syntax를 사용하여 사용할 수도 있습니다. 여기서 <#arg>는<br>argument index(0부터 시작)를 의미합니다. | #blEvent 또는 #a0(alias로 #p0 또는 #p<#arg> parameter notation도 사용할 수 있습니다) |
Table 2. Event metadata available in SpEL expressions
#root.event는 메서드 signature가 실제로 publish된 임의의 객체를 참조하더라도
기저 이벤트에 access를 제공한다는 점에 유의하십시오.
다른 이벤트를 처리한 결과로 이벤트를 publish해야 하는 경우, 다음 예제와 같이 publish되어야 할 이벤트를 반환하도록 메서드 signature를 변경할 수 있습니다:
1@EventListener 2public ListUpdateEvent handleBlockedListEvent(BlockedListEvent event) { 3 // notify appropriate parties via notificationAddress and 4 // then publish a ListUpdateEvent... 5}
1@EventListener 2fun handleBlockedListEvent(event: BlockedListEvent): ListUpdateEvent { 3 // notify appropriate parties via notificationAddress and 4 // then publish a ListUpdateEvent... 5}
| 제한 | 이 기능은<br>asynchronous listener에 대해서는 지원되지 않습니다. |
handleBlockedListEvent() 메서드는 처리하는 각 BlockedListEvent에 대해
새로운 ListUpdateEvent를 publish합니다. 여러 이벤트를 publish해야 하는 경우,
대신 Collection 또는 이벤트 array를 반환할 수 있습니다.
특정 listener가 이벤트를 비동기적으로 처리하도록 하려면,
regular @Async support를
재사용할 수 있습니다. 다음 예제는 이를 수행하는 방법을 보여줍니다:
1@EventListener 2@Async 3public void processBlockedListEvent(BlockedListEvent event) { 4 // BlockedListEvent is processed in a separate thread 5}
1@EventListener 2@Async 3fun processBlockedListEvent(event: BlockedListEvent) { 4 // BlockedListEvent is processed in a separate thread 5}
비동기 이벤트를 사용할 때는 다음과 같은 제한 사항에 유의하십시오:
비동기 이벤트 listener가 Exception을 throw하는 경우,
호출자에게 전파되지 않습니다. 자세한 내용은
AsyncUncaughtExceptionHandler를
참조하십시오.
비동기 이벤트 listener 메서드는 값을 반환하여 이후 이벤트를 publish할 수 없습니다.
처리 결과로 다른 이벤트를 publish해야 하는 경우,
이벤트를 수동으로 publish하기 위해
ApplicationEventPublisher를
주입하십시오.
이벤트 processing에 대해 ThreadLocal과 로깅 context는 기본적으로 전파되지 않습니다.
Observability 관련 정보는 the @EventListener Observability section을
참조하십시오.
하나의 listener가 다른 listener보다 먼저 호출되어야 하는 경우,
다음 예제와 같이 메서드 선언에 @Order annotation을 추가할 수 있습니다:
1@EventListener 2@Order(42) 3public void processBlockedListEvent(BlockedListEvent event) { 4 // notify appropriate parties via notificationAddress... 5}
1@EventListener 2@Order(42) 3fun processBlockedListEvent(event: BlockedListEvent) { 4 // notify appropriate parties via notificationAddress... 5}
이벤트의 구조를 더 정의하기 위해 generic을 사용할 수도 있습니다. 예를 들어,
T가 실제로 생성된 entity type인 EntityCreatedEvent<T>를 사용할 수 있습니다.
예를 들어, Person에 대한 EntityCreatedEvent만 수신하도록
다음과 같은 listener definition을 생성할 수 있습니다:
1@EventListener 2public void onPersonCreated(EntityCreatedEvent<Person> event) { 3 // ... 4}
1@EventListener 2fun onPersonCreated(event: EntityCreatedEvent<Person>) { 3 // ... 4}
type erasure로 인해, 이는 이벤트 listener가 filter하는 generic parameter를
발생한 이벤트가 resolve하는 경우에만 동작합니다(즉,
class PersonCreatedEvent extends EntityCreatedEvent<Person> { … }와 같은 경우).
특정 상황에서는 모든 이벤트가 동일한 구조를 따르는 경우(앞의 예제에서 이벤트가 그러해야 하는 것처럼),
이는 상당히 번거로울 수 있습니다. 이러한 경우,
runtime environment가 제공하는 것 이상의 정보를 프레임워크에 제공하기 위해
ResolvableTypeProvider를 구현할 수 있습니다. 다음 이벤트는 이를 수행하는 방법을 보여줍니다:
1public class EntityCreatedEvent<T> extends ApplicationEvent implements ResolvableTypeProvider { 2 3 public EntityCreatedEvent(T entity) { 4 super(entity); 5 } 6 7 @Override 8 public ResolvableType getResolvableType() { 9 return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getSource())); 10 } 11}
1class EntityCreatedEvent<T>(entity: T) : ApplicationEvent(entity), ResolvableTypeProvider { 2 3 override fun getResolvableType(): ResolvableType? { 4 return ResolvableType.forClassWithGenerics(javaClass, ResolvableType.forInstance(getSource())) 5 } 6}
| 참고 | 이는 ApplicationEvent에만 동작하는 것이 아니라, 이벤트로 보내는 임의의 객체에도<br>동작합니다. |
마지막으로, classic ApplicationListener 구현체와 마찬가지로,
실제 multicasting은 runtime 시 context-wide ApplicationEventMulticaster를 통해 이루어집니다.
기본적으로 이는 caller thread에서 동기적으로 이벤트를 publish하는
SimpleApplicationEventMulticaster입니다.
이는 예를 들어 모든 이벤트를 비동기적으로 처리하고 listener 예외를 처리하기 위해,
applicationEventMulticaster bean definition을 통해 교체/사용자 정의할 수 있습니다:
1@Bean 2ApplicationEventMulticaster applicationEventMulticaster() { 3 SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster(); 4 multicaster.setTaskExecutor(...); 5 multicaster.setErrorHandler(...); 6 return multicaster; 7}
애플리케이션 context를 최적으로 사용하고 이해하려면,
Resources에 설명된 대로
Spring의 Resource abstraction에 익숙해져야 합니다.
애플리케이션 context는 Resource 객체를 로드하는 데 사용할 수 있는 ResourceLoader입니다.
Resource는 본질적으로 JDK java.net.URL 클래스의 더 많은 기능을 가진 버전입니다.
실제로, Resource 구현체는 적절한 경우
java.net.URL 인스턴스를 wrapping합니다.
Resource는 classpath, 파일 시스템 위치,
표준 URL로 기술 가능한 모든 위치, 기타 변형을 포함하여 거의 모든 위치에서
low-level 리소스를 투명하게 얻을 수 있습니다. 리소스 location
string이 특별한 prefix가 없는 단순 path인 경우,
리소스가 어디에서 오는지는 실제 애플리케이션 context type에 따라
특정하고 적절하게 결정됩니다.
애플리케이션 context에 배포된 bean을 special callback 인터페이스인
ResourceLoaderAware를 구현하도록 구성하여,
initialization 시점에 애플리케이션 context 자체가 ResourceLoader로 전달되면서
자동으로 callback되도록 할 수 있습니다.
또한 static 리소스에 access하는 데 사용될 Resource type의 property를 노출할 수도 있습니다.
이들은 다른 property와 마찬가지로 주입됩니다. 이러한 Resource
property는 단순 String path로 지정하고,
bean이 배포될 때 해당 text string을 실제 Resource 객체로 자동 변환하는 데
의존할 수 있습니다.
ApplicationContext constructor에 제공되는 location path는 실제로
리소스 string이며, 단순한 형태에서는 특정 context 구현체에 따라
적절하게 처리됩니다. 예를 들어, ClassPathXmlApplicationContext는 단순한
location path를 classpath location으로 처리합니다. 또한 location path(리소스 string)에
special prefix를 사용하여, 실제 context type과 관계없이
classpath 또는 URL에서 definition을 강제로 로딩할 수 있습니다.
ApplicationContext는 Spring 애플리케이션의 lifecycle을 관리하고
컴포넌트를 둘러싼 풍부한 프로그래밍 모델을 제공합니다.
그 결과, 복잡한 애플리케이션은 동일하게 복잡한 컴포넌트 graph와 startup phase를 가질 수 있습니다.
특정 metric으로 애플리케이션 startup step을 추적하면, startup phase 동안 시간이 어디에 소비되는지 이해하는 데 도움이 될 뿐만 아니라, 전체적으로 context lifecycle을 더 잘 이해하는 방법으로도 사용될 수 있습니다.
AbstractApplicationContext(및 그 subclass)는 다양한 startup phase에 대한
StartupStep data를 수집하는 ApplicationStartup으로 instrument되어 있습니다:
다음은 AnnotationConfigApplicationContext에서의 instrumentation 예시입니다:
1// create a startup step and start recording 2try (StartupStep scanPackages = getApplicationStartup().start("spring.context.base-packages.scan")) { 3 // add tagging information to the current step 4 scanPackages.tag("packages", () -> Arrays.toString(basePackages)); 5 // perform the actual phase we're instrumenting 6 this.scanner.scan(basePackages); 7}
1// create a startup step and start recording 2try (val scanPackages = getApplicationStartup().start("spring.context.base-packages.scan")) { 3 // add tagging information to the current step 4 scanPackages.tag("packages", () -> Arrays.toString(basePackages)); 5 // perform the actual phase we're instrumenting 6 this.scanner.scan(basePackages); 7}
애플리케이션 context는 이미 여러 step으로 instrument되어 있습니다. 한 번 기록되면, 이러한 startup step은 특정 tool로 수집, 표시, 분석할 수 있습니다. 기존 startup step의 전체 목록은 dedicated appendix section을 참조하십시오.
기본 ApplicationStartup 구현체는 최소한의 overhead를 위한 no-op variant입니다.
이는 기본적으로 애플리케이션 startup 동안 metric이 수집되지 않음을 의미합니다.
Spring Framework는 Java Flight Recorder로 startup step을 추적하기 위한 구현체인
FlightRecorderApplicationStartup을 제공합니다. 이 variant를 사용하려면,
ApplicationContext가 생성되는 즉시 해당 인스턴스를 구성해야 합니다.
개발자는 자신만의 AbstractApplicationContext subclass를 제공하는 경우나
보다 정밀한 data를 수집하려는 경우,
ApplicationStartup infrastructure를 사용할 수도 있습니다.
| 참고 | ApplicationStartup은 애플리케이션 startup 동안과 core 컨테이너에 대해서만 사용되도록 설계되었습니다.<br>이는 Java profiler나 Micrometer와 같은 metric 라이브러리를 대체하기 위한 것이 아닙니다. |
custom StartupStep 수집을 시작하려면, 컴포넌트는 애플리케이션 context에서 직접
ApplicationStartup 인스턴스를 얻거나, 컴포넌트를 ApplicationStartupAware로 구현하거나,
어떤 injection point에서든 ApplicationStartup type을 요청할 수 있습니다.
| 주의 | 개발자는 custom startup step을 생성할 때 "spring.*" namespace를 사용해서는 안 됩니다.<br>이 namespace는 내부 Spring 용도로 예약되어 있으며 변경될 수 있습니다. |
예를 들어 ContextLoader를 사용하여 선언적으로
ApplicationContext 인스턴스를 생성할 수 있습니다.
물론, ApplicationContext 구현체 중 하나를 사용하여
프로그래매틱하게 ApplicationContext 인스턴스를 생성할 수도 있습니다.
다음 예제와 같이 ContextLoaderListener를 사용하여
ApplicationContext를 등록할 수 있습니다:
1<context-param> 2 <param-name>contextConfigLocation</param-name> 3 <param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value> 4</context-param> 5 6<listener> 7 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 8</listener>
listener는 contextConfigLocation parameter를 검사합니다. parameter가
존재하지 않으면 listener는 /WEB-INF/applicationContext.xml을 기본값으로 사용합니다.
parameter가 존재하면 listener는 미리 정의된 구분자(쉼표, 세미콜론, 공백)를 사용하여
String을 분리하고, 해당 값을 애플리케이션 context가 검색되는 위치로 사용합니다.
Ant 스타일 path pattern도 지원됩니다.
예를 들어 /WEB-INF/*Context.xml(이름이 Context.xml로 끝나고 WEB-INF 디렉터리에 위치한
모든 파일)과 /WEB-INF/**/*Context.xml
(WEB-INF의 모든 하위 디렉터리에 있는 모든 이러한 파일)에 해당합니다.
ApplicationContext as a Jakarta EE RAR FileSpring ApplicationContext를 Jakarta EE RAR deployment
unit에 context와 필요한 모든 bean 클래스 및 라이브러리 JAR를 캡슐화하여
RAR 파일로 배포하는 것이 가능합니다. 이는 Jakarta EE environment에서 host되지만
Jakarta EE 서버 기능에 access할 수 있는 stand-alone ApplicationContext를
bootstrapping하는 것과 동일합니다.
RAR deployment는 headless WAR 파일 배포 시나리오의
보다 자연스러운 대안입니다. 즉, Jakarta EE environment에서 Spring
ApplicationContext를 bootstrapping하는 데만 사용되는 HTTP entry point가 없는
WAR 파일입니다.
RAR deployment는 HTTP entry point가 필요 없고,
message endpoint와 scheduled job으로만 구성된 애플리케이션 context에 이상적입니다.
이러한 context의 bean은 JTA 트랜잭션 manager 및 JNDI-bound JDBC
DataSource 인스턴스와 JMS ConnectionFactory 인스턴스와 같은 애플리케이션 서버 리소스를 사용할 수 있으며,
Spring의 표준 트랜잭션 management, JNDI 및 JMX support 기능을 통해
platform의 JMX 서버에 등록할 수도 있습니다.
애플리케이션 컴포넌트는 또한 Spring의
TaskExecutor abstraction을 통해 애플리케이션 서버의 JCA WorkManager와
상호 작용할 수 있습니다.
RAR deployment에 관련된 configuration detail은
SpringContextResourceAdapter
클래스의 javadoc을 참조하십시오.
Spring ApplicationContext를 Jakarta EE RAR 파일로 간단히 배포하려면:
모든 애플리케이션 클래스를 RAR 파일(다른 파일 extension을 가진 표준 JAR 파일)로 packaging합니다.
필요한 모든 라이브러리 JAR를 RAR archive의 root에 추가합니다.
META-INF/ra.xml deployment descriptor(예시는
javadoc for SpringContextResourceAdapter 참조)와
해당 Spring XML bean definition 파일(일반적으로
META-INF/applicationContext.xml)을 추가합니다.
생성된 RAR 파일을 애플리케이션 서버의 deployment 디렉터리에 배치합니다.
| 참고 | 이러한 RAR deployment unit은 일반적으로 self-contained입니다. 이들은 외부, 심지어 동일한 애플리케이션의 다른 module에도 컴포넌트를 노출하지 않습니다.<br>RAR 기반 ApplicationContext와의 상호 작용은 일반적으로 다른 module과 공유하는 JMS destination을 통해 발생합니다.<br>RAR 기반 ApplicationContext는 또한 예를 들어, 일부 job을 scheduling하거나 파일 시스템(또는 이와 유사한 곳)의 새 파일에<br>react할 수 있습니다. 외부에서 synchronous access를 허용해야 하는 경우, 예를 들어 동일한 machine의 다른 애플리케이션 module이 사용할 수 있는<br>RMI endpoint를 export할 수 있습니다. |
Registering a LoadTimeWeaver
The BeanFactory API