1. 概述
Groovy 作为 JVM 上的动态语言,凭借其简洁语法和强大元编程能力,在 Spring 生态中有着不错的集成表现。尤其适合快速构建原型或简化样板代码。
本文将带你用 Spring Boot + Groovy 实现一个简易的待办事项(Todo)应用,涵盖 REST 接口开发、JPA 数据持久化,并重点展示 Groovy 如何让 Java 开发变得更“丝滑”。
2. 待办事项应用设计
目标是实现一个基于 REST 的 CRUD 应用,功能包括:
✅ 创建任务
✅ 编辑任务
✅ 删除任务
✅ 查看单个任务
✅ 查看所有任务
构建工具选用 Maven,数据层使用 H2 内存数据库,便于快速启动和测试。
2.1. Maven 依赖配置
核心依赖如下,注意 Groovy 和 GMavenPlus 插件的引入:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>3.1.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.1.5</version>
</dependency>
<dependency>
<groupId>org.apache.groovy</groupId>
<artifactId>groovy</artifactId>
<version>4.0.21</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>3.1.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.1.214</version>
<scope>runtime</scope>
</dependency>
⚠️ 关键点:spring-boot-starter-web
用于构建 REST 接口,groovy
提供语言支持,spring-boot-starter-data-jpa
+ h2
构成轻量级持久化方案。
此外,必须添加 gmavenplus-plugin
插件,用于编译 Groovy 源码:
<build>
<plugins>
<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<version>3.0.2</version>
<executions>
<execution>
<goals>
<goal>addSources</goal>
<goal>addTestSources</goal>
<goal>generateStubs</goal>
<goal>compile</goal>
<goal>generateTestStubs</goal>
<goal>compileTests</goal>
<goal>removeStubs</goal>
<goal>removeTestStubs</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
这个插件负责生成存根(stubs)、编译 Groovy 类,并确保与 Java 代码的互操作性,漏配会导致编译失败。
2.2. JPA 实体类(Groovy 版)
使用 Groovy 定义 Todo
实体,代码非常简洁:
@Entity
@Table(name = 'todo')
class Todo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Integer id
@Column
String task
@Column
Boolean isCompleted
}
✅ 踩坑提示:Groovy 默认将无访问修饰符的字段设为 private
,并自动生成 getter/setter,无需 Lombok。这在 Spring Data JPA 中完全兼容。
2.3. 数据访问层
定义一个继承 JpaRepository
的 Groovy 接口,Spring Data 会自动提供实现:
@Repository
interface TodoRepository extends JpaRepository<Todo, Integer> {}
简单粗暴,零代码实现 CRUD。
2.4. 服务层
先定义接口:
interface TodoService {
List<Todo> findAll()
Todo findById(Integer todoId)
Todo saveTodo(Todo todo)
Todo updateTodo(Todo todo)
Todo deleteTodo(Integer todoId)
}
再提供 Groovy 实现类:
@Service
class TodoServiceImpl implements TodoService {
@Autowired
TodoRepository todoRepository
@Override
List<Todo> findAll() {
todoRepository.findAll()
}
@Override
Todo findById(Integer todoId) {
todoRepository.findById(todoId).get()
}
@Override
Todo saveTodo(Todo todo){
todoRepository.save(todo)
}
@Override
Todo updateTodo(Todo todo){
todoRepository.save(todo)
}
@Override
Todo deleteTodo(Integer todoId){
todoRepository.deleteById(todoId)
}
}
注意:Groovy 中方法调用可省略括号和点号(dot),如 save todo
,但为了可读性,建议保留括号,尤其是参数较多时。
2.5. 控制器层
使用 @RestController
暴露 REST 接口:
@RestController
@RequestMapping('/todo')
class TodoController {
@Autowired
TodoService todoService
@GetMapping
List<Todo> getAllTodoList() {
todoService.findAll()
}
@PostMapping
Todo saveTodo(@RequestBody Todo todo) {
todoService.saveTodo(todo)
}
@PutMapping
Todo updateTodo(@RequestBody Todo todo) {
todoService.updateTodo(todo)
}
@DeleteMapping('/{todoId}')
void deleteTodo(@PathVariable Integer todoId) {
todoService.deleteTodo(todoId)
}
@GetMapping('/{todoId}')
Todo getTodoById(@PathVariable Integer todoId) {
todoService.findById(todoId)
}
}
每个接口对应一个 CRUD 操作,结构清晰。
2.6. 启动类
Spring Boot 启动类也用 Groovy 编写:
@SpringBootApplication
class SpringBootGroovyApplication {
static void main(String[] args) {
SpringApplication.run(SpringBootGroovyApplication, args)
}
}
✅ Groovy 小技巧:
- 方法调用可省略括号:
run SpringBootGroovyApplication, args
- 类名无需
.class
后缀
别忘了在 pom.xml
中指定启动类:
<properties>
<start-class>com.example.demo.SpringBootGroovyApplication</start-class>
</properties>
3. 运行应用
执行以下任一命令即可启动:
mvn spring-boot:run
应用默认启动在 http://localhost:8080
,可通过 Postman 或 curl 测试接口。
4. 应用测试
使用 RestAssured
编写集成测试,验证各接口行为。
4.1. 测试准备
定义测试常量和初始化数据:
class TodoAppTest {
static final String API_ROOT = "http://localhost:8080/todo"
static Integer readingTodoId
static Integer writingTodoId
@BeforeClass
static void populateDummyData() {
Todo readingTodo = new Todo(task: 'Reading', isCompleted: false)
Todo writingTodo = new Todo(task: 'Writing', isCompleted: false)
def readingResponse = RestAssured.given()
.contentType(MediaType.APPLICATION_JSON_VALUE)
.body(readingTodo)
.post(API_ROOT)
readingTodoId = readingResponse.as(Todo.class).id
def writingResponse = RestAssured.given()
.contentType(MediaType.APPLICATION_JSON_VALUE)
.body(writingTodo)
.post(API_ROOT)
writingTodoId = writingResponse.as(Todo.class).id
}
}
✅ Groovy 特性:
- 使用 命名参数 初始化对象:
new Todo(task: 'Reading', isCompleted: false)
- 字符串插值:
"$API_ROOT/$readingTodoId"
,比 Java 的String.format
更直观
4.2. CRUD 接口测试
查询所有任务
@Test
void whenGetAllTodoList_thenOk() {
def response = RestAssured.get(API_ROOT)
assertEquals(HttpStatus.OK.value(), response.getStatusCode())
assertTrue(response.jsonPath().getList(".").size() > 0)
}
查询单个任务
@Test
void whenGetTodoById_thenOk() {
def response = RestAssured.get("$API_ROOT/$readingTodoId")
assertEquals(HttpStatus.OK.value(), response.getStatusCode())
Todo todo = response.as(Todo.class)
assertEquals(readingTodoId, todo.id)
}
更新任务
@Test
void whenUpdateTodoById_thenOk() {
def todo = new Todo(id: readingTodoId, isCompleted: true)
def response = RestAssured.given()
.contentType(MediaType.APPLICATION_JSON_VALUE)
.body(todo)
.put(API_ROOT)
assertEquals(HttpStatus.OK.value(), response.getStatusCode())
Todo updated = response.as(Todo.class)
assertTrue(updated.isCompleted)
}
删除任务
@Test
void whenDeleteTodoById_thenOk() {
def response = RestAssured.given()
.delete("$API_ROOT/$writingTodoId")
assertEquals(HttpStatus.OK.value(), response.getStatusCode())
}
创建新任务
@Test
void whenSaveTodo_thenOk() {
def todo = new Todo(task: 'Blogging', isCompleted: false)
def response = RestAssured.given()
.contentType(MediaType.APPLICATION_JSON_VALUE)
.body(todo)
.post(API_ROOT)
assertEquals(HttpStatus.OK.value(), response.getStatusCode())
}
测试覆盖完整,确保各接口行为符合预期。
5. 总结
通过这个小例子,我们展示了:
✅ Spring Boot 与 Groovy 的无缝集成
✅ Groovy 如何通过省略语法噪音(如分号、getter/setter)提升开发效率
✅ 使用命名参数、字符串插值等特性让代码更简洁
✅ GMavenPlus 插件在 Maven 项目中的关键作用
虽然 Groovy 在现代 Java 项目中使用频率不如 Kotlin,但在脚本化、DSL、测试等领域仍有其独特优势。对于已有 Spring Boot 项目,尝试用 Groovy 重写部分模块,可能会有意想不到的“丝滑”体验。
完整代码示例可参考 GitHub 仓库:https://github.com/example/spring-boot-groovy-demo