spring 2.0 技術手冊第九章 - api 封裝
TRANSCRIPT
API封裝
對於一些企業服務 API,Spring 不提供直接的解決方案,而是提供一層抽象封裝,在這樣的封裝下,提供了簡化且一致的方式,讓您在使用一些 API 或元件服務時更加簡單。 這個章節中將介紹遠程服務、郵件服務與排程服務等在 Spring 中的使用方式。
9
Spring 2.0 技術手冊(林信良 – http://openhome.cc)
��2
9.1 遠程 遠程服務讓本地端的物件在使用伺服端服務時,就如同使用本地服務一樣,客戶端不需接觸到網路連結等細節,對於各種不同的遠程技術,Spring 提供了一致的使用方式,即使底層所採用的遠程服務技術各不相同,在 Spring 中運用它們的方式卻是一致的,這個小節將來介紹一下RMI、Hessian、Burlap以及 Http Invoker的使用。
9.1.1 RMI
RMI(Remote Method Invocation)是從 JDK 1.1開始就出現的 API,它讓客戶端在使用遠端物件提供的服務時,就如同使用本地物件一樣,然而 RMI 在使用時必須有一連串的手續,像是服務介面在定義時必須繼承java.rmi.Remote 介面、服務 Server 在實作時必須繼承 java.rmi.Unicast-
RemoteObject類別、必須使用 rmic指令產生 stub與 skeleton等,設定上的手續繁雜。 您可以在 Spring 中透過 org.springframework.remoting.rmi.Rmi-
ServiceExporter簡化使用 RMI的手續,來實際看看例子,了解 Spring在RMI上的使用與簡化,首先看一下 RMI伺服端的撰寫,然後定義一個服務物件的介面:
RMIServerDemo ISomeService.java
package onlyfun.caterpillar;
public interface ISomeService {
public String doSomeService(String some);
public int doOtherService(int other);
}
Chapter 9 API 封裝
��3
服務物件的介面不用繼承 java.rmi.Remote介面,而在實作 ISomeService時也不用繼承 java.rmi.UnicastRemoteObject類別,例如:
RMIServerDemo SomeServiceImpl.java
package onlyfun.caterpillar;
public class SomeServiceImpl implements ISomeService {
public String doSomeService(String some) {
return some + " is processed";
}
public int doOtherService(int other) {
return ++other;
}
} 這個實作只是個簡單的示範,兩個方法都只是傳回一個已經修改過的值,接下來只要在 Bean定義檔中定義,讓 Spring管理、生成 Bean實例,如此即可註冊、啟動 RMI服務,例如:
RMIServerDemo rmi-server.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="someService"
class="onlyfun.caterpillar.SomeServiceImpl"/>
<bean id="serviceExporter"
class="org.springframework.remoting.
→ rmi.RmiServiceExporter">
<property name="service" ref="someService"/>
<property name="serviceName" value="SomeService"/>
<property name="serviceInterface"
value="onlyfun.caterpillar.ISomeService"/>
Spring 2.0 技術手冊(林信良 – http://openhome.cc)
��4
</bean>
</beans> 只要告訴 org.springframework.remoting.rmi.RmiServiceExporter服務物件、名稱(注意在 "serviceName" 屬性上設定為 "SomeService")與要代理的介面,之後 Spring讀取定義檔並生成 Bean實例後,RMI服務就會啟動,來撰寫一個簡單的 RMIServer類別,以啟動 RMI服務:
RMIServerDemo RMIServer.java
package onlyfun.caterpillar;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.
support.ClassPathXmlApplicationContext;
import org.springframework.remoting.rmi.RmiServiceExporter;
public class RMIServer {
public static void main(String[] args)
throws IOException {
ApplicationContext context =
new ClassPathXmlApplicationContext("rmi-server.xml");
System.out.println("啟動 RMI Server..");
System.out.println("請輸入 exit 關閉 Server: ");
BufferedReader reader =
new BufferedReader(new InputStreamReader(System.in));
while(true) {
if(reader.readLine().equals("exit")) {
break;
}
}
Chapter 9 API 封裝
���
RmiServiceExporter rmiServiceExporter =
(RmiServiceExporter) context.getBean("serviceExporter");
rmiServiceExporter.destroy();
}
} 在運行上面的程式之後,RMI服務就會啟動,Spring會自動使用另一個執行緒來執行 RMI服務,所以不用關心執行緒的處理問題,您可以輸入
"exit" 直接離開程式,接著來看一下,如何實作一個 RMI客戶端以向 RMI伺服器要求服務,首先要記得的是,客戶端是依賴於抽象的介面,也就是先前的 ISomeService介面之 .class檔也必須在客戶端有一份。 在客戶端需要 RMI服務時,只要透過 org.springframework.remoting.
rmi.RmiProxyFactoryBean,並告知服務的 URL(對應至先前設定的"SomeService"名稱)、代理的介面即可,在撰寫程式時就好像在使用本地端管理的服務一樣,例如 Bean定義檔可以如下撰寫:
RMIClientDemo rmi-client.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="someServiceProxy"
class="org.springframework.remoting.
→ rmi.RmiProxyFactoryBean">
<property name="serviceUrl"
value="rmi://localhost/SomeService"/>
<property name="serviceInterface"
value="onlyfun.caterpillar.ISomeService"/>
</bean>
</beans>
Spring 2.0 技術手冊(林信良 – http://openhome.cc)
��6
注意到 "serviceUrl" 屬性的設定,它是以 "rmi://" 開頭,接著指定伺服器位址與服務名稱,來撰寫個簡單的客戶端程式以使用 RMI伺服器上的服務:
RMIClientDemo RMIClient.java
package onlyfun.caterpillar;
import org.springframework.context.ApplicationContext;
import org.springframework.context.
support.ClassPathXmlApplicationContext;
public class RMIClient {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext(
"rmi-client.xml");
ISomeService service =
(ISomeService) context.getBean("someServiceProxy");
String result1 = service.doSomeService("Some request");
System.out.println(result1);
int result2 = service.doOtherService(1);
System.out.println(result2);
}
} 在程式的實作中,完全不需要處理到有關服務連結的種種細節,代理物件會自動幫您完成這些細節,單從程式來看,根本不會注意到正在取得遠端伺服器上的服務,執行的結果如下所示:
圖 9.1 RMIClientDemo 專案的執行結果
Chapter 9 API 封裝
��7
9.1.2 Hessian、Burlap
Hessian、Burlap是由 Caucho Technology(http://www.caucho.com/)所提出,透過 HTTP實現的遠程服務。
Hessian 是將物件以中性的二進位訊息使用 HTTP 進行傳送,而不若RMI使用 Java的序列化格式,由於二進位訊息是中性的,因此不受限於某種程式語言所實現的客戶端或伺服端,二進位資料在傳輸時所需的頻寬較小是其優點。
Burlap則是將物件以 XML文件格式進行傳送,XML文件且有較高的可讀性,只要應用程式可以剖析 XML 文件就可以解讀所接收的訊息,當然也不受限於某種語言所實現的客戶端與伺服端。 在 Spring中使用 Hessian及 Burlap的方法是類似的,由於 Hessian、Burlap 是透過 HTTP 傳送,所以在使用時要搭配 Spring Web 框架,也就是使用 DispatcherServlet,舉個實際的例子來示範如何使用 Hessian,以9.1.1 中介紹的 RMI 例子來說,可以直接使用已撰寫好的 ISomeService、SomeServiceImpl,而伺服端要在 web.xml中配置 DispatcherServlet:
HessianServerDemo web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4">
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
Spring 2.0 技術手冊(林信良 – http://openhome.cc)
��8
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/service-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>*.service</url-pattern>
</servlet-mapping>
</web-app> 在 Hessian的伺服端這邊,使用 org.springframework.remoting.cau-
cho.HessianServiceExporter來發佈服務:
HessianServerDemo service-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="urlMapping"
class="org.springframework.web.servlet.
→ handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/some.service">serviceExporter</prop>
</props>
</property>
</bean>
<bean id="someService"
class="onlyfun.caterpillar.SomeServiceImpl"/>
<bean id="serviceExporter"
Chapter 9 API 封裝
���
class="org.springframework.remoting.
→ caucho.HessianServiceExporter">
<property name="service" ref="someService"/>
<property name="serviceInterface"
value="onlyfun.caterpillar.ISomeService"/>
</bean>
</beans> 注意到在 SimpleUrlHandlerMapping 的設置上,請求"some.service"的會分配給"serviceExporter",在這邊不用註冊服務名稱,也就是沒有
"serviceName" 屬性,Hessian、Burlap不需要,如果使用的是 Burlap,則設 定 上 在 "serviceExporter" 的 "class" 屬 性 只 要 改 用 org.spring-
framework. remoting.caucho.BurlapServiceExporter類別即可。 接下來只要啟動 Servlet容器,載入以上設計的 Web應用程式之後,則 Hessian 伺服端就會啟動了,記得要在 lib 目錄中加入 Hessian 所需的API類別之 .jar檔案,這可以使用 Spring下載檔案中 lib目錄下 caucho目錄的 hessian-3.0.20.jar檔案,如果使用 Burlap的話,要記得加入 Burlap API所需的 .jar檔案。
Hessian客戶端的撰寫則可以使用先前撰寫的 RMIClientDemo專案來改寫,事實上只要修改一下 Bean定義檔即可,例如:
HessianClientDemo hessian-client.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="someServiceProxy"
class="org.springframework.remoting.
→ caucho.HessianProxyFactoryBean">
<property name="serviceUrl" value="http://localhost:8080/
Spring 2.0 技術手冊(林信良 – http://openhome.cc)
��1�
→ HessianServerDemo/some.service"/>
<property name="serviceInterface"
value="onlyfun.caterpillar.ISomeService"/>
</bean>
</beans> 注意到 "serviceUrl" 屬性的設定,它是個標準的 HTTP請求位址,來撰寫個簡單的客戶端程式以使用 Hessian伺服器上的服務:
HessianClientDemo HessianClient.java
package onlyfun.caterpillar;
import org.springframework.context.ApplicationContext;
import org.springframework.context.
support.ClassPathXmlApplicationContext;
public class HessianClient {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext(
"hessian-client.xml");
ISomeService service =
(ISomeService) context.getBean("someServiceProxy");
String result1 = service.doSomeService("Some request");
System.out.println(result1);
int result2 = service.doOtherService(1);
System.out.println(result2);
}
} 同樣的必須記得,要加入包括 Hessian API 的 .jar 檔案,執行的結果與 RMIClientDemo 專案是相同的,可以參考圖 9.1 的畫面,如果要使用Burlap,則設定上只要改用 org.springframework.remoting.caucho.
Chapter 9 API 封裝
��11
BurlapProxyFactoryBean 即可,當然要記得必須加入 Burlap API 所需的 .jar檔案。
9.1.3 Http Invoker
Http Invoker使用 HTTP傳送物件,傳送時使用 Java的序列化機制來傳送,由於透過 HTTP傳送,所以在使用它們時要搭配 Spring Web框架來使用,也就是使用到 DispatcherServlet,可以改寫 9.1.2的 HessianServer-
Demo專案,只要修改一下 service-config.xml就可以了:
HttpInvokerDemo service-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="urlMapping"
class="org.springframework.web.servlet.
→ handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/some.service">serviceExporter</prop>
</props>
</property>
</bean>
<bean id="someService"
class="onlyfun.caterpillar.SomeServiceImpl"/>
<bean id="serviceExporter"
class="org.springframework.remoting.
→ httpinvoker.HttpInvokerServiceExporter">
<property name="service" ref="someService"/>
<property name="serviceInterface"
value="onlyfun.caterpillar.ISomeService"/>
</bean>
Spring 2.0 技術手冊(林信良 – http://openhome.cc)
��12
</beans> 接下來客戶端的部份,可以改寫 HessianClientDemo 專案的內容,修改一下 Bean定義檔的內容:
InvokerClientDemo invoker-client.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="someServiceProxy"
class="org.springframework.remoting.
→ httpinvoker.HttpInvokerProxyFactoryBean">
<property name="serviceUrl" value="http://localhost:8080/
→ HttpInvokerDemo/some.service"/>
<property name="serviceInterface"
value="onlyfun.caterpillar.ISomeService"/>
</bean>
</beans> 注意到 "serviceUrl" 屬性的設定,它是標準的 HTTP請求位址,來撰寫簡單的客戶端程式以使用 Http Invoker伺服器上的服務:
InvokerClientDemo InvokerClient.java
package onlyfun.caterpillar;
import org.springframework.context.ApplicationContext;
import org.springframework.context.
support.ClassPathXmlApplicationContext;
public class InvokerClient {
public static void main(String[] args) {
ApplicationContext context =
Chapter 9 API 封裝
��13
new ClassPathXmlApplicationContext(
"invoker-client.xml");
ISomeService service =
(ISomeService) context.getBean("someServiceProxy");
String result1 = service.doSomeService("Some request");
System.out.println(result1);
int result2 = service.doOtherService(1);
System.out.println(result2);
}
} 執行的結果與 RMIClientDemo專案是相同的,可以參考圖 9.1的畫面。
Spring 2.0 技術手冊(林信良 – http://openhome.cc)
��14
9.2 郵件 對於郵件發送服務的支援是由 Spring 的 org.springframework.mail.
MailSender介面所定義,它有兩個實作類別:org.springframework.mail.
cos.CosMailSenderImpl 與 org.springframework.mail.javamail.Java-
MailSenderImpl,前者為對 Jason Hunter's COS(在它的 Java Servlet
Programming書中提供的)之支援,後者為對 Java Mail之支援。
9.2.1 簡單郵件 在這邊將以 Java Mail的使用,示範 Spring如何提供 Java Mail郵件服務的封裝,首先來看個最簡單的純文字郵件之傳送如何實作,程式的示範如下:
SimpleMailDemo SimpleMailDemo.java
package onlyfun.caterpillar;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.SimpleMailMessage;
public class SimpleMailDemo {
public static void main(String[] args) throws Exception {
JavaMailSenderImpl senderImpl = new JavaMailSenderImpl();
// 設定 Mail Server
senderImpl.setHost("your_mail_server.com");
// 建立郵件訊息
SimpleMailMessage mailMessage = new SimpleMailMessage();
// 設定收件人、寄件人、主題與內文
mailMessage.setTo("xxx@your_mail_server.com");
mailMessage.setFrom("xxx@your_mail_server.com");
mailMessage.setSubject("Test");
mailMessage.setText("This is a test!!!");
Chapter 9 API 封裝
��1�
// 傳送郵件
senderImpl.send(mailMessage);
System.out.println("郵件傳送 OK..");
}
}
JavaMailSenderImple預設會與連接埠 25作溝通,如果您的 SMTP郵件伺服器不是使用 25連接埠,可以使用 setPort() 來設定連接埠,執行以上程式時,記得加入相關 API所需的 .jar檔案,在這邊所使用的是 Spring下載檔案中 lib目錄下 j2ee目錄的 activation.jar與 mail.jar檔案,傳送出去的郵件在接收到後如下所示:
圖 9.2 SimpleMailDemo 專案的執行結果
9.2.2 HTML 郵件 如果要使用 HTML 郵件的話,可以使用 Spring 的 org.springframe-
work.mail.javamail.MimeMessageHelper來建立 HTML郵件,直接使用實例來作示範:
HTMLMailDemo HTMLMailDemo.java
package onlyfun.caterpillar;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import javax.mail.internet.MimeMessage;
import org.springframework.mail.javamail.MimeMessageHelper;
Spring 2.0 技術手冊(林信良 – http://openhome.cc)
��16
public class HTMLMailDemo {
public static void main(String[] args) throws Exception {
JavaMailSenderImpl senderImpl =
new JavaMailSenderImpl();
// 設定 Mail Server
senderImpl.setHost("your_mail_server.com");
// 建立郵件訊息
MimeMessage mailMessage =
senderImpl.createMimeMessage();
MimeMessageHelper messageHelper =
new MimeMessageHelper(mailMessage);
// 設定收件人、寄件人、主題與內文
messageHelper.setTo("xxx@your_mail_server.com");
messageHelper.setFrom("xxx@your_mail_server.com");
messageHelper.setSubject("Test");
messageHelper.setText(
"<html><head></head><body><h1>Hello! Spring!"
+ "</h1></body></html>", true);
// 傳送郵件
senderImpl.send(mailMessage);
System.out.println("郵件傳送 OK...");
}
} 在MimeMessageHelper類別的 setText() 方法上的 boolean參數設定為true時,表示要啟用 HTML格式的郵件,來看一下傳送出去後的郵件內容:
圖 9.3 HTMLMailDemo 專案的執行結果
Chapter 9 API 封裝
��17
9.2.3 內嵌圖片或附檔 您也可以在郵件中內嵌圖片,在內嵌圖片時要給定一個 "cid" 值,直接來看程式的示範:
AttachedImageDemo AttachedImageDemo.java
package onlyfun.caterpillar;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import javax.mail.internet.MimeMessage;
import org.springframework.core.io.ClassPathResource;
import org.springframework.mail.javamail.MimeMessageHelper;
public class AttachedImageDemo {
public static void main(String[] args) throws Exception {
JavaMailSenderImpl senderImpl =
new JavaMailSenderImpl();
// 設定 Mail Server
senderImpl.setHost("your_mail_server.com");
// 建立郵件訊息
MimeMessage mailMessage =
senderImpl.createMimeMessage();
MimeMessageHelper messageHelper =
new MimeMessageHelper(mailMessage, true);
// 設定收件人、寄件人、主題與內文
messageHelper.setTo("xxx@your_mail_server.com");
messageHelper.setFrom("xxx@your_mail_server.com");
messageHelper.setSubject("Test");
messageHelper.setText(
"<html><head></head><body><h1>Hello! Spring!"
+ "</h1><img src=\"cid:caterpillar\">"
+ "</body></html>", true);
ClassPathResource img =
new ClassPathResource("caterpillar.jpg");
messageHelper.addInline("wish", img);
Spring 2.0 技術手冊(林信良 – http://openhome.cc)
��18
// 傳送郵件
senderImpl.send(mailMessage);
System.out.println("郵件傳送成功...");
}
} 在建構 MimeMessageHelper 類別的實例時所給定的 boolean 值為true,表示要啟用 multipart 模式,在 setText() 方法指定 "cid" 值為
"caterpillar",這個設定與 addInline() 方法中的 "cid" 值指定對應,addInline() 方法可以將影像檔案加入至郵件中,來看一下傳送後的郵件之畫面:
圖 9.4 AttachedImageDemo 專案的執行結果 直接將影像檔案加入郵件中有好有壞,好處是不用像給定連結的方式,必須在伺服器上提供檔案,以讓使用者讀取郵件時可以取得所連結的圖片,壞處是有的郵件客戶端對於內嵌圖片的支援會有問題,可能在某些客戶端軟體中會發生無法顯示圖片的情況。
Chapter 9 API 封裝
��1�
您也可以在郵件中使用附加檔案(Attachment file),只要使用 Mime-
MessageHelper的 addAttachment() 方法,其中 "cid" 值是用來顯示附加檔案的名稱,一個實例如下:
AttachedFileDemo AttachedFileDemo.java
package onlyfun.caterpillar;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import javax.mail.internet.MimeMessage;
import org.springframework.core.io.ClassPathResource;
import org.springframework.mail.javamail.MimeMessageHelper;
public class AttachedFileDemo {
public static void main(String[] args) throws Exception {
JavaMailSenderImpl senderImpl =
new JavaMailSenderImpl();
// 設定 Mail Server
senderImpl.setHost("your_mail_server.com");
// 建立郵件訊息
MimeMessage mailMessage =
senderImpl.createMimeMessage();
MimeMessageHelper messageHelper =
new MimeMessageHelper(mailMessage, true);
// 設定收件人、寄件人、主題與內文
messageHelper.setTo("xxx@your_mail_server.com");
messageHelper.setFrom("xxx@your_mail_server.com");
messageHelper.setSubject("Test");
messageHelper.setText(
"<html><head></head><body><h1>Hello! Spring!"
+ "</h1></body></html>", true);
ClassPathResource file =
new ClassPathResource("caterpillar.zip");
messageHelper.addAttachment("caterpillar.zip", file);
// 傳送郵件
senderImpl.send(mailMessage);
Spring 2.0 技術手冊(林信良 – http://openhome.cc)
��2�
System.out.println("郵件傳送成功...");
}
} 來看一下傳送後的郵件畫面:
圖 9.5 AttachedFileDemo 專案的執行結果
Chapter 9 API 封裝
��21
9.3 排程 對於排程定時執行的工作,JDK的標準 API提供有 java.util.Timer與java.util.TimerTask 類別,Spring 則對它提供了抽象封裝,讓您可以善用Spring的容器管理功能,然而 Timer功能有限,只能指定任務與任務之間的週期(Period),無法指定某個時間點定時執行任務,您可以使用 Quartz(http://www.opensymphony.com/quartz/),它提供了更多的排程功能,而 Spring對 Quartz進行了封裝,在使用上更加方便。
9.3.1 使用 TimerTask 要定義一個排程任務(Task),可以繼承 java.util.TimerTask類別,例如:
TimerTaskDemo DemoTask.java
package onlyfun.caterpillar;
import java.util.TimerTask;
public class DemoTask extends TimerTask {
public void run() {
System.out.println("Task is executed.");
}
} 接著可以使用 Spring 的 org.springframework.scheduling.timer.
ScheduledTimerTask來定義任務的執行週期,例如:
Spring 2.0 技術手冊(林信良 – http://openhome.cc)
��22
TimerTaskDemo beans-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="demoTask" class="onlyfun.caterpillar.DemoTask"/>
<bean id="scheduledTimerTask"
class="org.springframework.scheduling.
→ timer.ScheduledTimerTask">
<property name="timerTask" ref="demoTask"/>
<property name="period" value="600000"/>
<property name="delay" value="10000"/>
</bean>
<bean id="timerFactoryBean"
class="org.springframework.scheduling.
→ timer.TimerFactoryBean">
<property name="scheduledTimerTasks">
<list>
<ref bean="scheduledTimerTask"/>
</list>
</property>
</bean>
</beans> 在 ScheduledTimerTask類別的 "period" 屬性中,定義的單位是毫秒,因此根據以上的定義,將每 10 分鐘(600000 毫秒)執行一次所定義的任務,而 "delay" 屬性定義了 Timer啟動後,第一次執行任務前要延遲多少毫秒。 定義好的 ScheduledTimerTask 要使用 org.springframework.sche-
duling.timer.TimerFactoryBean類別來加入所有的排程任務,接下來只要Spring容器啟動讀取完定義檔,就會開始進行所排定的任務,例如:
Chapter 9 API 封裝
��23
TimerTaskDemo TimerTaskDemo.java
package onlyfun.caterpillar;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Timer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.
support.ClassPathXmlApplicationContext;
public class TimerTaskDemo {
public static void main(String[] args) throws IOException {
ApplicationContext context =
new ClassPathXmlApplicationContext("beans-config.xml");
System.out.println("啟動 Task..");
System.out.println("請輸入 exit 關閉 Task: ");
BufferedReader reader =
new BufferedReader(
new InputStreamReader(System.in));
while(true) {
if(reader.readLine().equals("exit")) {
break;
}
}
Timer timer = (Timer) context.getBean("timerFactoryBean");
timer.cancel();
}
} 根據 Bean 定義檔的內容,這個程式在啟動後 10 秒會執行第一次任務,之後每 10分鐘執行一次任務。
Spring 2.0 技術手冊(林信良 – http://openhome.cc)
��24
9.3.2 使用 MethodInvokingTimerTaskFactoryBean 使用 Spring時,並不一定要繼承 TimerTask來定義一個任務,Spring提 供 org.springframework.scheduling.timer.MethodInvokingTimerTask-
FactoryBean,可以直接指定執行某個物件的方法,例如可以改寫一下TimerTaskDemo專案中的 DemoTask類別,這次不用繼承 TimerTask類別:
TimerTaskDemo2 DemoTask.java
package onlyfun.caterpillar;
public class DemoTask {
public void execute() {
System.out.println("Task is executed.");
}
} 接著只要在 Bean 定義檔中使用 MethodInvokingTimerTaskFactory-
Bean即可,例如:
TimerTaskDemo2 beans-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="demoTask" class="onlyfun.caterpillar.DemoTask"/>
<bean id="timerTaskBean"
class="org.springframework.scheduling.
→ timer.MethodInvokingTimerTaskFactoryBean">
<property name="targetObject" ref="demoTask"/>
<property name="targetMethod" value="execute"/>
</bean>
Chapter 9 API 封裝
��2�
<bean id="scheduledTimerTask"
Spring 2.0 技術手冊(林信良 – http://openhome.cc)
��26
class="org.springframework.scheduling.
→ timer.ScheduledTimerTask">
<property name="timerTask" ref="timerTaskBean"/>
<property name="period" value="5000"/>
<property name="delay" value="1000"/>
</bean>
<bean id="timerFactoryBean"
class="org.springframework.scheduling.
→ timer.TimerFactoryBean">
<property name="scheduledTimerTasks">
<list>
<ref bean="scheduledTimerTask"/>
</list>
</property>
</bean>
</beans> 執行時可以直接使用 TimerTaskDemo 專案中的 TimerTaskDemo 類別,在底層,MethodInvokingTimerTaskFactoryBean會自動建立 TimerTask的實例以執行目標物件上的指定方法。
9.3.3 使用 Quartz
JDK標準 API中所提供的 Timer功能有限,只能指定任務與任務之間的週期(Period),無法指定某個時間點來定時執行任務,您可以使用 Quartz(http://www.opensymphony.com/quartz/),它提供了更多的排程功能,而 Spring則對 Quartz進行了封裝,在使用上更加方便。 可以繼承 org.springframework.scheduling.quartz.QuartzJobBean來實作一個 Job類別,例如:
Chapter 9 API 封裝
��27
QuartzDemo DemoJob.java
package onlyfun.caterpillar;
import org.quartz.JobExecutionContext;
import org.springframework.scheduling.
quartz.QuartzJobBean;
public class DemoJob extends QuartzJobBean {
private JobData jobData;
public void executeInternal(
JobExecutionContext context) {
System.out.println(
jobData.getData() + " is executed.");
}
public void setJobData(JobData jobData) {
this.jobData = jobData;
}
public JobData getJobData() {
return jobData;
}
}
JobData只是一個 Job資料物件的示範類別,為了能看出排程 Job被執行時的週期性,它傳回一個 Date物件,表示執行 Job時所需資料的傳回時間,例如:
QuartzDemo JobData.java
package onlyfun.caterpillar;
import java.util.Date;
public class JobData {
public String getData() {
return "Data from "
+ new Date().toString();
}
}
Spring 2.0 技術手冊(林信良 – http://openhome.cc)
��28
直接來看定義檔如何定義:
QuartzDemo beans-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="someData" class="onlyfun.caterpillar.JobData"/>
<bean id="jobDetailBean"
class="org.springframework.scheduling.
→ quartz.JobDetailBean">
<property name="jobClass"
value="onlyfun.caterpillar.DemoJob"/>
<property name="jobDataAsMap">
<map>
<entry key="jobData" value-ref="someData"/>
</map>
</property>
</bean>
<bean id="simpleTriggerBean"
class="org.springframework.scheduling.
→ quartz.SimpleTriggerBean">
<property name="jobDetail" ref="jobDetailBean"/>
<property name="repeatInterval" value="5000"/>
<property name="startDelay" value="1000"/>
</bean>
<bean id="schedulerFactoryBean"
class="org.springframework.scheduling.
→ quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="simpleTriggerBean"/>
</list>
</property>
</bean>
</beans>
Chapter 9 API 封裝
��2�
在以上設定中特別要注意的是,org.springframework.scheduling.
quartz.JobDetailBean的 "jobClass" 屬性必須提供 Job的類別名稱,而不是 Job的 Bean實例,而 Job所需的資料可以在 "jobDataAsMap" 屬性中來提供。 在排程任務的週期指定上,使用 org.springframework.scheduling.
quartz.SimpleTriggerBean來指定,這點與 TimerTask排程中的指定方式類似,指定的時間同樣也是以毫秒作為單位,而排定 Job時,所使用的是org.springframework.scheduling.quartz.SchedulerFactoryBean。 完成設定之後,只要啟動 Spring並讀取定義檔後,排程任務就會進行,例如撰寫一個簡單的任務啟動類別:
QuartzDemo QuartzDemo.java
package onlyfun.caterpillar;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.
support.ClassPathXmlApplicationContext;
public class QuartzDemo {
public static void main(String[] args)
throws IOException, SchedulerException {
ApplicationContext context =
new ClassPathXmlApplicationContext("beans-config.xml");
System.out.println("啟動 Task..");
System.out.println("請輸入 exit 關閉 Task: ");
BufferedReader reader =
new BufferedReader(
new InputStreamReader(System.in));
Spring 2.0 技術手冊(林信良 – http://openhome.cc)
��3�
while(true) {
if(reader.readLine().equals("exit")) {
break;
}
}
Scheduler scheduler =
(Scheduler) context.getBean("schedulerFactoryBean");
scheduler.shutdown();
}
} 來看看這個程式執行時的一個參考畫面,當中可以看到每個任務的執行時間:
圖 9.6 QuartzDemo 專案的執行結果 使用 SimpleTriggerBean 只能作簡單的 Job 與 Job 之間執行的週期(Period)指定,如果要直接作時間點的指定,則可以使用 org.spring-
framework.scheduling.quartz.CronTriggerBean,例如:
QuartzDemo2 beans-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
Chapter 9 API 封裝
��31
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="someData" class="onlyfun.caterpillar.JobData"/>
<bean id="jobDetailBean"
class="org.springframework.scheduling.
→ quartz.JobDetailBean">
<property name="jobClass"
value="onlyfun.caterpillar.DemoJob"/>
<property name="jobDataAsMap">
<map>
<entry key="jobData" value-ref="someData"/>
</map>
</property>
</bean>
<bean id="cronTriggerBean"
class="org.springframework.scheduling.
→ quartz.CronTriggerBean">
<property name="jobDetail" ref="jobDetailBean"/>
<property name="cronExpression" value="0 0 19 * * ?"/>
</bean>
<bean id="schedulerFactoryBean"
class="org.springframework.scheduling.
→ quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="cronTriggerBean"/>
</list>
</property>
</bean>
</beans> 重點在於 "cronExpression" 屬性的指定,指定的格式是至少六個時間元素,最多七個時間元素,例如上面的指定是每天的 19時要執行 Job一次,"cronExpression" 屬性指定的格式如下:
Spring 2.0 技術手冊(林信良 – http://openhome.cc)
��32
� 秒(0-59)
� 分(0-59)
� 小時(0-23)
� 每月第幾天(1-31)
� 月(1-12 或 JAN-DEC)
� 每星期第幾天(1-7 或 SUN-SAT)
� 年(1970-2099) 其中「每月第幾天」與「每星期第幾天」是互斥的,兩個只能設定一個,不設定的以 ? 符號撰寫,如果有好幾個時間點,可以使用 , 符號,例如:「0 0 10,12,14 * * ?」表示每天的 10時、12時、14時要執行 Job;對於連續的時間可以使用 - 符號,例如「0 0 10,12,14 1-15 * ?」表示每月的1到 15日每 10時、12時、14時要執行 Job,時間格式中的年指定可有可無,例如:「0 0 10,12,14 ? * MON 2006」表示 2006年每星期一的 10時、12時、14時要執行 Job。
9.3.4 使用 MethodInvokingJobDetailFactoryBean 如果使用 Spring的話,並不一定要繼承 QuartzJobBean類別來定義一個 Job 類別, Spring 提供 org.springframework.scheduling.quartz.
MethodInvokingJobDetailFactoryBean,可以直接指定執行某個物件的方法,例如改寫一下 QuartzDemo專案中的 DemoJob類別:
QuartzDemo3 DemoJob.java
package onlyfun.caterpillar;
public class DemoJob {
private JobData jobData;
public void execute() {
System.out.println(
Chapter 9 API 封裝
��33
jobData.getData() + " is executed.");
}
public void setJobData(JobData jobData) {
this.jobData = jobData;
}
public JobData getJobData() {
return jobData;
}
} 接著只要在 Bean定義檔中使用MethodInvokingJobDetailFactoryBean即可,例如改寫一下 QuartzDemo專案中的 beans-config.xml:
QuartzDemo3 beans-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="someData" class="onlyfun.caterpillar.JobData"/>
<bean id="someJob"
class="onlyfun.caterpillar.DemoJob">
<property name="jobData" ref="someData"/>
</bean>
<bean id="jobDetailBean"
class="org.springframework.scheduling.
→ quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="someJob"/>
<property name="targetMethod" value="execute"/>
</bean>
<bean id="cronTriggerBean"
class="org.springframework.scheduling.
→ quartz.CronTriggerBean">
<property name="jobDetail" ref="jobDetailBean"/>
Spring 2.0 技術手冊(林信良 – http://openhome.cc)
��34
<property name="cronExpression" value="0 30 18 * * ?"/>
</bean>
<bean id="schedulerFactoryBean"
class="org.springframework.scheduling.
→ quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="cronTriggerBean"/>
</list>
</property>
</bean>
</beans> 在上面的指定中,每天的 18時 30分都會執行指定的任務,在底層,MethodInvokingJobDetailFactoryBean會自動建立 JobDetail的實例,以執行目標物件的指定方法。
Chapter 9 API 封裝
��3�
9.4 接下來的主題 到這個章節為止,本書對於 Spring個別功能的介紹先告一段落,事實上關於 Spring的功能還有許多,唯篇幅與個人所涉獵的範圍有限,無法每一個都詳加介紹,建議您可以參考 Spring所提供的參考文件,有了本書的基礎加上參考文件的對照,相信對於 Spring的其它功能都可以很快上手。
圖 9.7 體驗 Spring 的開發快感