Spring Web Flow 2简化页面流的开发,结合Spring MVC更俊,Spirng Security 3添加安全机制

  闲来无事,看看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。

    下面说一下一个流的基本定义图,如下图:

 

Spring Web Flow 2简化页面流的开发,结合Spring MVC更俊,Spirng Security 3添加安全机制_第1张图片

     还是看图一目了然吧,下面是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图,看得直观,如下:

 


Spring Web Flow 2简化页面流的开发,结合Spring MVC更俊,Spirng Security 3添加安全机制_第2张图片
   最后,想说几点关于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文件了,就可以避免掉冲突了。

 

你可能感兴趣的:(spring,spring,spring,Web,mvc,Security,jpa,flow,2,3,Spring-JPA)