1. 程式人生 > >Spring事件ApplicationEvent(ContextRefreshEvent)

Spring事件ApplicationEvent(ContextRefreshEvent)

最近有一個業務需要用到Spring的ContextRefreshedEvent事件來處理,於是就順便學習了以下Spring的事件原理

個人理解Spring事件主要是為了解決各個Bean之間的通訊問題

首先Spring框架定義了一個抽象類ApplicationEvent(實現了javaSE的ObjectEvent介面)供開發人員自定義事件,也就是自己定義一個事件類繼承ApplicationEvent示例程式碼如下:

public class DemoEvent extends ApplicationEvent {

    private String msg;

    public DemoEvent(Object source,String msg) {
        super(source);
        this.setMsg(msg);
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

其次有了事件就肯定會有監聽者,Spring也為我們定義好了規範ApplicationListener介面(繼承自JavaSe的EventListener);我們需要自定義一個監聽者實現該介面重寫onApplicationEvent方法執行程式碼邏輯

@Component
public class DemoListener implements ApplicationListener<DemoEvent> {

    @Override
    public void onApplicationEvent(DemoEvent event) {
        if (!(event instanceof DemoEvent)){
            return;
        }
        System.out.println("demoListener接受到了demoPublisher釋出的訊息:" + event.getMsg());
    }
}

最後就是釋出者了Spring的ApplicationContext上下文繼承了ApplicationEventPublisher接口裡邊的publishEvent方法這樣我們就可以通過applicationContext來發布自己定義的事件了

@Component
public class DemoPublisher implements ApplicationContextAware {

    @Autowired
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public void publisher(){

        DemoEvent event = new DemoEvent(applicationContext,"i am demo");
        System.out.println("發部event:"+event);
        applicationContext.publishEvent(event);
    }
}

最後直接測試就可以達到釋出與監聽的效果

@Configuration
@ComponentScan("com.example.configclient.event")
public class EventConfig {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(EventConfig.class);
        DemoPublisher publisher = context.getBean(DemoPublisher.class);
        publisher.publisher();
        context.close();
    }

}

寫了半天你會發現還是沒有提到ContextRefreshedEvent這個事件,這個事件是Spring框架自定義的事件,主要作用是用於spring容器載入完畢做一件你想做的事情,Spring框架是如何做到自己的事件釋出與監聽的呢,下圖就是一個很好的展示了,Spring提供了ApplicationEventMulticaster介面,負責管理ApplicationListener和釋出ApplicationEvent。ApplicationContext會把相應的事件相關工作委派給ApplicationEventMulticaster介面來做

SimpleApplicationEventMulticaster實現了ApplicationEventMulticaster介面並執行事件釋出廣播的,執行每一個監聽者事件裡邊的onApplicationEvent方法,下邊是原始碼
	@Override
	public void multicastEvent(ApplicationEvent event) {
		multicastEvent(event, resolveDefaultEventType(event));
	}

	@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);
			}
		}
	}

	private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
		try {
			listener.onApplicationEvent(event);
		}
		catch (ClassCastException ex) {
			String msg = ex.getMessage();
			if (msg == null || matchesClassCastMessage(msg, event.getClass())) {
				// Possibly a lambda-defined listener which we could not resolve the generic event type for
				// -> let's suppress the exception and just log a debug message.
				Log logger = LogFactory.getLog(getClass());
				if (logger.isDebugEnabled()) {
					logger.debug("Non-matching event type for listener: " + listener, ex);
				}
			}
			else {
				throw ex;
			}
		}
	}

下面給一個具體的應用例項:在專案中我們想要在專案載入時候就去獲取加了某個註解的compent並將其資訊放入map中,這就需要用到ContextRefreshEvent,示例程式碼如下:

@Component
public class SourceServiceProxyLoad extends ApplicationObjectSupport implements ApplicationListener<ContextRefreshedEvent> {
    private static Map<String, Object> proxyServiceMAP = new HashMap<>();

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (event.getApplicationContext().getParent() == null) {//主要是因為spring有很多context容器,我們只需要當最頂層的root容器也就是ApplicationContext載入完成才去做操作,防止多次操作
            loadService();
        }
    }

    private void loadService() {
        Map<String, Object> map = this.getApplicationContext().getBeansWithAnnotation(SourceProxyService.class);
        if (map != null) {
            for (Object obj : map.values()) {
                if(AopUtils.isAopProxy(obj)) {
                    Advised advised = (Advised) obj;
                    SingletonTargetSource singTarget = (SingletonTargetSource) advised
                            .getTargetSource();
                    obj =  singTarget.getTarget();
                }
                SourceProxyService annotation = obj.getClass().getAnnotation(SourceProxyService.class);
                proxyServiceMAP.put(annotation.servicePrefix(),  obj);
            }
        }
    }

好了到這裡就結束了,寫這些主要是為了自己做一個總結,然後跟大家互相交流一下,如果有不對的地方大家看到了一定要指出來,互相學習最重要了