让数据流通在Html Java Python
一、介绍
首先说一下为什么需要让数据在Html Java Python之间流通。
前端Html使用的 thymeleaf [1] 模板,Java用 Spring Boot [2] ,Python采用 FastAPI [3] 框架。
目前有一个数据分析的功能,要求从html前端接受数据,后台进行处理。但是Java直接做数据分析是不太方便的,所以这里想接入Python来做。
接入Python调查到有两种方式,但是都有其局限性, [4] [5] :
- Java调用Python脚本 。直接写好Python脚本,然后用Java执行该脚本。参数以args的形式传给python脚本,用python用print来返回数据。 但是该方法对参数的传输不太友好,功能很局限。无法传输复杂的数据类型。
- 通过 Jython 。Jython是Python用Java的实现,所以可以很自然的用Java调Jython。 但是Jython对第三方模块支持很少,无法满足这里需要用到的数据分析第三方模块。
由于这些局限性,所以上面这两种方式都被否定。采用了以下方式:
- 前端Html传一个 form 表单数据,发送请求到Java。
- Spring框架的Controller对该数据进行封装,整理成 json ,发送 请求 到Python服务。
- Python中的服务接受该 json 数据,对json进行解析得到结果。
- 结果再交由数据分析的功能模块进行处理。
- 处理后将最终结果组装成json返回给Spring。
- Spring再将结果放到Model中返回给Html。
后面就从代码上描述如何完成以上步骤。
二、Html提交form
定义了两个input框,用来输入字符串数据。
为form设定post请求,发送给 Spring的 /data 服务。
代码和结果图如下
<form th:action="@{/data}" method="post">
<div class="form-group">
<label for="coordinate1" class="col-sm-2 control-label">coordinate1</label>
<input type="text" class="form-control" name="coordinate" id="coordinate1" placeholder="coordinate1"/>
</div>
<div class="form-group">
<label for="coordinate2" class="col-sm-2 control-label" >coordinate2</label>
<input type="text" class="form-control" name="coordinate" id="coordinate2" placeholder="coordinate2"/>
</div>
<div class="form-group">
<input type="submit" value="Submit" class="btn btn-info" />
<input type="reset" value="Reset" class="btn btn-info" />
</div>
</form>
当点击了Submit按钮,两个input框中的数据就会传输到Controller。
三、Spring Controller接受并转发给Python
1. Controller接受数据
Controller设定好RequestMapping("/data"),直接在形参列表中加入 List<String> coordinates即可,数据会自动封装到coordinates中,接收该数据很轻松。
2. 传递Json
已经获取到了List<String> coordinates对象,要把该对象封装好,以合适的形式发送给Python。
显然,不同服务之间传输数据的最好方式是将数据转成Json传输。
这里采用Jackon工具类将对象转换成json,使用writeValueAsString方法将对象转成String,极其简单。
ObjectMapper objectMapper = new ObjectMapper();
String paramString = objectMapper.writeValueAsString(任意对象);
这里先将coordinates装载到Map中,然后转换成Json字符串。
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<String, Object>();
paramMap.add("coordinates",coordinates);
System.out.println(paramMap);
String paramString = objectMapper.writeValueAsString(paramMap);
这里有一个MultiValueMap,我理解的是,HashMap的key value,本来是一一对应的,但是这里使用MultiValue,就使得一个key可以对应多个value,多个value组成一个数组(Java)或者列表(Python)。网上有提到一个错误,我暂时没有遇到,但还是记录下来。 [6]
3. 使用RestTemplate发送请求
RestTemplate是Spring提供的一个可以访问其他服务(url)的一个类,更专业的解释在官方文档 [7] 。简而言之就是,通过该类,可以给其他服务发送请求。
单独使用起来也非常简单,如下代码:
RestTemplate restTemplate = new RestTemplate();
String content = restTemplate.getForObject("https://www.baidu.com/",String.class);
System.out.println(content);
运行该代码,可以得到如下输出
该类提供了很多方法,其中就包括传递参数的post方法。
public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables) throws RestClientException
只需使用该方法,就可以传递参数给python服务器。
String content = restTemplate.postForObject(pythonServerUrl+"/data",paramString,String.class);
在content中,就是python返回的结果。
使用Model将结果保存,就可以返回给前端。
4. 完整代码
@RequestMapping("/data")
public String dataToPython(@RequestParam("coordinate") List<String> coordinates) throws JsonProcessingException {
System.out.println(coordinates.get(0));
System.out.println(coordinates.get(0).length());
// HashMap<String,Object> paramMap = new HashMap<>();
// paramMap.put("coordinates",coordinates);
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<String, Object>();
paramMap.add("coordinates",coordinates);
System.out.println(paramMap);
String paramString = objectMapper.writeValueAsString(paramMap);
String content = restTemplate.postForObject(pythonServerUrl+"/data",paramString,String.class);
System.out.println("返回值为: "+content);
return "redirect:/";
}
四、python接受数据并返回结果
Python部分,使用FastAPI搭建一个微服务非常简单,参考这个文章 [8] 。
贴出我的代码,再给出解释。
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List
import uvicorn
class PointList(BaseModel):
coordinates:List
app = FastAPI()
@app.post("/data")
def data(x:PointList):
print(x)
return "ooooook"