本章向读者展示了在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
谭明 阅读(492)
评论(0) 编辑 收藏 所属分类:
Spring