闲来无事,看看Spring这个生态系统中的各个模块挺不错,简化了很多事情,本文参考Spring Web Flow项目中的booking-mvc这个sample进行了仔细学习,很有收获,该sample主要讲解了Spirng Web Flow的一些核心概念,以及与spring mvc,spring security的组合使用,功力大大增强。
还是典型的3层模型,dao,service,controller,涉及到Java EE 6的规范主要是JPA2.0,JSR303和JSR250。废话不多说先上例子吧。
由于dao和service都比较简单,重点讲web层,稍后你会看到dao层我们只声明了接口,并没有写实现代码,这是由于我们借助了spring-data-jpa这个子项目的功能,由spring动态代理实现。大大简化了dao层。spring-data旨在提供一个统一的数据访问层接口。不管你是关系型数据库,还是no database,还是key-value,访问数据接口统一,是门面模式的体现。关于spring-data项目的详细信息,请查看官方信息:http://www.springsource.org/spring-data
首先,我们来谈谈spring web flow 的一些核心概念:什么是flow呢,引用官方文档的一句话:A flow encapsulates a reusable sequence of steps that can execute in different contexts.大致意思就是说,流就是封装了一个可复用的序列步骤,这些序列步骤可以在不同的上下文环境中执行。哎呀,翻译的好别扭啊。那什么是”state“呢,在spring web flow中,把组成流的一系列的步骤称之为state(姑且叫状态吧),通常,进入一个状态就意味着一个页面视图将要呈现给用户,在这个页面视图中用户可以输入一些数据,而且还可以触发一些事件,比如点击了某个button之类的,而这些事件通常又会把当前的state转移到另外一个state,这就是state的transition(姑且叫转移,过渡也行)。在这些state中还可以执行一些动作,比如将收集到的用户数据,持久化到db中等等。
介绍完一些基本概念后,我们来具体说一下操作。首先先讲spring web flow 和 spring mvc 的集成,在web.xml中配置spring mvc的前端控制器。配置很简单,如下:
- <!-- Loads the Spring web application context -->
- <listener>
- <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
- </listener>
- <servlet>
- <servlet-name>mvc</servlet-name>
- <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
- <load-on-startup>1</load-on-startup>
- </servlet>
- <servlet-mapping>
- <servlet-name>mvc</servlet-name>
- <url-pattern>/</url-pattern>
- </servlet-mapping>
由于我们是基于约定的编程,当mvc这个servlet加载时,会在/WEB-INF/目录下查找文件名为mvc-servlet.xml这个配置文件。下面我们就具体看看spring mvc的配置,首先,我们让spring 扫描web层的controller,代码如下:
- <context:component-scan base-package="org.leochen.samples.web" />
第2步,充分利用mvc这个命名空间的作用,
- <mvc:annotation-driven />
这个打包了一些列功能配置,比如支持JSR 303的验证,以及sprng 3的类型转换和字段的格式化等。
第3步,添加spring mvc对静态资源的处理,结合在web.xml中配置的url-pattern为”/“,
- <mvc:resources mapping="/resources/**"
- location="/resources/,classpath:/META-INF/web-resources/" />
- <mvc:default-servlet-handler />
静态资源都放在web根目录下的resources文件下,对所有以/resources开头的请求,都会被映射到web根目录下的resources目录下和类路径下面的META-INF/web-resources(这个是spring-webflow 中一些jar包中的资源文件)目录下查找。
第4步,添加对国际化资源文件的处理,代码如下:
- <mvc:interceptors>
- <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" />
- </mvc:interceptors>
- <bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"/>
- <bean id="messageSource"
- class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
- <property name="basenames">
- <array>
- <value>/WEB-INF/messages/globalMessage</value>
- <value>/WEB-INF/messages/validationMessage</value>
- </array>
- </property>
- <property name="defaultEncoding" value="UTF-8" />
- <property name="cacheSeconds" value="0" />
- </bean>
第5步,配置tiles
- <bean class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
- <property name="definitions">
- <list>
- <value>/WEB-INF/**/views.xml</value>
- </list>
- </property>
- </bean>
- <bean id="tilesViewResolver" class="org.springframework.js.ajax.AjaxUrlBasedViewResolver">
- <property name="viewClass" value="org.springframework.webflow.mvc.view.FlowAjaxTilesView" />
- </bean>
这里tilesViewResolver 使用的spring web flow中提供的实现类。
下面来讲讲spring web flow 需要配置的一些东西,我们把这些配置单独放到一个文件中,叫做mvc-webflows.xml,方便管理,最后会在mvc-servlet.xml中include这个spring web flow 的配置文件。
第1步,配置flow-registry,顾名思义,这是spring-webflow流的注册入口,代码如下:
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:webflow="http://www.springframework.org/schema/webflow-config"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
- http://www.springframework.org/schema/webflow-config
- http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.3.xsd">
- <webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices"
- base-path="/WEB-INF/views">
- <webflow:flow-location-pattern value="/**/*-flow.xml" />
- </webflow:flow-registry>
在这里要着重讲几点:首先这个base-path,表示所有的flow都是在/WEB-INF/views目录下,flow-location-pattern 这个主要是用来查找各个流定义文件,关于流(flow)的id的确定,有以下两种分配算法,如果base-path存在,那么流的id就是从base-path到流的定义文件之间的目录路径,比如说流的定义文件为/WEB-INF/views/hotels/booking/booking-flow.xml,而base-path是/WEB-INF/views,所以flow的id就为hotels/booking.如果base-path不存在或者流的定义文件就在base-path目录下,那么这时flow的id就为流的定义文件名减去后缀(这里我们定义的后缀为-flow.xml),比如说我们的流定义文件叫booking-flow.xml,那么这时flow的id就为booking。
第2步,配置flow executor,流执行器
- <webflow:flow-executor id="flowExecutor" flow-registry="flowRegistry">
- <webflow:flow-execution-listeners>
- <webflow:listener ref="securityFlowExecutionListener" />
- </webflow:flow-execution-listeners>
- </webflow:flow-executor>
这里flow-execution-listeners子元素是用来跟spring security 进行集成的,稍后会谈到。
第3步,补齐flow-registry的flow-builder-services属性的相关依赖配置。代码如下:
- <webflow:flow-builder-services id="flowBuilderServices"
- view-factory-creator="mvcViewFactoryCreator"
- development="true"
- validator="validator" />
- <!-- Installs a listener to apply Spring Security authorities -->
- <bean id="securityFlowExecutionListener"
- class="org.springframework.webflow.security.SecurityFlowExecutionListener" />
- <!-- Configures Web Flow to use Tiles to create views for rendering -->
- <bean id="mvcViewFactoryCreator"
- class="org.springframework.webflow.mvc.builder.MvcViewFactoryCreator">
- <property name="viewResolvers">
- <list>
- <ref bean="tilesViewResolver" />
- </list>
- </property>
- <property name="useSpringBeanBinding" value="true" />
- </bean>
- <bean id="validator"
- class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
简单说一下webflow:flow-builder-services 几个属性的含义,view-factory-creator表示使用的是哪个视图工厂,validator是用来在处理页面流的过程使用的是JSR303的验证,bean id为validator就是spring对jsr303的支持类。development可以扫描flow定义文件的变化,开发时这么用比较好。
好了,spring web flow 的定义文件就这么多了,下面来将spring mvc与spring web flow 集成。代码如下:
- <!-- Spring MVC Integration With Spring Web Flow -->
- <!-- 1.Registering the FlowHandlerAdapter, Enables FlowHandler URL mapping -->
- <bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter">
- <property name="flowExecutor" ref="flowExecutor" />
- </bean>
- <!-- 2.Defining flow mappings, Maps request paths to flows in the flowRegistry -->
- <bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping">
- <property name="flowRegistry" ref="flowRegistry" />
- <property name="order" value="-1" />
- </bean>
ok,spring mvc 和spring web flow 集成完毕,下面加入spring security 3.
第1步,当然是配置web.xml文件了,代码如下:
- <!-- Enables Spring Security -->
- <filter>
- <filter-name>springSecurityFilterChain</filter-name>
- <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>springSecurityFilterChain</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
注意这里的filter-name可不是随便起的,这个name为成为spring security filter chain中的一个filter相匹配上,从而完成spring security的功能。注意到filter-class只是spring-web中的一个类而已,并不是spring-security中的类。
第2步,配置spring-security,applicationContext-security.xml,全部配置如下:
- <?xml version="1.0" encoding="UTF-8"?>
- <beans:beans xmlns="http://www.springframework.org/schema/security"
- xmlns:beans="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
- http://www.springframework.org/schema/security
- http://www.springframework.org/schema/security/spring-security-3.1.xsd">
- <http security="none" pattern="/resources/**" />
- <http auto-config="true" use-expressions="true">
- <intercept-url pattern="/sec/**" access="hasRole('ROLE_USER')" />
- <form-login login-page="/login"
- login-processing-url="/loginProcess"
- authentication-failure-url="/login?error=1"
- default-target-url="/" />
- <remember-me key="SpringWebFlowTutorial-rmkey-BUUttZnBJCa#U=4Dwg@%5_ptCC8wHtlY"
- services-ref="ipTokenBasedRememberMeService" />
- <logout logout-url="/logout" logout-success-url="/" invalidate-session="true" />
- </http>
- <authentication-manager alias="authenticationManager">
- <authentication-provider user-service-ref="customJdbcDao">
- <password-encoder ref="passwordEncoder">
- <salt-source ref="saltSource" />
- </password-encoder>
- </authentication-provider>
- </authentication-manager>
- <beans:bean id="customJdbcDao" class="org.leochen.samples.dao.impl.CustomJdbcDaoImpl">
- <beans:property name="dataSource" ref="dataSource" />
- <beans:property name="enableGroups" value="true" />
- <beans:property name="enableAuthorities" value="false" />
- </beans:bean>
- <!-- the property of 'key' must be have -->
- <beans:bean id="ipTokenBasedRememberMeService"
- class="org.leochen.samples.web.security.IPTokenBasedRememberMeService">
- <beans:property name="userDetailsService" ref="customJdbcDao" />
- <beans:property name="key"
- value="SpringWebFlowTutorial-rmkey-BUUttZnBJCa#U=4Dwg@%5_ptCC8wHtlY" />
- <beans:property name="tokenValiditySeconds" value="1209600" />
- <beans:property name="parameter" value="_remember_me" />
- <beans:property name="cookieName" value="LOGIN_REMEMBER_ME" />
- </beans:bean>
- </beans:beans>
spring security 可以参考spring security的文档和 Spring Security 3这本书来参考学习,很有帮助。
这样基本配置就这么着了,对于那些datasource,jpa 以及spring-jpa的配置会在附件中的代码中给出,附件中的代码可以完整运行。当然了,要熟悉maven。
下面说一下一个流的基本定义图,如下图:
还是看图一目了然吧,下面是flow定义文件,/WEB-INF/views/hotels/booking/booking-flow.xml,如下:
- <?xml version="1.0" encoding="UTF-8"?>
- <flow xmlns="http://www.springframework.org/schema/webflow"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://www.springframework.org/schema/webflow
- http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
- <!--notice: 'attributes' property cann't use the form like hasRole('ROLE_USER'),
- it's just the role name -->
- <secured attributes="ROLE_USER"/>
- <var name="searchCriteria" class="org.leochen.samples.web.controllers.hotels.SearchCriteria"/>
- <input name="hotelId" required="true"/>
- <on-start>
- <evaluate expression="bookingService.createBooking(hotelId,currentUser.name)"
- result="flowScope.booking"/>
- </on-start>
- <view-state id="enterBookingDetails" model="booking">
- <binder>
- <binding property="checkinDate" />
- <binding property="checkoutDate" />
- <binding property="beds" />
- <binding property="smoking" />
- <binding property="creditCard" />
- <binding property="creditCardName" />
- <binding property="creditCardExpiryMonth" />
- <binding property="creditCardExpiryYear" />
- <binding property="amenities" />
- </binder>
- <on-render>
- <render fragments="main"/>
- </on-render>
- <transition on="submit" to="reviewBooking"/>
- <transition on="cancel" to="bookingCancelled" bind="false" />
- </view-state>
- <view-state id="reviewBooking">
- <on-render>
- <render fragments="main"/>
- </on-render>
- <transition on="confirm" to="bookingConfirmed">
- <evaluate expression="bookingService.save(booking)" />
- </transition>
- <transition on="revise" to="enterBookingDetails"/>
- <transition on="cancel" to="bookingCancelled"/>
- </view-state>
- <end-state id="bookingConfirmed" />
- <end-state id="bookingCancelled" />
- </flow>
下面贴上E-R图,看得直观,如下:
最后,想说几点关于jsr303验证的几个注意点,使用hibernate validator(jsr 303的实现)为spring mvc提供验证
的时候,需要把org.hibernate.validator包下面的ValidationMessages.properties文件拷贝到类路径下,这样就可以
自定义验证消息了,对于spring web flow 的验证,只需在flow定义文件的相同目录下定义messages.properties文件
就可以添加验证消息了,spring web flow 对于验证消息key的生成遵循这么一个约定,key值有3部分组成,第一部分是model的名称,比如booking,第二部分是model的property,比如booking的checkinDate属性,第三部分是error
code,举个全的例子,booking.checkinDate.NotNull=,booking.creditCardExpiryMonth.typeMismatch=
Spring3.1新特性,配置JPA无需persistence.xml描述文件,SpringWebFlowTutorial-without-persistence.xml.7z这个是修改后的打包文件,里面还将SpringWebFlowTutorial.sql文件中的booking_amenities表的amenity 字段长度增大些,一个小bug修正了一下。原先的版本在应用服务器glassfish中还不能部署成功,是因为spring提供的
LocalContainerEntityManagerFactoryBean与Java EE应用服务器有些冲突,在没有Spring3.1新特性之前,
可以有其他的解决方案,具体请参考Spring官方文档。现在spring不用persistence.xml文件了,就可以避免掉冲突了。