本章向讀者展示了在Spring中如何集成其他企業服務,讀者將了解到使用Spring集成郵件服務、JMS甚至EJB都是那么的容易。
Spring并沒有對一些企業服務提供直接的支持。它依賴其他API來提供有關服務,但對這些服務通過相應的抽象層進行了封裝,因此使用起來更為方便。
一、從JNDI中獲取對象
JNDI為Java應用程序提供了一個用于存儲應用對象的中心倉庫。
Spring的JNDI抽象使你可以在應用的配置文件中聲明JNDI對象。然后你就可以將這些對象裝配到其他Bean的屬性中,就如同JNDI對象與其他的POJO一樣。
1.使用傳統的JNDI
使用傳統的JNDI API,你編寫的代碼也許和下面的看上去差不多:
InitialContext ctx = null;
try {
ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/myDatasource");
} catch (NamingException ne) {
//exception
} finally {
if (ctx != null) {
try {
ctx.close();
} catch (NamingException ne) {}
}
}
這段代碼有點笨拙,總的說來,這種從JNDI中獲取對象的傳統方式的問題都來源于違背了依賴注入的原則。不是賦給你的代碼一個對象,而是你的代碼必須親自去獲取這個對象。這意味著你的代碼所做的不是真正的工作。這也意味著你的代碼不必要地與JNDI耦合了。
2.代理JNDI對象
Spring的JndiObjectFactoryBean允許你同時得到這兩個不同領域中的好處。它是一個工廠Bean,這意味著當把它裝配到一個屬性上時,實際上它會創建一個其他類型的對象供裝配時使用。對于JndiObjectFactoryBean,它實際裝配的是一個從JNDI獲取的對象。
當Spring裝配sessionFactory Bean時,它會把從JNDI中獲取的DataSource對象注入到會話工廠Bean的dataSource屬性中。使用JndiObjectFactoryBean從JNDI中查找對象的最大優勢在于惟一知道DataSource是從JNDI中獲取的代碼就是那段dataSource Bean的XML聲明。sessionFactory Bean不知道(也不關心)DataSource來自哪里。這意味著如果以后你決定改從一個JDBC驅動管理器獲取DataSource,只需要重新定義dataSource Bean為一個DriverManagerDataSource即可。
二、發送電子郵件
Spring中的郵件發送器由Spring的MailSender接口定義。郵件發送器抽象了某個特定的郵件實現。這樣就使應用代碼和實際使用的郵件實現之間沒有耦合。Spring提供了這個接口的兩個實現:
? CosMailSenderImpl——以Jason Hunter的Java Servlet Programming一書(O’Reilly,1998)中的COS(com.oreilly.servlet)實現為基礎的SMTP郵件發送器的簡單實現。
? JavaMailSenderImpl——一個基于JavaMail API的郵件發送器實現。允許發送MIME郵件以及非SMTP郵件(比如Lotus Notes)。
采用哪個都將完成發送的工作,但是我們將選擇JavaMailSenderImpl,因為在兩者間它的功能更全面。你可以在Spring配置文件中按以下方式聲明它:
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
<property name="host">
<value>mail.springtraining.com</value>
</property>
</bean>
屬性host指定了郵件服務器的主機名,在這里是Spring培訓應用的SMTP服務器。在默認情況下,郵件發送器假設SMTP服務器監聽25端口(標準的SMTP端口),但如果你的SMTP服務器監聽在不同的端口上,可以使用JavaMailSenderImpl的port屬性來指定端口。
上面的mailSender聲明中顯式地命名了用于發送郵件的郵件服務器。然而,如果你有一個位于JNDI中的javax.mail.MailSession對象(可能位于你的應用服務器中),則也可以選擇從JNDI中獲取它。只需簡單地使用JndiObjectFactoryBean(如第7.1節中描述的)來獲取郵件會話對象,并按照下面的方式將它裝配到mailSender的mailSession屬性中即可:
<bean id="mailSession" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>java:comp/env/mail/Session</value>
</property>
</bean>
<bean id="mailSender" class="org.springrframework.mail.javamail.JavaMailSenderImpl">
<property name="session"><ref bean="mailSession"/></property>
</bean>
三、調度任務
Java的Timer類和OpenSymphony的Quartz調度器是兩個流行的調度API。Spring為這兩個調度器提供了一個抽象層,使你可以更容易地使用它們。
1.使用Java Timer調度任務
從Java 1.3開始,Java SDK就通過java.util.Timer類提供了基本的調度功能。這個類允許你調度一個任務(通過java.util.TimerTask子類定義)按任意周期運行。
創建一個定時器任務
使用Java Timer來調度發送注冊報表郵件的第一步是從java.util.TimerTask中派生出郵件任務:
publicclass EmailReportTask extends TimerTask {
public EmailReportTask() {}
publicvoid run() {
courseService.sendCourseEnrollmentReport();
}
private CourseService courseService;
publicvoid setCourseService(CourseService courseService) {
this.courseService = courseService;
}
}
run()方法定義了當任務運行時該做什么。在上面的例子中,它調用CourseService的sendCourseEnrollmentReport()方法來發送注冊報表郵件。CourseService是通過依賴注入方式提供給EmailReportTask的。
按以下方式在Spring配置文件中聲明EmailReportTask:
<bean id="reportTimerTask" class="com.springinaction.training.schedule.EmailReportTask">
<property name="courseService">
<ref bean="courseService"/>
</property>
</bean>
這個聲明本身只是將EmailReportTask放到應用上下文中,并在courseService屬性中裝配courseService Bean。在你調度它之前,它不會做任何有用的事。
調度定時器任務
Spring的ScheduledTimerTask定義了一個定時器任務的運行周期。既然課程主任要求每天向她發送注冊報表,你應該以如下方式裝配一個ScheduledTimerTask:
<bean id="scheduledReportTask" class="org.springframework.scheduling.timer.ScheduledTimerTask">
<property name="timerTask">
<ref bean="reportTimerTask"/>
</property>
<property name="period">
<value>86400000</value>
</property>
</bean>
屬性timerTask告訴ScheduledTimerTask運行哪個TimerTask。在這里,該屬性裝配了指向reportTimerTask的一個引用,它就是EmailReportTask。屬性period告訴ScheduledTimerTask以怎樣的頻度調用TimerTask的run()方法。這個屬性以毫秒作為單位,它被設置為86400000,指定這個任務應該每24小時運行一次。
啟動定時器
最后一步是啟動定時器。Spring的TimerFactoryBean負責啟動定時任務。按以下方式在Spring配置文件中聲明它:
<bean class="org.springframework.scheduling.timer.TimerFactoryBean">
<property name="scheduledTimerTasks">
<list>
<ref bean="scheduledReportTask"/>
</list>
</property>
</bean>
屬性scheduledTimerTasks要求一個需要啟動的定時器任務的列表。既然你現在只有一個定時器任務,這個列表中只包含一個指向scheduledReportTask Bean的引用。
遺憾的是,即使這個任務已經能夠每隔24小時運行一次了,在這里你無法指定它應該在一天中的哪個時間點執行。ScheduledTimerTask有一個delay屬性,允許你指定當任務第一次運行之前應該等待多久。例如,要將EmailReportTask的第一次運行延遲1小時,可以按照以下方式進行配置:
<bean id="scheduledReportTask" class="org.springframework.scheduling.timer.ScheduledTimerTask">
<property name="timerTask">
<ref bean="reportTimerTask"/>
</property>
<property name="period">
<value>86400000</value>
</property>
<property name="delay">
<value>3600000</value>
</property>
</bean>
2.使用Quartz調度器
Quartz調度器為調度工作提供了更豐富的支持。和Java定時器一樣,可以使用Quartz來每隔多少毫秒執行一個工作。但Quartz比Java Timer更先進之處在于它允許你調度一個工作在某個特定的時間或日期執行。
關于Quartz的更多信息,可以訪問Quartz位于http://www.opensymphony.com/quartz的主頁。
3.按調度計劃調用方法
Spring提供了MethodInvokingTimerTaskFactoryBean和MethodInvokingJobDetailFactoryBean,可以分別使用Java的定時器支持或Quartz調度器對方法調用進行調度。
四、使用JMS發送消息
Spring提供了JMS的一個抽象層,使得訪問一個消息隊列或主題(抽象地稱為一個目標)并向目標發布消息成為一件簡單的事。而且,Spring以非檢查的org.springframework.jms. JmsException的形式重新拋出JMS異常,使你的應用不必處理javax.jms.JMSException。
1.使用JMS模板發送消息
當對支付進行授權時,必須等待信用卡處理器的響應,因為你需要知道信用卡的發卡行是否授權進行支付。但當已經擁有恰當的授權之后,支付的結算可以以異步的方式進行。在這種情況下,不需要等待響應——你可以安全地假設支付將會被結算。
信用卡處理系統接受一個通過JMS發送的異步消息用于支付的結算。它接受的消息是一個javax.jms.MapMessage,其中包含了以下字段:
? authCode——從信息卡處理器得到的授權碼;
? creditCardNumber——信用卡號;
? customerName——信用卡持有人的姓名;
? expirationMonth——信用卡過期的月份;
? expirationYear——信用卡過期的年份。
Spring采用回調方式處理JMS消息。這種回調方式讓人回想起第4章中描述的JDBC回調機制。回調機制由兩部分組成:一個消息創建器負責構造一個JMS消息(javax.jms.Message)和一個真正發送消息的JMS模板。
2.消費消息
現在假設你在編寫結算過程的接收端代碼。你需要接收消息,將它轉換成一個PaySettlement對象,然后把它傳遞到處理過程中。幸運的是,JmsTemplate既可以用于發送消息,也可以用于接收消息。
JmsTemplate的receive()方法嘗試從指定的目標接受一個消息。根據之前在Spring配置文件中的聲明,receive()方法會試圖從一個JNDI名字為creditCardQueue的目標中接收一個消息。
一旦接收到消息,它會將它強制轉換成一個MapMessage,并使用MapMessage中字段的值來初始化一個PaySettlement對象。
在默認情況下,receive()方法會無限期地等待消息。然而,你可能不希望讓你的應用在等待接收消息時無限期地阻塞。最好能夠設置一個超時間隔,使receive()方法在等待一定時間后放棄。幸運的是,可以通過設置jmsTemplate Bean的receiveTimeout屬性來指定這個超時。例如:
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="receiveTimeout">
<value>10000</value>
</property>
</bean>
屬性receiveTimeout以一個消息等待時間的毫秒數作為參數。設置它為10000,這規定了receive()方法會在10秒后放棄。如果在10秒中內沒有收到任何消息,JmsTemplate會拋出一個非檢查的JmsException。
3.轉換消息
轉換PaySettlement消息
盡管你可以編寫自己的工具對象來處理消息轉換,Spring的org.springframework.jms.support. converter.MessageConverter接口定義了一個公共的機制在JMS Message對象和對象間進行相互轉換。
為了演示這一點,PaySettlementConverter實現了MessageConverter,使得在PaySettlement對象與JMS Message對象間的相互轉換更方便。
裝配一個消息轉換器
要使用消息轉換器,首先必須在Spring配置文件中將它作為一個Bean加以聲明:
<bean id="settlementConverter" class="com.springinaction.training.service.PaySettlementConverter">
…
</bean>
接著需要讓JmsTemplate知道消息轉換器。你是通過將PaySettlementConverter裝配到JmsTemplate的messageConverter屬性中讓JmsTemplate知道它的:
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
…
<property name="messageConverter">
<ref bean="settlementConverter"/>
</property>
</bean>
使用SimpleMessageConverter
Spring提供了一個現成的MessageConverter接口的實現。SimpleMessageConverter在MapMessage、TextMessage以及ByteMessage和java.util.Map集合對象、String以及byte數組之間分別進行相互轉換。
五、小結
盡管Spring提供的功能免去了大多數的EJB使用需求,仍有很多企業服務在Spring中沒有直接的替代實現。在那些情況下,Spring提供了各種抽象層,使你能夠容易地將這些企業服務裝配到你的Spring應用程序中。
在本章中,你已經看到如何取得保存在JNDI中的對象的引用。這些引用可以像本地定義的Bean一樣裝配到Bean的屬性中。本章中處處體現了這種方式的實用性,比如使用Spring的JNDI抽象層來查詢諸如郵件會話和JMS連接工廠等等JNDI對象。
你也看到如何通過Spring的電子郵件抽象層來發送電子郵件,并使用Java Timer或者OpenSymphony的Quartz調度器來調度任務。
最后,你看到如何通過Spring的JMS抽象層來發送和接收異步消息。
毫無疑問,Spring所提供的功能是非常強大的。本章的知識,在我目前的工作中依然用不上,向上一章一樣,做些簡單的摘抄以后用到再回頭好好看看。
posted on 2007-10-23 21:52
譚明 閱讀(502)
評論(0) 編輯 收藏 所屬分類:
Spring