Spring WebFlow(貌似)随机停止在Spring Boot应用程序中工作

时间:2014-11-27 11:01:51

标签: spring-boot spring-webflow spring-webflow-2

我有一个流程似乎工作正常,直到昨天,突然我开始在我的HTML页面中得到以下异常,映射到我的流程中的第一个状态:

org.springframework.expression.spel.SpelEvaluationException: EL1007E:(pos 0): Property or field 'flowScope' cannot be found on null

令人讨厌的代码行是:

<h3 th:text="${flowRequestContext.flowScope}"/>

进一步调查表明,没有任何流量变量可用。此外,如果我将print语句放入流程进行各种调用的Service中,我可以看到这些方法都不再被调用 - 就像流程根本就没有运行一样。

以前工作正常。我甚至将所有本地更改还原为以前稳定版本的代码,同样的问题也发生在那里。似乎暂时解决问题的唯一办法就是重新启动计算机 - 问题暂时消失但随后又回来了。

说实话,我完全没有想到可能导致这种间歇性问题的原因。我正在考虑在后台运行的过时Java进程干扰应用程序的未来运行,但是在部署之间检查并杀死任何剩余进程都无济于事。

我已将我希望的内容包含在下面的相关文件中。任何帮助解决这个问题都将非常感激。

checkout.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">

    <on-start>
        <set name="flowScope.paymentMethods" value="checkoutWidgetService.getPaymentMethods()"/>
        <set name="flowScope.deliveryAddress" value="checkoutWidgetService.getDeliveryAddress()"/>
        <set name="flowScope.sessionId" value="externalContext.nativeRequest.session.id"/>
    </on-start>

    <view-state id="payment-methods" view="payment-methods">
        <transition on="selectPaymentMethod" to="new-details">
            <evaluate expression="checkoutWidgetService.getCardDetails(requestParameters.type)" result="flowScope.cardDetails"/>
        </transition>
    </view-state>

    <view-state id="new-details" view="new-details">
        <transition on="submitDetails" to="summary">
            <evaluate expression="checkoutWidgetService.buildCardDetails(requestParameters)" result="flowScope.cardDetails"/>
        </transition>
    </view-state>

    <view-state id="summary" view="summary">
        <transition on="completeCheckout" to="redirect">
            <evaluate expression="checkoutWidgetService.completeCheckout(externalContext.nativeRequest.session, flowRequestContext, flowScope.cardDetails)"/>
        </transition>
        <transition on="cancelCheckout" to="redirect">
            <evaluate expression="checkoutWidgetService.cancelCheckout(externalContext.nativeRequest.session, flowRequestContext)"/>
        </transition>
    </view-state>

    <end-state id="redirect" view="externalRedirect:contextRelative:/payments/checkout-widgets/end"/>
</flow>

WebflowConfig.java

@Configuration
@AutoConfigureAfter(MvcConfig.class)
public class WebflowConfig extends AbstractFlowConfiguration {

    @Autowired
    private SpringTemplateEngine templateEngine;

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

    @Bean
    public FlowDefinitionRegistry flowRegistry() {
        return getFlowDefinitionRegistryBuilder(flowBuilderServices())
                .addFlowLocation("classpath:/templates/checkout.xml", "payments/checkout-widget/start")
                .build();
    }

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

    @Bean
    public FlowController flowController() {
        FlowController flowController = new FlowController();
        flowController.setFlowExecutor(flowExecutor());
        return flowController;
    }

    @Bean
    public FlowHandlerMapping flowHandlerMapping() {
        FlowHandlerMapping flowHandlerMapping = new FlowHandlerMapping();
        flowHandlerMapping.setFlowRegistry(flowRegistry());
        flowHandlerMapping.setOrder(-1);
        return flowHandlerMapping;
    }

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

    @Bean
    public AjaxThymeleafViewResolver thymeleafViewResolver() {
        AjaxThymeleafViewResolver viewResolver = new AjaxThymeleafViewResolver();
        viewResolver.setViewClass(FlowAjaxThymeleafView.class);
        viewResolver.setTemplateEngine(templateEngine);
        return viewResolver;
    }

    @Bean
    public MvcViewFactoryCreator mvcViewFactoryCreator() {
        List<ViewResolver> viewResolvers = new ArrayList<>();
        viewResolvers.add(thymeleafViewResolver());

        MvcViewFactoryCreator mvcViewFactoryCreator = new MvcViewFactoryCreator();
        mvcViewFactoryCreator.setViewResolvers(viewResolvers);
        mvcViewFactoryCreator.setUseSpringBeanBinding(true);
        return mvcViewFactoryCreator;
    }

}

CheckoutWidgetSessionMvcController.java

@Controller
@RequestMapping("/payments/checkout-widgets")
public class CheckoutWidgetSessionMvcController {

    @Inject
    private CheckoutWidgetService service;

    @RequestMapping(value = {"/start"}, method = RequestMethod.GET)
    public ModelAndView paymentMethods() {
        return new ModelAndView("payment-methods", null);
    }

    @RequestMapping(value = "/end", method = RequestMethod.GET)
    public String invalidateSession(HttpSession session) {
        service.invalidateSession(session);
        return "dummy-redirect-post";
    }
}

CheckoutWidgetService.java

public interface CheckoutWidgetService {

    List<PaymentMethod> getPaymentMethods();

    CardDetails getCardDetails(String name);

    CardDetails buildCardDetails(LocalParameterMap params);

    String getDeliveryAddress();

    void completeCheckout(HttpSession session, RequestContext context, CardDetails cardDetails);

    void cancelCheckout(HttpSession session, RequestContext context);

    void invalidateSession(HttpSession session);
}

CheckoutWidgetServiceImpl.java

@Service("checkoutWidgetService")
public class CheckoutWidgetServiceImpl implements CheckoutWidgetService {

    @Inject
    private CheckoutWidgetSessionService sessionService;

    private final List<PaymentMethod> paymentMethods = new ArrayList<>();

    private final String deliveryAddress;

    public CheckoutWidgetServiceImpl() {
        paymentMethods.add(new PaymentMethod("PayPal", "/images/paypal-logo.png")); 
        paymentMethods.add(new PaymentMethod("Mastercard", "/images/mc-logo.png"));
        paymentMethods.add(new PaymentMethod("Visa", "/images/visa-logo.png"));
        paymentMethods.add(new PaymentMethod("Amex", "/images/amex-logo.png"));
        paymentMethods.add(new PaymentMethod("Google Checkout", "/images/google-logo.png"));
        deliveryAddress = "xxxxx";
    }
    @Override
    public List<PaymentMethod> getPaymentMethods() {
        System.out.println("Returning paymentMethods: " + paymentMethods);
        return paymentMethods;
    }

    @Override
    public CardDetails getCardDetails(String name) {
        CardDetails cardDetails = new CardDetails();
        cardDetails.setCardType(name);
        return cardDetails;
    }

    @Override
    public CardDetails buildCardDetails(LocalParameterMap params) {
        CardDetails cardDetails = new CardDetails();
        cardDetails.setCardNumber(params.get("cardNumber"));
        cardDetails.setExpiryMonth(params.get("expiryMonth"));
        cardDetails.setExpiryYear(params.get("expiryYear"));
        cardDetails.setNameOnCard(params.get("nameOnCard"));
        cardDetails.setCvv2(params.get("cvv2"));
        return cardDetails;
    }

    @Override
    public String getDeliveryAddress() {
        return deliveryAddress;
    }

    @Override
    public void invalidateSession(HttpSession session) {
        session.invalidate();
    }

    private RedirectUrls getRedirectUrls(String sessionId) {
        CheckoutWidgetSession widgetSession = sessionService.getCheckoutWidgetSession(sessionId).get();
        return widgetSession.getRedirectUrls();
    }

    @Override
    public void completeCheckout(HttpSession session, RequestContext context, CardDetails cardDetails) {
        RedirectUrls redirects = getRedirectUrls(session.getId());

        context.getFlowScope().remove("paymentMethods");

        UriBuilder uriBuilder = UriBuilder.fromUri(URI.create(redirects.getSuccessUrl()));
        String forwardUrl = uriBuilder.queryParam("transactionId", "12345").toString();
        context.getFlowScope().put("forwardUrl", forwardUrl);
        context.getFlowScope().put("target", "_top");
    }

    @Override
    public void cancelCheckout(HttpSession session, RequestContext context) {
        RedirectUrls redirects = getRedirectUrls(session.getId());

        context.getFlowScope().remove("paymentMethods");

        String forwardUrl = redirects.getCancelUrl();
        context.getFlowScope().put("forwardUrl", forwardUrl);
        context.getFlowScope().put("target", "_top");
    }
}

Application.java

@EnableAutoConfiguration
@ComponentScan(basePackages = {"com.pay.widgets.checkout"})
@Import(JerseyAutoConfiguration.class)
public class Application extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(Application.class);
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);

    }
}

1 个答案:

答案 0 :(得分:0)

好吧,这对我来说是一个非常愚蠢的错误,问题结果是在控制器上的@RequestParameter注释中出现了错字:

payments/checkout-widgets

哪个与WebflowConfig定义的flowRegistry中的内容不一致:

payments/checkout-widget

我只能假设资源是由Tomcat缓存的,这就是为什么花了这么长时间来解决这个问题,并且让我从怀疑自己的变化负责的角度抛弃我的气味。