Morning@Weblog

3/31/2005

Spring JPetStore学习小结(3)

Filed under: — site admin @ 10:19 am

※ 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紧密耦合的。

3/29/2005

Spring JPetStore学习小结(2)

Filed under: — site admin @ 9:51 am

※ 数据访问层

在这一层里,所有的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包中。

3/28/2005

Spring JPetStore学习小结(1)

Filed under: — site admin @ 11:23 am

※ 业务层

主要的类和接口位于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,因此不需要持久化。

3/18/2005

Case Study: The Spring Experience

Filed under: — site admin @ 9:21 am

来自Rod Jhonson的Spring经验,非常受用的一些关于unit test的实践指南:
- Accept that the test suite is more important than anything else but the deliverable code itself.
- Work in small steps.
- If you’re looking at a piece of code for which tests can be improved, add more tests.
- Write a unit test that fails because of a reported bug before attempting to fix the bug.
- Design code so that it’s easy to test.
- Ensure that the entire test suite runs in seconds.
- Use meaningful names for tests that show their purpose
- Minimize the number of configuration files that need to be loaded to run tests.
- Tests should have no side effects.
- Don’t depend on the ordering of test cases, and don’t create test suites programmatically.
- Unit tests should not depend on a database or external resources.(If your test suite takes minutes rather than seconds, you can’t practice test first development.)
- If a configuration file is used, load it from the class path
- Have Ant scripts that run all tests and run test coverage analysis.
- Run individual tests in an IDE.
- Look at test coverage reports regularly.
- Use mock objects to avoid dependence on external resources.
- Refactor test code when necessary.
- Refactor application code to eliminate duplication and improve quality.
- Configuring IDE to throw UnsupportedOperationException() instead of doing nothing or returning null in all generated method stubs.
- Writing each test (each JUnit test method) to test one thing.

3/17/2005

不要老说“以后”

Filed under: — site admin @ 9:41 am

我时常听人说类似这样的话:“以后我们要逐渐建立统一的异常处理机制”,“以后我们要对程序中出现的重复代码做出改进”,……

举一个生活中的例子,我和我的邻居在做饭时的一个显著差别是,我喜欢在做完饭之后即刻刷锅,而我的邻居总是想在吃完美味可口的饭菜之后再处理这些杂务,而事情的结果往往是到第二天开始做饭之前,锅碗瓢盆依旧。

不要将对错误的改正希望寄托于“以后的某个时间点”。通常,在一个缺乏经验的小型开发团队中,没有人能够想到会主动发起这样的“改良运动”。解决方法有三:

- 首先是意识上的提高,主观拒绝类似“以后”这样的字眼;
- 随手可做的事情就随手做,在新加入模块/代码的时候,不要重复已有的过错,虽然明明知道那样不对,比如重复代码,不良设计等等,而是要尽可能做到统一,充分考虑体系的一致性,概念完整性;
- 在迫于压力的前提下(如果客观上这是真实的,并非主管的),可以考虑某种为将来做好打算的过渡手段;

3/15/2005

有感于软件过程实践的彼此关联性

Filed under: — site admin @ 7:37 pm

软件过程中的很多实践都是彼此关联的。在试图努力做好某项实践的时候,你也不得不承认,你需要同时考虑其他的关联点。尤其当你在某项实践上所花费的时间相当客观的时候,为了使这样的投入不致于失去意义,就迫使你更有理由关注其关联点。以下就是几个例子:

为了使Refactoring能够更快捷、方便地进行,你的代码必须有良好的风格和大家都同意的代码标准(coding standard)。

如果你们成对编程(pair Programming),那么你们就能更好地处理复杂的Refactoring,也使得Refactoring出错的可能性大大减小。

更进一步,测试(testing)能够使得Refactoring更加安全。

3/14/2005

规则的启示

Filed under: — site admin @ 8:44 pm

规则不是强制性的,而是辅助性的,其目的在于以一种"将侵入性降至最低"的方式来提醒大家如何工作。一旦规则发挥了作用,境况得到了改善,那么你就可以进一步修改规则,让开发进行得更顺利。最后,当规则成为习惯之后,就可以把它们完全置之脑后了。不过此前,如果没有规则,你可能无法开始改善近境况,不会有任何起步的机会。

3/11/2005

基于Spring template的dao及其单元测试的一般模式

Filed under: — site admin @ 11:14 am

利用Spring的template,比如:JdbcTemplate,可以大大简化dao代码的实现。不过template要求数据源作为传入参数,因此可以将data source做为dao类的member,然后使用setter由外部传入:

public XXXDbDaoImpl implements XXXDao {
DataSource dataSource;
public void setDataSource(DataSource dataSource)
{
this.dataSource = dataSource;
}
// dao methods …
}

这样的dao实现类简洁清晰,同时也有助于以后进一步利用IoC来完成容器管理的data source配置。不过此前,可以先定义一个dao的factory作为过度,由factory来完成对data source设置的封装。相应的,单元测试可以利用spring的DriverManagerDataSource来手工配置data source:

public class XXXDaoTest extends TestCase {
protected void setUp() throws Exception {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(TestConfig.getDbDriver());
ds.setUrl(TestConfig.getDbUrl());
ds.setUsername(TestConfig.getDbUsername());
ds.setPassword(TestConfig.getDbPassword());
dao.setDataSource(ds);
// other setup …
}
// test methods …
}

3/8/2005

objective and replicable test

Filed under: — site admin @ 10:16 pm

在unit testing方面有这样的一个“公式”:objective test + replicable test = simple test programme,可以看出objective和replicable是unit testing的最关心的两样东西。objective意味着每个TestCase都应该是有目的的,并非人云亦云的“每个class都应该进行unit testing”;replicable则意味着TestCase应该是可重用的。

Powered by WordPress