盘点 Flow : SpringFlow 配置篇

2,026 阅读8分钟

这是我参与更文挑战的第3天,活动详情查看: 更文挑战

总文档 :文章目录
Github : github.com/black-ant

一 . 前言

Spring Web Flow 是一个 Spring 的流处理框架 , 这是个使用不多的框架

一开始我也陷入一种误区 , 一直在查找他和 SpringMVC 的优劣 , 但是实际上 , 他们2者是不冲突的 .

SpringWebFlow 建立在 SpringMVC 之上,并允许实现 Web 应用程序的“流程”。流封装了指导用户执行某些业务任务的一系列步 .

也就是说 : 它可以作为一种完善 SpringMVC 的角色 , 以满足 SpringMVC 不能做到的复杂功能

作用官方说明 : Spring Web Flow 的最佳选择是有状态的 Web 应用程序,它具有可控导航,比如登机、申请贷款、购物车结账,甚至在表单中添加确认步骤 ,他们通常有以下特征 :

  • 有一个清晰的起点和终点
  • 用户必须按照特定的顺序浏览一组视图(表单)
  • 直到最后一步,更改才最终确定
  • 一旦完成,就不可能意外地重复一个事务

以上是官方说法 ,但是个人在生产中 , 也体验过该框架 , 说说感受 :

  • 对视图依赖高 , 可以做到但是不好做到前后端分离 (视图不限定于 Thymeleaf 等引擎 , 但是如果不使用引擎 , 会丢失很多特性)
  • invoke 代理复杂 , 对框架不熟悉基本是很难 debug 流程
  • 只适合单流程 , 不易做到多人审批操作
  • 没有可视化的配置途径 (至少我没看到)

但是他也有其他的优点:

  • 在不考虑前后端分离时 , scope 域用来渲染参数很方便
  • 集成简单 , 不需要对外部有过多依赖
  • 当需要做一个负载的单流程时 , 可以最大化的梳理流程减少耦合提高可视度 (不考虑其他 Flow 插件)
  • 与 MVC 无冲突

二 . 基础使用

2.1 Java Config 配置

@Configuration
public class WebFlowConfig extends AbstractFlowConfiguration {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private ThymeleafViewResolver thymeleafViewResolver;

    @Autowired
    private ApplicationContext context;

    @Bean
    public FlowHandlerMapping flowHandlerMapping() {
        logger.info("------> 构建映射关系 <-------");
        FlowHandlerMapping handlerMapping = new FlowHandlerMapping();
        handlerMapping.setOrder(-1);
        handlerMapping.setFlowRegistry(flowRegistry());
        return handlerMapping;
    }


    @Bean
    public FlowHandlerAdapter flowHandlerAdapter() {
        logger.info("------> 构建处理器 <-------");
        FlowHandlerAdapter handlerAdapter = new FlowHandlerAdapter();
        handlerAdapter.setFlowExecutor(flowExecutor());
        handlerAdapter.setSaveOutputToFlashScopeOnRedirect(true);
        return handlerAdapter;
    }

    @Bean
    public FlowDefinitionRegistry flowRegistry() {
        return new FlowDefinitionRegistryBuilder(context)
                .setFlowBuilderServices(flowBuilderServices())
                .setBasePath("classpath:/flows")
                .addFlowLocation("activation-flow.xml", "activationFlow")
                .build();
    }


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

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

    @Bean
    public MvcViewFactoryCreator mvcViewFactoryCreator() {
        logger.info("------> [构建 View 工厂] <-------");
        MvcViewFactoryCreator factoryCreator = new MvcViewFactoryCreator();
        // PS : 此句会覆盖下方语句 . 这里感觉设计的有问题
        factoryCreator.setUseSpringBeanBinding(true);
        factoryCreator.setViewResolvers(Collections.singletonList(thymeleafViewResolver));
        return factoryCreator;
    }


}



2.2 WebFlow 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.xsd">

    <view-state id="activation">
        <transition on="activate" to="success"/>
        <transition on="cancel" to="failure"/>
    </view-state>

    <view-state id="success" />

    <view-state id="failure" />

</flow>

2.3 WebView 文件

其中包含 4 个 html 文件 , 可以查看源码获取 @GitHub 源码

三 . 源码解析

因为 Spring Web Flow 的文档较少 , 而如果因为一些原因而使用该框架 , 以下分析流程会对你有所帮助:

从上述流程中 ,可以看到配置了以下几个对象 , 按照依赖关系进行展示:

Step 1 : 配置和映射

  • MvcViewFactoryCreator : View 创建工厂
  • FlowBuilderServices : Web Flow build 构建
  • FlowDefinitionRegistry : Flow Definition 创建
  • FlowHandlerMapping : 映射处理器

Step 2 : 业务处理

  • FlowExecutor : WebFlow 执行器
  • flowHandlerAdapter : WebFlow 适配器

3.1 MvcViewFactoryCreator 的配置

Step 1 : 配置文件入口

@Bean
public MvcViewFactoryCreator mvcViewFactoryCreator() {
    MvcViewFactoryCreator factoryCreator = new MvcViewFactoryCreator();
    // 设置视图解析器
    factoryCreator.setViewResolvers(Collections.singletonList(thymeleafViewResolver));
    // 设置是否启用Spring的BeanWrapper使用数据绑定
    factoryCreator.setUseSpringBeanBinding(true);
    return factoryCreator;
}

Step 2 : MvcViewFactoryCreator 创建详情

功能 : 返回ViewFactory视图工厂,该视图工厂创建基于Spring的原生视图。SpringMVC的视图工厂来配置流的视图状态

使用 :

  • 创建视图工厂,这些视图工厂通过加载流相关资源(比如位于流工作目录中的.jsp模板)来解析它们的视图
  • 这个类还支持呈现由预先存在的Spring MVC ViewResolver视图解析器解析的视图

从下图的方法中 ,我们大概可以看到提供了以下主要的功能 :

  • setDefaultViewSuffix : 设置视图的后缀
  • setUseSpringBeanBinding : 设置是否启用Spring的BeanWrapper使用数据绑定
  • setFlowViewResolver : 设置 View 解析器
  • setViewResolvers : 使用不同的 SpringMVC 解析器 , 托解析由流选择的视图
  • setMessageCodesResolver : 设置用于解析绑定和验证错误消息代码的消息代码解析器策略

SpringFlow_MVCViewFactoryCreatoer.jpg

小总结 : 可以看到 , 实际上 WebFlow 和 MVC 有很多共用的类 ,并不是2个完全独立的个体.

3.2 FlowBuilderServices 详情

FlowBuilderServices 是构建 Flow 的主流程 , 主要用于配置流构建器使用的服务的简单holder , 从其内部资源就可以看到一二 :

  • FlowArtifactFactory : 封装中心流构件(如流和状态)创建的工厂
  • ViewFactoryCreator : 视图工厂创建器,用于创建在流执行期间呈现的视图
  • ConversionService : 用于从一种对象类型转换为另一种对象类型的转换服务
  • ExpressionParser : 用于将表达式字符串解析为表达式对象的解析器。默认是Web Flow的默认表达式解析器实现
  • Validator : 验证器实例,用于验证在视图状态上声明的模型
  • ValidationHintResolver : 用于解析基于验证提示的字符串的ValidationHintResolver
  • ApplicationContext : 主容器

PS : 这个类其实就是一个综合类 , 用于将多个业务处理类进行整合

3.3 FlowDefinitionRegistry 详情

Step 1 : 配置的入口

@Bean
public FlowDefinitionRegistry flowRegistry() {
    // 通过构建器构建
    return getFlowDefinitionRegistryBuilder(flowBuilderServices())
        // 添加扫描路径 
        .addFlowLocation("/WEB-INF/flows/activation-flow.xml", "activationFlow")
        .build();
}

[Pro] : FlowDefinitionRegistry 的作用是什么 ?

FlowDefinitionRegistry 用于访问在运行时执行的注册流定义 , 该对象会扫描 xml 文件

Step 2 : Builder 构建流程

可以看到 , 就是 new 一个对象 , 包括当前的 ApplicationContext 以及 BuildService

protected FlowDefinitionRegistryBuilder getFlowDefinitionRegistryBuilder(FlowBuilderServices flowBuilderServices) {
    return new FlowDefinitionRegistryBuilder(this.applicationContext, flowBuilderServices);
}

Step 3 : FlowDefinitionRegistryBuilder 构建

public FlowDefinitionRegistryBuilder(ApplicationContext appContext, FlowBuilderServices builderServices) {
    // 构建了一个 FlowDefinitionResourceFactory
    this.flowResourceFactory = new FlowDefinitionResourceFactory(appContext);
    if (builderServices != null) {
        this.flowBuilderServices = builderServices;
    } else {
        this.flowBuilderServices = new FlowBuilderServicesBuilder().build();
        this.flowBuilderServices.setApplicationContext(appContext);
    }
}

[Pro] : FlowDefinitionResourceFactory 作用 ?

用于创建流定义资源的工厂,这些资源用作指向外部流定义文件的指针

  • setBasePath : 设置在确定默认流id时从流路径中删除的基础路径
  • createResource: 从提供的路径位置创建流定义资源
  • createFileResource : 从提供的文件路径创建基于文件的资源
  • getFlowId :

Step 4 : FlowLocation 构建

每一个 Flow.xml 会被映射为一个 FlowLocation 对象 , 该对象映射一个 xml 文件

private static class FlowLocation {
    // Flow.xml 路径
    private final String path;
    
    // 唯一 ID
    private final String id;
    
    // 额外属性 , 默认为 null
    private final AttributeMap<Object> attributes;
    
    //............
    
}


Step 5 : FlowLocation 的扫描

在 FlowDefinitionRegistryBuilder 中会扫描所有的 FlowLocation 对象 , 并且进行处理

private void registerFlowLocations(DefaultFlowRegistry flowRegistry) {
    for (FlowLocation location : this.flowLocations) {
        String path = location.getPath();
        String id = location.getId();
        AttributeMap<Object> attributes = location.getAttributes();
        updateFlowAttributes(attributes);
        // 流定义资源的抽象表示。保存从外部文件构建流定义所需的数据,并在流定义注册表中注册流定义
        FlowDefinitionResource resource = this.flowResourceFactory.createResource(path, attributes, id);
        registerFlow(resource, flowRegistry);
    }
}

[Pro] : registerFlowLocations 被调用的方式

在 build 方法中创建 FlowDefinitionRegistry

C- FlowDefinitionRegistryBuilder
public FlowDefinitionRegistry build() {

    DefaultFlowRegistry flowRegistry = new DefaultFlowRegistry();
    flowRegistry.setParent(this.parent);

    registerFlowLocations(flowRegistry);
    registerFlowLocationPatterns(flowRegistry);
    registerFlowBuilders(flowRegistry);

    return flowRegistry;
}


[Pro] : FlowDefinitionResource 构建方式

FlowDefinitionResource 通过其工程类构建 , FlowDefinitionResourceFactory在构造器中默认创建 this.flowResourceFactory = new FlowDefinitionResourceFactory(appContext);

public FlowDefinitionResource createResource(String path, AttributeMap<Object> attributes, String flowId) {
    Resource resource;
    // 判断是否存在根路径来决定如何使用相对路径
    if (basePath == null) {
        resource = resourceLoader.getResource(path);
    } else {
        try {
            String basePath = this.basePath;
            if (!basePath.endsWith(SLASH)) {
                // basePath必须以斜杠结尾来创建一个相对资源
                basePath = basePath + SLASH;
            }
            resource = resourceLoader.getResource(basePath).createRelative(path);
        } catch (IOException e) {
            throw new IllegalStateException(....);
        }
    }
    if (flowId == null || flowId.length() == 0) {
        flowId = getFlowId(resource);
    }
    return new FlowDefinitionResource(flowId, resource, attributes);
}

Step 6 : Flow 注册

private void registerFlow(FlowDefinitionResource resource, DefaultFlowRegistry flowRegistry) {
    FlowModelBuilder flowModelBuilder;
    if (resource.getPath().getFilename().endsWith(".xml")) {
        flowModelBuilder = new XmlFlowModelBuilder(resource.getPath(), flowRegistry.getFlowModelRegistry());
    } else {
        throw new IllegalArgumentException(resource 
            + " is not a supported resource type; supported types are [.xml]");
    }
    FlowModelHolder flowModelHolder = new DefaultFlowModelHolder(flowModelBuilder);
    FlowBuilder flowBuilder = new FlowModelFlowBuilder(flowModelHolder);
    FlowBuilderContext builderContext = new FlowBuilderContextImpl(
    resource.getId(), resource.getAttributes(), flowRegistry, this.flowBuilderServices);
    FlowAssembler assembler = new FlowAssembler(flowBuilder, builderContext);
    DefaultFlowHolder flowHolder = new DefaultFlowHolder(assembler);

    flowRegistry.getFlowModelRegistry().registerFlowModel(resource.getId(), flowModelHolder);
    flowRegistry.registerFlowDefinition(flowHolder);
}


[Pro] : FlowModelBuilder 作用

用于构建流模型的构建器接口。构建流模型的过程包括以下步骤 >>

  1. 通过调用 #init()初始化这个生成器
  2. 调用#build()创建流模型
  3. 调用#getFlowModel()返回完全构建的FlowModel模型
  4. 释放此构建器,通过调用# Dispose()释放构建过程中分配的任何资源

Step 7 : FlowDefinitionRegistryImpl 注册

flowDefinitions.put(definitionHolder.getFlowDefinitionId(), definitionHolder)

3.4 FlowHandlerMapping 构建

通过 FlowDefinitionRegistry 构建 FlowHandlerMapping , 用于后续处理

作用 : HandlerMapping的实现,遵循一个简单的约定,从注册的FlowDefinition的id来创建URL路径映射 返回 : 该实现返回一个FlowHandler,如果当前请求路径与配置的FlowDefinitionRegistry中的流的id匹配,该FlowHandler将调用流。

FlowUrlHandler 是一个接口 , 他有3个实现类 , 此处主要为 DefaultFlowUrlHandler

  • DefaultFlowUrlHandler
  • FilenameFlowUrlHandler
  • WebFlow1FlowUrlHandler
//  FlowHandlerMapping 属性
public class FlowHandlerMapping extends AbstractHandlerMapping {

    private FlowDefinitionRegistry flowRegistry;
    private FlowUrlHandler flowUrlHandler;
  
}

// FlowHandlerMapping # getHandlerInternal 处理请求 , 核心2句话

String flowId = flowUrlHandler.getFlowId(request);
Object handler = getApplicationContext().getBean(flowId);



3.5 HandlerAdapter

作用 : 一个自定义的MVC HandlerAdapter ,封装了与Servlet环境中执行流相关的通用工作流。委托映射的FlowHandler流处理程序来管理与特定流定义执行的交互

WebFlowHandlerAdapter.png

此处主要使用 FlowHandlerAdapter , 简单看一下其主要方法就知道其作用了

// 可以看到还是使用 ModelAndView 返回视图对象
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)

// 同时提供重定向等功能
void sendRedirect(String url, HttpServletRequest request, HttpServletResponse response)

总结

总结一下 , FlowBuilderServices 作为全局业务类 , MvcViewFactoryCreator 用于创建 View 工厂 , ,通过 FlowHandlerMapping 拦截请求后, 再通过 FlowDefinitionRegistry 注册 Flow , 执行 FlowExecutor 和 flowHandlerAdapter 返回 view 对象

不过在我自己的使用中 , 通常会选用其他的 flow 框架 , Spring Flow 从社区到文档 , 都不是一个较好的选择 ,除非自己项目不想做的太复杂 , 也有相关的限制.

后续我会对比他和其他的 flow 框架的区别 , 拭目以待 >>>