首先还是先看看 web.xml.
<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>
这个配置我们很熟悉了,定义了spring 要使用的配置文件。dataAccessContext-jta.xml 从名字上就可以看出来,
这是一个用于 jta 方式管理事务的配置文件。
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<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>
<servlet-name>remoting</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>4</load-on-startup>
</servlet>
<servlet>
<servlet-name>axis</servlet-name>
<servlet-class>org.apache.axis.transport.http.AxisServlet</servlet-class>
<load-on-startup>5</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>petstore</servlet-name>
<!--
<servlet-name>action</servlet-name>
-->
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>remoting</servlet-name>
<url-pattern>/remoting/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>axis</servlet-name>
<url-pattern>/axis/*</url-pattern>
</servlet-mapping>
这段配置也不陌生,就是定义了一些 servlet. 不过从 web.xml 里也可以知道,spring 注释掉了一些 structs 的配置,
也就是说如果你想整合 structs 和 spring, 那么只要开启那些注释就可以了。
看了一些 spring 的例子以后,我们对 spring 的配置习惯也有了一些了解。
首先,spring 肯定会有一个 applicationContext.xml , 这里定义的都是一些业务,事务控制的方面的内容。
其次,会有一个 servlet名字+"-servlet.xml" 的配置文件,如这里的 jpetstore-servlet.xml. 这里定义的都是请求的
处理的配置。如 controller , resolver 等。
最后,还有一个配置文件,要么是配置其它内容,如 imagedb 里的计划任务;要么是数据库连接及相关DAO类的配置,
例如本例的 dataAccessContext-local.xml 。
遵循 spring 的这些习惯,对于使用 spring 来创建项目,是十分有好处的。
那么下面我们就先看看 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>
这个用法我们已经在 imagedb 例子里学过了,读取属性文件,用在配置文件里。
<bean id="accountValidator" class="org.springframework.samples.jpetstore.domain.logic.AccountValidator"/>
<bean id="orderValidator" class="org.springframework.samples.jpetstore.domain.logic.OrderValidator"/>
<bean id="petStore" class="org.springframework.samples.jpetstore.domain.logic.PetStoreImpl">
<property name="accountDao" ref="accountDao"/>
<property name="categoryDao" ref="categoryDao"/>
<property name="productDao" ref="productDao"/>
<property name="itemDao" ref="itemDao"/>
<property name="orderDao" ref="orderDao"/>
</bean>
这些都是逻辑类的定义:2个校验类,1个逻辑处理类。
<aop:config>
<aop:advisor pointcut="execution(* *..PetStoreFacade.*(..))" advice-ref="txAdvice"/>
</aop:config>
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="insert*"/>
<tx:method name="update*"/>
<tx:method name="*" read-only="true"/>
</tx:attributes>
</tx:advice>
这个就是 spring 的最大特色之一了,利用 AOP 来管理事务。详细的信息可以察看
http://www.redsaga.com/spring_ref/2.0/html/aop.html
http://www.redsaga.com/spring_ref/2.0/html/transaction.html
好,接着去看看 dataAccessContext-local.xml 里的配置。
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
这里定义了一个 dataSource. ${jdbc.url} 不用说也知道怎么来的吧?
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
定义了一个事物管理的 bean.
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation" value="WEB-INF/sql-map-config.xml"/>
<property name="dataSource" ref="dataSource"/>
</bean>
这个是用来整个 ibatis 的配置,关于这个的详细信息,可以参考
http://www.redsaga.com/spring_ref/2.0/html/orm.html#orm-ibatis
<bean id="accountDao" class="org.springframework.samples.jpetstore.dao.ibatis.SqlMapAccountDao">
<property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
<bean id="categoryDao" class="org.springframework.samples.jpetstore.dao.ibatis.SqlMapCategoryDao">
<property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
<bean id="productDao" class="org.springframework.samples.jpetstore.dao.ibatis.SqlMapProductDao">
<property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
<bean id="itemDao" class="org.springframework.samples.jpetstore.dao.ibatis.SqlMapItemDao">
<property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
<bean id="orderDao" class="org.springframework.samples.jpetstore.dao.ibatis.SqlMapOrderDao">
<property name="sqlMapClient" ref="sqlMapClient"/>
<property name="sequenceDao" ref="sequenceDao"/>
</bean>
<bean id="orderDao" class="org.springframework.samples.jpetstore.dao.ibatis.MsSqlOrderDao">
<property name="sqlMapClient" ref="sqlMapClient"/>
<property name="sequenceDao" ref="sequenceDao"/>
</bean>
<bean id="sequenceDao" class="org.springframework.samples.jpetstore.dao.ibatis.SqlMapSequenceDao">
<property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
这些都是对 DAO的类的定义。
到目前为止,我们应该可以看出来了,imagedb 例子里用的还是 spring 1.2 的配置方式,
而 jpetstore 里,用的则是 spring 2.0 的配置方式。
从这个例子里,两者最大的区别就是 事务的配置方式。spring2.0 用一种表达式的方式来配置事务,
这就使得事务的配置更加灵活。
好,下面继续看看 petstore-servlet.xml. 经验告诉我们,这个配置文件里定义的都是请求的处理,
以及视图的处理。
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/spring/"/>
<property name="suffix" value=".jsp"/>
</bean>
一开始,这个例子就给我们了一个新的视图处理方式。这个配置更容易懂,jsp 的文件放在 "/WEB-INF/jsp/spring/"里,
扩展名就是 “.jsp”。
<bean id="defaultHandlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
这又是一个新的请求处理的定义。从这个名字“BeanNameUrlHandlerMapping”可以看出来,似乎是通过 bean 的名字来处理url请求。
<bean name="/shop/addItemToCart.do" class="org.springframework.samples.jpetstore.web.spring.AddItemToCartController">
<property name="petStore" ref="petStore"/>
</bean>
<bean name="/shop/checkout.do" class="org.springframework.samples.jpetstore.web.spring.ViewCartController">
<property name="successView" value="Checkout"/>
</bean>
.... 省略
果真不出所料。spring 直接定义了一个请求该由哪个类来处理。
这和 countries 和 imagedb 的例子都不同。
spring 果真是九九八十一招,但是到底哪一招更好?自己决定吧!
<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" value-ref="secure_editAccount"/>
<entry key="/shop/listOrders.do" value-ref="secure_listOrders"/>
<entry key="/shop/newOrder.do" value-ref="secure_newOrder"/>
<entry key="/shop/viewOrder.do" value-ref="secure_viewOrder"/>
</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">
<property name="petStore" ref="petStore"/>
<property name="validator" ref="accountValidator"/>
<property name="successView" value="index"/>
</bean>
.... 省略
刚刚感叹完 spring 的招数之多,没想到下面的配置又给我们展示了一个新的招数。
从名字就可以看出来,这些操作都是属于安全级别的,所以,用了一个"interceptors"来做安全检查。
安全校验和业务逻辑,就好像零件一样非常容易的组装到到一起,想合并就合并,想拆卸就拆卸!
好了,配置文件看完了,下面来看看代码吧!
jetstore 是一个比较复杂的例子了,所以代码也比较多,有以下几个 package:
org.springframework.samples.jpetstore.dao
org.springframework.samples.jpetstore.dao.ibatis
org.springframework.samples.jpetstore.dao.ibatis.maps
org.springframework.samples.jpetstore.domain
org.springframework.samples.jpetstore.domain.logic
org.springframework.samples.jpetstore.service
org.springframework.samples.jpetstore.service.client
org.springframework.samples.jpetstore.web.spring
org.springframework.samples.jpetstore.web.structs
幸好 java 的世界一切都是那么有规有矩,看看这些package 的名字,也能猜到一二。
dao 里面应该都是数据库相关的。
domain, service 里,应该都是逻辑相关的
web 里,应该是 controller 相关的。
先从一个controller 看起吧。很多,挑 AccountFormController 看吧。
先看看构造函数。
public AccountFormController() {
setSessionForm(true);
setValidateOnBinding(false);
setCommandName("accountForm");
setFormView("EditAccountForm");
}
这段代码作了4件事情:
第一件 设置这个controller 的 command object 保存在session 里。
第二件 设置 validateOnBinding = false. 为什么设置这个?为了能详细的解释,我们还是看看
SimpleFormController 的父类 AbstractFormController 的父类 BaseCommandController。
注意这个方法:
protected final ServletRequestDataBinder bindAndValidate(HttpServletRequest request, Object command)
throws Exception {
ServletRequestDataBinder binder = createBinder(request, command);
BindException errors = new BindException(binder.getBindingResult());
if (!suppressBinding(request)) {
binder.bind(request);
onBind(request, command, errors);
if (this.validators != null && isValidateOnBinding() && !suppressValidation(request, command, errors)) {
for (int i = 0; i < this.validators.length; i++) {
ValidationUtils.invokeValidator(this.validators[i], command, errors);
}
}
onBindAndValidate(request, command, errors);
}
return binder;
}
这段代码是调用 validator 的方法。注意这行:“onBindAndValidate(request, command, errors);”,
然后再看这个判断“if (this.validators != null && isValidateOnBinding() 。。。省略”
可见,这个例子重写了 onBindAndValidate 方法,同时设置 validateOnBinding = false,
就是不使用默认的校验方法。
其实在 BaseCommandController 里,onBindAndValidate 方法就是抽象方法。
第三件 设置了 commandName, 这是 sessionForm 所需要的。
第四件 设置显示的视图名称。
如果您用过 spring 1.2 的话,应该知道,对于这种会提交数据的controller来说,
会在配置文件里定义 command object (也就类似structs 里的form bean).
但是我们在配置文件里没有看到,看看代码里怎么写得吧!
protected Object formBackingObject(HttpServletRequest request) throws Exception {
UserSession userSession = (UserSession) WebUtils.getSessionAttribute(request, "userSession");
if (userSession != null) {
return new AccountForm(this.petStore.getAccount(userSession.getAccount().getUsername()));
}
else {
return new AccountForm();
}
}
原来这个controller 重载了 formBackingObject 来自己构造 command object.
之所以这么做,是因为 AccountForm 需要从 session 中拿数据。
这个 controller 里的 onSubmit 方法就是处理提交的请求了。
注意这个类中所使用的 “private PetStoreFacade petStore;”,是在配置文件里注入的,
并不是在 controller 里声称的。而且, petStore 里的方法都具有事务属性的。
比 EJB 简单,但是却做着EJB的工作
再看看 AddItemToCartController 吧。
这个 controller 的父类是 Controller。
我想我们已经接触过3个controller了!只能说spring真的是灵活的framework,选择非常多,
目前接触的3种 controller 都各有特点,自己体会吧
看看这个controller 的代码:
Cart cart = (Cart) WebUtils.getOrCreateSessionAttribute(request.getSession(), "sessionCart", Cart.class);
一行代码,却告诉我们,这个“WebUtils”类里肯定有不少好东西!快去看看吧,省得我们写重复的代码。
这个方法不用详细说,看名字就知道了:从 session 拿出一个变量,如果没有就创建它。
再来看看 OrderFormController 吧。
这个 controller 的父类是 AbstractWizardFormController。
我已经不再惊奇了。。。就是再看到5个controller 我也不惊奇了。
从这个名字可以看出来,这是一个类似向导功能的controller.
这部分内容你可以参考
http://www.redsaga.com/spring_ref/2.0/html/mvc.html#mvc-controller
的 “13.3.4. 命令控制器”的部分。
逐一看过这些controller 后,我们发现在这个例子中,
如果涉及到提交表单,就用 SimpleFormController,
如果有向导功能的,就用 AbstractWizardFormController,
否则,就直接继承 Controller.
逻辑部分和数据库部分代码就不说了,与 ibatis 的集成可以参考
http://www.redsaga.com/spring_ref/2.0/html/orm.html#orm-ibatis
看了这些例子,我们越来越觉得spring就是一个“大容器”,许多东西都可以轻易地整合到里面,
同时,spring 也十分灵活,好在目前看得这些例子,每个招数都是各有特点,也能让我们在
遇到问题时有所选择。
同时,这个例子的 annotation 目录下,提供了用 annotation 方式的例子。
但是,个人还是觉得 xml 更好一些--虽然有那么一点乱,但是管理起来很方便,而且,总会
有办法来简化 xml 的编写的。