1. 程式人生 > 程式設計 >Spring 面向事件驅動程式設計

Spring 面向事件驅動程式設計

談到Spring 事件驅動模型,我想大家都不陌生,事件驅動模型,通常也可以說是觀察者設計模式,對觀察者設計模式不熟悉的朋友可以看我之前寫的筆記,設計模式java語言實現之觀察者模式,在java事件驅動的支援中,EventBus做移動端開發的朋友應該都比較瞭解,其實,java本身也自帶了對事件驅動的支援,但是大部分都是用於我們的客戶端開發,比如GUI ,Swing這些,而Spring 則在java的基礎上,擴充套件了對事件驅動的支援。

不說廢話,直接上程式碼

1.程式碼實戰

首先,我們新建一個類NotifyEvent 繼承ApplicationEvent,用於封裝我們事件額外的資訊,這裡則是String型別的msg,用於記錄詳細的事件內容。

public class NotifyEvent extends ApplicationEvent {

    private String msg;

    public NotifyEvent(Object source,String msg) {
        super(source);
        this.msg = msg;
    }

    public String getMsg() {
        return msg;
    }
}

複製程式碼

其中,ApplicationEvent 是一個抽象類,擴充套件了java本身的EventObject 類,每一個繼承了ApplicationEvent的子類都表示一類事件,可以攜帶資料。

然後新建一個NotifyPublisher用於我們事件的釋出工作,該類實現了ApplicationContextAware並重寫了setApplicationContext 方法,這一步的目的是可以獲取我們Spring的應用上下文,因為事件的釋出是需要應用上下文來做的,不瞭解應用上下文的同學可以去看我的另外一篇筆記:到底什麼是上下文?

@Component //宣告成元件,為了後期注入方便
public class NotifyPublisher implements ApplicationContextAware {

    private ApplicationContext ctx; //應用上下文
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.ctx= applicationContext; } // 釋出一個訊息,這裡大家可以根據不同的狀態實現釋出不同的事件,我這裡就只寫了一個事件類,所以if else //都發布NotifyEvent事件。 public void publishEvent(int status,String msg) { if (status == 0) { ctx.publishEvent(new NotifyEvent(this,msg)); } else { ctx.publishEvent(new NotifyEvent(this,msg)) ; } } } 複製程式碼

最後一步就是實現一個類作為事件的訂閱者啦,當事件釋出時,會通知訂閱者,然後訂閱者做相關的處理,比如新使用者註冊傳送事件自動傳送歡迎郵件等等。同時,Spring 4.2 版本更新的EventListener,可以很方便幫助我們實現事件與方法的繫結,只需要在目標方法上加上EventListener即可。

@Component
public class NotifyListener {

    @EventListener
    //引數NotifyEvent ,當有NotifyEvent 型別的事件發生時,交給sayHello方法處理
    public void sayHello(NotifyEvent notifyEvent){
       System.out.println("收到事件:"+notifyEvent.getMsg());
    }

}

複製程式碼

**測試:**編寫我們的測試類TestController。

@RestController
public class TestController {

    @Autowired
    private NotifyPublisher notifyPublisher;

    @GetMapping("/sayHello")
    public String sayHello(){
        notifyPublisher.publishEvent(1,"我釋出了一個事件");
        return "Hello Word";

    }

}


複製程式碼

啟動我們的應用,在瀏覽器中輸入http://127.0.0.1:8080/sayHello,控制檯輸出:

2019-09-28 16:55:51.902  INFO 716 --- [on(4)-127.0.0.1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 12 ms
收到事件:我釋出了一個事件
複製程式碼

劃個知識點:

如果一個新的事件繼承了NotifyEvent,當我們推送NotifyEvent型別的事件時,NotifyEvent和其子類的監聽器都可以收到該事件。

完了嗎,還沒有,日常除了聽到過事件驅動程式設計,偶爾還會見到非同步事件驅動程式設計這幾個字,同樣的Spring 也提供了@Async 註解來實現非同步事件的消費。用起來也很簡單,只需要在 @EventListener上加上@Async 就好了。

2. Spring 非同步事件實現:

程式碼如下:

@Component
public class NotifyListener {

    @Async
    @EventListener
    public void sayHello(NotifyEvent notifyEvent){
       System.out.println("收到事件:"+notifyEvent.getMsg());
    }

}

複製程式碼

最後配置一個執行緒池

@Configuration
@EnableAsync
public class AysncListenerConfig implements AsyncConfigurer {
   
    /**
     * 獲取非同步執行緒池執行物件
     *
     * @return
     */
    @Override
    @Bean(name = "taskExecutor")
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.initialize();
        executor.setCorePoolSize(10); //核心執行緒數
        executor.setMaxPoolSize(20);  //最大執行緒數
        executor.setQueueCapacity(1000); //佇列大小
        executor.setKeepAliveSeconds(300); //執行緒最大空閒時間
        executor.setThreadNamePrefix("ics-Executor-"); ////指定用於新建立的執行緒名稱的字首。
        executor.setRejectedExecutionHandler(
                new ThreadPoolExecutor.CallerRunsPolicy()); // 拒絕策略
        return new ExceptionHandlingAsyncTaskExecutor(executor);
    }
    
    

}
複製程式碼
public class ExceptionHandlingAsyncTaskExecutor implements AsyncTaskExecutor {

    private AsyncTaskExecutor executor;

    public ExceptionHandlingAsyncTaskExecutor(AsyncTaskExecutor executor) {
        this.executor = executor;
    }

    //用獨立的執行緒來包裝,@Async其本質就是如此
    public void execute(Runnable task) {
        executor.execute(createWrappedRunnable(task));
    }
    
    public void execute(Runnable task,long startTimeout) {
        //用獨立的執行緒來包裝,@Async其本質就是如此
                executor.execute(createWrappedRunnable(task),startTimeout);
    }
    
    
    public Future submit(Runnable task) { return executor.submit(createWrappedRunnable(task));
        //用獨立的執行緒來包裝,@Async其本質就是如此。
    }
    
    
    public Future submit(final Callable task) {
        //用獨立的執行緒來包裝,@Async其本質就是如此。
        return executor.submit(createCallable(task));
    }

    
    private Callable createCallable(final Callable task) {
        return new Callable(){

            @Override
            public Object call() throws Exception {
                try {
                    return task.call();
                } catch (Exception ex) {
                    handle(ex);
                    throw ex;
                }
            }
        };
    }

   
    private Runnable createWrappedRunnable(final Runnable task) {
        return new Runnable() {
            public void run() {
                try {
                    task.run();
                } catch (Exception ex) {
                    handle(ex);
                }
            }
        };
    }
    private void handle(Exception ex) {
        //具體的異常邏輯處理的地方
        System.err.println("Error during @Async execution: " + ex);
    }
}
複製程式碼

測試:編寫我們的測試類TestController。

2019-09-28 16:55:51.902  INFO 716 --- [on(4)-127.0.0.1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 12 ms
收到事件:我釋出了一個事件
複製程式碼

大功告成啦。

電子版筆記和程式碼已經開源至github(歡迎star哦):

github.com/hanshuaikan…