SpringBoot 整合 Weka 机器学习框架实战
在 SpringBoot 项目中整合 Weka 机器学习框架的实战方案。内容涵盖 Weka、Spark MLlib 及 EasyRec 三种技术选型对比,重点演示了基于本地 CSV 和数据库样本数据构建决策树模型(J48)实现货品上架推荐的具体步骤。此外,还探讨了结合 AI 大模型进行推荐的实现思路。案例包含数据准备、依赖导入、模型训练、预测评估及代码实现,适用于小型系统的推荐场景开发。

在 SpringBoot 项目中整合 Weka 机器学习框架的实战方案。内容涵盖 Weka、Spark MLlib 及 EasyRec 三种技术选型对比,重点演示了基于本地 CSV 和数据库样本数据构建决策树模型(J48)实现货品上架推荐的具体步骤。此外,还探讨了结合 AI 大模型进行推荐的实现思路。案例包含数据准备、依赖导入、模型训练、预测评估及代码实现,适用于小型系统的推荐场景开发。

在微服务开发中,经常会遇到需要进行推荐的场景,比如在电商场景中,需要基于用户的过往购买历史,选品喜欢,轨迹等指标信息进行选品推荐,再比如在社交应用中,需要根据用户的操作历史、个人标签等推荐一些功能从而展示给用户。尽管这样的推荐操作可以结合一些大数据、机器学习等推荐算法来做,但是对一些小型的系统来说,考虑到对接成本、学习成本和维护成本等因素,本篇将详细介绍下在微服务应用中如何对接第三方的机器学习框架,从而实现特定场景下的业务推荐。
接下来介绍几种适合 Java 开发语言模式下的机器学习框架技术方案。
Weka(Waikato Environment for Knowledge Analysis)是一款由新西兰怀卡托大学开发的开源机器学习与数据挖掘软件,以其易用性、丰富的算法库和可视化界面闻名。它支持从数据预处理到模型评估的全流程,适合教学、研究和快速原型开发。官网:https://ml.cms.waikato.ac.nz/weka/
Weka,是一款免费、非商业化、基于 Java 的开源的机器学习与数据挖掘软件,并提供了 maven 依赖,拥有丰富的 Java API。

Github 地址:Weka Wiki

Weka 具备如下特点:
Weka 通过不同界面模块实现数据挖掘流程:
Weka 支持如下算法类型
Weka 在下面的一些场景中可以考虑选择使用:
Apache Spark MLlib 是一个专为大规模数据处理设计的分布式机器学习库,作为 Apache Spark 生态系统的核心组件,它通过利用 Spark 的分布式计算框架,能够高效处理海量数据,并加速模型的训练和预测过程。官网:MLlib | Apache Spark

MLlib 的架构由多个核心模块组成,协同工作以简化大规模数据处理中的机器学习任务复杂性:
Transformer(数据转换组件,如标准化、归一化)和 Estimator(可训练模型,如逻辑回归、决策树),构建模块化的机器学习工作流,简化任务流程并支持复用。RDD(弹性分布式数据集,提供容错和并行计算能力)和 DataFrame(类似 SQL 表的高层次数据类型,支持模式化数据结构和 SQL 查询),其中 DataFrame 是当前主要支持的数据类型,能更好地与 Spark SQL 集成。MLlib 核心功能如下:
MLlib 具备如下技术优势:
在下面的一些场景中可以考虑使用 MLlib:
EasyRec 是阿里巴巴开源的推荐系统框架,基于 TensorFlow 和 PyTorch 构建,提供模块化设计、分布式训练和在线预测等功能,适用于电商、音乐等领域,旨在简化推荐系统开发并提升推荐效果。
EasyRec 核心模块架构如下:
EasyRec 具备如下优势:
EasyRec 可广泛应用于电商、音乐、视频、新闻等各类需要个性化推荐的场景,无论是商品推荐、内容推送还是广告定向,都能帮助提升用户体验和业务转化率。
EasyRec 已经在阿里巴巴集团内部多个业务场景中得到广泛应用,如淘宝、天猫、优酷等,取得了显著的业务效果。同时,EasyRec 也积极与社区合作,推动推荐系统技术的发展和应用。
接下来将通过一个实际案例,适用 Weka 实现一个货品的推荐上架案例。
完整的需求描述如下:
使用 Weka 框架最后进行推荐的使用,需要导入样本数据,构建决策树,配合上述的需求,在本地提前准备一个 CSV 文件,里面有如下样本数据:
length,width,height,weight,fragile,shelf_type 50,40,30,5,false,small_shelf 120,80,150,120,false,floor_stack 30,20,10,1,false,small_shelf 200,60,40,80,true,high_rack 10,10,5,0.5,false,small_shelf 300,120,80,300,false,floor_stack 80,60,180,50,false,high_rack 25,20,15,2,true,small_shelf 150,100,200,150,false,floor_stack 60,40,160,40,false,high_rack
创建一个 springboot 工程,导入如下核心依赖:
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<satoken.version>1.37.0</satoken.version>
<spring-ai.version>1.0.0-M6</spring-ai.version>
<spring-ai-alibaba.version>1.0.0-M6.1</spring-ai-alibaba.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.2</version>
<relativePath/>
</parent>
<dependencies>
<!-- For handling CSV files -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv
1.9.0
nz.ac.waikato.cms.weka
weka-stable
3.8.6
com.alibaba
fastjson
1.2.83
org.springframework.boot
spring-boot-starter-aop
org.springframework.boot
spring-boot-starter-web
mysql
mysql-connector-java
8.0.28
org.projectlombok
lombok
com.alibaba.cloud.ai
spring-ai-alibaba-starter
${spring-ai-alibaba.version}
org.springframework.ai
spring-ai-bom
${spring-ai.version}
pom
import
Central Portal Snapshots
central-portal-snapshots
https://central.sonatype.com/repository/maven-snapshots/
false
true
spring-milestones
Spring Milestones
https://repo.spring.io/milestone
false
spring-snapshots
Spring Snapshots
https://repo.spring.io/snapshot
false
基于上述的本地样本数据,完成一个基于 Weka 机器学习框架的推荐上架的案例实现。
pom 中导入 Weka 的依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>nz.ac.waikato.cms.weka</groupId>
<artifactId>weka-stable</artifactId>
<version>3.8.6</version>
</dependency>
在本地的 resources 目录下增加一个 csv 样本文件,样本的数据如下:

完整的实现代码如下:
package com.congge.command.v3;
import weka.classifiers.trees.J48;
import weka.core.Instances;
import weka.core.converters.ConverterUtils.DataSource;
/**
* 基于本地的训练文件样本数据进行预测
*/
public class ShelfRecommendationSystemV3 {
private J48 decisionTree;
private Instances dataStructure;
/**
* 加载训练数据并构建模型
*/
public void trainModel(String csvFilePath) throws Exception {
// 1. 加载 CSV 数据
DataSource source = new DataSource(csvFilePath);
Instances data = source.getDataSet();
// 2. 设置类别属性(货架类型)
if (data.classIndex() == -1) {
data.setClassIndex(data.numAttributes() - 1);
}
// 3. 保存数据结构用于后续预测
this.dataStructure = new Instances(data, 0);
// 4. 构建决策树模型
decisionTree = new J48();
decisionTree.buildClassifier(data);
System.out.println("模型训练完成!");
}
/**
* 预测新货物的推荐货架类型
*/
public String predictShelfType(double length, double width, double height, double weight, fragile) Exception {
(decisionTree == || dataStructure == ) {
();
}
weka.core. .core.DenseInstance();
instance.setDataset(dataStructure);
instance.setValue(, length);
instance.setValue(, width);
instance.setValue(, height);
instance.setValue(, weight);
instance.setValue(, fragile ? : );
decisionTree.classifyInstance(instance);
dataStructure.classAttribute().value(() prediction);
}
Exception {
(csvFilePath);
source.getDataSet();
(data.classIndex() == -) {
data.setClassIndex(data.numAttributes() - );
}
weka.classifiers. .classifiers.Evaluation(data);
eval.evaluateModel(decisionTree, data);
System.out.println();
System.out.println(eval.toSummaryString());
System.out.println(eval.toClassDetailsString());
System.out.println(eval.toMatrixString());
}
{
();
{
;
system.trainModel(csvPath);
system.evaluateModel(csvPath);
System.out.println();
system.predictShelfType(, , , , );
System.out.printf(, recommendation1);
system.predictShelfType(, , , , );
System.out.printf(, recommendation2);
system.predictShelfType(, , , , );
System.out.printf(, recommendation3);
} (Exception e) {
e.printStackTrace();
}
}
}
运行一下,效果如下:

仍然是基于 Weka 机器学习框架,假如我们的样本数据存储在数据库的表中,下面看具体的实现
增加如下的样本数据测试表,并添加一些初始化数据
CREATE TABLE product_shelf_mapping (
id INT AUTO_INCREMENT PRIMARY KEY,
product_name VARCHAR(100),
length DOUBLE NOT NULL COMMENT '长度 (cm)',
width DOUBLE NOT NULL COMMENT '宽度 (cm)',
height DOUBLE NOT NULL COMMENT '高度 (cm)',
weight DOUBLE NOT NULL COMMENT '重量 (kg)',
shelf_type ENUM('small_shelf', 'high_shelf', 'floor_stack') NOT NULL COMMENT '货架类型',
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
-- 清空表(测试时使用,生产环境请移除)
TRUNCATE TABLE product_shelf_mapping;
-- 插入测试数据
INSERT INTO product_shelf_mapping (product_name, length, width, height, weight, shelf_type) VALUES
-- 小货架(体积小、重量轻)
('小型电子元件', 15, 10, 5, 0.2, 'small_shelf'),
('办公文具套装', 30, 20, 8, 0.5, ),
(, , , , , ),
(, , , , , ),
(, , , , , ),
(, , , , , ),
(, , , , , ),
(, , , , , ),
(, , , , , ),
(, , , , , ),
(, , , , , ),
(, , , , , ),
(, , , , , ),
(, , , , , ),
(, , , , , ),
(, , , , , ),
(, , , , , ),
(, , , , , ),
(, , , , , );
除了上面的 Weka 框架库,还需要引入 mysql 依赖
<!-- MySQL 连接驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
下面是基于 mysql 表作为样本数据的完整代码
package com.congge.command.v2;
import lombok.Data;
import weka.classifiers.trees.J48;
import weka.core.*;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* 基于数据库的样本数据进行预测
*/
public class ShelfRecommendationSystem {
// MySQL 数据库配置
private static final String DB_URL = "jdbc:mysql://<host>:3306/<database>";
private static final String DB_USER = "<username>";
private static final String DB_PASSWORD = "<password>";
public static void main(String[] args) {
try {
// 1. 从 MySQL 加载训练数据
List<Product> trainingData = loadTrainingDataFromMySQL();
// 2. 转换为 Weka Instances 格式
Instances instances = createInstances(trainingData);
// 3. 训练决策树模型
J48 tree = trainDecisionTree(instances);
testModel(tree, instances);
();
newProduct.setLength();
newProduct.setWidth();
newProduct.setHeight();
newProduct.setWeight();
recommendShelf(tree, newProduct);
System.out.println( + recommendedShelf);
} (Exception e) {
e.printStackTrace();
}
}
List<Product> SQLException {
List<Product> products = <>();
( DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD)) {
;
( conn.createStatement(); stmt.executeQuery(query)) {
(rs.next()) {
();
product.setLength(rs.getDouble());
product.setWidth(rs.getDouble());
product.setHeight(rs.getDouble());
product.setWeight(rs.getDouble());
product.setShelfType(rs.getString());
products.add(product);
}
}
}
System.out.println( + products.size() + );
products;
}
Instances {
ArrayList<Attribute> attributes = <>();
attributes.add( ());
attributes.add( ());
attributes.add( ());
attributes.add( ());
ArrayList<String> shelfTypes = <>();
shelfTypes.add();
shelfTypes.add();
shelfTypes.add();
attributes.add( (, shelfTypes));
(, attributes, );
instances.setClassIndex(instances.numAttributes() - );
(Product product : products) {
[] values = [instances.numAttributes()];
values[] = product.getLength();
values[] = product.getWidth();
values[] = product.getHeight();
values[] = product.getWeight();
(product.getShelfType()) {
: values[] = ; ;
: values[] = ; ;
: values[] = ; ;
: values[] = Utils.missingValue();
}
instances.add( (, values));
}
instances;
}
J48 Exception {
String[] options = [];
options[] = ;
();
tree.setOptions(options);
tree.buildClassifier(instances);
System.out.println();
System.out.println(tree);
tree;
}
Exception {
instances.randomize( ());
() Math.round(instances.numInstances() * );
instances.numInstances() - trainSize;
(instances, , trainSize);
(instances, trainSize, testSize);
tree.buildClassifier(train);
;
( ; i < test.numInstances(); i++) {
test.instance(i);
tree.classifyInstance(inst);
inst.classValue();
(predicted == actual) {
correct++;
}
}
() correct / test.numInstances() * ;
System.out.printf(, accuracy, correct, test.numInstances());
}
String Exception {
ArrayList<Attribute> attributes = <>();
attributes.add( ());
attributes.add( ());
attributes.add( ());
attributes.add( ());
ArrayList<String> shelfTypes = <>();
shelfTypes.add();
shelfTypes.add();
shelfTypes.add();
attributes.add( (, shelfTypes));
(, attributes, );
instances.setClassIndex(instances.numAttributes() - );
[] values = [instances.numAttributes()];
values[] = product.getLength();
values[] = product.getWidth();
values[] = product.getHeight();
values[] = product.getWeight();
values[] = Utils.missingValue();
instances.add( (, values));
tree.classifyInstance(instances.instance());
shelfTypes.get(() prediction);
}
}
{
length;
width;
height;
weight;
String shelfType;
}
运行上面的代码做一下测试


核心实现思路:
在配置文件中增加如下 AI 相关的配置信息
spring:
# 增加 ai 的配置
ai:
dashscope:
api-key: <your_api_key>
chat:
options:
model: qwen-max
增加一个测试接口,便于查看效果
package com.congge.command.v5;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/client/ai")
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
// localhost:8082/client/ai/chat?message=今天北京的天气如何
// localhost:8082/client/ai/chat?message=你是谁
// localhost:8082/client/ai/chat?message=当前时间是多少
@PostMapping("/chat")
public String chat(@RequestBody Product product){
Prompt prompt = new Prompt(new UserMessage(buildPrompt(product)));
String content = chatClient.prompt(prompt).call().content();
return content;
}
private static String buildPrompt(Product product) {
return String.format(, product.getProductName(), product.getLength(), product.getWidth(), product.getHeight(), product.getWeight(), product.getVolume(), product.getDensity());
}
}
Product 实体类
package com.congge.command.v5;
import lombok.Data;
@Data
public class Product {
private Long id;
private String productName;
private double length;
private double width;
private double height;
private double weight;
private String shelfType;
// 计算体积
public double getVolume() {
return length * width * height;
}
// 计算密度
public double getDensity() {
return weight / getVolume();
}
}
启动工程后,调用下接口,这里随机给一组产品的长宽高进行测试,结果如下:

本文通过较大的篇幅详细介绍了基于 Java 技术栈,整合机器学习算法框架 Weka 结合一个实际案例实现一个货品上架的功能。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online