java+spark 应用构建(1)
java+spark 应用构建(1)
前言
需要做一个小型的后端
但是现在看到 springboot 有点犯恶心, 于是想着有没有什么东西能够替换掉 spring
软件需求:
简单的 CRUD + 用户登录
技术栈需求:
打包体量小 + 性能要求低
由以上两点分析出了基本的技术路线
- 简单的 CRUD
sqlite3 + sparkframework - 用户登录需求
jsonwebtoken + jbcrypt - 前端交互 + 软件测试需求
swagger + junit + mockito - 简易部署 + 方便迁移的开发环境
Gradle + shadowjar
下面我将介绍具体的工程目录以及构建方法
主要内容
gradle 简介及初始化
为何使用 gradle
- 部署方便,不需要再安装一个 maven
- 在使用难度上比 maven 要低一些,使用 maven 需要清楚的分别软件的生命周期,但是 gradle 不需要
如何安装并初始化一个 gradle 工程
- 从gralde 官网 下载一个二进制包
- 安装 java (默认已经安装)
- 输入以下命令,完成 gradle 安装
❯ mkdir /opt/gradle
❯ unzip -d /opt/gradle gradle-0.0.0-bin.zip // 可能不是这个版本号,需要注意
❯ ls /opt/gradle/gradle-0.0.0
LICENSE NOTICE bin README init.d lib media
❯ echo `export PATH=$PATH:/opt/gradle/gradle-9.3.1/bin` >> ~/.zshrc
❯ source ~/.zshrc
❯ gradle -v
------------------------------------------------------------
Gradle 9.0.0-rc-4
------------------------------------------------------------
Build time: 2025-07-28 15:10:02 UTC
Revision: a0a6deb6f9937f9e9894a4460f4158267a13ad03
Kotlin: 2.2.0
Groovy: 4.0.27
Ant: Apache Ant(TM) version 1.10.15 compiled on August 25 2024
Launcher JVM: 21.0.6 (Eclipse Adoptium 21.0.6+7-LTS)
Daemon JVM: /${mypath}/jdk-21.0.6+7 (no JDK specified, using current Java home)
OS: Linux 6.18.7-arch1-1 amd64
- 在需要的目录中初始化项目
❯ mkdir ${project name}
❯ cd ${the path where the project located}
❯ gradle init --type java-application
Enter target Java version (min: 7, default: 21): 21
Project name (default: example):
Select application structure:
1: Single application project
2: Application and library project
Enter selection (default: Single application project) [1..2] 1
Select build script DSL: 2
1: Kotlin
2: Groovy
Enter selection (default: Kotlin) [1..2] 2
Select test framework: 1
1: JUnit 4
2: TestNG
3: Spock
4: JUnit Jupiter
Enter selection (default: JUnit Jupiter) [1..4] 1
Generate build using new APIs and behavior (some features may change in the next minor release)? (default: no) [yes, no] no
> Task :init
Learn more about Gradle by exploring our Samples at https://docs.gradle.org/9.0.0-rc-4/samples/sample_building_java_applications.html
BUILD SUCCESSFUL in 13s
1 actionable task: 1 executed
Consider enabling configuration cache to speed up this build: https://docs.gradle.org/9.0.0-rc-4/userguide/configuration_cache_enabling.html
❯ tree -L2 ./
./
├── app
│ ├── build.gradle
│ └── src
├── gradle
│ ├── libs.versions.toml
│ └── wrapper
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
5 directories, 6 files
至此,已经完成了 gradle 的安装与项目的新建
gradle 简易用法
接下来简单列举一些 gradle 会用到的命令
❯ gradle clean // 清理 build 文件夹
❯ gradle build // 执行编译命令
❯ gradle run // 运行工程
❯ gradle clean build run // 清除编译缓存,重新编译并运行
❯ gradle assemble // 编译项目但不跑 test
❯ gradle test // 跑项目的测试
❯ gradle tasks // 列举 gradle 的任务
ℹ️: gradle 使用注意事项
我们在本机安装了 gradle , 主要是用于初始化应用。实际上我们编译的时候都会使用工程内的
./gradlew
Windows 使用./gradlew.bat
工程组织方法
在面向简单的 CRUD 项目来说,一般会采用单体或者分层架构进行工程组织
单体架构是指所有的功能模块集中在一个或者几个文件之中
在具体的操作层面指一个文件包含单个或几个功能模块
// UserController.java
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService; // 直接注入UserService
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.getUserById(id);
}
}
// UserService.java
@Service
public class UserService {
@Autowired
private UserRepository userRepository; // 直接注入UserRepository
public User getUserById(Long id) {
// 业务逻辑
return userRepository.findById(id).orElse(null);
}
}
// UserRepository.java
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// 数据访问方法
}
// User.java
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// ... getters and setters
}
如上文代码所示,一个文件写入一个功能模块,并没有进行分包
这样子减轻了代码的工程量
分层结构是指依据一定的原则对工程进行分层
基本的分层方式通常为 表示层 / 业务逻辑层 / 数据访问层
根据上述三层,还可以做出如下改变
- 在表示层和业务逻辑层中加入应用层, 该层主要用于在多个业务逻辑层之上进行进一步封装
从而处理更复杂的业务逻辑 - 在数据访问层之下加入基础设施层,该层主要用于处理消息队列,日志等的技术细节
根据上文的需求,本文使用三层分层结构构建本项目
工程具体各层级作用
在实际的分层(三层)架构中,通常有 MVC , MVVM , MVP 等几种细分方法
但是我个人觉得没必要拘泥于怎么划分,就从最基本的工程实践进行处理即可
❯ tree -L4 ./
./
├── java
│ └── org
│ └── example
│ ├── App.java
│ ├── config // 对工程进行设置,比如 mybatis 以及 log 的设置
│ ├── controllers // 控制层,用于接受前端用户的操作
│ ├── filters // 过滤层,过滤登录用户权限用
│ ├── mappers // mybatis 的具体实践, 用于注册具体的 CRUD 操作
│ ├── models // 实体层
│ ├── services // 业务层
│ └── utils // 定义通用功能,比如 swagger , 数据库链接方式等
└ ...
其中各层级的功能如图所示
一般的各个层级的模板语法
本章节主要介绍各个层的具体工作,所需要的依赖并给出简易的示例代码
model 层
model 层主要用于定义与数据库链接的实体,通常来讲一张表对应一个实体
⚠️注意事项:
如果需要联合实体 (如数据表里可能有 join 表之类的存在), 则可以加入一个 DTO 层
在本工程中, 定义 model 层需要使用到 lombok 以及 mybatis-plus 库
- lombok 用于减少代码量,让我们只需要定义实体,而不需要写 getter / setter / 构造器 / tostring 等的方法
- mybatis-plus 使得 model 类的成员能够与具体的数据库表及属性名映射起来
package org.example.models;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor // 生成一个全参数构造器
@NoArgsConstructor // 生成一个无参数构造器
@TableName("work_info") // 将该模型类与 'work_info' 表进行对应
public class WorkInfo {
@TableId(value = "id", type = IdType.AUTO) // 使用数据库的 autoincrement 作为 primary key
private Integer id;
@TableField("work_content") // 将 WorkInfo 类的 `worktontent` 成员与 `work_info` 表中的 `work_content` 列相对应
private String workContent;
}
mapper 层
mapper 层主要用于定义 mybatis-plus 的 CRUD 方法
其主要原理是继承 mybatis-plus 的 basemapper 类, 该类封装了通用的数据库操作
而如果要定义一些更复杂的数据库操作,
在 mybatis-plus 中, 可以有以下途径
- 使用注解 (本文使用方法)
- 使用 XML 映射文件 (mybatis-plus-generate 插件)
- 使用 mybatis-plus 的条件构造器
package org.example.mappers;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Select; // ibatis 的动态 sql 语句,用于取出数据库记录
import org.apache.ibatis.annotations.Update;
import org.example.models.User;
public interface UserMapper extends BaseMapper<User> {
// 自定义方法, 执行 select 方法
@Select("SELECT * FROM user WHERE username = #{username}")
User getUserByUsername(String username);
@Update(
"UPDATE USER SET password = #{password} ,created_at= #{createdAt}"
+ " ,updated_at=#{updatedAt}WHERE id = #{id}")
boolean updatePassword(Integer id, String password, String createdAt, String updatedAt);
}
services 层
服务层用于定义具体的业务逻辑
在本文中定义了一个接口再定义一个服务实现层
服务层的职能是链接数据库并处理业务逻辑
所以在服务层中需要 ibatis / mybatis
package org.example.services;
import org.example.models.User;
public interface UserService {
// 定义接口,由 impl 实现
User getUserByUsername(String username);
boolean verifyPassword(String rawPassword, String hashedPassword);
}
package org.example.services.impl;
import org.apache.ibatis.session.SqlSession;
import org.example.config.MyBatisPlusConfig;
import org.example.mappers.UserMapper;
import org.example.models.User;
import org.example.services.UserService;
import org.mindrot.jbcrypt.BCrypt;
public class UserServiceImpl implements UserService {
private static UserServiceImpl instance;
private UserServiceImpl() {}
// 单例模式,如果在 spring 中就不需要处理服务层的单例,但本文使用的是 spark
// 所以需要新建单例为 controller 提供
public static synchronized UserServiceImpl getInstance() {
if (instance == null) {
instance = new UserServiceImpl();
}
return instance;
}
// 覆写 mybatis 的方法,在 spark 中, 与写删有关的方法都无法使用
// 所以写删方法需要自己重新写
@Override
public User getUserByUsername(String username) {
// 获取数据库会话,从而对数据库进行操作
try (SqlSession sqlSession = MyBatisPlusConfig.getSqlSessionFactory().openSession(false)) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.getUserByUsername(username);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
public boolean verifyPassword(String rawPassword, String hashedPassword) {
if (rawPassword == null || hashedPassword == null) {
return false;
}
try {
return BCrypt.checkpw(rawPassword, hashedPassword);
} catch (Exception e) {
return false;
}
}
}
controllers 层
controller 层, 主要处理从用户的操作逻辑
在 spark 框架中需要定义相关的路由以及动作
package org.example.controllers;
import java.util.List;
import org.example.models.MainWork;
import org.example.services.MainWorkService;
import org.example.services.impl.MainWorkServiceImpl;
// 数据 json 化
import com.google.gson.Gson;
// 引入 spark 依赖
import static spark.Spark.delete;
import static spark.Spark.get;
import static spark.Spark.post;
public class MainWorkController {
private MainWorkService mainWorkService;
private static final Gson gson = new Gson();
public MainWorkController() {
this.mainWorkService = MainWorkServiceImpl.getInstance();
setupRoutes();
}
// 配置 spark 路由
private void setupRoutes() {
// 在 setupRoutes 这个方法中配置路由映射
get(
"/mainwork/:id",
(req, res) -> {
int id = Integer.parseInt(req.params(":id"));
MainWork mainWork = mainWorkService.getById(id);
if (mainWork != null) {
res.type("application/json");
return gson.toJson(mainWork);
} else {
res.status(404);
return "DailyInfo not found";
}
});
}
}
同时在 main 中设置这一段路由
public class Main {
public static void main(String[] args) {
Router router = Router.router(vertx);
UserController userController = new UserController();
userController.setupRoutes(router); // 设置路由
vertx.createHttpServer()
.requestHandler(router)
.listen(8080);
}
}
总结
本文章介绍了以下内容
- Gradle 项目以及其组织方法
- 各个层级的作用以及相应的模板语法

浙公网安备 33010602011771号