Spring Web Flow —— 配置 - 001

  1. 本文基於Spring Web Flow 2.4.5,其它版本配置方式可能略有不同,請參考相應版本的官方文檔

Maven依賴

maven庫查詢推薦地址:http://mvnrepository.com/

<dependency>
    <groupId>org.springframework.webflow</groupId>
    <artifactId>spring-webflow</artifactId>
    <version>2.4.5.RELEASE</version>
</dependency>

Web Flow嵌入到Spring MVC工作流簡介

請求被DispatcherServlet攔截 -> 分發flow進行處理,返回view -> viewResolver解析 -> 返回請求


配置項預覽

  • FlowRegistry:必須,註冊流程,指明流程配置文件所在位置;指定流程id(用於請求訪問標識);指定流程屬性;此外還可以傳入FlowBuilderServices進行更多個性化配置
  • FlowBuilderServices:必須,用於設定流程配置文件中EL表達式的解析器、form屬性綁定時的轉換器、view-state的view解析器等,很重要
  • FlowExecutor:必須,用於執行流程,可指定執行監聽器(可選,常用於流程安全和持久化)
  • FlowHandlerAdapter:必須,用於適配Spring MVC,配置時傳入FlowExecutor
  • FlowHandlerMapping:必須,用於將請求映射到對應的flow,配置時傳入FlowRegistry

配置I - 註冊流程 - FlowRegistry

FlowRegistry用於註冊流程實例,指定流程位置和流程id,並可自定義流程創建相關內容

註冊flow的各種方式

  • 直接指定流程位置
    默認情況下,web-flow的id爲其文件名減去後綴名,如下配置的id爲booking。指定了基地址或使用了通配符時除外。
<webflow:flow-location path="/WEB-INF/flows/booking/booking.xml" />
// 註冊了一個路徑爲/WEB-INF/flows/booking/booking.xml的流程,其餘爲默認配置。
  • 自定義id
<webflow:flow-location path="/WEB-INF/flows/booking/booking.xml" id="bookHotel" />
  • 定義流程屬性
    如下定義了一個帶有屬性caption,其值爲”Books a hotel”的流程。屬性的使用方法暫時不瞭解
<webflow:flow-location path="/WEB-INF/flows/booking/booking.xml">
    <webflow:flow-definition-attributes>
        <webflow:attribute name="caption" value="Books a hotel" />
    </webflow:flow-definition-attributes>
</webflow:flow-location>
  • 使用通配定義流程位置
    使用該方法並沒有正確實驗出id,這點作爲參考
<webflow:flow-location-pattern value="/WEB-INF/flows/**/*-flow.xml" />
  • 使用基地址
    使用基地址的flow的id爲其path屬性減去文件名,如下配置的id爲/hotels/booking;如果path中沒有路徑信息,只有文件名,則id爲文件名減去後綴。
<webflow:flow-registry id="flowRegistry" base-path="/WEB-INF">
    <webflow:flow-location path="/hotels/booking/booking.xml" />
</webflow:flow-registry>

flow id屬性總結

  1. id的作用
    id用於請求定位到某個確切的flow,如當請求路徑爲http://localhost:8090/Floyd/search-flow,其中Floyd是項目名,如果有id爲search-flow的flow存在,則會訪問該flow
  2. id的定義
    • 沒有基地址或通配符時,flow的id爲文件名減去後綴,如<webflow:flow-location path="/WEB-INF/flows/booking/booking.xml" />這裏的id爲booking
    • 有基地址時,id爲path的值減去文件名,如基地址爲/WEB-INF, path=/flows/booking/booking.xml時,id被確定爲flows/booking
    • 有通配符時,該情況比較特殊,按照官方說明並沒有驗證通過,這裏略過。
      當顯式指定了id屬性時,則使用指定的id。推薦自定義id

FlowRegistry繼承

FlowRegistry是可以繼承的,可以定義一個公用的註冊器,在多個子註冊器中繼承該註冊器

<!-- my-system-config.xml -->
<webflow:flow-registry id="flowRegistry" parent="sharedFlowRegistry">
    <webflow:flow-location path="/WEB-INF/flows/booking/booking.xml" />
</webflow:flow-registry>

<!-- shared-config.xml -->
<webflow:flow-registry id="sharedFlowRegistry">
    <!-- Global flows shared by several applications -->
</webflow:flow-registry>

配置II - 使用FlowBuilder services

使用FlowBuilder Services可以在build流程時自定義服務,比如視圖解析器、EL表達式解析器、類型格式化和轉換服務等。如無顯式設定FlowBuilder Services,系統將使用默認實現。

<webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices">
    <webflow:flow-location path="/WEB-INF/flows/booking/booking.xml" />
</webflow:flow-registry>

<webflow:flow-builder-services id="flowBuilderServices"
    conversion-service="conversionService"
    expression-parser="expressionParser"
    view-factory-creator="viewFactoryCreator" />

<bean id="conversionService" class="..." />
<bean id="expressionParser" class="..." />
<bean id="viewFactoryCreator" class="..." />

自定義視圖解析器 view-factory-creator

view-factory-creator用於視圖解析工作,默認的creator可以支持Spring MVC支持的幾種視圖類型:JSP, Velocity,FreeMarker等。

<webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices">
    <webflow:location path="/WEB-INF/hotels/booking/booking.xml" />
</webflow:flow-registry>

<!-- 尤其注意這裏的development,設置爲true時表示開啓開發模式,該模式下會在flow定義改變時自動re-load,甚至flow中引用的resource bundle發生了變化都會熱重載 -->
<webflow:flow-builder-services id="flowBuilderServices" view-factory-creator="mvcViewFactoryCreator" development="true"/>

<bean id="mvcViewFactoryCreator" class="org.springframework.webflow.mvc.builder.MvcViewFactoryCreator">
    <!-- myExistingViewResolverToUseForFlows就是我們常用的視圖解析器,注意這裏引用的是一個列表 -->
    <property name="viewResolvers" ref="myExistingViewResolverToUseForFlows"/>
</bean>

... ... 
... ...
<!-- 做驗證時使用的是javaconfig的形式,這種形式並沒有驗證過 -->
<property name="myExistingViewResolverToUseForFlows">
    <list>
        <ref bean="viewResolver" />
    </list>
</property>

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
   <property name="prefix" value="/WEB-INF/"/>
   <property name="suffix" value=".jsp"/>
</bean>

自定義表達式解析器 expression-parser

expression-parser用於定義表達式解析器,默認的解析器使用邏輯:當類路徑下有Unified EL解析器時,則使用;沒有時,則使用OGNL表達式解析器(官方文檔在EL表達式一章和系統設置一章關於默認表達式解析說法有出入,自己認爲比較可信的是Spring Web Flow 2.1以後,默認使用Spring EL表達式解析器)。
下面是手動配置成Unified EL解析器的方式:

<webflow:flow-builder-services expression-parser="expressionParser"/>

<bean id="expressionParser" class="org.springframework.webflow.expression.el.WebFlowELExpressionParser">
    <constructor-arg>
        <bean class="org.jboss.el.ExpressionFactoryImpl" />
    </constructor-arg>
</bean>

自定義類型轉換器 conversion-service

詳細內容參見本系列視圖渲染相關章節或官方文檔

<webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices" ... />

<webflow:flow-builder-services id="flowBuilderServices" conversion-service="defaultConversionService" ... />

<bean id="defaultConversionService" class="org.springframework.binding.convert.service.DefaultConversionService">
    <constructor-arg ref="applicationConversionSevice"/>
</bean>

<bean id="applicationConversionService" class="somepackage.ApplicationConversionServiceFactoryBean">
public class ApplicationConversionServiceFactoryBean extends FormattingConversionServiceFactoryBean {
    @Override
    protected void installFormatters(FormatterRegistry registry) {
        // ...
    }
}

配置III - 發佈流程執行器 - FlowExecutor

FlowExecutor用於執行flow並管理flow執行過程,常見的自定義項有:設置監聽器,監聽流程執行過程並作出相應,如security監聽器,用於監聽並控制流程的訪問權限;調整flow的部分持久化選項等。

  • 註冊監聽器
<!-- 這裏是爲特定的flow應用該監聽器,當不設criteria屬性時,將對所有flow應用 -->
<webflow:flow-execution-listeners>
    <webflow:listener ref="securityListener" criteria="securedFlow1,securedFlow2"/>
</webflow:flow-execution-listeners>
  • 調整持久化參數
    • max-executions: 設定爲每個用戶保留的執行數(沒錯,就是執行數),當超過該數量時,最先的那個執行會被清除。(這裏的執行,我認爲是一個新開的且處於激活狀態下的flow實例)
    • max-execution-snapshots: 設定每個執行保留的最大快照數。不允許保留時,設爲0。允許無線保留時,設爲-1。(快照用於瀏覽器的返回按鈕)
<webflow:flow-executor id="flowExecutor" flow-registry="flowRegistry">
    <webflow:flow-execution-repository max-executions="5" max-execution-snapshots="30" />
</webflow:flow-executor>

配置IV - Spring MVC集成

基礎配置(將請求轉發給flow)

  • Spring MVC基礎配置
<servlet>
    <servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/web-application-config.xml</param-value>
    </init-param>
</servlet>

<servlet-mapping>
    <servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
    <!-- 不能使用類型/*的通配符進行匹配,否則會出現jsp不解析直接回發給瀏覽器的情況 -->
    <url-pattern>/</url-pattern>
</servlet-mapping>

配置IV - FlowHandlerAdapter

該步驟是使得Web Flow能夠適配Spring MVC
當一個對flow的請求被接收到時,FlowHandlerAdapter會決定是啓動一個全新的flow還是繼續之前的流程(如從一個view-state跳轉到下一個state就屬於繼續之前的流程),繼續之前的flow是需要在請求有相關信息才行。有關這一方面,web flow默認有如下設定:
  1.Http請求參數在任何情況下都是可以使用的
  2.當一個flow執行結束,且結束時沒有想瀏覽器發送最後的響應時,默認的handler會嘗試在同一個request中啓動一個新的flow執行
  3.除了NoSuchFlowExecutionException異常外,所有其它異常都會以冒泡的方式拋到Dispatcher中。這是因爲默認的handler會嘗試自動從該異常中恢復過來,恢復的方式是新開一個全新的flow執行
針對大多數設定,都可以通過實現FlowHandlerAdapter類的子類進行自定義。

<!-- Enables FlowHandler URL mapping -->
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter">
    <!-- flowExecutor就是前文聲明的執行器 -->
    <property name="flowExecutor" ref="flowExecutor" />
</bean>

配置V - FlowHandlerMapping

<!-- Maps request paths to flows in the flowRegistry;
    e.g. a path of /hotels/booking looks for a flow with id "hotels/booking" -->
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping">
    <!-- flowRegistry就是前文聲明的註冊器 -->
    <property name="flowRegistry" ref="flowRegistry"/>
    <property name="order" value="0"/>
</bean>

有了Flow的基本配置和這裏的兩個Spring MVC的集成配置,請求就能夠映射到flow中了。接下來就是寫Flow了。想要快速上手的可忽略本文後面的內容,轉而直接看本系列其它文章。


配置VI - 實現自定義的FlowHandler

  • FlowHandler講解
    FlowHandler可用於自定義flow在HTTP Servlert環境中執行的方式,FlowHandler在FlowHandlerAdapter中被使用,主要負責如下內容:

      - 返回flow的id以用於執行該flow
      - 在新流程開始時創建輸入
      - 在流程結束時處理流程的輸出
      - 在流程發生異常時,處理這些異常

    其主要方法如下:

public interface FlowHandler {

    public String getFlowId();

    public MutableAttributeMap createExecutionInputMap(HttpServletRequest request);

    public String handleExecutionOutcome(FlowExecutionOutcome outcome,
        HttpServletRequest request, HttpServletResponse response);

    public String handleException(FlowException e,
        HttpServletRequest request, HttpServletResponse response);
}

  該接口的直接實現爲AbstractFlowHandler,當我們想要自定義某個設定時,繼承該抽象類並重寫其中的方法即可。

  • 發佈一個FlowHandler
    一個FlowHandler負責一個flow,發佈方式是在Spring環境中聲明一個bean,該bean的name必須和我們想要處理的flow的id一致,配置好後,當方位該flow的id,則會定位到新發布的FlowHandler中,我們自定義的方法也就生效了。
<!-- 這裏的BookingHandler只是一個例子,實際用時替換成我們自己的Handler -->
<bean name="hotels/booking" class="org.springframework.webflow.samples.booking.BookingFlowHandler" />
  • 着重講一下FlowHandler中handleExecutionOutcome(…)方法
    該方法用於處理flow結束時產生的FlowExecutionOutcome(系統自動產生),我們常用它來在流程結束後進行重定向,比如
public class BookingFlowHandler extends AbstractFlowHandler {
    // handlExecutionOutcome()方法返回的String代表的是重定向的地址
    public String handleExecutionOutcome(FlowExecutionOutcome outcome,
                                        HttpServletRequest request, HttpServletResponse response) {
        if (outcome.getId().equals("bookingConfirmed")) {
            return "/booking/show";
        } else {
            return "/hotels/index";
        }
    }
}
// 當flow id爲bookinConfirmed時,重定向到/booking/show,否則重定向到/hotels/index

  默認情況下,該方法返回的地址是相關於當前Servlet的。但是我們也可以顯式地指定一些前綴來擴展重定向的範圍

   – servletRelative: - 相對於當前Servlet重定向
   – contextRelative: - 相對於當前應用重定向
   – serverRelative: - 相對於當前服務器的基地址重定向
   – http:// or https:// - 重定向到一個完整的URI地址

  相同的前綴同樣適用於聲明state時的view屬性配上externalRedirect的情況

<end-state view="externalRedirect:http://springframework.org"/>
<!-- 在流程結束時,跳轉到spring首頁 -->

—————————————————–手動分割線——————————————————————————————

一個能用的配置(採用Java config的方式配置)

WebInitializer(與web.xml作用類似)

public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return null;
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] {MainConfig.class, RegisterConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] {"/"};
    }

}

MainConfig(Spring的主要配置文件)

@Configuration
@EnableWebMvc
@ComponentScan("cn.floyd.pw")
public class MainConfig extends WebMvcConfigurerAdapter{

    @Bean(name="viewResolver")
    public ViewResolver getViewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/jsp/");
        resolver.setSuffix(".jsp");
        return resolver;
    }

    @Bean
    public MultipartResolver multipartResolver() throws IOException {
        CommonsMultipartResolver resolver = new CommonsMultipartResolver();
        return resolver;
    }

    // enable the static resource
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    @Bean
    public FlowHandlerAdapter flowHandlerAdapter(@Autowired FlowExecutor flowExecutor) {
        System.out.println("init flowHandlerAdapter");
        FlowHandlerAdapter adapter = new FlowHandlerAdapter();
        adapter.setFlowExecutor(flowExecutor);

        return adapter;
    }

    @Bean
    public FlowHandlerMapping flowHandlerMapping(@Autowired FlowDefinitionRegistry flowRegistry) {
        System.out.println("init flowHandlerMapping");
        FlowHandlerMapping mapping = new FlowHandlerMapping();
        mapping.setOrder(0);
        mapping.setFlowRegistry(flowRegistry);

        return mapping;
    }

}

RegisterConfig(配置FlowRegister和flowExecutor)

@Configuration
public class RegisterConfig extends AbstractFlowConfiguration {

    // 用於註冊flow
    @Bean("flowRegistry")
    public FlowDefinitionRegistry flowRegistry(@Autowired FlowBuilderServices flowBuilderServices) {

        return getFlowDefinitionRegistryBuilder()
                .setBasePath("/WEB-INF/jsp/flow")
                .addFlowLocation("/config/search-flow.xml")
                .setFlowBuilderServices(flowBuilderServices)
                .build();
    }

    @Bean
    public FlowBuilderServices flowBuilderServices(@Autowired MvcViewFactoryCreator mvcViewFactoryCreator) {
        return getFlowBuilderServicesBuilder()
                .setViewFactoryCreator(mvcViewFactoryCreator)
                .build();
    }

    @Bean
    public MvcViewFactoryCreator mvcViewFactoryCreator(@Autowired ViewResolver viewResolver) {
        MvcViewFactoryCreator creator = new MvcViewFactoryCreator();
        List<ViewResolver> list = new ArrayList<ViewResolver>();
        list.add(viewResolver);
        creator.setViewResolvers(list);

        return creator;
    }

    @Bean
    public FlowExecutor flowExecutor(@Autowired FlowDefinitionRegistry flowRegistry) {
        return getFlowExecutorBuilder(flowRegistry).build();
    }

}

配置時踩過的坑

flow id的坑

最開始對flow id的認識不夠,導致不知道到底該如何定位到flow,其實訪問方式就是 …/appName/flowId

Web MVC Environment null not supported

啓動時正常,訪問flow時報異常java.lang.IllegalStateException: Web MVC Environment null not supported
是在創建viewFactory時出錯,由於我沒有采用在config中聲明bean的方式創建MvcViewFactoryCreator,因此出現找不到mvc環境的問題,原來是spring在自動檢測並創建bean時,會同時設置該bean的環境,因此不能自己隨意採用new的方式創建這些配置類

JSP文件不解析

出現JSP文件不經過解析就直接傳送給了瀏覽器的問題(對如下闡述的原理並不是很清楚)
url-pattern爲”/*”時,能夠匹配到任何路徑,因此當controller返回.jsp文件時,也會被攔截,從而返回jsp源碼(這裏不是很理解,主要是跟自己認識的spring mvc處理流程有差別)。
url-pattern爲”/”時,只能夠匹配不帶後綴的路徑,因此jsp就不會被dispatcherServlet攔截,而是會被jspServlet攔截並處理。但是”/”在配置使能的情況下也能夠攔截並允許靜態資源的訪問

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章