Home [Spring] 1.15.2. Standard and Custom Events
Post
Cancel

[Spring] 1.15.2. Standard and Custom Events

1.15.2. Standard and Custom Events

Event handling in the ApplicationContext is provided through the ApplicationEvent class and the ApplicationListener interface. If a bean that implements the ApplicationListener interface is deployed into the context, every time an ApplicationEvent gets published to the ApplicationContext, that bean is notified. Essentially, this is the standard Observer design pattern.

  • ApplicationContext의 이벤트 처리 기능(Event handling)은 ApplicationEvent 클래스와 ApplicationListener 인터페이스를 통해 제공된다.
  • ApplicationListener 인터페이스를 구현하는 bean이 컨텍스트에 배포되는 경우 ApplicationEventApplicationContext에 게시될 때마다 해당 bean이 통지된다.
  • 본질적으로, 이것은 표준 Observer design pattern이다.

As of Spring 4.2, the event infrastructure has been significantly improved and offers an annotation-based model as well as the ability to publish any arbitrary event (that is, an object that does not necessarily extend from ApplicationEvent). When such an object is published, we wrap it in an event for you.

  • Spring 4.2를 기준으로 이벤트 인프라가 상당히 개선되었으며 주석 기반 모델(annotation-based model)뿐만 아니라 임의 이벤트(즉, ApplicationEvent에서 반드시 확장되지 않는 개체)를 게시할 수 있는 기능을 제공한다.
  • 그런 물건이 배포되면, 우리는 사용자를 위해 그것을 이벤트로 포장한다.

The following table describes the standard events that Spring provides:

  • 다음 테이블은 Spring이 제공하는 기본 events들에 대한 설명이다.

Built-in Events

  • ContextRefreshedEvent

    Published when the ApplicationContext is initialized or refreshed (for example, by using the refresh() method on the ConfigurableApplicationContext interface). Here, “initialized” means that all beans are loaded, post-processor beans are detected and activated, singletons are pre-instantiated, and the ApplicationContext object is ready for use. As long as the context has not been closed, a refresh can be triggered multiple times, provided that the chosen ApplicationContext actually supports such “hot” refreshes. For example, XmlWebApplicationContext supports hot refreshes, but GenericApplicationContext does not.

    • ApplicationContext가 초기화되거나 새로 고쳐질 때(예: ConfigurableApplicationContext 인터페이스에서 refresh() 메서드를 사용하는 경우) 게시된다.
    • 여기서 “초기화(initialized)”란 모든 beans가 적재되고, post-processor beans 이 검출되어 활성화되며, singletons은 미리 인스턴스화되며, ApplicationContext 개체를 사용할 준비가 되었다는 것을 의미한다.
    • 컨텍스트가 닫히지 않은 한 선택한 ApplicationContext가 실제로 이러한 “hot” 리프레시를 지원하는 경우 refresh을 여러 번 트리거할 수 있다.
    • 예를 들어, XmlWebApplicationContext는 hot 리프레시를 지원하지만 GenericApplicationContext는 지원하지 않는다.
  • ContextStartedEvent

    Published when the ApplicationContext is started by using the start() method on the ConfigurableApplicationContext interface. Here, “started” means that all Lifecycle beans receive an explicit start signal. Typically, this signal is used to restart beans after an explicit stop, but it may also be used to start components that have not been configured for autostart (for example, components that have not already started on initialization).

    • ConfigurableApplicationContext 인터페이스에서 start() 방법을 사용하여 ApplicationContext를 시작할 때 게시된다.
    • 여기서 “started”은 모든 Lifecycle beans이 명시적인 시작 신호를 받는 것을 의미한다.
    • 일반적으로 이 신호는 명시적 정지 후 beans을 다시 시작하는 데 사용되지만, autostart(예: 초기화 시 아직 시작되지 않은 components)에 대해 구성되지 않은 components을 시작하는 데도 사용할 수 있다.
  • ContextStoppedEvent

    Published when the ApplicationContext is stopped by using the stop() method on the ConfigurableApplicationContext interface. Here, “stopped” means that all Lifecycle beans receive an explicit stop signal. A stopped context may be restarted through a start() call.

    • ConfigurableApplicationContext 인터페이스에서 stop() 방법을 사용하여 ApplicationContext가 중지될 때 게시된다.
    • 여기서 “stopped”는 모든 Lifecycle beans이 명시적인 정지 신호를 받는 것을 의미한다.
    • 정지된 컨텍스트는 start() 호출을 통해 재시작될 수 있다.
  • ContextClosedEvent

    Published when the ApplicationContext is being closed by using the close() method on the ConfigurableApplicationContext interface or via a JVM shutdown hook. Here, “closed” means that all singleton beans will be destroyed. Once the context is closed, it reaches its end of life and cannot be refreshed or restarted.

    • ConfigurableApplicationContext 인터페이스의 close() 메서드를 사용하거나 JVM 종료 hook를 통해 ApplicationContext가 닫힐 때 게시된다.
    • 여기서 “closed”는 모든 singleton beans이 파괴된다는 것을 의미한다.
    • 일단 context가 닫히면 수명이 다하여 refreshed하거나 restarted할 수 없다.
  • RequestHandledEvent

    A web-specific event telling all beans that an HTTP request has been serviced. This event is published after the request is complete. This event is only applicable to web applications that use Spring’s DispatcherServlet.

    • 모든 beans에 HTTP 요청이 서비스되었음을 알리는 web-specific event.
    • 이 이벤트는 요청이 완료된 후 게시된다.
    • 이 이벤트는 Spring’s DispatcherServlet을 사용하는 웹 어플리케이션에만 적용된다.
  • ServletRequestHandledEvent

    A subclass of RequestHandledEvent that adds Servlet-specific context information.

    • 서블릿별(Servlet-specific) context 정보를 추가하는 RequestHandledEvent 하위 클래스.

You can also create and publish your own custom events. The following example shows a simple class that extends Spring’s ApplicationEvent base class:

  • 사용자 정의 이벤트를 직접 생성 및 게시할 수도 있다.
  • 다음 예제는 Spring의 ApplicationEvent 기본 클래스를 확장하는 단순 클래스를 보여준다.
1
2
3
class BlackListEvent(source: Any,
                    val address: String,
                    val content: String) : ApplicationEvent(source)

To publish a custom ApplicationEvent, call the publishEvent() method on an ApplicationEventPublisher. Typically, this is done by creating a class that implements ApplicationEventPublisherAware and registering it as a Spring bean. The following example shows such a class:

  • 사용자 정의 ApplicationEvent를 게시하려면 ApplicationEventPublisher에서 publishEvent() 메서드를 호출하자.
  • 일반적으로 ApplicationEventPublisherAware를 구현하는 클래스를 만들고 이를 Spring Bean으로 등록하면 된다.
  • 다음 예제는 그러한 클래스를 보여준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class EmailService : ApplicationEventPublisherAware {

    private lateinit var blackList: List<String>
    private lateinit var publisher: ApplicationEventPublisher

    fun setBlackList(blackList: List<String>) {
        this.blackList = blackList
    }

    override fun setApplicationEventPublisher(publisher: ApplicationEventPublisher) {
        this.publisher = publisher
    }

    fun sendEmail(address: String, content: String) {
        if (blackList!!.contains(address)) {
            publisher!!.publishEvent(BlackListEvent(this, address, content))
            return
        }
        // send email...
    }
}

At configuration time, the Spring container detects that EmailService implements ApplicationEventPublisherAware and automatically calls setApplicationEventPublisher(). In reality, the parameter passed in is the Spring container itself. You are interacting with the application context through its ApplicationEventPublisher interface.

  • 구성 시 Spring 컨테이너는 EmailServiceApplicationEventPublisherAware를 구현하고 setApplicationEventPublisher()를 자동으로 호출하는 것을 감지한다.
  • 실제로 전달된 매개변수는 스프링 컨테이너 그 자체다.
  • 우리는 ApplicationEventPublisher 인터페이스를 통해 application context와 상호 작용한다.

To receive the custom ApplicationEvent, you can create a class that implements ApplicationListener and register it as a Spring bean. The following example shows such a class:

  • 사용자 정의 ApplicationEvent를 받으려면 ApplicationListener를 구현하는 클래스를 만들어 Spring Bean으로 등록하면 된다.
  • 다음 예제는 그러한 클래스를 보여준다.
1
2
3
4
5
6
7
8
class BlackListNotifier : ApplicationListener<BlackListEvent> {

    lateinit var notificationAddres: String

    override fun onApplicationEvent(event: BlackListEvent) {
        // notify appropriate parties via notificationAddress...
    }
}

Notice that ApplicationListener is generically parameterized with the type of your custom event (BlackListEvent in the preceding example). This means that the onApplicationEvent() method can remain type-safe, avoiding any need for downcasting. You can register as many event listeners as you wish, but note that, by default, event listeners receive events synchronously. This means that the publishEvent() method blocks until all listeners have finished processing the event. One advantage of this synchronous and single-threaded approach is that, when a listener receives an event, it operates inside the transaction context of the publisher if a transaction context is available. If another strategy for event publication becomes necessary, see the javadoc for Spring’s ApplicationEventMulticaster interface and SimpleApplicationEventMulticaster implementation for configuration options.

  • ApplicationListener는 일반적으로 사용자 정의 이벤트 유형(이전 예의 BlackListEvent)으로 매개 변수화됨을 참고하자.
  • 즉, onApplicationEvent() 방법은 다운캐스팅을 피할 수 있으므로 type-safe을 유지될 수 있다.
  • 원하는 만큼 event listeners를 등록할 수 있지만 기본적으로 event listeners는 이벤트를 동기적으로 수신한다는 점에 유의하자.
  • 즉, 모든 listeners가 이벤트 처리를 마칠 때까지 publishEvent() 메서드가 차단된다.
  • 이러한 동기식 및 단일 스레드식 접근법의 한 가지 장점은 listener가 이벤트를 수신할 때, transaction 컨텍스트를 이용할 수 있는 경우 publisher의 transaction 컨텍스트 내에서 작동한다는 것이다.
  • event publication를 위한 다른 전략이 필요할 경우 Spring의 ApplicationEventMulticaster 인터페이스에 대한 javadoc과 구성 옵션에 대한 SimpleApplicationEventMulticaster 구현을 참조하자.

The following example shows the bean definitions used to register and configure each of the classes above:

  • 다음 예제는 위의 각 클래스를 등록하고 구성하는 데 사용되는 bean 정의를 보여준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
<bean id="emailService" class="example.EmailService">
    <property name="blackList">
        <list>
            <value>known.spammer@example.org</value>
            <value>known.hacker@example.org</value>
            <value>john.doe@example.org</value>
        </list>
    </property>
</bean>

<bean id="blackListNotifier" class="example.BlackListNotifier">
    <property name="notificationAddress" value="blacklist@example.org"/>
</bean>

Putting it all together, when the sendEmail() method of the emailService bean is called, if there are any email messages that should be blacklisted, a custom event of type BlackListEvent is published. The blackListNotifier bean is registered as an ApplicationListener and receives the BlackListEvent, at which point it can notify appropriate parties.

  • 이를 종합하면 emailService bean의 sendEmail() 메서드가 호출될 때 블랙리스트에 올려야 할 이메일 메시지가 있을 경우 BlackListEvent 유형의 사용자 지정 이벤트가 게시된다.
  • blackListNotifier bean은 ApplicationListener로 등록되어 BlackListEvent를 수신하며, 이때 적절한 당사자에게 통지할 수 있다.

Spring’s eventing mechanism is designed for simple communication between Spring beans within the same application context. However, for more sophisticated enterprise integration needs, the separately maintained Spring Integration project provides complete support for building lightweight, pattern-oriented, event-driven architectures that build upon the well-known Spring programming model.

  • 스프링의 eventing mechanism은 동일한 어플리케이션 컨텍스트 내에서 스프링 bean들 간의 간단한 communication을 위해 설계되었다.
  • 그러나, 보다 정교한 enterprise integration 요구를 위해, 별도로 유지되는 Spring Integration 프로젝트는 잘 알려진 Spring 프로그래밍 모델을 기반으로 하는 경량, 패턴 지향(pattern-oriented)의 이벤트 중심 아키텍처 구축을 위한 완벽한 지원을 제공한다.

Annotation-based Event Listeners

As of Spring 4.2, you can register an event listener on any public method of a managed bean by using the @EventListener annotation. The BlackListNotifier can be rewritten as follows:

  • Spring 4.2를 기준으로 @EventListener 주석을 사용하여 관리되는 bean의 모든 공개 방법에 대해 event listener를 등록할 수 있다.
  • BlackListNotifier는 다음과 같이 다시 쓸 수 있다.
1
2
3
4
5
6
7
8
9
class BlackListNotifier {

    lateinit var notificationAddress: String

    @EventListener
    fun processBlackListEvent(event: BlackListEvent) {
        // notify appropriate parties via notificationAddress...
    }
}

The method signature once again declares the event type to which it listens, but, this time, with a flexible name and without implementing a specific listener interface. The event type can also be narrowed through generics as long as the actual event type resolves your generic parameter in its implementation hierarchy.

  • 메서드 signature는 자신이 듣는 이벤트 유형을 다시 한 번 선언하지만, 이번에는 유연한 이름으로 특정 listener 인터페이스를 구현하지 않고 있다.
  • 실제 이벤트 유형이 구현 계층 구조(implementation hierarchy)에서 일반 매개 변수를 해결하는 한 이벤트 유형은 제네릭을 통해 좁혀질 수도 있다.

If your method should listen to several events or if you want to define it with no parameter at all, the event types can also be specified on the annotation itself. The following example shows how to do so:

  • 메소드가 여러 이벤트를 listen해야 하거나 매개 변수 없이 이벤트를 정의하려면 주석 자체에서도 이벤트 유형을 지정할 수 있다.
  • 다음 예제는 이를 수행하는 방법을 보여준다.
1
2
3
4
@EventListener(ContextStartedEvent::class, ContextRefreshedEvent::class)
fun handleContextStart() {
    // ...
}

It is also possible to add additional runtime filtering by using the condition attribute of the annotation that defines a [SpEL expression](https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#expressions) , which should match to actually invoke the method for a particular event.

  • 또한 [SpEL expression](https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#expressions)을 정의하는 주석의 조건 속성을 사용하여 런타임 필터링을 추가할 수도 있으며, 이 특성은 특정 이벤트에 대한 메서드를 실제로 호출하는 데 일치해야 한다.

The following example shows how our notifier can be rewritten to be invoked only if the content attribute of the event is equal to my-event:

  • 다음 예는 이벤트의 content 속성이 my-event와 동일한 경우에만 호출되도록 우리의 notifier를 다시 작성할 수 있는 방법을 보여준다.
1
2
3
4
@EventListener(condition = "#blEvent.content == 'my-event'")
fun processBlackListEvent(blEvent: BlackListEvent) {
    // notify appropriate parties via notificationAddress...
}

Each SpEL expression evaluates against a dedicated context. The following table lists the items made available to the context so that you can use them for conditional event processing:

  • SpEL expression은 전용 컨텍스트를 기준으로 평가한다.
  • 다음 표에는 조건부 이벤트 처리에 사용할 수 있도록 컨텍스트에서 사용할 수 있는 항목이 나열되어 있다.

Event SpEL available metadata

NameLocationDesscriptionExample
Eventroot objectThe actual ApplicationEvent.#root.event or event
Arguments arrayroot object메서드를 호출하는 데 사용된 인수(개체 배열)#root.args or args;
args[0] to access the first argument, etc.
Argument nameevaluation context메서드 인수의 이름.
어떤 이유에서인지 이름을 사용할 수 없는 경우
(예를 들어 컴파일된 바이트 코드에 디버그 정보가 없기 때문에),
<#arg>가 인수 색인(0부터 시작)을 나타내는
#a<#arg> 구문을 사용하여 개별 인수도 사용할 수 있다.
#blEvent or #a0
(you can also use #p0 or #p<#arg> parameter notation as an alias)

Note that #root.event gives you access to the underlying event, even if your method signature actually refers to an arbitrary object that was published.

  • 메서드 signature가 실제로 게시된 임의 개체를 참조하더라도 #root.event는 기본 이벤트에 대한 액세스를 제공한다는 점에 유의하자.

If you need to publish an event as the result of processing another event, you can change the method signature to return the event that should be published, as the following example shows:

  • 다른 이벤트를 처리한 결과로 이벤트를 게시해야 하는 경우 다음 예에서 볼 수 있듯이 게시해야 하는 이벤트를 반환하도록 메서드 signature을 변경할 수 있다.
1
2
3
4
5
@EventListener
fun handleBlackListEvent(event: BlackListEvent): ListUpdateEvent {
    // notify appropriate parties via notificationAddress and
    // then publish a ListUpdateEvent...
}

This feature is not supported for asynchronous listeners.

This new method publishes a new ListUpdateEvent for every BlackListEvent handled by the method above. If you need to publish several events, you can return a Collection of events instead.

  • 이 새로운 방법은 위의 방법으로 처리되는 모든 BlackListEvent에 대해 새로운 ListUpdateEvent를 게시한다.
  • 여러 이벤트를 게시해야 하는 경우 대신 이벤트 Collection을 반환할 수 있다.

Asynchronous Listeners

If you want a particular listener to process events asynchronously, you can reuse the regular @Async support. The following example shows how to do so:

  • 특정 listener가 비동기적으로 이벤트를 처리하도록 하려면 regular @Async support을 재사용할 수 있다.
  • 다음 예제는 이를 수행하는 방법을 보여준다.
1
2
3
4
5
@EventListener
@Async
fun processBlackListEvent(event: BlackListEvent) {
    // BlackListEvent is processed in a separate thread
}

Be aware of the following limitations when using asynchronous events:

  • 비동기 이벤트 사용 시 다음 제한 사항에 주의하십시오.
    • If an asynchronous event listener throws an Exception, it is not propagated to the caller. See AsyncUncaughtExceptionHandler for more details.
      • 비동기 이벤트 listener가 Exception를 발생시키는 경우, 예외는 caller에게 전파되지 않는다.
      • 자세한 내용은 AsyncUncaughtExceptionHandler를 참조하십시오.
    • Asynchronous event listener methods cannot publish a subsequent event by returning a value. If you need to publish another event as the result of the processing, inject an [ApplicationEventPublisher](https://docs.spring.io/spring-framework/docs/5.2.7.RELEASE/javadoc-api/org/springframework/aop/interceptor/AsyncUncaughtExceptionHandler.html) to publish the event manually.
      • 비동기 이벤트 listener 메서드는 값을 반환하여 후속 이벤트를 게시할 수 없다.
      • 처리 결과로 다른 이벤트를 게시해야 하는 경우 [ApplicationEventPublisher](https://docs.spring.io/spring-framework/docs/5.2.7.RELEASE/javadoc-api/org/springframework/aop/interceptor/AsyncUncaughtExceptionHandler.html)를 주입하여 이벤트를 수동으로 게시하십시오.

Ordering Listeners

If you need one listener to be invoked before another one, you can add the @Order annotation to the method declaration, as the following example shows:

  • 한 listener를 다른 listener보다 먼저 호출해야 하는 경우 다음 예에서 보는 것처럼 @Order 주석을 메서드 선언에 추가할 수 있다.
1
2
3
4
5
@EventListener
@Order(42)
fun processBlackListEvent(event: BlackListEvent) {
    // notify appropriate parties via notificationAddress...
}

Generic Events

You can also use generics to further define the structure of your event. Consider using an EntityCreatedEvent<T> where T is the type of the actual entity that got created. For example, you can create the following listener definition to receive only EntityCreatedEvent for a Person:

  • 제네릭을 사용하여 이벤트 구조를 추가로 정의할 수도 있다.
  • EntityCreatedEvent<T> (T는 생성된 실제 실체의 유형) 을 사용해보자.
  • 예를 들어, 다음 listener 정의를 작성하여 Person에 대한 EntityCreatedEvent만 수신할 수 있다.
1
2
3
4
@EventListener
fun onPersonCreated(event: EntityCreatedEvent<Person>) {
    // ...
}

Due to type erasure, this works only if the event that is fired resolves the generic parameters on which the event listener filters (that is, something like class PersonCreatedEvent extends EntityCreatedEvent<Person> { … }).

  • 유형 삭제로 인해 이 작업은 이벤트 listener가 필터링하는 일반 매개 변수가 resolves되는 경우에만 작동된다. (즉, class PersonCreatedEvent extends EntityCreatedEvent<Person> { … } 같은 경우)

In certain circumstances, this may become quite tedious if all events follow the same structure (as should be the case for the event in the preceding example). In such a case, you can implement ResolvableTypeProvider to guide the framework beyond what the runtime environment provides. The following event shows how to do so:

  • 특정 상황에서 모든 events가 동일한 구조를 따른다면(앞의 예에서 해당 event의 경우처럼) 이는 상당히 지루해질 수 있다.
  • 이 경우, 런타임 환경이 제공하는 것 이상으로 프레임워크를 안내하는 ResolvableTypeProvider를 구현할 수 있다.
  • 다음 event는 그 방법을 보여준다.
1
2
3
4
5
6
class EntityCreatedEvent<T>(entity: T) : ApplicationEvent(entity), ResolvableTypeProvider {

    override fun getResolvableType(): ResolvableType? {
        return ResolvableType.forClassWithGenerics(javaClass, ResolvableType.forInstance(getSource()))
    }
}

This works not only for ApplicationEvent but any arbitrary object that you send as an event.

  • 이 기능은 ApplicationEvent뿐만 아니라 이벤트로 보내는 임의의 개체에도 사용할 수 있다.

References

This post is licensed under CC BY 4.0 by the author.

[Spring] 1.15.1. Internationalization using MessageSource

[Spring] 1.15.3. Convenient Access to Low-level Resources