Springのイベントパブリケーションとイベントサブスクリプション(ApplicationEventPublisher)


Springのイベントパブリッシュツール
通常の開発ではSpringは、イベントのパブリケーションとサブスクリプションを提供する一連のツールを提供しています.事件の発表は主にApplicationEventPublisherによって行われた.
イベントのパブリケーションを実現するには、タスク、イベント、イベントのリスニングなど、主に次のオブジェクトが必要です.
タスクタスクは、パブリッシュするイベントの内容を保存するための一般的なクラスです.これは特に制限はなく、自分の業務に応じて自由に設定できます.
import lombok.Data;

/**
 *
 * @author daify
 * @date 2019-08-19
 **/
@Data
public class Task {

    private Long id;

    private String taskName;

    private String taskContext;

    private boolean finish;
}

イベント・イベント・クラスは、Springによって識別されるように、org.springframework.context.ApplicationEventを継承する必要があります.
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEvent;

/**
 *
 * @author daify
 * @date 2019-08-19
 **/
@Slf4j
public class MyEvent extends ApplicationEvent {
    
    private Task task;
    
    public MyEvent(Task task) {
        super(task);
        this.task = task;
    }

    public Task getTask() {
        return task;
    }
}


イベントリスニング
イベントのリスナーは、org.springframework.context.ApplicationListenerを実装する必要があり、容器に注入する必要がある.
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

import java.util.Objects;

/**
 *
 * @author daify
 * @date 2019-08-19
 **/
@Component
@Slf4j
public class MyEventListener implements ApplicationListener<MyEvent> {
    
    @Override 
    public void onApplicationEvent(MyEvent myEvent) {
        if (Objects.isNull(myEvent)) {
            return;
        }
        Task task = myEvent.getTask();
        log.info("      :{}",JSON.toJSONString(task));
        task.setFinish(true);
        log.info("      ");
    }
}


テスト
これにより、次のテスト内容でイベントパブリッシュのテストを完了できます.
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 *
 * @author daify
 * @date 2019-08-19
 **/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes =          .class)
@Slf4j
public class EventTest {
    
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    
    @Test
    public void publishTest() {
        Task task = new Task();
        task.setId(1L);
        task.setTaskName("    ");
        task.setTaskContext("    ");
        task.setFinish(false);
        MyEvent event = new MyEvent(task);
        log.info("      ");
        eventPublisher.publishEvent(event);
        log.info("      ");
    }
}


Springイベント発行プロセス
Springのイベントパブリケーションとサブスクリプションはかなり簡単ですが、ここではイベントパブリケーション全体の流れを簡単に説明します.
ApplicationEventPublisher
そのpublishEventの方法でタスクを発行すると、コードはorgに入る.springframework.context.support.AbstraactApplicationContext論理内.
AbstractApplicationContext
イベントパブリケーションロジック全体がこのクラスおよびそのサブクラスにあり、その最終的なイベントをパブリッシュする方法はpublishEvent(Object event, @Nullable ResolvableType eventType)である.
	protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
		Assert.notNull(event, "Event must not be null");
		//     
		if (logger.isTraceEnabled()) {
			logger.trace("Publishing event in " + getDisplayName() + ": " + event);
		}

		//     
		ApplicationEvent applicationEvent;
		if (event instanceof ApplicationEvent) {
			applicationEvent = (ApplicationEvent) event;
		}
		else {
			applicationEvent = new PayloadApplicationEvent<>(this, event);
			if (eventType == null) {
				eventType = ((PayloadApplicationEvent) applicationEvent).getResolvableType();
			}
		}

		//      ,         ,    
		if (this.earlyApplicationEvents != null) {
			this.earlyApplicationEvents.add(applicationEvent);
		}
		else {
		    // ②            
			getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
		}

		//             ,   
		if (this.parent != null) {
			if (this.parent instanceof AbstractApplicationContext) {
				((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
			}
			else {
				this.parent.publishEvent(event);
			}
		}
	}

イベント全体が発行されるコアロジックはgetApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);行にあります.コンテンツは2つの部分に分けられます.
  • g e t p r i c a tionEventMulticaster()は、容器中のApplicationEventMulticasterを得る.このコンテンツの主なSpringは、タスクのパブリッシュを支援するツールクラスです.
  • ApplicationEventMulticaster.MulticastEvent(アプリケーションEvent,eventType)がイベントのパブリッシュを本格的に行うコンテンツ.

  • ApplicationEventMulticaster
    これはorg.springframework.context.event.AbstractApplicationEventMulticaster実装クラスであり、主にイベントのパブリケーションを支援するために行われ、その内部パブリケーションタスクの主要なコアロジックはmulticastEventにある.
    	@Override
    	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    		for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
    			Executor executor = getTaskExecutor();
    			if (executor != null) {
    				executor.execute(() -> invokeListener(listener, event));
    			}
    			else {
    				invokeListener(listener, event);
    			}
    		}
    	}
    

    次のような流れがあります.
  • イベントタイプを取得し、主にSpring Eventの実際のタイプを取得するために使用されます.resolveDefaultEventType(event))
  • getApplicationListeners(event, type)このイベントおよびイベントタイプをイベントおよびイベントタイプに従って取得するリスナー
  • .
  • は、invokeListener(listener, event)を使用してイベントを実行する.

  • AbstractApplicationEventMulticaster
    取得リスナーの主なロジックorg.springframework.context.event.AbstractApplicationEventMulticastergetApplicationListeners(event, type)
    	protected Collection<ApplicationListener<?>> getApplicationListeners(
    			ApplicationEvent event, ResolvableType eventType) {
    
    		Object source = event.getSource();
    		Class<?> sourceType = (source != null ? source.getClass() : null);
    		ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
    
    		// Quick check for existing entry on ConcurrentHashMap...
    		ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
    		if (retriever != null) {
    			return retriever.getApplicationListeners();
    		}
    
    		if (this.beanClassLoader == null ||
    				(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
    						(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
    			// Fully synchronized building and caching of a ListenerRetriever
    			synchronized (this.retrievalMutex) {
    				retriever = this.retrieverCache.get(cacheKey);
    				if (retriever != null) {
    					return retriever.getApplicationListeners();
    				}
    				retriever = new ListenerRetriever(true);
    				Collection<ApplicationListener<?>> listeners =
    						retrieveApplicationListeners(eventType, sourceType, retriever);
    				this.retrieverCache.put(cacheKey, retriever);
    				return listeners;
    			}
    		}
    		else {
    			// No ListenerRetriever caching -> no synchronization necessary
    			return retrieveApplicationListeners(eventType, sourceType, null);
    		}
    	}
    

    大体の流れ:時間タイプとイベント内のデータソースタイプを通じて、キャッシュキーを構築し、このキーに対応するイベントプロセッサの有無をキャッシュに取得します.存在しない場合、新しいListenerRetrieverが構築され、retrieveApplicationListenersメソッドがリスニングされたlistenerを取得するために呼び出される.
    retrieveApplicationListeners
    	private Collection<ApplicationListener<?>> retrieveApplicationListeners(
    			ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable ListenerRetriever retriever) {
    
    		List<ApplicationListener<?>> allListeners = new ArrayList<>();
    		Set<ApplicationListener<?>> listeners;
    		Set<String> listenerBeans;
    		synchronized (this.retrievalMutex) {
    			listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
    			listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
    		}
    		for (ApplicationListener<?> listener : listeners) {
    			if (supportsEvent(listener, eventType, sourceType)) {
    				if (retriever != null) {
    					retriever.applicationListeners.add(listener);
    				}
    				allListeners.add(listener);
    			}
    		}
    		if (!listenerBeans.isEmpty()) {
    			BeanFactory beanFactory = getBeanFactory();
    			for (String listenerBeanName : listenerBeans) {
    				try {
    					Class<?> listenerType = beanFactory.getType(listenerBeanName);
    					if (listenerType == null || supportsEvent(listenerType, eventType)) {
    						ApplicationListener<?> listener =
    								beanFactory.getBean(listenerBeanName, ApplicationListener.class);
    						if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
    							if (retriever != null) {
    								if (beanFactory.isSingleton(listenerBeanName)) {
    									retriever.applicationListeners.add(listener);
    								}
    								else {
    									retriever.applicationListenerBeans.add(listenerBeanName);
    								}
    							}
    							allListeners.add(listener);
    						}
    					}
    				}
    				catch (NoSuchBeanDefinitionException ex) {
    					// Singleton listener instance (without backing bean definition) disappeared -
    					// probably in the middle of the destruction phase
    				}
    			}
    		}
    		AnnotationAwareOrderComparator.sort(allListeners);
    		if (retriever != null && retriever.applicationListenerBeans.isEmpty()) {
    			retriever.applicationListeners.clear();
    			retriever.applicationListeners.addAll(allListeners);
    		}
    		return allListeners;
    	}
    

    この方法で主に行われる論理は簡単です.リスニングマッチングは、主にListenersをループすることによって行われる.Listenersのソースは主に2つの部分です.
    			listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
    			listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
    

    一部はコンテナにすでに存在するリスナーであり、一部はリスナーbeanの文字列識別の名前である.アプリケーションListenerBeansは、主に、それらの宣言を処理した後もリスナーセットに追加されていないbeanを処理するために使用されます.
    イベントの発行プロセス
    イベントパブリッシュツールの取得
    イベントのパブリッシュ
    イベントリスニングパブリケーションツールの取得
    パッチワークイベントリスニングキャッシュkey
    Listenerをキャッシュから取り出す
    存在する
    存在しません.コンテナからListenerのマッチングを開始します.
    コンテナのListenerと一致
    コンテナ内の文字列に一致するBean
    Listenerメソッドの実行
    Listenerメソッドの実行
    Listenerメソッドの実行
    イベントの発行
    ApplicationEventPublisher.publishEvent
    AbstractApplicationContext.getApplicationEventMulticaster
    SimpleApplicationEventMulticaster.multicastEvent
    AbstractApplicationEventMulticaster.getApplicationListeners
    ListenerCacheKey
    retrieverCache
    結果を返す
    retrieveApplicationListeners
    applicationListeners
    applicationListenerBeans
    Listenerの結果を返す
    invokeListener