Pages

搜尋此網誌

2014年2月20日 星期四

spring-boot: server startup or shutdown listener

spring-boot:伺服器啟動或關閉執行程式

在之前的專案中遇到一種狀況,使用者說他們的服務沒有反應?通常會有幾種情形:

  1. 服務被人為關閉
  2. 作業系統被關閉
  3. 記憶體不足造成服務終止

第三點的原因有很多,這邊先不談,關於 1、2 點可能是人為,或者管理者在做系統維護時,不知道需要在重新啟動服務,或是錯誤關閉,不管哪一種,都會造成服務無法運作,接著你就會接到電話或信件,系統不 work 了。

通常,也只能問有沒有人去操作主機,或是有沒有人去關閉服務?常常問不出所以然,當然就如同上面所舉的幾點,不一定是人為因素,但我們希望可以加快系統出現異常時的反應速度,將服務停止時間降到最低。

因此,主動通知是比較積極的作法,唯有當下接收到服務開啟或關閉的訊息,才能夠在最接近問題發生的點來釐清問題,另一方面,也是較有效率的作法,誰也不想定期每天去檢查你所安裝的服務今天是否有正常運行。

說這麼多,此篇要介紹的是如何在使用 spring-boot 時,定義 server 開啟或關閉時執行特定的程式,比如發 mail 或是寫 log,當然使用傳統 spring 也可以,參考:Spring @PostConstruct And @PreDestroy Example

在實作上,我們可以利用 @Configuration 的特性,在類別中,只要我們打上 @Configuration,該類別就會作為類似 xml 設定檔,在服務啟動時將在類別中定義的方法根據每個方法的 annotation 註冊,下面將分別針對啟動與關閉服務利用 @PostConstruct,以及 @PreDestroy 來達成。

服務啟動時 @PostConstruct

@PostConstruct,官方 api 參考下面連結 Annotation Type PostConstruct,文件中有詳細說明使用時機,其中:

The PostConstruct annotation is used on a method that needs to be executed after dependency injection is done to perform any initialization.

可以看到執行的時機點在依賴注入後進行初始作業的執行,正是我們想要的。

服務關閉時 @PreDestroy

@PreDestroy,官方 api 參考下面連結 Annotation Type PreDestroy

The PreDestroy annotation is used on methods as a callback notification to signal that the instance is in the process of being removed by the container.

文中說明,當執行實體被容器移除時會執行該動作,若我們將 @PreDestroy 定義在有 @Configuration 標註的類別中,因為 @Configuration 在服務啟動時只會產生一個實體,因此一旦服務被關閉時進行類別 GC 時,就會連帶觸動執行有標註 @PreDestroy 的函式。

透過上述兩個 annotation 就可以做到啟動或關閉時發出 mail 通知,不過這邊要注意關閉的情形,若使用者強制將服務關閉,不等待 server 的後續處理,那標註 @PreDestroy 的函式將無法完全執行完畢,目前個人沒有更好的方式可以避免,若讀者有不錯的作法,務必讓我知道,或者是關於上述應用情境有更好的處理方式,比如該 annotation 有錯用的地方,也請不吝指教。

最後附上開關 server 自動送出 mail 的程式碼給大家參考,以 gmail 為例:

定義 MailSender 以及預設的 SimpleMailMessage

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSenderImpl;

import java.util.Properties;

@Configuration
public class ApplicationConfig{

    @Bean
    public MailSender mailSender(){
        JavaMailSenderImpl javaMailSenderImpl = new JavaMailSenderImpl();
        javaMailSenderImpl.setHost("smtp.gmail.com");
        javaMailSenderImpl.setPort(587);
        javaMailSenderImpl.setUsername("user");
        javaMailSenderImpl.setPassword("password");

        Properties mailProp = new Properties();
        mailProp.put("mail.smtp.auth", true);
        mailProp.put("mail.smtp.starttls.enable", true);

        javaMailSenderImpl.setJavaMailProperties(mailProp);
        return (MailSender) javaMailSenderImpl;
    }

    @Bean
    public SimpleMailMessage simpleMailMessage() {
        SimpleMailMessage msg = new SimpleMailMessage();
        msg.setTo("to@gmail.com");
        msg.setFrom("from@gmail.com");
        msg.setText("內文");
        return msg;
    }

}

定義開關服務處理函式,將定義好的 MailSender 以及 SimpleMailMessage 注入並且使用:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.MailException;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@Configuration
public class ContextListener {

    @Autowired
    MailSender mailSender;

    @Autowired
    SimpleMailMessage simpleMailMessage;

    @PostConstruct
    public void startupMailNotify(){

        simpleMailMessage.setSubject("服務啟動");

        try{
            mailSender.send(simpleMailMessage);
        }
        catch(MailException ex) {
            System.err.println(ex.getMessage());
        }
    }
    @PreDestroy
    public void shutdownMailNotify(){
        simpleMailMessage.setSubject("服務關閉");
        try{
            mailSender.send(simpleMailMessage);
        }
        catch(MailException ex) {
            System.err.println(ex.getMessage());
        }
    }

}

其實可以在精簡,不過也夠簡單了,並且降低耦合,是吧~

在 spring 與 spring-boot 其中之一的差別,就是 spring-boot 可以不需要 xml 定義,寫起來又更方便了點、精簡了點。

張貼留言