发表于 3/28/2005 由 site admin
※ 业务层
主要的类和接口位于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框架的。
另外,还有一个AOP advice,SendOrderConfirmationEmailAdvice,用于在完成一条order的数据库插入之后,向用户发送一封确认邮件,相应的配置位于applicationContext.xml中。
在另一个包org.springframework.samples.jpetstore.domain中,包含了在整个JPetStore各个层中都会使用到的domain object。这些基本的model类,除了Cart,在数据访问层均有对应的Dao类,而由于Cart仅作为在Web层内部传递的model,因此不需要持久化。
Spring JPetStore学习小结(2)
发表于 3/29/2005 由 site admin
※ 数据访问层
在这一层里,所有的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中的相关定义:
update sequence set nextid = #nextId# where name = #name#
在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定义如下:
另有一个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包中。
Spring JPetStore学习小结(3)
发表于 3/31/2005 由 site admin
※ 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,则:
DispatcherServlet是Spring Web MVC中负责请求调度的核心引擎,通过内嵌的 参数,该参数指定了Spring专属的Application Context配置文件的位置。如果忽略此设定,则默认为“/WEB-INF/ 在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紧密耦合的。 Spring JPetStore学习小结(4) 发表于 4/1/2005 由 site admin ※ 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文件中有如下一段配置信息: … 由此,我们可以看到,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中包含了出错以后的后继视图标识,因此最终将会导向该视图(在本例中,它导向用户登录页面)。 Spring JPetStore学习小结(5) 发表于 4/4/2005 由 site admin ※ 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学习小结(6) 发表于 4/5/2005 由 site admin ※ 配置 以下是Spring JPetStore中使用的配置文件: - web.xml 主要包含了如下内容: 定义log4j配置文件所在路径: 定义application context配置文件的所在路径: /WEB-INF/dataAccessContext-local.xml /WEB-INF/applicationContext.xml /WEB-INF/dataAccessContext-jta.xml /WEB-INF/applicationContext.xml –> 几个重要的servlet,以及servlet-mapping定义: –> - applicationContext.xml 与应用相关的context配置信息,包含bean定义,以及对email和remoting的配置,主要涉及中间层,也是其他servlet-specific context的root。在代码中可以通过WebApplicationContextUtils.getWebApplicationContext()访问到: 在该文件以及后面的dataAccessContext-local中都引入了形如“${}”的属性,这些属性是单独定义在外部属性文件中的,需要利用PropertyPlaceholderConfigurer来读入,该bean也在applicationContext.xml中定义。 在声明事务时,applicationContext.xml文件中首先定义了一个名为baseTransactionProxy的bean,其中包含了有关事务的基本配置(对所有的insert方法和update方法使用PROPAGATION_REQUIRED,对其他方法使用PROPAGATION_REQUIRED,readOnly)。其abstract属性为true,作为parent bean被petStore 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: 另外,还有关于ibatis sql map的配置信息: 这里的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: Rod Johnson对Java领域未来关注的技术的看法 发表于 4/10/2005 由 site admin Rod Johnson对Java领域未来关注的技术的看法: - Inversion of Control and dependency injection design patterns; - unit testing and TDD (test-driven development); - O/R (object/relational) mapping; - post struts 1.x Web technologies such as JavaServer Faces, Spring Model-View-Controller and Tapestry, and value-add Web technologies such as Apache Beehive; - the rich client space; a way of known-to-unknown 发表于 4/19/2005 由 site admin 我们通常所说的软件设计与开发过程,无论是top-down(自顶向下)还是bottom-up(自底向上),都是一种过于简单化的认识。由此产生的对TDD的错误认识也就变得可以理解了。关于这一点,Kent Beck在他的TDD一书中讲的非常透彻: A program grown from tests can appear to be written top-down, because you can begin with a test that represents a simple case of the entire computation. A program grown from tests can also appear to be written bottom-up, because you start with small pieces and aggregate them larger and larger. Neither top-down nor bottom-up really describes the process helpfully. First, a vertical metaphor is a simplistic visualization of how programs change over time. “Growth” implies a kind of self-similar feedback loop where the environment affects the program and the program affects the environment. Second, if we have to have a direction in our metaphor, “known-to-unknown” is a helpful description. Known-to-unknown implies that we have some knowledge and experience on which to draw, and that we expect to learn in the course of development. Put these two together and we have programs growing from known to unknown.