Spring web flow how to add flash attribute when exiting from flow to external redirect

自作多情 提交于 2019-12-10 22:47:16

问题


I'm new to Spring Web Flow (2.3.1) and this is what I'm trying to do:

I have a JSP page which lists a paginated table of Books at the bottom of the page for the Author selected at the top of the page. I have a button/link at the top 'Add Book' (just below the Author drop-down) clicking which launches a Spring web flow that takes to a page where user can enter details of the Book in steps 1, 2 & 3 (in 3 different views/pages). Clicking Save creates the new Book and should take the user back to the view with paginated list of books.

Now, I want to add a flash attribute (success message) after the Save action and take user back to Books page (paginated), have the 'previous' author pre-selected and show the success message.

I have the following the web flow XML for the end state:

<end-state id="home" view="externalRedirect:/books/" >
    <output name="author" value="book.author" />
</end-state>

The reason I'm doing the externalRedirect is that I want the URL to read as if the user just clicked on the Books listing page after adding a new Book. If I don't do the redirect but instead point to the view name from tiles.xml I see the flash message correctly but the URL still shows the web flow e.g., ?execution=e1s1. In both cases the author is not automatically selected.

How do I preserve the flash success message AND the Author selection after a redirect?

Does output variable have any meaning in an external redirect?

I'm also setting the following in the Save action:

    requestContext.getFlowScope().put("authorId", book.getAuthorId());

回答1:


the 'output' tag is meant to be used as a container to transfer pojos between subflows flows and the parent flow callers. However, the following enhancement request:

https://jira.spring.io/browse/SWF-1561

expanded the role of the 'output' tag to allow the transfer of flash variables from the 'end-state' of a webflow to a spring MVC controller's flashMap.

Unfortunately, this enhancement did NOT include what you want to achieve which is passing variables via an externalRedirect from flow A -> new Flow A. So as with any 3rd party lib that doesn't have the desired functionality... we're going to have to 'hack' it.

1. First you need to have your FlowHandlerAdapter configured like this to utilize the enhancement above: the "saveOutputToFlashScopeOnRedirect" set to true is the important part for this discussion (set to false by default).

<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter">
    <property name="flowExecutor" ref="flowExecutor"/>
    <property name="saveOutputToFlashScopeOnRedirect" value="true"/>
</bean> 

2. You will need to create (extend) and configure a FlowExecutionListenerAdapter.

import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.servlet.support.RequestContextUtils;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.core.collection.SharedAttributeMap;
import org.springframework.webflow.definition.FlowDefinition;
import org.springframework.webflow.execution.FlowExecutionListenerAdapter;
import org.springframework.webflow.execution.RequestContext;

public class TestFlowExecutionListenerAdapter extends FlowExecutionListenerAdapter{
    @Override
    public void sessionCreating(RequestContext context, FlowDefinition definition) {

        MutableAttributeMap<Object> flashScopeWebFlow = context.getFlashScope();
        MutableAttributeMap<Object> flashMapWebFlow = context.getFlowScope();

        SharedAttributeMap<Object> sessionMap = context.getExternalContext().getSessionMap();
        MutableAttributeMap<Object> requestMap = context.getExternalContext().getRequestMap();

        HttpServletRequest request = (HttpServletRequest)context.getExternalContext().getNativeRequest();
        Map<String, ?> flashMapMvc = RequestContextUtils.getInputFlashMap(request);

        if(flashMapMvc != null)
            putAllFlashMapToFlashScope(flashMapMvc,flashScopeWebFlow);   
        System.out.println("here");
    }

    public static void putAllFlashMapToFlashScope(Map<String, ?> map, MutableAttributeMap<Object> mutableAttributeMap) {
        for( Entry<String, ?> entry : map.entrySet()) {
            mutableAttributeMap.put(entry.getKey(), entry.getValue());
        }
    }
} 

And init the bean like this:

    <webflow:flow-executor id="flowExecutor" flow-registry="flowRegistry">
        <webflow:flow-execution-listeners>
            <webflow:listener ref="testFlowExecutionListenerAdapter" />          
        </webflow:flow-execution-listeners>
    </webflow:flow-executor>

<bean id="testFlowExecutionListenerAdapter" class="com.foo.bar.flowexeclisteners.TestFlowExecutionListenerAdapter"/>

Note: Change "com.foo.bar.flowexeclisteners" to your actual package path.

3. The FlowExecutionListenerAdapter above allows to to monitor life cycle events of Spring Webflow framework by @Overriding certain methods. In our case, I chose to @Override the sessionCreating() method but you can use from many different methods to meet your use case or to increase efficiency.

http://docs.spring.io/spring-webflow/docs/current/api/org/springframework/webflow/execution/FlowExecutionListenerAdapter.html

So given the above configuration we can now begin passing back variables in 2 ways. One via sessionMap or Two via the 'output' tag which is now configured to be stored in the flashMap (of Spring MVC not flashScope). Here is an example 'end-state' demonstrating this.

  <end-state id="home" view="externalRedirect:/books/">
        <on-entry>
            <evaluate expression="externalContext.sessionMap.put('author', 'William Brian Jennings')"/>
        </on-entry>
        <output name="responseMsg" value="'Added author to the sessionMap'"/>
  </end-state> 

4. When this 'end-state' is triggered (if you place a break point inside our FlowExecutionListenerAdapter) you will see that flashMapMvc will hold your 'responseMsg' variable and that 'author' variable will be in the sessionMap. The static method 'putAllFlashMapToFlashScope' will automatically make your 'responseMsg' avaliable for consumption by your view but for the sessionMap you will need to explicitly extract your variable out in your receiving flow like so:

<set name="flowScope.author" value="externalContext.sessionMap.get('author')"/>

A few notes here:

  1. The hacky part of all this is we have to convert the flashMapMvc to flashScopeWebFlow because they are not compatible (2 different containers). I used a static method 'putAllFlashMapToFlashScope()' within the FlowExecutionListenerAdapater class to demonstrate this. I don't think this is a good practice but I just did it here for brevity so you can see what exactly what the issues are and how to solve them.

  2. sessionMap variables will stay during the entire session and across all flows. If you use this map becareful of this point.

  3. I included other (unused) maps in the FlowExecutionListenerAdapter to demonstrate what you have access to.

  4. Obviously this is a hack solution and what really needs to happen is an enhancement request to achieve what your trying to do (flow A ends) -> pass output back to -> (new flow A) via external redirect.

Because of issues like this I've personally stopped using WebFlow for all use cases and have limited it only to simple cases (i.e page A -> Page B -> page C) and now I use Spring MVC exclusively for all other use cases.

Sorry for the long answer but this is not a trivial issue :)



来源:https://stackoverflow.com/questions/28182255/spring-web-flow-how-to-add-flash-attribute-when-exiting-from-flow-to-external-re

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!