spring 2.0 技術手冊第九章 - api 封裝

35
API 封裝 API Spring API Spring 9

Upload: justin-lin

Post on 18-Jul-2015

1.298 views

Category:

Technology


9 download

TRANSCRIPT

Page 1: Spring 2.0 技術手冊第九章 - API 封裝

API封裝

對於一些企業服務 API,Spring 不提供直接的解決方案,而是提供一層抽象封裝,在這樣的封裝下,提供了簡化且一致的方式,讓您在使用一些 API 或元件服務時更加簡單。 這個章節中將介紹遠程服務、郵件服務與排程服務等在 Spring 中的使用方式。

9

Page 2: Spring 2.0 技術手冊第九章 - API 封裝

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

}

Page 3: Spring 2.0 技術手冊第九章 - API 封裝

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"/>

Page 4: Spring 2.0 技術手冊第九章 - API 封裝

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;

}

}

Page 5: Spring 2.0 技術手冊第九章 - API 封裝

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>

Page 6: Spring 2.0 技術手冊第九章 - API 封裝

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 專案的執行結果

Page 7: Spring 2.0 技術手冊第九章 - API 封裝

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>

Page 8: Spring 2.0 技術手冊第九章 - API 封裝

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"

Page 9: Spring 2.0 技術手冊第九章 - API 封裝

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/

Page 10: Spring 2.0 技術手冊第九章 - API 封裝

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.

Page 11: Spring 2.0 技術手冊第九章 - API 封裝

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>

Page 12: Spring 2.0 技術手冊第九章 - API 封裝

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 =

Page 13: Spring 2.0 技術手冊第九章 - API 封裝

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的畫面。

Page 14: Spring 2.0 技術手冊第九章 - API 封裝

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!!!");

Page 15: Spring 2.0 技術手冊第九章 - API 封裝

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;

Page 16: Spring 2.0 技術手冊第九章 - API 封裝

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 專案的執行結果

Page 17: Spring 2.0 技術手冊第九章 - API 封裝

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

Page 18: Spring 2.0 技術手冊第九章 - API 封裝

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 專案的執行結果 直接將影像檔案加入郵件中有好有壞,好處是不用像給定連結的方式,必須在伺服器上提供檔案,以讓使用者讀取郵件時可以取得所連結的圖片,壞處是有的郵件客戶端對於內嵌圖片的支援會有問題,可能在某些客戶端軟體中會發生無法顯示圖片的情況。

Page 19: Spring 2.0 技術手冊第九章 - API 封裝

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

Page 20: Spring 2.0 技術手冊第九章 - API 封裝

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

��2�

System.out.println("郵件傳送成功...");

}

} 來看一下傳送後的郵件畫面:

圖 9.5 AttachedFileDemo 專案的執行結果

Page 21: Spring 2.0 技術手冊第九章 - API 封裝

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來定義任務的執行週期,例如:

Page 22: Spring 2.0 技術手冊第九章 - API 封裝

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容器啟動讀取完定義檔,就會開始進行所排定的任務,例如:

Page 23: Spring 2.0 技術手冊第九章 - API 封裝

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分鐘執行一次任務。

Page 24: Spring 2.0 技術手冊第九章 - API 封裝

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>

Page 25: Spring 2.0 技術手冊第九章 - API 封裝

Chapter 9 API 封裝

��2�

<bean id="scheduledTimerTask"

Page 26: Spring 2.0 技術手冊第九章 - API 封裝

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類別,例如:

Page 27: Spring 2.0 技術手冊第九章 - API 封裝

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();

}

}

Page 28: Spring 2.0 技術手冊第九章 - API 封裝

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>

Page 29: Spring 2.0 技術手冊第九章 - API 封裝

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

Page 30: Spring 2.0 技術手冊第九章 - API 封裝

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

Page 31: Spring 2.0 技術手冊第九章 - API 封裝

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" 屬性指定的格式如下:

Page 32: Spring 2.0 技術手冊第九章 - API 封裝

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(

Page 33: Spring 2.0 技術手冊第九章 - API 封裝

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"/>

Page 34: Spring 2.0 技術手冊第九章 - API 封裝

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的實例,以執行目標物件的指定方法。

Page 35: Spring 2.0 技術手冊第九章 - API 封裝

Chapter 9 API 封裝

��3�

9.4 接下來的主題 到這個章節為止,本書對於 Spring個別功能的介紹先告一段落,事實上關於 Spring的功能還有許多,唯篇幅與個人所涉獵的範圍有限,無法每一個都詳加介紹,建議您可以參考 Spring所提供的參考文件,有了本書的基礎加上參考文件的對照,相信對於 Spring的其它功能都可以很快上手。

圖 9.7 體驗 Spring 的開發快感