如果你已经读到了这里,我完全能够理解你要实现第一个基于SpringMVC的Web应用程序的迫切心情,毕竟,一味地纸上谈兵令人腻烦,那么让我们开始一个我称之为SimpleFx的Web应用程序的开发吧!
一个外汇交易系统与其他的软件系统在本质上没有任何差别,为了能够便于一些数据的管理,我们也需要一个用于后台管理的应用程序。既然是讲解SpringMVC,我们打算以Web应用程序的形式来开发后台管理功能,这个用于后台管理的Web应用程序我们暂且就称其为SimpleFX。因为不打算完全实现它所有的功能,只要能够通过几个功能的开发阐明SpringMVC的相应特点即可。
整个后台管理程序的显示布局如图23-4所示。
其中,左边的各个FunctionSet表示相应的管理功能集合,我们可以通过点击这些FunctionSet的链接来触发相应的管理请求,剩下的部分为主画面显示,一看便知。
本次我们要开发的是一个简单的显示外汇交易当天评价汇率的Web请求处理流程。在点击相应的链接之后,我们可以获取当天评价汇率的显示信息,类似于图23-5所示的主画面显示。
Spring MVC应用的物理结构虽然我们已经从总体上了解了SpringMVC框架在处理Web请求过程中所涉及的几个主要成员,但目前为止,它们只是逻辑上的概念,对于我们来说,只是一座座的空中楼阁。我们现在得面对现实,从实体结构上看一下,一个典型的基于SpringMVC的Web应用程序,通常是如何组装而成的(听起来好像我们在造一辆汽车的感觉),然后再真正地开始着手构建我们的第一个基于SpringMVC的Web应用程序。
一个基于SpringMVC框架的Web应用,依然是一个遵循Servlet规范的Web应用程序,自然,它也就拥有一个遵循Servlet规范的Web应用程序应有的目录结构和相应的部署描述符(Deployment Descriptor)文件。只不过,在此基础上,基于SpringMVC框架的Web应用会再增加两个(或者多个)配置文件,整个图景如图23-6所示。
我们以web.xml作为出发点,它是整个Web应用程序的部署描述符文件,这是所有基于Servlet规范的Web应用程序都有的东西,我想大家对它应该并不陌生。在图23-6右边的就是web.xml的具体内容。
当然,在这个部署描述符文件中,我们还可以根据需要添加其他元素,比如可能需要进行字符编码的转换,那会增加一个Filter的定义,或者添加welcome文件列表以及异常处理页面相关内容等。不过为了避免混淆视听,我们这里仅挑了与SpringMVC有关的元素进行重点阐述。
1.ContextLoaderListener与WEB-INF/applicationContext.xml从web.xml开始,我们首先通过
ContextLoaderListener的职责在于,它将为整个的Web应用程序加载顶层的WebApplicationContext(ROOT WebApplicationContext)。该顶层WebApplicationContext主要用于提供应用所使用的中间层服务。
我们所使用的数据源(DataSource)定义、数据访问对象(DAO)定义、服务对象(Services)定义等,都在该WebApplicationContext中注册。你完全可以将其比作独立运行的应用程序中我们所使用的ClassPathXmlApplicationContext或者FileSystemXmlApp1icationContext。
只不过,WebApplicationContext专门用于Web环境下,在这种容器中,我们可以使用在讲解Spring的IoC容器部分提到的自定义scope来注册相应的bean定义了,包括request、session等。
ContextLoaderListener加载的webApplicationContext的默认配置文件路径为WEB-INF/applicationContext.xml,这也就是图23-6中的/WEB-INF/目录下存在一个applicationContext.xml配置文件的原因。该文件符合通用的Spring IoC容器配置文件格式。
实际开发中,不管是出于团队并行开发效率的考虑,还是出于便于管理的因素考虑,很少会使用默认的单一的/WEB-INF/applicationContext.xml来管理整个Web应用程序的所有中间层服务对象。我们要么会按照应用程序的层次进行配置文件的分割,要么会按照系统功能模块进行配置文件的分割。当存在多个分割后的配置文件的时候,ContextLoaderListener的默认加载行为将成为我们的制约。这时,我们可以通过在web.xml中指定名称为contextConfigLocation的配置参数来打破默认行为的制约,如下方代码所示。
contextConfigLocation /WEB-TNF/applicationContext.xml,/WEB-INF/applicationContextmodule1.xml org.springframework.Web.context.ContextLoaderListener 1istener>
我们可以在
ContextLoaderListener或者ContextLoaderServlet加载相应路径下的容器配置文件,并在构建完成相应的顶层WebApplicationContext后,将该顶层WebApplicationContext绑定到ServletContext。
如果想获取绑定到ServletContext的WebApplicationContext,我们可以通过org.springframework.web.context.support.WebApplicationContextUtils类。这样就不需要知道顶层WebApplicationContext绑定到ServletContext的时候使用的Key是什么,例如:
WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
或者
WebApplicationContext wac = WebApplicationContextUtils.getRequiredApplicationContext(getServletContext());
wac.getBean("serviceBeanName");
了解这一点的主要目的在于,绑定到ServletContext的顶层webApplicationContext并非只能在基于SpringMVC的Web应用程序中使用。通过ContextLoaderListener或者ContextLoaderServlet将其绑定到ServletContext,任何类型的Web应用程序(当然指的是基于Java平台技术的Web应用程序),只要能够获取ServletContext的引用,就能获取并使用该WebApplicationContext。
换句话说就是,即使当前使用的Web开发框架没有类似于Spring提供的基于IoC容器的中间层业务对象管理支持,通过在web.xml中注册ContextLoaderListener或者ContextLoaderServlet,也可以获取这种支持。所谓的集成Spring与第三方Web应用框架,最多只是在此基础上做进一步的扩展。
2. DispatcherServlet与XXX-servlet.xml对于web.xml中org.springframework.Web.servlet.DispatcherServlet的注册,表面看起来并无太多玄机,与注册其他Servlet并无二致,但事实并非如此。
我们说过,DispatcherServlet是基于SpringMVC框架Web应用程序的FrontController,它将负责几乎所有对应当前Web应用程序的Web请求的处理。DispatcherServlet使用了一个外部化的配置文件,用来配置SpringMVC框架在处理Web请求过程中所涉及的各个组件,包括HandlerMapping定义、Controller定义、ViewResolver定义等。
该外部化的配置文件存在的默认路径也是/WEB-INF/,名称需要参照web.xml中定义的DispatcherServlet的
如果我们定义DispatcherServlet的
只不过,相对于/WEB-INF/applicationContext.xml来说,
... ...
DispatcherServlet启动之后将加载
这样,如果需要,
即使Web层通常很薄,但随着Web应用程序的开发进程,单一的
controller org.springframework.Web.servlet.DispatcherServ1et contextConfigLocatlon /WEB-INF/controller-servlet.xml,/WEB-INF/module1-servlet.xml,.. 2 controller *.do
我们可以指定多个配置文件用逗号或空格来分隔。更多定制DispatcherServlet的信息,可以参照Spring参考文档或者相应类的Javadoc。
现在,你已经对基于SpringMVC的Web应用程序都由哪些物理实体组成,以及如何定制它们有了一定的认识,那么,热身活动结束!
按部就班地开始工作我们要实现的当日评价汇率显示流程可以简单描述,如图23-8所示。
从浏览器中点击后台管理画面的相应链接之后,Web请求将被发送到DispatcherServlet进行处理。DispatcherServlet将寻求相应的HandlerMapping对Web请求进行分析,然后调用匹配结果对应的Controller实现,具体到当前场景就是我们要实现的TTMRateListController。
TTMRateListController处理完毕将视图名称ttmRateList和模型数据一同返回,然后DispatcherServlet则借助于相应的ViewResolver,根据返回的视图名选择相应的视图(tmRateList.jsp)并显示,这就是整个流程。
下面则是实现这个Web处理流程的详细步骤,各位上眼瞧啦!
当日评价汇率Web处理流程实现步骤概况如下。
(1)配置基础设置每个符合JavaEE规范的Web应用程序都需要符合相应的目录结构,如图23-6所示。工作之初,我们需要构建Web应用的基础结构。不过,现在的IDE通常都有良好的Web开发支持,使用EclipseIDE的话,添加WTP(Web Tools Platform)或者MyEclipse插件的支持即可。剩下的只是按照Wizard的说明构建一个Web应用的工程就行了,我们这里要建立的是simplefx工程。simplefx工程建立之后,我们还有如下两件事情要做。
a)配置web.xml我们需要将org.springframework.Web.servlet.DispatcherServlet和org.springframework.Web.context.ContextLoaderListener通过
另外,出于其他目的考虑,我们还可以添加相应的Filter以及ServletContextListener以处理字符编码和Log4j初始化等配置内容,这些完全根据当前应用程序的需求情况来决定。现在,我们的web.xml看起来如下方代码所示。
simplefx encodingFilter org.springframework.Web.filter.CharacterEncodingFilter encoding UTF-8 encodingFilter controller org.springframework.web.util.Log4jConfigListener org.springframework.Web.context.ContextLoaderListener controller org.springframework.Web,servlet.DispatcherServlet 2 controller *.do index.jsp
其他配置元素可能随着内容的深入继续添加。
b)WebApplicationContext文件的添加我们需要在/WEB-INF/目录下添加org.springframe-work.Web.servlet.DispatcherServlet和org.springframework.Web.context.ContextLoaderListener对应的WebApplicationContext配置文件(/WEB-INF/applicationContext.xml和/WEB-INF/[servletname]-servlet.xml)。
这完全可以从Spring下载文件包中的sample目录下任何一个Web应用程序中复制过来。只不过,需要根据命名规则修改相应的文件名称。当然,复制过来的文件内容最好是清空,只保留可以在当前应用中能够通用的配置内容。对于我们的simplefx来说,我们暂且清空对应的两个WebApplicationContext配置文件内容。以上基础设施构建完成之后,我们开始“盖楼”。
(2)开发独立的业务逻辑对于一个设计良好的Web应用程序来说,虽然Web层依赖于业务层对象,但业务层却不应该对Web层有任何的依赖。Web层只应该看作是公开业务逻辑的一种视角或者交互方式。这样做或者说这样看待系统的好处在于,业务层可以独立设计并实现,而不需要关心最终通过什么手段将服务公开给用户。
鉴于这样的理念,我们完全可以从业务层开始着手设计和实现我们的当日评价汇率显示逻辑,而且在稍后介绍Spring Remoting支持的时候,你将会更深地感受到这种看待系统的视角所带给我们的系统设计与实现的福利。
既然涉及评价汇率,那么首先需要设计并实现的对象即对应评价汇率的域对象(Domain Object),其简单定义如下方代码所示。
public class TTMRate implements Serializable {
private static final long serialVersionUID = 2641189625594925925L;
private TradeDate frontDate;
private String currencyPair;
private BigDecimal value;
public TTMRate(TradeDate frontDate, String currencyPair, BigDecimal value) {
this.frontDate = frontDate;
this.currencyPair = currencyPair;
this.value = value;
}
public TradeDate getFrontDate() {
return frontDate;
}
public void setFrontDate(TradeDate frontDate) {
this.frontDate = frontDate;
}
public String getCurrencyPair() {
return currencyPair;
}
public void setCurrencyPair(String currencyPair) {
this.currencyPair = currencyPair;
}
public BigDecimal getValue() {
return value;
}
public void setValue(BigDecimal value) {
this.value = value;
}
//其他方法定义,比如toString{)等
}
每个营业日内对应系统中可交易的货币对,都存在一个评价汇率,所以,TTMRate中定义了必需的三个字段分别对应营业日、货币对和对应的评价汇率值。
为了能够使各种客户端(当然包括Web客户端)能够获取当日的评价汇率,我们需要规定相应的接口并公开给相应的客户端使用。现在这个接口定义很简单,定义如下:
public interface ITTMRateService {
ListgetTTMRatesToday();
}
只要客户端调用getTTMRatesToday()方法,就将返回当前营业日内,系统所支持的所有货币对对应的评价汇率。单有接口并不能工作,我们还需要给出相应的实现。
正常情况下,我们需要结合相应的数据访问对象构建一个ITTMRateService的实现,以便能够从系统数据库中获取真正生产环境下的数据。不过,我不打算这么做。出于简化实例考虑,我们现在只实现一个ITTMRateService的Mock对象。定义如下:
public class MockTTMRateService implements ITTMRateService {
public List getTTMRatesToday() {
TradeDate tradeDate20080302 = TradeDate.valueOf("20080302");
TTMRate USD_JPY = new TTMRate(tradeDate20080302, "USD/JPY",
new BigDecimal("121.53"));
TTMRate EUR_USD = new TTMRate(tradeDate20080302, " EUR/USD",
new BigDecimal("1.8950"));
List rateList = new ArrayList();
ratelist.add(USD_JPY);
ratelist.add(EUR_USD);
return ratelist;
}
}
我们假设当天营业日对应20080302,并将对应该日期的USD/JPY和EUR/USD两货币对的评价汇率信息添加到返回列表中。
整个Web应用的中间层服务支持,默认都是通过/WEB-INF/applicationContext.xml注册(即ROOT WebApplicationContext)。所以,现在我们要将ITTMRateService的实现增加到Web应用程序项层容器的配置文件中,如下方代码所示。
真实环境下,我们可能还需要向该容器配置文件中添加数据源(DataSource)等一系列依赖对象。
在此之后,我们才真正开始Web层的开发。
(3)添加Web请求入口要让用户能够通过Web方式访问我们的评价汇率服务,我们得为用户提供一个入口点。首先在后台管理的主画面左侧的FunctionSet中添加超链接,如下所示:
">当日评价汇率
在用户点击该链接之后,浏览器将以http://host:port/simplefx/ttmRateList.do的形式向服务器发起Web请求。我们在web.xml中将所有以.do结尾的请求模式都映射给了DispatcherServlet来处理。接下来的事情就由DispatcherServlet接手了。
(4)添加HandlerMappingDispatcherServlet在接收到Web请求之后,将寻求相应的HandlerMapping进行Web请求到具体的Controller实现的匹配。所以,我们需要为DispatcherServlet提供一个HandlerMapping的实现。
SpringMVC框架默认提供了多个HandlerMapping的实现。我们将在稍后详细介绍。不过,当前我们暂且使用org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,它将根据URL与Controller的bean定义的名称进行匹配。
现在我们将它添加到DispatcherServlet特定的WebApplicationContext中,即/WEB-INF/controller-servlet.xml,如下所示:
实际上,如果没有配置任何HandlerMapping的话,SpringMVC也会默认使用BeanNameUrlHandlerMapping进行URL到具体Controller的匹配。所以,以上的bean定义并非必须的。
但是,这样做的目的是要明确告知你,Dispatcherservlet所使用的HandlerMapping要在哪里进行配置。稍后你还将看到,如果默认的HandlerMapping的匹配模式并不讨人喜欢,我们还可以替换掉它,转而使用自己喜欢的HandlerMapping实现。
总之,我们使用BeanNameUrlHandlerMapping进行URL到具体Controller的匹配之后,BeanNameUrlHandlerMapping将根据http://host:port/simplef/ttmRateList.do这一URL信息,在当前容器内寻找名称为/ttmRateList.do的Controller定义。这将是我们下一步要实现的东西。
(5)实现对应的Controller并添加到配置文件针对当前评价汇率的Web请求,我们需要实现一个Controller来处理它。通常情况下,我们会扩展SpringMVC的org.springframework.Web.servlet.mvc.AbstractController来实现具体的Controller。
正如我们的处理流程图(图23-8)所演示的那样,我们的Controller名称为TTMRateListController,具体定义如下方代码所示。
public class TIMRateListController extends AbstractController {
private ITTMRateService ttmRateService;
private String viewName;
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request,
HttpServletResponse response) throws Exception {
List ttmRateList = getTtmRateService().getTTMRatesToday()
ModelAndView mav = new ModelAndView(getVi ewName());
mav.addobject(" ttmRates", ttmRatelist);
return mav;
}
public ITTMRateService getTtmRateService() {
return ttmRateService;
}
public void setTtmRateService(ITTMRateService t tmRateService) {
this.ttmRateService = ttmRateService;
}
public String getViewName() {
return viewName;
}
public void setViewName(String viewName) {
this.viewName = viewName;
}
}
在TTMRateListController中,我们引用了ITTMRateService并借助它获取了当日的评价汇率列表。
viewName是将要转向的逻辑视图名称,可以将它写死到代码。不过,通过WebApplicationContext的依赖注入,我们可以获取更多的灵活度。在视图名称和要显示的模型数据全都获取之后,我们构造了一个ModelAndView来封装这两部分信息,并返回。
只是实现了TTMRateListController,DispatcherServlet并不会知道它的存在,也不会知道如何引用到它,所以,我们还得将TTMRateListController添加到DispatcherServlet特定的WebApplicationContext中。现在,/WEB-INF/controller-servlet.xml看起来更加充实了,如下所示。
这里需要注意的是,TTMRateListController的ttmRateService注入的依赖来自org.springframework.Web.context.ContextLoaderListener加载的顶层WebApplicationContext。
我们说过,DispatcherServlet特定的WebApplicationContext以顶层WebApplicationContext作为父容器。另外,BeanNameUrlHandlerMapping要求相应的Controller的bean定义名称以/开头,所以,我们得使用
TTMRateListController通过ModelAndView返回了一个逻辑视图名ttmRateList,而DispatcherServlet要根据这个逻辑视图名查找相应的视图实现,就需要一个ViewResolver来帮它。
目前我们还不能在DispatcherServlet的特定WebApplicationContext中找到一个ViewResolver的实现,所以至少得添加一个,否则也太令DispatcherServlet失望了。
SpringMVC也为ViewResolver提供了多种实现,我们当前决定使用比较普及的JST/JSTL作为视图技术,所以,将org.springframework.Web.servlet.view.InternalResourceViewResolver添加到了DispatcherServlet特定的WebApplicationContext中,如下所示:
我们为InternalResourceViewResolver指定了prefix和suffix两个属性的自定义值。这样,当我们的逻辑视图名(viewName)为ttmRateList的时候,InternalResourceViewResolver将去寻找[prefix]+viewName+[suffix]名称的视图模板文件,也就是/WEB-INF/jp/tmRateList.jsp。不过看来该JSP文件还没有创建。
(7)实现相应视图我们得提供InternalResourceViewResolver根据逻辑视图名所查找的视图模板文件/WEB-INF/jsp/ttmRateList.jsp。我们通过相应的IDE在/WEB-INF/jsp/目录下创建一个ttmRateList.jsp文件,代码就不贴过来了。
本章小结在深入SpringMVC腹地之前,为了帮助你尽快融入到SpringMVC的世界中,我们首先从总体上对SpringMVC的设计和实现流程进行了简单的介绍。
实例通常是快速了解一件事物的最好方式,所以,我们在简单分析了基于SpringMVC的Web应用程序物理结构之后,一起开发了我们的实例应用simplefx。接下来,我们将在simplefx的基础上,进一步扩展视野,引入SpringMVC中的概念。



