spring jpetstore (2.56)项目分析

spring jpetstore (2.56)项目分析

近期对spring 的官方示例程序做相应的分析,在其官方网站下载spring-framework-2.5.6-with-dependencies,其中的samples当中有对应的几个示例项目,近期开始研究第一个项目jpetstore。

Petstore是一个简单的电子宠物商店,包含的功能如下:

登陆/退出

浏览宠物

宠物展示

购物车添加/减少宠物

提交订单

针对这几个功能,系统将对象划分为:

 

对应将各个对象的db操作封装:

 

对应的sqlmap为:

 

有了以上对应的对象、db操作并不能完成电子商店的功能。下面将详细讲解相关业务实现,并通过业务实现讲解如何使用spring的功能。

 

 

业务层分析:

主要的类和接口位于org.springframework.samples.jpetstore.domain.logic包中。业务层其实很简单,主要是一个PetStoreFacade接口,该接口在JPetStore中只有一个唯一的实现类PetStoreImpl,它提供了很多供Web层调用的方法,而绝大多数方法都只是简单的调用了数据访问层的Dao类所提供的方法。

 

domain.logic包中还有两个供Web层访问的validator,AccountValidator和OrderValidator,它们实现自spring的Validator接口,由于使用了spring提供的helper class ValidationUtils,因此看起来也十分简洁。通过在servlet配置文件中为AccountFormController和AccountFormController指定validator属性,从而为Account和Order提供了验证功能。不过在这里,这些Validator无疑是依赖于spring框架的。

 

<bean name=\”/shop/newAccount.do\”

class=\”org.springframework.samples.jpetstore.web.spring.AccountFormController\”>

<property name=\”petStore\”><ref bean=\”petStore\”/></property>

<property name=\”validator\”><ref bean=\”accountValidator\”/></property>

<property name=\”successView\”><value>index</value></property>

</bean>

<bean id=\”secure_editAccount\”

class=\”org.springframework.samples.jpetstore.web.spring.AccountFormController\”>

<property name=\”petStore\”><ref bean=\”petStore\”/></property>

<property name=\”validator\”><ref bean=\”accountValidator\”/></property>

<property name=\”successView\”><value>index</value></property>

</bean>

另外,还有一个AOP advice,SendOrderConfirmationEmailAdvice,用于在完成一条order的数据库插入之后,向用户发送一封确认邮件,相应的配置位于applicationContext.xml中。

<bean id=\”mailSender\”

class=\”org.springframework.mail.javamail.JavaMailSenderImpl\”>

<property name=\”host\”><value>${mail.host}</value></property>

</bean>

<bean id=\”emailAdvice\”

class=\”org.springframework.samples.jpetstore.domain.logic.SendOrderConfirmationEmailAdvice\”>

<property name=\”mailSender\”><ref local=\”mailSender\”/></property>

</bean>

<bean id=\”emailAdvisor\”

class=\”org.springframework.aop.support.RegexpMethodPointcutAdvisor\”>

<constructor-arg><ref local=\”emailAdvice\”/></constructor-arg>

<property name=\”pattern\”><value>.*insertOrder.*</value></property>

</bean>

在另一个包org.springframework.samples.jpetstore.domain中,包含了在整个JPetStore各个层中都会使用到的domain object。这些基本的model类,除了Cart,在数据访问层均有对应的Dao类,而由于Cart仅作为在Web层内部传递的model,因此不需要持久化。

 

 

数据访问层

在这一层里,所有的Dao类由于使用了ibatis,所以代码显得格外干净。实际上,绝大部分逻辑都被搬到了外部的sql map文件里,这些文件位于org.springframework.samples.jpetstore.dao.ibatis.maps包下。值得一提的是,JPetStore对于Sequence的处理。JPetStore专门定义了一个Sequence的sql map,里面分别针对普通情况和Oracle数据库做了单独配置。

通常情况下是在数据库中单独维护一张代表sequence的表,使用时首先利用“getSequence”获取对应name的nextid,然后代码实现累加1,完成后再利用“updateSequence”更新sequence表。以下是Sequence.xml中的相关定义:

<select id=\”getSequence\” resultMap=\”result\”>

select name, nextid from sequence where name = #name#

</select>

<update id=\”updateSequence\”>

update sequence set nextid = #nextId# where name = #name#

</update>

在SqlMapSequenceDao中的getNextId方法实现了上述的代码处理逻辑:

public int getNextId(String name) throws DataAccessException {

Sequence sequence = new Sequence(name, -1);

sequence =

(Sequence) getSqlMapClientTemplate().queryForObject(\”getSequence\”, sequence);

// …

Object parameterObject = new Sequence(name, sequence.getNextId() + 1);

getSqlMapClientTemplate().update(\”updateSequence\”, parameterObject, 1);

return sequence.getNextId();

}

而对于Oracle,则直接利用其sequence功能,相应的sql map定义如下:

<select id=\”oracleSequence\” resultMap=\”result\”>

select \’$name$\’ as name, $name$.nextval as nextid from dual

</select>

另有一个OracleSequenceDao实现,其getNextId方法如下:

public int getNextId(String name) throws DataAccessException {

Sequence sequence = new Sequence();

sequence.setName(name);

sequence = (Sequence) getSqlMapClientTemplate().queryForObject(\”oracleSequence\”, sequence);

return sequence.getNextId();

}

包括SqlMapSequenceDao在内的所有Dao类都使用了SqlMapClientDaoSupport作为基类,同时,多数Dao类实现相应的Dao接口。Dao实现类中会调用SqlMapClientDaoSupport提供了getSqlMapClientTemplate方法。这样就可以很方便的从sql map文件中读取sql配置,而无需将sql语句hard code到代码中了。spring为ibatis提供了一层简单的封装,然后将自底层抛出的异常转义为spring框架所定义的runtime异常DataAccessException,可以在上层视情况决定是否catch该异常。

所有的Dao接口都位于org.springframework.samples.jpetstore.dao包中,而所有的ibatis实现类都位于org.springframework.samples.jpetstore.dao.ibatis包

 

 

Web

Web层是Spring JPetStore相对最为复杂的地方。Spring JPetStore同时支持了两种Web Framework:Struts和Spring Web MVC,分别位于org.springframework.samples.jpetstore.web.struts和org.springframework.samples.jpetstore.web.spring这两个包内。两者的切换只需要修改一下web.xml文件的相应配置即可。如果选择Spring Web MVC,则:

<servlet>

<servlet-name>petstore</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<load-on-startup>2</load-on-startup>

</servlet>

DispatcherServlet是Spring Web MVC中负责请求调度的核心引擎,通过内嵌的<init-param>节点可以为其配置名为“contextConfigLocation”的

参数,该参数指定了Spring专属的Application Context配置文件的位置。如果忽略此设定,则默认为“/WEB-INF/<servlet name>-servlet.xml”,其中<servlet name>以此处的Servlet名替换(比如:petstore-servlet.xml)

在org.springframework.samples.jpetstore.web.spring包中,绝大部分类属于Controller,它们均维护了一个PetStoreFacade的实例变量,通过PetStoreFacade来访问业务层的功能。利用Spring的配置文件petstore-servlets.xml,可以将PetStoreFacade的实现类(PetStoreImpl)通过reference bean的方式“注入”到各个Controller中。

这些Controller几乎都实现自org.springframework.web.servlet.mvc.Controller接口,该接口只有一个抽象方法:

ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response);

handleRequest的处理逻辑大致是:

- 从request中获取信息(并没有什么特别的,一切数据都是通过request以及session在众多jsp页面和controller间传递的)

- 调用相应的petStore业务方法(处理Cart相关的Controller例外)

- 装配并返回ModelAndView实例

- 由于可以在handleRequest中直接操纵response,因此另一种返回形式是调用reponse.sendRedirect

从中可以看出,这是与Servlet API紧密耦合的。

Web层(续)

在众多Controller当中,有两个Controller比较特殊,它们涉及表单处理,分别是OrderFormController和AccountFormController,前者派生自AbstractWizardFormController,后者派生自SimpleFormController,而它们的父类则同为AbstractFormController。Spring Web MVC对一般的Form处理,从流程(workflow)的角度做了分类(form workflow),并以抽象类的形式封装与框架代码中。像OrderFormController和AccountFormController即是从这些抽象类中扩展派生而来,它们对部分callback方法做了覆盖。这是一个典型的Template Method Pattern的运用。当然,这也使得Web层的应用代码严重依赖于Spring Web MVC框架本身。

以SimpleFormController为例,从其核心方法之一processFormSubmission的实现代码中可以看出大致的处理流程:

protected ModelAndView processFormSubmission(

  HttpServletRequest request, HttpServletResponse response,

  Object command, BindException errors)

  throws Exception {

 if (errors.hasErrors() || isFormChangeRequest(request)) {

  return showForm(request, response, errors);

 }

 else {

  return onSubmit(request, response, command, errors);

 }

}

在表单验证之后,如果用户输入有误,或者表单需要再次刷新,则会重新导向当前表单页面,否则说明一切正常,调用onSubmit方法。onSubmit最终会调用doSubmitAction方法,然后调用getSuccessView获得后继视图的标识,配合errors.getModel(),组装成ModelAndView返回。而这里的doSubmitAction则是一个需要派生类覆盖的protected方法。

与上述Controller配套的还有两个Form Object:OrderForm,AccountForm,其所包含的实例变量对应于表单字段。

另一个值得一提的是拦截器SignOnInterceptor,它对部分用户操作实施了认证保护。代码实现逻辑大致如下:

public boolean preHandle(

  HttpServletRequest request, HttpServletResponse response, Object handler)

  throws Exception {

 UserSession userSession =

  (UserSession) WebUtils.getSessionAttribute(request, \”userSession\”);

 if (userSession == null) {

  ModelAndView modelAndView = new ModelAndView(\”SignonForm\”);

  throw new ModelAndViewDefiningException(modelAndView);

 }

 else {

  return true;

 }

}

这里的UserSession是一个封装了包括帐号在内的用户信息的helper class。它被当作session属性在Web层的不同对象之间传递。在petstore-servlets.xml文件中有如下一段配置信息:

<bean id=\”secureHandlerMapping\”

 class=\”org.springframework.web.servlet.handler.SimpleUrlHandlerMapping\”>

 <property name=\”interceptors\”>

  <list>

   <ref bean=\”signonInterceptor\”/>

  </list>

 </property>

 <property name=\”urlMap\”>

  <map>

   <entry key=\”/shop/editAccount.do\”><ref local=\”secure_editAccount\”/></entry>

   …

  </map>

 </property>

</bean>

<bean id=\”signonInterceptor\”

 class=\”org.springframework.samples.jpetstore.web.spring.SignonInterceptor\”/>

<bean id=\”secure_editAccount\”

 class=\”org.springframework.samples.jpetstore.web.spring.AccountFormController\”>

 …

</bean>

由此,我们可以看到,SignonInterceptor的preHandle方法,将会在AccountFormController执行之前被调用。而当userSession为空时,则会被当作非法操作抛出ModelAndViewDefiningException异常,否则AccountFormController将会顺利执行。至于如何处理ModelAndViewDefiningException,则要“上溯”到Spring Web MVC的核心请求处理类:org.springframework.web.servlet.DispatcherServlet。在该Servlet的doDispatch方法中有如下一段代码:

try {

 …

}

catch (ModelAndViewDefiningException ex) {

 mv = ex.getModelAndView();

}

if (mv != null && !mv.isEmpty()) {

 render(mv, processedRequest, response);

}

由于ModelAndViewDefiningException中包含了出错以后的后继视图标识,因此最终将会导向该视图(在本例中,它导向用户登录页面)。

 

Web层(续2)——分页机制

在Spring JPetStore中,为列表提供了简单的分页处理,这里使用了Spring的helper class:PagedListHolder。它虽是用于Web UI,不过实际上是针对bean list的维护,因此位于org.springframework.beans.support包中。在Web层中,PagedListHolder的实例会被当作session属性传递,然后在jsp页面中以model获取。

PagedListHolder itemList = new PagedListHolder(this.petStore.getItemListByProduct(productId));

itemList.setPageSize(4);

Product product = this.petStore.getProduct(productId);

request.getSession().setAttribute(\”ViewProductAction_itemList\”, itemList);

request.getSession().setAttribute(\”ViewProductAction_product\”, product);

model.put(\”itemList\”, itemList);

model.put(\”product\”, product);

在PageListHolder中提供了很多分页时经常用到的方法:isFirstPage、isLastPage、previousPage、nextPage、getPageList等。通过设置其成员变量sort,PageListHolder也支持排序,该成员变量是一个SortDefinition接口的实现类,缺省使用的是MutableSortDefinition。实际上MutableSortDefinition只不过维护了有关排序的一些状态信息,比如:以那个属性排序、降序还是升序、是否忽略大小写等。而真正的排序则是由PropertyComparator完成的,该类实现了java.util.Comparator接口,用于根据指定的bean property来比较两个bean的先后顺序。在PageListHolder的resort方法中调用了PropertyComparator的静态方法sort。

PagedHolderList提供了对不可更新的bean list的分页支持,如果需要处理可更新的bean list,可以使用RefreshablePagedListHolder。RefreshablePagedListHolder是PagedListHolder的子类,具备reloading功能。调用其refresh方法,能够根据Locale和filter的更新情况自动实现数据的reloading。为了让RefreshablePagedListHolder能够成功reload数据,我们还需要编写一个PagedListSourceProvider接口的实现类,因为RefreshablePagedListHolder会调用该接口的loadList方法。

Spring的org.springframework.beans.support包为Web分页机制提供了统一的解决方案,使用起来十分方便。不过,需要指出的是,使用PagedHolderList/RefreshablePagedHolderList的隐含前提是,你需要将后台数据表中的所有数据悉数全部取出来,然后再交由它们进行分页处理。

 

配置

以下是Spring JPetStore中使用的配置文件:

- web.xml

主要包含了如下内容:

定义log4j配置文件所在路径:

<context-param>

 <param-name>log4jConfigLocation</param-name>

 <param-value>/WEB-INF/log4j.properties</param-value>

</context-param>

定义application context配置文件的所在路径:

<context-param>

 <param-name>contextConfigLocation</param-name>

 <param-value>

  /WEB-INF/dataAccessContext-local.xml /WEB-INF/applicationContext.xml

 </param-value>

 <!–

 <param-value>

  /WEB-INF/dataAccessContext-jta.xml /WEB-INF/applicationContext.xml

 </param-value>

 –>

</context-param>

几个重要的servlet,以及servlet-mapping定义:

<servlet>

 <servlet-name>context</servlet-name>

 <servlet-class>org.springframework.web.context.ContextLoaderServlet</servlet-class>

 <load-on-startup>1</load-on-startup>

</servlet>

<servlet>

 <servlet-name>petstore</servlet-name>

 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

 <load-on-startup>2</load-on-startup>

</servlet>

<servlet>

 <servlet-name>action</servlet-name>

 <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>

 <load-on-startup>3</load-on-startup>

</servlet>

<servlet-mapping>

 <servlet-name>petstore</servlet-name>

 <!–

 <servlet-name>action</servlet-name>

 –>

 <url-pattern>*.do</url-pattern>

</servlet-mapping>

- applicationContext.xml

与应用相关的context配置信息,包含bean定义,以及对email和remoting的配置,主要涉及中间层,也是其他servlet-specific context的root。在代码中可以通过WebApplicationContextUtils.getWebApplicationContext()访问到:

在该文件以及后面的dataAccessContext-local中都引入了形如“${}”的属性,这些属性是单独定义在外部属性文件中的,需要利用PropertyPlaceholderConfigurer来读入,该bean也在applicationContext.xml中定义。

<bean id=\”propertyConfigurer\” class=\”org.springframework.beans.factory.config.PropertyPlaceholderConfigurer\”>

 <property name=\”locations\”>

  <list>

   <value>WEB-INF/mail.properties</value>

   <value>WEB-INF/jdbc.properties</value>

  </list>

 </property>

</bean>

在声明事务时,applicationContext.xml文件中首先定义了一个名为baseTransactionProxy的bean,其中包含了有关事务的基本配置(对所有的insert方法和update方法使用PROPAGATION_REQUIRED,对其他方法使用PROPAGATION_REQUIRED,readOnly)。其abstract属性为true,作为parent bean被petStore bean所继承,并得到扩展。

<bean id=\”baseTransactionProxy\” class=\”org.springframework.transaction.interceptor.TransactionProxyFactoryBean\”

 abstract=\”true\”>

 <property name=\”transactionManager\”><ref bean=\”transactionManager\”/></property>

 <property name=\”transactionAttributes\”>

  <props>

   <prop key=\”insert*\”>PROPAGATION_REQUIRED</prop>

   <prop key=\”update*\”>PROPAGATION_REQUIRED</prop>

   <prop key=\”*\”>PROPAGATION_REQUIRED,readOnly</prop>

  </props>

 </property>

</bean>

<bean id=\”petStore\” parent=\”baseTransactionProxy\”>

 <property name=\”target\”>

  <bean class=\”org.springframework.samples.jpetstore.domain.logic.PetStoreImpl\”>

   <property name=\”accountDao\”><ref bean=\”accountDao\”/></property>

   <property name=\”categoryDao\”><ref bean=\”categoryDao\”/></property>

   <property name=\”productDao\”><ref bean=\”productDao\”/></property>

   <property name=\”itemDao\”><ref bean=\”itemDao\”/></property>

   <property name=\”orderDao\”><ref bean=\”orderDao\”/></property>

  </bean>

 </property>

</bean>

petSotre bean即对应于中间层的PetStoreImpl类,在其bean reference中所出现的Dao bean定义于dataAccessContext-local.xml中。

- dataAccessContext-local.xml/dataAccessContext-jta.xml

Spring JPetStore将对Dao bean的定义单独放到了一个文件中,这种按层分文件来描述context的方式值得借鉴。local后缀的文件用于单一数据库场景,jta后缀的文件用于多数据库场景。以dataAccessContext-local为例,除了Dao bean定义,该文件中还包括了dataSource的定义,所用的是apache的DBCP:

<bean id=\”dataSource\” class=\”org.apache.commons.dbcp.BasicDataSource\” destroy-method=\”close\”>

 <property name=\”driverClassName\”><value>${jdbc.driverClassName}</value></property>

 <property name=\”url\”><value>${jdbc.url}</value></property>

 <property name=\”username\”><value>${jdbc.username}</value></property>

 <property name=\”password\”><value>${jdbc.password}</value></property>

</bean>

另外,还有关于ibatis sql map的配置信息:

<bean id=\”sqlMapClient\” class=\”org.springframework.orm.ibatis.SqlMapClientFactoryBean\”>

 <property name=\”configLocation\”><value>WEB-INF/sql-map-config.xml</value></property>

 <property name=\”dataSource\”><ref local=\”dataSource\”/></property>

</bean>

这里的sql-map-config.xml文件中包含了所有ibatis sql map文件所在的物理位置。

- petstore-servlet.xml

Spring web MVC的Web层context配置文件,依然是有关bean的定义,主要是Controller,定义了url与class的映射,以及一些属性,包括:对petStore bean的引用,successView,validator,viewName等。另外,viewResolver bean定义了view所在的位置和扩展名,以及对应的viewClass:

<bean id=\”viewResolver\” class=\”org.springframework.web.servlet.view.InternalResourceViewResolver\”>

 <property name=\”viewClass\”><value>org.springframework.web.servlet.view.JstlView</value></property>

 <property name=\”prefix\”><value>/WEB-INF/jsp/spring/</value></property>

 <property name=\”suffix\”><value>.jsp</value></property>

</bean>

 

posted @ 2013-01-11 11:40  夏花绚烂  阅读(396)  评论(0编辑  收藏  举报