This is the 14th day of my participation in Gwen Challenge

This article is participating in “Java Theme Month – Java Development in Action”, see the activity link for details

I am Chen PI, an ITer of Internet Coding. I search “Chen PI’s JavaLib” on wechat and read the latest articles as soon as possible, reply to [information], and then I can get the technical materials, electronic books, interview materials of first-line big factories and excellent resume templates carefully arranged by me.

preface

Spring provides the ApplicationContext event mechanism to publish and listen for events, which is a very useful feature.

Spring has some built-in events and listeners. For example, before the Spring container is started, after the Spring container is started, after the application startup fails, and other events occur, the listeners listening on these events will make corresponding response processing.

Of course, we can also customize listeners to listen for events that Spring already has. Or we can customize our own events and listeners to publish events at the necessary point in time, and then the listener listens to the event and responds.

ApplicationContext event mechanism

The ApplicationContext event mechanism is implemented using the observer design pattern. The ApplicationEvent class and the ApplicationListener listener interface are used to publish and process ApplicationContext events.

Whenever ApplicationContext publishes ApplicationEvent, if there is an ApplicationListener bean in the Spring container, the listener is triggered to perform the appropriate processing. Of course, the publication of the ApplicationEvent event needs to show the trigger, and either Spring shows the trigger or we show the trigger.

ApplicationListener listener

Defines the interface that application listeners need to implement. This interface inherits from the JDK’s standard EventListener interface, which is an empty tag interface that is recommended for all event listeners.

package org.springframework.context;

import java.util.EventListener;

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

	/** * Handle application events */
	void onApplicationEvent(E event);
}
Copy the code
package java.util;

public interface EventListener {}Copy the code

ApplicationListener is a generic interface. When we customize the implementation class of this interface, if we specify a generic concrete event class, only this event will be listened for. If no concrete generics are specified, all subclasses of the ApplicationEvent abstract class are listened for.

Below we define a listener that listens for specific events, such as the ApplicationStartedEvent event.

package com.chenpi;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/ * * *@Description
 * @AuthorDried tangerine or orange peel *@Date 2021/6/26
 * @Version1.0 * /
@Slf4j
@Component
public class MyApplicationListener implements ApplicationListener<ApplicationStartedEvent> {
    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        log.info("> > > MyApplicationListener: {}", event); }}Copy the code

Start the service and see that this listener is triggered after the service is started.

If no concrete generic class is specified, all subclass events of the ApplicationEvent abstract class are listened for. As follows:

package com.chenpi;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/ * * *@Description
 * @AuthorDried tangerine or orange peel *@Date 2021/6/26
 * @Version1.0 * /
@Slf4j
@Component
public class MyApplicationListener implements ApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        log.info("> > > MyApplicationListener: {}", event); }}Copy the code

Note that the bean of the listener class will not take effect unless it is injected into the Spring container. One is to use annotation injection, such as @Component. Also can use SpringApplicationBuilder. Listeners () method to add, but the two ways of distinguishing, look at the following example.

First we use the @Component annotation. When the service starts, two events are monitored:

  • ApplicationStartedEvent
  • ApplicationReadyEvent
package com.chenpi;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.SpringApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/ * * *@Description
 * @AuthorDried tangerine or orange peel *@Date 2021/6/26
 * @Version1.0 * /
@Slf4j
@Component
public class MyApplicationListener implements ApplicationListener<SpringApplicationEvent> {
    @Override
    public void onApplicationEvent(SpringApplicationEvent event) {
        log.info("> > > MyApplicationListener: {}", event); }}Copy the code

Using SpringApplicationBuilder. Listeners () method to add listeners, service starts, listening to the five events:

  • ApplicationEnvironmentPreparedEvent
  • ApplicationContextInitializedEvent
  • ApplicationPreparedEvent
  • ApplicationStartedEvent
  • ApplicationReadyEvent
package com.chenpi;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class Application {

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

        SpringApplication app = new SpringApplicationBuilder(Application.class)
                .listeners(newMyApplicationListener()).build(); app.run(args); }}Copy the code

This is related to the timing of the listener bean registration. The @ Component annotation listener bean can only be used after the bean is initially registered. By SpringApplicationBuilder. Listeners () to add listeners bean is before the start of the container, so listen more to the event. However, do not use both at the same time, or the listener will repeat the execution twice.

If you want to inject other beans (such as @AutoWired) into the listener bean, it is best to use the annotation form, because if you publish the listener too early, the other beans may not have been initialized and an error may be reported.

ApplicationEvent event

ApplicationEvent is an abstract class that all application events inherit from. It inherits EventObject, the root class for all events, which has an Object of type Object, Source, representing the source of the event. All constructors of classes that inherit from it must display the passing event source.

package org.springframework.context;

import java.util.EventObject;

public abstract class ApplicationEvent extends EventObject {

	private static final long serialVersionUID = 7099057708183571937L;

	// Publish the system time of the event
	private final long timestamp;

	public ApplicationEvent(Object source) {
		super(source);
		this.timestamp = System.currentTimeMillis();
	}

	public final long getTimestamp(a) {
		return this.timestamp; }}Copy the code
package java.util;

public class EventObject implements java.io.Serializable {

    private static final long serialVersionUID = 5516075349620653480L;

    protected transient Object  source;

    public EventObject(Object source) {
        if (source == null)
            throw new IllegalArgumentException("null source");

        this.source = source;
    }

    public Object getSource(a) {
        return source;
    }

    public String toString(a) {
        return getClass().getName() + "[source=" + source + "]"; }}Copy the code

In Spring, the important event class is SpringApplicationEvent. Spring has built-in events that fire when an operation is completed. These built-in events inherit from the SpringApplicationEvent abstract class. SpringApplicationEvent inherits ApplicationEvent and adds the string array parameter field args.

/**
 * Base class for {@link ApplicationEvent} related to a {@link SpringApplication}.
 *
 * @author Phillip Webb
 * @since1.0.0 * /
@SuppressWarnings("serial")
public abstract class SpringApplicationEvent extends ApplicationEvent {

	private final String[] args;

	public SpringApplicationEvent(SpringApplication application, String[] args) {
		super(application);
		this.args = args;
	}

	public SpringApplication getSpringApplication(a) {
		return (SpringApplication) getSource();
	}

	public final String[] getArgs() {
		return this.args; }}Copy the code

We can write our own listeners and then listen for these events to implement our own business logic. For example, we write an implementation class for the ApplicationListener interface that listens for the ContextStartedEvent event, which is published when the application container ApplicationContext is started, so our listeners are fired.

  • ContextRefreshedEvent: Events are published when ApplicationContext is initialized or refreshed. ConfigurableApplicationContext interface the refresh () method is called will trigger event publishing. Initialization means that all beans are successfully loaded, the post-processing beans are detected and activated, all singleton beans are pre-instantiated, and the ApplicationContext container is ready for use.
  • ContextStartedEvent: Publish this event after the application context is refreshed, but before any ApplicationRunner and CommandLineRunner are invoked.
  • ApplicationReadyEvent: This event is published as late as possible to indicate that the application is ready to service the request. The source of the event is the SpringApplication itself, but be careful to modify its internal state, since all initialization steps will have been completed by then.
  • ContextStoppedEvent: ConfigurableApplicationContext interface of the stop () is called when they stop ApplicationContext events are released.
  • ContextClosedEvent: ConfigurableApplicationContext interface of the close () is called closed ApplicationContext, events are released. Note that after a closed context reaches the end of its life cycle, it cannot be refreshed or restarted.
  • ApplicationFailedEvent: Releases an event when an application fails to start.
  • ApplicationEnvironmentPreparedEvent: events are released when SpringApplication start, first check and change the Environment, and on the ApplicationContext has not been created.
  • ApplicationPreparedEvent: When the event is published, the SpringApplication is starting and the ApplicationContext is fully ready but not refreshed. At this stage, you load the Bean Definitions and are ready to use the Environment.
  • RequestHandledEvent: This is a Web event and only applies to Web applications that use DispatcherServlet. When Spring is used as the front-end MVC controller, the system automatically fires this event when Spring finishes processing the user request.

Custom events and listeners

You learned earlier about custom listeners, which then listen for Spring’s native events. The following describes custom events and custom listeners, and then you publish events in your program that trigger the listener to execute and implement your own business logic.

You start by customizing events, inheriting ApplicationEvent, which can of course customize its own properties.

package com.chenpi;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.context.ApplicationEvent;

/ * * *@DescriptionCustom events *@AuthorDried tangerine or orange peel *@Date 2021/6/26
 * @Version1.0 * /
@Getter
@Setter
public class MyApplicationEvent extends ApplicationEvent {

    // Events can add their own attributes
    private String myField;

    public MyApplicationEvent(Object source, String myField) {
        // Bind the event source
        super(source);
        this.myField = myField;
    }

    @Override
    public String toString(a) {
        return "MyApplicationEvent{" + "myField='" + myField + '\' ' + ", source=" + source + '} '; }}Copy the code

And then custom listeners to listen for our custom events.

package com.chenpi;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;

/ * * *@DescriptionCustom listeners *@AuthorDried tangerine or orange peel *@Date 2021/6/26
 * @Version1.0 * /
@Slf4j
public class MyApplicationListener implements ApplicationListener<MyApplicationEvent> {
    @Override
    public void onApplicationEvent(MyApplicationEvent event) {
        log.info("> > > MyApplicationListener: {}", event); }}Copy the code

Register listeners and publish events. There are two ways to register listeners as explained above. Events published by ApplicationEventPublisher. PublishEvent () method. Demo with configurableApplicationContext directly release here, it implements the ApplicationEventPublisher interface.

package com.chenpi;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        // SpringApplication.run(Application.class, args);
        // Register the listener
        SpringApplication app = new SpringApplicationBuilder(Application.class)
                .listeners(new MyApplicationListener()).build();
        ConfigurableApplicationContext configurableApplicationContext = app.run(args);
        // For demonstration purposes, publish events after the project has started, but also at other operations and other points in time
        configurableApplicationContext
                .publishEvent(new MyApplicationEvent("I am the event source, publish the event when the project is successfully launched."."I'm a custom event property")); }}Copy the code

Start the service, and the results show that the published event was indeed listened to.

2021- 0626 - 16:15:09.584  INFO 10992-- -- -main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8081 (http) with context path ' '
2021- 0626 - 16:15:09.601  INFO 10992-- -- -main] com.chenpi.Application                   : Started Application in 2.563 seconds (JVM running for 4.012)
2021- 0626 - 16:15:09.606  INFO 10992-- -- -main] com. Chenpi. MyApplicationListener: > > > MyApplicationListener: MyApplicationEvent {myField ='I'm a custom event property', source= I am the event source, the event will be published after the project starts successfully}Copy the code

The event listening mechanism can achieve the effect of distribution and decoupling. For example, the event can be published in the business class, and the listener listening to the event can perform its own business processing. Such as:

package com.chenpi;

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;

/ * * *@Description
 * @AuthorDried tangerine or orange peel *@Date 2021/6/26
 * @Version1.0 * /
@Service
public class MyService implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    public void testEvent(a) {
        applicationEventPublisher
                .publishEvent(new MyApplicationEvent("I am the source."."I'm a custom event property")); }}Copy the code

Annotated listeners

In addition to implementing the ApplicationListener interface to create listeners, Spring also provides an annotation @EventListener to create listeners.

package com.chenpi;

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

/ * * *@DescriptionCustom listeners *@AuthorDried tangerine or orange peel *@Date 2021/6/26
 * @Version1.0 * /
@Slf4j
@Component
public class MyApplicationListener01 {
    @EventListener
    public void onApplicationEvent(MyApplicationEvent event) {
        log.info("> > > MyApplicationListener: {}", event); }}Copy the code

Annotations can also be conditional filtered to listen only for events with specified conditions. For example, an event whose myField property value is equal to “tangerine peel”.

package com.chenpi;

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

/ * * *@DescriptionCustom listeners *@AuthorDried tangerine or orange peel *@Date 2021/6/26
 * @Version1.0 * /
@Slf4j
@Component
public class MyApplicationListener01 {
    @eventListener (condition = "# event.myfield. Equals (' tangerine ')")
    public void onApplicationEvent(MyApplicationEvent event) {
        log.info("> > > MyApplicationListener: {}", event); }}Copy the code

You can also define multiple listeners in the same class, and you can specify an order for different listeners on the same event. The smaller the order value, the earlier the execution.

package com.chenpi;

import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

/ * * *@DescriptionCustom listeners *@AuthorDried tangerine or orange peel *@Date 2021/6/26
 * @Version1.0 * /
@Slf4j
@Component
public class MyApplicationListener01 {
    @Order(2)
    @EventListener
    public void onApplicationEvent(MyApplicationEvent event) {
        log.info(">>> onApplicationEvent Order =2: {}", event);
    }

    @Order(1)
    @EventListener
    public void onApplicationEvent01(MyApplicationEvent event) {
        log.info(">>> onApplicationEvent Order =1: {}, event);
    }

    @EventListener
    public void otherEvent(YourApplicationEvent event) {
        log.info("> > > otherEvent: {}", event); }}Copy the code

The result is as follows:

>>> onApplicationEvent order=1: MyApplicationEvent {myField ='dried tangerine or orange peel, source= I am the event source} >>> onApplicationEvent Order =2: MyApplicationEvent {myField ='dried tangerine or orange peel, source= I am event source} >>> otherEvent: MyApplicationEvent{myField='I am a custom event attribute 01', source= I am the event source01}
Copy the code

The listening handling of the event is synchronous, as follows:

package com.chenpi;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;

/ * * *@Description
 * @AuthorDried tangerine or orange peel *@Date 2021/6/26
 * @Version1.0 * /
@Service
@Slf4j
public class MyService implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    public void testEvent(a) {
        log.info(">>> testEvent begin");
        applicationEventPublisher.publishEvent(new MyApplicationEvent("I am the source."."Orange"));
        applicationEventPublisher.publishEvent(new YourApplicationEvent("This is Source 01."."I am a custom event attribute 01"));
        log.info(">>> testEvent end"); }}Copy the code

The result is as follows:

2021- 0626 - 20:34:27.990  INFO 12936-- -- -nio-8081-exec-1] com.chenpi.MyService                     : >>> testEvent begin
2021- 0626 - 20:34:27.990  INFO 12936-- -- -nio-8081-exec-1] com.chenpi.MyApplicationListener01       : >>> onApplicationEvent order=1: MyApplicationEvent {myField ='dried tangerine or orange peel, source= I am the event source}2021- 0626 - 20:34:27.991  INFO 12936-- -- -nio-8081-exec-1] com.chenpi.MyApplicationListener01       : >>> onApplicationEvent order=2: MyApplicationEvent {myField ='dried tangerine or orange peel, source= I am the event source}2021- 0626 - 20:34:27.992  INFO 12936-- -- -nio-8081-exec-1] com. Chenpi. MyApplicationListener01: > > > otherEvent: MyApplicationEvent {myField ='I am a custom event attribute 01', source= I am the event source01}
2021- 0626 - 20:34:27.992  INFO 12936-- -- -nio-8081-exec-1] com.chenpi.MyService                     : >>> testEvent end
Copy the code

However, we can also display the specified asynchronous mode to execute the listener, remember to add @enableAsync annotation to the service to enable asynchronous annotation.

package com.chenpi;

import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

/ * * *@DescriptionCustom listeners *@AuthorDried tangerine or orange peel *@Date 2021/6/26
 * @Version1.0 * /
@Slf4j
@Component
public class MyApplicationListener01 {

    @Async
    @Order(2)
    @EventListener
    public void onApplicationEvent(MyApplicationEvent event) {
        log.info(">>> onApplicationEvent Order =2: {}", event);
    }

    @Order(1)
    @EventListener
    public void onApplicationEvent01(MyApplicationEvent event) {
        log.info(">>> onApplicationEvent Order =1: {}, event);
    }

    @Async
    @EventListener
    public void otherEvent(YourApplicationEvent event) {
        log.info("> > > otherEvent: {}", event); }}Copy the code

The result is as follows. Note the printed thread name.

2021- 0626 - 20:37:04.807  INFO 9092-- -- -nio-8081-exec-1] com.chenpi.MyService                     : >>> testEvent begin
2021- 0626 - 20:37:04.819  INFO 9092-- -- -nio-8081-exec-1] com.chenpi.MyApplicationListener01       : >>> onApplicationEvent order=1: MyApplicationEvent {myField ='dried tangerine or orange peel, source= I am the event source}2021- 0626 - 20:37:04.831  INFO 9092-- -- -task-1] com.chenpi.MyApplicationListener01       : >>> onApplicationEvent order=2: MyApplicationEvent {myField ='dried tangerine or orange peel, source= I am the event source}2021- 0626 - 20:37:04.831  INFO 9092-- -- -nio-8081-exec-1] com.chenpi.MyService                     : >>> testEvent end
2021- 0626 - 20:37:04.831  INFO 9092-- -- -task-2] com. Chenpi. MyApplicationListener01: > > > otherEvent: MyApplicationEvent {myField ='I am a custom event attribute 01', source= I am the event source01}
Copy the code