一、介绍
首先说一下为什么需要让数据在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 服务。
代码和结果图如下
<
html界面图
当点击了Submit按钮,两个input框中的数据就会传输到Controller。
F12 Network查看数据传输
三、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
运行该代码,可以得到如下输出
该类提供了很多方法,其中就包括传递参数的post方法。
public
只需使用该方法,就可以传递参数给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是python web框架,包含了一些基本路由的功能。
BaseModel用来接收上游传过来的参数。PointList继承了该类,就可以在服务中接受并自动装载参数。
在PointList类中,使用了一个List,是用来接收数组。因为上游传过来的是List。
@app.post("data")相当于Spring中的RequestMapping功能。
data函数形参列表中的x就可以接受到上游的参数,函数体中用print输出x可以看到结果,然后return一个字符串就返回结果给Java。
uvicorn是一个微型服务器,类似于Tomcat,可以设置需要启动的服务,host,port等参数。
五、总结
先展示整套流程的结果图。
然后总结一下在该数据传递过程中需要用的技术。
- Html
- thymeleaf
- form发送请求
- Java
- Spring Boot
- Controller接收参数
- MultiValueMap包装数据
- Jackson工具类转json
- RestTemplate发请求
- Python
- FastAPI
- BaseModel接收参数
- uvicorn
改进方向
目前是手写了一个请求转发,后续看有没有什么中间件,rpc,微服务的东西能用的。以便更好的扩展。
参考
- ^Thymeleaf官网 https://www.thymeleaf.org/documentation.html
- ^Spring Boot官网 https://spring.io/projects/spring-boot
- ^FastAPI官网 https://fastapi.tiangolo.com/
- ^Java调用python
- ^Java调用python
- ^为什么用MultiValueMap代替HashMap
- ^Spring RestTemplate官方文档 https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html
- ^Python迅速搭建http接口服务