GenericApplicationContext cannot be cast to WebApplicationContext : Spring Web Flow

3.1k views Asked by At

I'm trying to set up Spring Web Flow using only Java annotations in a Spring environment that also uses only Java annotations. However when I attempt to access my flow I get the following exception

SEVERE: Servlet.service() for servlet [dispatcher] in context with path [/forms] threw exception [Request processing failed; nested exception is org.springframework.webflow.execution.FlowExecutionException: Exception thrown in state 'referral' of flow 'test-flow'] with root cause
java.lang.ClassCastException: org.springframework.context.support.GenericApplicationContext cannot be cast to org.springframework.web.context.WebApplicationContext
at org.springframework.web.servlet.support.RequestContext.initContext(RequestContext.java:235)
at org.springframework.web.servlet.support.RequestContext.<init>(RequestContext.java:202)
at org.springframework.web.servlet.view.AbstractView.createRequestContext(AbstractView.java:316)
at org.springframework.web.servlet.view.AbstractView.createMergedOutputModel(AbstractView.java:296)
at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:265)
at org.springframework.webflow.mvc.servlet.ServletMvcView.doRender(ServletMvcView.java:55)
at org.springframework.webflow.mvc.view.AbstractMvcView.render(AbstractMvcView.java:196)
at org.springframework.webflow.engine.ViewState.render(ViewState.java:293)
at org.springframework.webflow.engine.ViewState.refresh(ViewState.java:242)
at org.springframework.webflow.engine.ViewState.resume(ViewState.java:220)
at org.springframework.webflow.engine.Flow.resume(Flow.java:537)
at org.springframework.webflow.engine.impl.FlowExecutionImpl.resume(FlowExecutionImpl.java:259)
at org.springframework.webflow.executor.FlowExecutorImpl.resumeExecution(FlowExecutorImpl.java:169)
at org.springframework.webflow.mvc.servlet.FlowHandlerAdapter.handle(FlowHandlerAdapter.java:228)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:938)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:870)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:961)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:852)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:624)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:731)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:957)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:423)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1079)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:620)
at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2476)
at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2465)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)

I've traced the source of the error to the instantiation of a RequestContext object, which is passed to views in order for them to access the ApplicationContext (among other things). Since Spring is able to invoke my flows I'm lead to believe that this exception is being thrown due to a bad configuration somewhere in my Web Flow files. I've reviewed them many times and can't find anything that seems glaringly obvious. You can find my Web Flow configuration files below:

DispatcherConfig

@Configuration
@ComponentScan(basePackages = "----")
public class DispatcherConfig {

    @Autowired
    private FlowDefinitionRegistry flowDefinitionRegistry;
    @Autowired
    private FlowExecutor flowExecutor;
    @Autowired
    private AnnotationConfigWebApplicationContext appContext;

    @Bean
    public FlowHandlerAdapter flowHandlerAdapter()
    {
        FlowHandlerAdapter flowHandlerAdapter = new FlowHandlerAdapter();
        flowHandlerAdapter.setFlowExecutor(flowExecutor);
        return flowHandlerAdapter;
    }

    @Bean
    public FlowHandlerMapping flowHandlerMapping()
    {
        FlowHandlerMapping mapping = new FlowHandlerMapping();
        mapping.setFlowRegistry(flowDefinitionRegistry);
        mapping.setApplicationContext(appContext);
        mapping.setOrder(-1);
        return mapping;
    }
} 

WebFlowConfig

@Configuration
public class WebFlowConfig extends AbstractFlowConfiguration {

    @Autowired
    private List<ViewResolver> viewResolvers;
    @Autowired
    private ConversionService conversionService;
    @Autowired
    private AnnotationConfigWebApplicationContext appContext;

    @Bean
    public FlowDefinitionRegistry flowRegistry()
    {
        return getFlowDefinitionRegistryBuilder()
            .setBasePath("/WEB-INF/flows")
            .addFlowLocationPattern("/**/*-flow.xml")
            .setFlowBuilderServices(getFlowBuilderServices())
            .build();
    }

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

    public ExpressionParser getExpressionParser()
    {
        SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
        return new WebFlowSpringELExpressionParser(spelExpressionParser);
    }

    public FlowBuilderServices getFlowBuilderServices()
    {
        MvcViewFactoryCreator creator = new MvcViewFactoryCreator();
        creator.setViewResolvers(viewResolvers);
        creator.setApplicationContext(appContext);
        creator.setUseSpringBeanBinding(true);

        FlowBuilderServices services = new FlowBuilderServices();
        services.setViewFactoryCreator(creator);
        services.setConversionService(conversionService);
        services.setExpressionParser(getExpressionParser());
        return services;
    }
}

WebMvcConfig

@EnableWebMvc
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Bean
    public UrlBasedViewResolver velocityViewResolver()
    {
        UrlBasedViewResolver resolver = new VelocityViewResolver();
        resolver.setSuffix(".vm");
        resolver.setViewClass(VelocityView.class);
        return resolver;
    }

    @Bean
    public VelocityConfigurer velocityConfig()
    {
        VelocityConfigurer velocityConfigurer = new VelocityConfigurer();
        velocityConfigurer.setResourceLoaderPath("/templates/views/");
        return velocityConfigurer;
    }

    @Bean
    public List<ViewResolver> viewResolvers()
    {
        List<ViewResolver> resolvers = new ArrayList<>();

        resolvers.add(velocityViewResolver());

        return resolvers;
    }
}

The view technology I'm using is Velocity. Spring Webflow version 2.4.1.RELEASE, Spring version 4.1.2.RELEASE, Java 1.8.0_45, Tomcat 7.0.62.

Any ideas what may be causing this exception?

UPDATE: Fixed

Thanks to Roman for providing the answer. I made my FlowBuilderServices and MvcViewFactoryCreator @Bean and this got rid of the ClassCastException. However, there were still some problems with my configuration. My @Autowired List<ViewResolver> viewResolvers was not being instantiated until after the MvcViewFactoryCreator was. Thus I had to make a method that creates the ViewResolver list for the MvcViewFactoryCreator. I'm looking into ways to get the order of creation correct, but for now this works. Below are the changes I had to make to get the Web Flow functioning properly

WebFlowConfig

@Configuration
public class WebFlowConfig extends AbstractFlowConfiguration {
    @Autowired
    ===
    -private List<ViewResolver> viewResolvers;
    ====
    +private UrlBasedViewResolver viewResolver
    ===

    ...

    @Autowired
    ===
    -private AnnotationConfigWebApplicationContext appContext;
    ===
    +private MvcViewFactoryCreator mvcViewFactoryCreator;
    ===

    ...

    +public List<ViewResolver> getViewResolvers()
    +{
    +    List<ViewResolver> resolvers = new ArrayList<>();
    +    resolvers.add(viewResolver);
    +
    +    return resolvers;
    +}

    ...

    +@Bean
    +public MvcViewFactoryCreator mvcViewFactoryCreator()
    +{
    +    MvcViewFactoryCreator creator = new MvcViewFactoryCreator();
    +    creator.setViewResolvers(getViewResolvers());
    +    creator.setUseSpringBeanBinding(true);
    +    return creator;
    +}

    @Bean
    public FlowBuilderServices flowBuilderServices()
    {
        ===
        -MvcViewFactoryCreator creator = new MvcViewFactoryCreator();
        -creator.setViewResolvers(viewResolvers);
        -creator.setApplicationContext(appContext);
        -creator.setUseSpringBeanBinding(true);
        ===

        FlowBuilderServices services = new FlowBuilderServices();
        ===
        -services.setViewFactoryCreator(creator);
        ===
        +services.setViewFactoryCreator(mvcViewFactoryCreator);
        ===
        services.setConversionService(conversionService);
        services.setExpressionParser(getExpressionParser());
        return services;
     }
}
1

There are 1 answers

0
Roman On BEST ANSWER

FlowBuilderServices is meant to be a Spring-managed bean, but in your config it is just a new instance. It likes to be ApplicationContextAware and InitializingBean, but that is gonna work only if managed by Spring.

The solution is simple: put @Bean on getFlowBuilderServices() method. And I think you should also make MvcViewFactoryCreator a separate @Bean and not use its setApplicationContext() manually.