概述

注意:本文中 Activiti 的版本为 5.22,为 5.X 系列的最后一个 RELEASE 版本。目前 Activiti 已经发展到了 7.X 版本,为啥还用 5.X 版本,还是存量项目的原因。

我们的需求是将 Activiti 官网的 Demo 的流程设计器功能整合到我们项目中,主要需要的功能是 Web 流程设计器 —— Activiti Modeler。Web 流程设计器的查找和保存功能是使用了 RestFul API 调用的,将源码中的 editor-app 引入再定制化改造即可。除了查找和保存之外,我们还需要查询流程图列表、新建流程图、删除流程图等功能,可是发现这部分并没有使用 RestFul API,这几个功能是在后台实现的,需要阅读源码查看这几个功能如何实现。

在阅读源码发现 Activiti 在前端 Demo 部分还是比较混乱,前后端严重耦合,现在理清如下:

  • Activiti Webapp Explorer:指的是官方的 Demo Webapp 工程
  • Activiti Explorer:主要使用 vaadin 来渲染 Activiti Webapp Explorer 中的页面
  • Activiti Modeler: Web 端流程设计器,后端项目只有 3 个类提供 RestFul 接口,而前端项目则放到了Activiti Webapp Explorer 工程中,使用的是 AngularJS,Activiti Modeler 在 Activiti@5.17 版本由 Alfresco Activiti Enterprise 捐赠。
  • Diagram Viewer: Activiti 官方在 5.12 版本中添加的新组件,以 Raphaël 为基础库,用 REST 方式(从 Activiti - Diagram - REST 模块)获取 JSON 数据生成流程图并把流程的处理过程用不同的颜色加以标注。

Activiti Webapp Explorer

Activiti Explorer的 Webapp 工程在源码中的位置为 modules/activiti-webapp-explorer2/src/main/webapp,这个工程是官方Demo的核心工程。

比较奇怪的是这个 module 在 pom 文件中的 name 为 Activiti - Webapp - Explorer V2,而整个工程并没有 V1 版本,而且这个 module 没有放到 activiti-explorer 中,而是独立开来,在 pom.xml 文件中依赖了 activiti-explorer


主要的 activiti 相关依赖:

  • activiti-engine
  • activiti-spring
  • activiti-explorer
  • activiti-modeler
  • activiti-diagram-rest
  • activiti-simple-workflow

查看 web.xml 文件:

Listener

首先添加了一个 WebConfigurer 的 listener 用来加载 Spring context:

<!-- To load the Spring context -->
<listener><listener-class>org.activiti.explorer.servlet.WebConfigurer</listener-class>
</listener>
复制代码

WebConfigurer 中配置了一个 mapping 用来相应相关的 REST 请求:

...
dispatcherServlet.addMapping("/service/*");
...
复制代码

第二个 listener 用来支持 Spring Beans 的 session-scoped 作用域:

<!-- To allow session-scoped beans in Spring -->
<listener><listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
复制代码

Filter

定义了两个 filter

第一个 filter 为 ExplorerFilter

<filter><filter-name>UIFilter</filter-name><filter-class>org.activiti.explorer.filter.ExplorerFilter</filter-class>
</filter>
...
<filter-mapping><filter-name>UIFilter</filter-name><url-pattern>/*</url-pattern>
</filter-mapping>
复制代码

查看 ExplorerFilter 类主要做了如下事情:

判断请求地址是否以 "/ui""/VAADIN""/modeler.html""/editor-app""/service""/diagram-viewer"开头:

  • 若否,则将请求地址添加 "/ui"
  • 若是,判断是否以 "/service" 开头
    • 若是,判断用户是否登陆,没登录报错,登陆则继续
    • 若否,则继续

第二个 filter 为 JSONPFilter, 对 "/service" 开头的请求进行过滤,处理 jsonp 的请求:

<filter><filter-name>JSONPFilter</filter-name><filter-class>org.activiti.explorer.servlet.JsonpCallbackFilter</filter-class>
</filter>
...
<filter-mapping><filter-name>JSONPFilter</filter-name><url-pattern>/service/*</url-pattern>
</filter-mapping>
复制代码

servlet

定义了名为 Vaadin Application Servlet 的 servlet,响应 "/ui""/VAADIN" 开头的请求:

<servlet><servlet-name>Vaadin Application Servlet</servlet-name><servlet-class>org.activiti.explorer.servlet.ExplorerApplicationServlet</servlet-class><init-param><param-name>widgetset</param-name><param-value>org.activiti.explorer.CustomWidgetset</param-value></init-param>
</servlet><servlet-mapping><servlet-name>Vaadin Application Servlet</servlet-name><url-pattern>/ui/*</url-pattern>
</servlet-mapping><servlet-mapping><servlet-name>Vaadin Application Servlet</servlet-name><url-pattern>/VAADIN/*</url-pattern>
</servlet-mapping>
复制代码

查看 ExplorerApplicationServlet 发现出现了一大段用java拼html片段的代码,主要作用是渲染根据浏览器类型渲染不同的css:

  @Overrideprotected void writeAjaxPageHtmlVaadinScripts(Window window, String themeName, Application application, BufferedWriter page, String appUrl, String themeUri,String appId, HttpServletRequest request) throws ServletException, IOException {super.writeAjaxPageHtmlVaadinScripts(window, themeName, application, page, appUrl, themeUri, appId, request);String browserDependentCss = "<script type=\"text/javascript\">//<![CDATA[" +"var mobi = ['opera', 'iemobile', 'webos', 'android', 'blackberry', 'ipad', 'safari'];" +"var midp = ['blackberry', 'symbian'];" +"var ua = navigator.userAgent.toLowerCase();" +"if ((ua.indexOf('midp') != -1) || (ua.indexOf('mobi') != -1) || ((ua.indexOf('ppc') != -1) && (ua.indexOf('mac') == -1)) || (ua.indexOf('webos') != -1)) {" +"  document.write('<link rel=\"stylesheet\" href=\"" + themeUri +"/allmobile.css\" type=\"text/css\" media=\"all\"/>');" +"  if (ua.indexOf('midp') != -1) {" +"    for (var i = 0; i < midp.length; i++) {" +"      if (ua.indexOf(midp[i]) != -1) {" +"        document.write('<link rel=\"stylesheet\" href=\"" + themeUri +"' + midp[i] + '.css\" type=\"text/css\"/>');" +"      }" +"    }"+"  }" +"   else {"+"     if ((ua.indexOf('mobi') != -1) || (ua.indexOf('ppc') != -1) || (ua.indexOf('webos') != -1)) {" +"       for (var i = 0; i < mobi.length; i++) {" +"         if (ua.indexOf(mobi[i]) != -1) {" +"           if ((mobi[i].indexOf('blackberry') != -1) && (ua.indexOf('6.0') != -1)) {" +"             document.write('<link rel=\"stylesheet\" href=\"" + themeUri + "' + mobi[i] + '6.0.css\" type=\"text/css\"/>');" +"           }" +"           else {" +"             document.write('<link rel=\"stylesheet\" href=\"" + themeUri + "' + mobi[i] + '.css\" type=\"text/css\"/>');" +"           }" +"          break;" +"         }" +"       }" +"     }" +"   }" +" }" +"if ((navigator.userAgent.indexOf('iPhone') != -1) || (navigator.userAgent.indexOf('iPad') != -1)) {" +" document.write('<meta name=\"viewport\" content=\"width=device-width\" />');" +"}" +"  //]]>" +"</script>" +"<!--[if lt IE 7]><link rel=\"stylesheet\" type=\"text/css\" href=\"" + themeUri + "/lt7.css\" /><![endif]-->";page.write(browserDependentCss);}
复制代码

从下面代码可以看出,整个前端页面由ExplorerApp 来渲染:

  @Overrideprotected Class< ? extends Application> getApplicationClass() throws ClassNotFoundException {return ExplorerApp.class;}@Overrideprotected Application getNewApplication(HttpServletRequest request) {return (Application) applicationContext.getBean(ExplorerApp.class);}
复制代码

至此,终于找到了Activiti App的页面入口 ExplorerApp

Activiti App的页面入口 ExplorerApp

初始化显示Login页面:

  public void init() {setMainWindow(mainWindow);mainWindow.showLoginPage();}
复制代码

页面与类的对应关系:

  • 登陆页面:LoginPage
  • 主页面布局:MainLayout
  • 已部署流程定义:ProcessDefinitionPage
  • 流程设计工作区:EditorProcessDefinitionPage

已部署流程定义:ProcessDefinitionPage

主要关注 4 个功能的代码:

  • 查询已部署的流程定义的列表
  • 启动流程
  • 转换为可编辑模型?(有一些是禁用的)
  • 根据流程定义ID查询流程定义信息

1、查询已部署的流程定义的列表

主要查询的代码如下:

  // DefaultProcessDefinitionFilter.java...public ProcessDefinitionQuery getQuery(RepositoryService repositoryService) {return getBaseQuery(repositoryService).orderByProcessDefinitionName().asc().orderByProcessDefinitionKey().asc(); // name is not unique, so we add the order on key (so we can use it in the comparsion of ProcessDefinitionListItem)}...protected ProcessDefinitionQuery getBaseQuery(RepositoryService repositoryService) {return repositoryService.createProcessDefinitionQuery().latestVersion().active();}...
复制代码

获取到了 ProcessDefinitionQuery 就可以获取 ProcessDefinition 列表:


List<ProcessDefinition> processDefinitions = processDefinitionQuery.listPage(start, count);复制代码

processDefinition 的结构如下:


2、启动流程

需要注意的是,在启动流程还增加了一个判断 process-definition 是否定义了一个 start-form,由于在我们实际使用中不会使用到 start-form,所以我们在改写这块功能的时候增加一个能否发布的判断:是否存在 start-form,若存在,则不能启动;后面的启动后判断当前用户是否存在该流程的任务功能可以忽略。

// StartProcessInstanceClickListener.javapublic void buttonClick(ClickEvent event) {// Check if process-definition defines a start-formStartFormData startFormData = formService.getStartFormData(processDefinition.getId());if(startFormData != null && ((startFormData.getFormProperties() != null && !startFormData.getFormProperties().isEmpty()) || startFormData.getFormKey() != null)) {parentPage.showStartForm(processDefinition, startFormData);} else {// Just start the process-instance since it has no form.// TODO: Error handlingProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinition.getId());// Show notification of successnotificationManager.showInformationNotification(Messages.PROCESS_STARTED_NOTIFICATION, getProcessDisplayName(processDefinition));// Switch to inbox page in case a task of this process was createdList<Task> loggedInUsersTasks = taskService.createTaskQuery().taskAssignee(ExplorerApp.get().getLoggedInUser().getId()).processInstanceId(processInstance.getId()).list();if (!loggedInUsersTasks.isEmpty()) {ExplorerApp.get().getViewManager().showInboxPage(loggedInUsersTasks.get(0).getId());}}}复制代码

TODO:判断是否能够启动流程逻辑:

  • 已经启动过的流程不能再启动;
  • 存在start-form,若存在不能启动

3、转换为可编辑模型

能否转换为可编辑模块逻辑:

// ProcessDefinitionDetailPanel.javaif(((ProcessDefinitionEntity) processDefinition).isGraphicalNotationDefined() == false) {editProcessDefinitionButton.setEnabled(false);}复制代码

需要注意的是转换需要弹窗让用户二次确认

转换逻辑:


// ConvertProcessDefinitionPopupWindow.javatry {InputStream bpmnStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), processDefinition.getResourceName());XMLInputFactory xif = XmlUtil.createSafeXmlInputFactory();InputStreamReader in = new InputStreamReader(bpmnStream, "UTF-8");XMLStreamReader xtr = xif.createXMLStreamReader(in);BpmnModel bpmnModel = new BpmnXMLConverter().convertToBpmnModel(xtr);if (bpmnModel.getMainProcess() == null || bpmnModel.getMainProcess().getId() == null) {notificationManager.showErrorNotification(Messages.MODEL_IMPORT_FAILED, i18nManager.getMessage(Messages.MODEL_IMPORT_INVALID_BPMN_EXPLANATION));} else {if (bpmnModel.getLocationMap().isEmpty()) {notificationManager.showErrorNotification(Messages.MODEL_IMPORT_INVALID_BPMNDI,i18nManager.getMessage(Messages.MODEL_IMPORT_INVALID_BPMNDI_EXPLANATION));} else {BpmnJsonConverter converter = new BpmnJsonConverter();ObjectNode modelNode = converter.convertToJson(bpmnModel);Model modelData = repositoryService.newModel();ObjectNode modelObjectNode = new ObjectMapper().createObjectNode();modelObjectNode.put(MODEL_NAME, processDefinition.getName());modelObjectNode.put(MODEL_REVISION, 1);modelObjectNode.put(MODEL_DESCRIPTION, processDefinition.getDescription());modelData.setMetaInfo(modelObjectNode.toString());modelData.setName(processDefinition.getName());repositoryService.saveModel(modelData);repositoryService.addModelEditorSource(modelData.getId(), modelNode.toString().getBytes("utf-8"));close();ExplorerApp.get().getViewManager().showEditorProcessDefinitionPage(modelData.getId());URL explorerURL = ExplorerApp.get().getURL();URL url = new URL(explorerURL.getProtocol(), explorerURL.getHost(), explorerURL.getPort(),explorerURL.getPath().replace("/ui", "") + "modeler.html?modelId=" + modelData.getId());ExplorerApp.get().getMainWindow().open(new ExternalResource(url));}}} catch(Exception e) {notificationManager.showErrorNotification("error", e);}复制代码

4、根据流程定义ID查询流程定义信息

分为两种:

  • 通过 rest 接口获取流程 diagram 信息,并通过 Diagram Viewer 模块渲染图标
  • 直接获取图片
方式一:通过 rest 接口获取流程 diagram 信息
// ProcessDefinitionDiagramLayoutResource.java@RestController
public class ProcessDefinitionDiagramLayoutResource extends BaseProcessDefinitionDiagramLayoutResource {@RequestMapping(value="/process-definition/{processDefinitionId}/diagram-layout", method = RequestMethod.GET, produces = "application/json")public ObjectNode getDiagram(@PathVariable String processDefinitionId) {return getDiagramNode(null, processDefinitionId);}
}
复制代码

!注意获取 processDefinition 和 deployment 的方法:

// AbstractProcessDefinitionDetailPanel.java
// Membersprotected ProcessDefinition processDefinition;protected Deployment deployment;this.processDefinition = repositoryService.getProcessDefinition(processDefinitionId);if(processDefinition != null) {deployment = repositoryService.createDeploymentQuery().deploymentId(processDefinition.getDeploymentId()).singleResult();}
复制代码
方式二:直接获取图片
// ProcessDefinitionInfoComponent.javaStreamResource diagram = null;// Try generating process-image streamif(processDefinition.getDiagramResourceName() != null) {diagram = new ProcessDefinitionImageStreamResourceBuilder().buildStreamResource(processDefinition, repositoryService);}
复制代码

流程设计工作区:EditorProcessDefinitionPage

主要关注 8 个功能的代码:

  • 查询流程设计工作区模型列表
  • 根据模型Id获取模型信息
  • 新建模型
  • 删除模型
  • 部署模型
  • 导入模型
  • 导出模型
  • 查询左侧stencilset列表信息

1、查询流程设计工作区模型列表

获取列表方法:

List<Model> modelList = repositoryService.createModelQuery().list();
复制代码

Model的结构如下:

2、根据模型Id获取模型信息

// ModelEditorJsonRestResource.java@RequestMapping(value="/model/{modelId}/json", method = RequestMethod.GET, produces = "application/json")public ObjectNode getEditorJson(@PathVariable String modelId) {ObjectNode modelNode = null;Model model = repositoryService.getModel(modelId);if (model != null) {try {if (StringUtils.isNotEmpty(model.getMetaInfo())) {modelNode = (ObjectNode) objectMapper.readTree(model.getMetaInfo());} else {modelNode = objectMapper.createObjectNode();modelNode.put(MODEL_NAME, model.getName());}modelNode.put(MODEL_ID, model.getId());ObjectNode editorJsonNode = (ObjectNode) objectMapper.readTree(new String(repositoryService.getModelEditorSource(model.getId()), "utf-8"));modelNode.put("model", editorJsonNode);} catch (Exception e) {LOGGER.error("Error creating model JSON", e);throw new ActivitiException("Error creating model JSON", e);}}return modelNode;}
复制代码

3、新建模型


// NewModelPopupWindow.javaObjectMapper objectMapper = new ObjectMapper();ObjectNode editorNode = objectMapper.createObjectNode();editorNode.put("id", "canvas");editorNode.put("resourceId", "canvas");ObjectNode stencilSetNode = objectMapper.createObjectNode();stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");editorNode.put("stencilset", stencilSetNode);Model modelData = repositoryService.newModel();ObjectNode modelObjectNode = objectMapper.createObjectNode();modelObjectNode.put(MODEL_NAME, (String) nameTextField.getValue());modelObjectNode.put(MODEL_REVISION, 1);String description = null;if (StringUtils.isNotEmpty((String) descriptionTextArea.getValue())) {description = (String) descriptionTextArea.getValue();} else {description = "";}modelObjectNode.put(MODEL_DESCRIPTION, description);modelData.setMetaInfo(modelObjectNode.toString());modelData.setName((String) nameTextField.getValue());repositoryService.saveModel(modelData);repositoryService.addModelEditorSource(modelData.getId(), editorNode.toString().getBytes("utf-8"));
复制代码

4、删除模型

repositoryService.deleteModel(modelData.getId());
复制代码

5、部署模型

// EditorProcessDefinitionDetailPanel.java
protected void deployModelerModel(final ObjectNode modelNode) {BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode);byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(model);String processName = modelData.getName() + ".bpmn20.xml";Deployment deployment = repositoryService.createDeployment().name(modelData.getName()).addString(processName, new String(bpmnBytes)).deploy();ExplorerApp.get().getViewManager().showDeploymentPage(deployment.getId());}
复制代码

6、导入模型

参考这段代码:

// ImportUploadReceiver.java
public class ImportUploadReceiver implements Receiver, FinishedListener, ModelDataJsonConstants {...protected void deployUploadedFile() {try {try {if (fileName.endsWith(".bpmn20.xml") || fileName.endsWith(".bpmn")) {validFile = true;XMLInputFactory xif = XmlUtil.createSafeXmlInputFactory();InputStreamReader in = new InputStreamReader(new ByteArrayInputStream(outputStream.toByteArray()), "UTF-8");XMLStreamReader xtr = xif.createXMLStreamReader(in);BpmnModel bpmnModel = new BpmnXMLConverter().convertToBpmnModel(xtr);if (bpmnModel.getMainProcess() == null || bpmnModel.getMainProcess().getId() == null) {notificationManager.showErrorNotification(Messages.MODEL_IMPORT_FAILED, i18nManager.getMessage(Messages.MODEL_IMPORT_INVALID_BPMN_EXPLANATION));} else {if (bpmnModel.getLocationMap().isEmpty()) {notificationManager.showErrorNotification(Messages.MODEL_IMPORT_INVALID_BPMNDI,i18nManager.getMessage(Messages.MODEL_IMPORT_INVALID_BPMNDI_EXPLANATION));} else {String processName = null;if (StringUtils.isNotEmpty(bpmnModel.getMainProcess().getName())) {processName = bpmnModel.getMainProcess().getName();} else {processName = bpmnModel.getMainProcess().getId();}modelData = repositoryService.newModel();ObjectNode modelObjectNode = new ObjectMapper().createObjectNode();modelObjectNode.put(MODEL_NAME, processName);modelObjectNode.put(MODEL_REVISION, 1);modelData.setMetaInfo(modelObjectNode.toString());modelData.setName(processName);repositoryService.saveModel(modelData);BpmnJsonConverter jsonConverter = new BpmnJsonConverter();ObjectNode editorNode = jsonConverter.convertToJson(bpmnModel);repositoryService.addModelEditorSource(modelData.getId(), editorNode.toString().getBytes("utf-8"));}}} else {notificationManager.showErrorNotification(Messages.MODEL_IMPORT_INVALID_FILE,i18nManager.getMessage(Messages.MODEL_IMPORT_INVALID_FILE_EXPLANATION));}} catch (Exception e) {String errorMsg = e.getMessage().replace(System.getProperty("line.separator"), "<br/>");notificationManager.showErrorNotification(Messages.MODEL_IMPORT_FAILED, errorMsg);}} finally {if (outputStream != null) {try {outputStream.close();} catch (IOException e) {notificationManager.showErrorNotification("Server-side error", e.getMessage());}}}}protected void showUploadedDeployment() {viewManager.showEditorProcessDefinitionPage(modelData.getId());}}
复制代码

7、导出模型

// EditorProcessDefinitionDetailPanel.javaprotected void exportModel() {final FileResource stream = new FileResource(new File(""), ExplorerApp.get()) {private static final long serialVersionUID = 1L;@Overridepublic DownloadStream getStream() {DownloadStream ds = null;try {byte[] bpmnBytes = null;String filename = null;if (SimpleTableEditorConstants.TABLE_EDITOR_CATEGORY.equals(modelData.getCategory())) {WorkflowDefinition workflowDefinition = ExplorerApp.get().getSimpleWorkflowJsonConverter().readWorkflowDefinition(repositoryService.getModelEditorSource(modelData.getId()));filename = workflowDefinition.getName();WorkflowDefinitionConversion conversion = ExplorerApp.get().getWorkflowDefinitionConversionFactory().createWorkflowDefinitionConversion(workflowDefinition);bpmnBytes = conversion.getBpmn20Xml().getBytes("utf-8");} else {JsonNode editorNode = new ObjectMapper().readTree(repositoryService.getModelEditorSource(modelData.getId()));BpmnJsonConverter jsonConverter = new BpmnJsonConverter();BpmnModel model = jsonConverter.convertToBpmnModel(editorNode);filename = model.getMainProcess().getId() + ".bpmn20.xml";bpmnBytes = new BpmnXMLConverter().convertToXML(model);}ByteArrayInputStream in = new ByteArrayInputStream(bpmnBytes);ds = new DownloadStream(in, "application/xml", filename);// Need a file download POPUPds.setParameter("Content-Disposition", "attachment; filename=" + filename);} catch(Exception e) {LOGGER.error("failed to export model to BPMN XML", e);ExplorerApp.get().getNotificationManager().showErrorNotification(Messages.PROCESS_TOXML_FAILED, e);}return ds;}};stream.setCacheTime(0);ExplorerApp.get().getMainWindow().open(stream);}
复制代码

8、查询左侧stencilset列表信息

// StencilsetRestResource.javapublic class StencilsetRestResource {@RequestMapping(value="/editor/stencilset", method = RequestMethod.GET, produces = "application/json;charset=utf-8")public @ResponseBody String getStencilset() {InputStream stencilsetStream = this.getClass().getClassLoader().getResourceAsStream("stencilset.json");try {return IOUtils.toString(stencilsetStream, "utf-8");} catch (Exception e) {throw new ActivitiException("Error while loading stencil set", e);}}
}
复制代码

转载于:https://juejin.im/post/5cc65e5a51882525d6473008

Activiti Explorer 源码浅析相关推荐

  1. hashmap允许null键和值吗_hashMap底层源码浅析

    来源:https://blog.csdn.net/qq_35824590/article/details/111769203 hashmap是我们经常使用的一个工具类.那么知道它的一些原理和特性吗? ...

  2. Android Loader机制全面详解及源码浅析

    原文出处:csdn@工匠若水,http://blog.csdn.net/yanbober/article/details/48861457 一.概述 在Android中任何耗时的操作都不能放在UI主线 ...

  3. 内核启动流程分析(四)源码浅析

    目录 kernel(四)源码浅析 建立工程 启动简析 head.s 入口点 查询处理器 查询机器ID 启动MMU 其他操作 start_kernel 处理命令行 分区 kernel(四)源码浅析 建立 ...

  4. harbor登录验证_Harbor 源码浅析

    Harbor 源码浅析​www.qikqiak.com Harbor 是一个CNCF基金会托管的开源的可信的云原生docker registry项目,可以用于存储.签名.扫描镜像内容,Harbor 通 ...

  5. fetch first mysql_MySQL多版本并发控制机制(MVCC)源码浅析

    MySQL多版本并发控制机制(MVCC)-源码浅析 前言 作为一个数据库爱好者,自己动手写过简单的SQL解析器以及存储引擎,但感觉还是不够过瘾.<>诚然讲的非常透彻,但只能提纲挈领,不能让 ...

  6. 【flink】Flink 1.12.2 源码浅析 : Task数据输入

    1.概述 转载:Flink 1.12.2 源码浅析 : Task数据输入 在 Task 中,InputGate 是对输入的封装,InputGate 是和 JobGraph 中 JobEdge 一一对应 ...

  7. 【flink】Flink 1.12.2 源码浅析 :Task数据输出

    1.概述 转载:Flink 1.12.2 源码浅析 :Task数据输出 Stream的计算模型采用的是PUSH模式, 上游主动向下游推送数据, 上下游之间采用生产者-消费者模式, 下游收到数据触发计算 ...

  8. 【flink】Flink 1.12.2 源码浅析 : StreamTask 浅析

    1.概述 转载:Flink 1.12.2 源码浅析 : StreamTask 浅析 在Task类的doRun方法中, 首先会构建一个运行环境变量RuntimeEnvironment . 然后会调用lo ...

  9. 【flink】Flink 1.12.2 源码浅析 : Task 浅析

    1.概述 转载:Flink 1.12.2 源码浅析 : Task 浅析 Task 表示TaskManager上并行 subtask 的一次执行. Task封装了一个Flink operator(也可能 ...

最新文章

  1. 十分钟上手2022latex安装与入门
  2. 关于c++预编译符的使用
  3. 以太网帧,ARP报文
  4. HDU 1412 {A} + {B}
  5. 1119: 零起点学算法26——判断奇偶数
  6. 【听哥一句劝,C++水很深,你把握不住啊!】C++提高班之 符与*符
  7. js 实现简单的轮询
  8. 修改npm全局安装模式的路径
  9. php 提取前一百个字,如何在PHP中提取字符串的前100個字符?
  10. hive动态分区shell_hive-shell批量操作分区文件
  11. preempt_disable宏
  12. http Headers字段汇总
  13. 常见国际贸易专业术语
  14. 故事系列之二:佛法世界中看天赋与勤奋
  15. Bash shell语言学习
  16. PPT绘论文图之导出分辨率
  17. 2020.11.07 使用OpenCV进行图像边缘提取(Sobel算子)【OpenCV C++ Sobel】
  18. 电子电路仿真设计与制版软件综述 - PCB, OrCAD, PADs
  19. 基于PHP+MySQL信息技术学习网站设计与实现
  20. html 获取页面缩略图,html – 从Vimeo获取img缩略图?

热门文章

  1. SAP物料移动过账之差异科目
  2. SAP SD基础知识之自动信用控制
  3. 卷积神经网络新手指南之二
  4. 5分钟带你读懂“语音识别”工作原理
  5. 大众高层:中国将成为全球自动驾驶软件研发中心
  6. AI工程师面试知识点:机器学习算法类
  7. 想用Python学机器学习?Google大神替你写好了所有的编程示范代码
  8. 《数学之美》第28章 逻辑回归和搜索广告
  9. Python 之 Numpy (二)array
  10. 汇总pandas中dataframe的删除操作