使用Spring Boot和Groovy构建一个简单的Web应用程序

2023/05/12

1. 概述

Groovy具有许多我们可能希望在Spring Web应用程序中使用的功能

因此,在本教程中,我们将使用Spring Boot和Groovy构建一个简单的Todo应用程序。此外,我们将探讨它们的集成点。

2. Todo应用程序

我们的应用程序将具有以下功能:

  • 创建任务
  • 编辑任务
  • 删除任务
  • 查看具体任务
  • 查看所有任务

它将是一个基于REST的应用程序,我们将使用Maven作为我们的构建工具

2.1 Maven依赖项

首先我们在pom.xml文件中包含所需的所有依赖项:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    <version>2.5.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.5.1</version>
</dependency>
<dependency>
    <groupId>org.codehaus.groovy</groupId>
    <artifactId>groovy</artifactId>
    <version>3.0.3</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <version>2.5.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.4.200</version>
    <scope>runtime</scope>
</dependency>

在这里,我们包括spring-boot-starter-web来构建REST端点,并导入groovy依赖项以为我们的项目提供Groovy支持

对于持久层,我们使用spring-boot-starter-data-jpa,而h2是嵌入式数据库

此外,我们必须在pom.xml中包含具有所有目标gmavenplus-plugin

<build>
    <plugins>
        //...
        <plugin>
            <groupId>org.codehaus.gmavenplus</groupId>
            <artifactId>gmavenplus-plugin</artifactId>
            <version>1.9.0</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>

2.2 JPA实体类

让我们编写一个简单的Todo Groovy类,其中包含3个字段-id、task和isCompleted:

@Entity
@Table(name = 'todo')
class Todo {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Integer id

    @Column
    String task

    @Column
    Boolean isCompleted
}

这里的id字段是任务的唯一标识,task包含任务的详细信息,isCompleted显示任务是否已完成。

请注意,当我们不为字段提供访问修饰符时,Groovy编译器会将该字段设为私有字段,并为其生成getter和setter方法

2.3 持久层

让我们创建一个Groovy接口-继承JpaRepository的TodoRepository,它将处理我们应用程序中的所有CRUD操作:

@Repository
interface TodoRepository extends JpaRepository<Todo, Integer> {}

2.4 服务层

TodoService接口包含我们的CRUD操作所需的所有抽象方法

interface TodoService {

    List<Todo> findAll()

    Todo findById(Integer todoId)

    Todo saveTodo(Todo todo)

    Todo updateTodo(Todo todo)

    Todo deleteTodo(Integer todoId)
}

TodoServiceImpl是一个实现TodoService所有方法的实现类:

@Service
class TodoServiceImpl implements TodoService {

    //...

    @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
    }
}

2.5 控制器层

现在,让我们在TodoController中定义所有REST API,这是我们的@RestController

@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}')
    deleteTodo(@PathVariable Integer todoId){
        todoService.deleteTodo todoId
    }

    @GetMapping('/{todoId}')
    Todo getTodoById(@PathVariable Integer todoId){
        todoService.findById todoId
    }
}

在这里,我们定义了五个端点,用户可以调用它们来执行CRUD操作。

2.6 引导Spring Boot应用程序

现在,让我们编写一个包含用于启动应用程序的main方法的类:

@SpringBootApplication
class SpringBootGroovyApplication {
    
    static void main(String[] args) {
        SpringApplication.run SpringBootGroovyApplication, args
    }
}

请注意,在Groovy中,当通过传递参数调用方法时,括号的使用是可选,这就是我们在上面的示例中所做的。

此外,Groovy中的任何类都不需要后缀.class,这就是我们直接使用SpringBootGroovyApplication的原因。

现在,让我们在pom.xml中将这个类定义为start-class

<properties>
    <start-class>cn.tuyucheng.taketoday.app.SpringBootGroovyApplication</start-class>
</properties>

3. 运行应用程序

最后,我们的应用程序已准备好运行,我们应该简单地将SpringBootGroovyApplication类作为Java应用程序运行或运行Maven构建:

spring-boot:run

这应该在http://localhost:8080上启动应用程序,我们应该能够访问它的端点。

4. 测试应用

我们的应用程序已准备好进行测试,让我们创建一个Groovy类TodoAppTest来测试我们的应用程序。

4.1 初始设置

让我们在类中定义三个静态变量-API_ROOT、readingTodoId和writingTodoId:

static API_ROOT = "http://localhost:8080/todo"
static readingTodoId
static writingTodoId

在这里,API_ROOT包含我们应用程序的根URL,readingTodoId和writingTodoId是我们测试数据的主键,稍后我们将使用它们来执行测试。

现在,让我们创建另一个方法populateDummyData(),使用注解@BeforeAll来填充测试数据:

@BeforeAll
static void populateDummyData() {
	Todo readingTodo = new Todo(task: 'Reading', isCompleted: false)
	Todo writingTodo = new Todo(task: 'Writing', isCompleted: false)

	final Response readingResponse =
		RestAssured.given()
			.contentType(MediaType.APPLICATION_JSON_VALUE)
			.body(readingTodo).post(API_ROOT)

	Todo cookingTodoResponse = readingResponse.as Todo.class
	readingTodoId = cookingTodoResponse.getId()

	final Response writingResponse =
		RestAssured.given()
			.contentType(MediaType.APPLICATION_JSON_VALUE)
			.body(writingTodo).post(API_ROOT)

	Todo writingTodoResponse = writingResponse.as Todo.class
	writingTodoId = writingTodoResponse.getId()
}

我们还将在同一方法中填充变量readingTodoId和writingTodoId以存储我们正在保存的记录的主键。

请注意,在Groovy中,我们还可以使用命名参数和默认构造函数来初始化bean,就像我们在上面的代码片段中对readingTodo和writingTodo这样的bean所做的那样。

4.2 测试CRUD操作

接下来,我们从Todo列表中找出所有任务:

@Test
void whenGetAllTodoList_thenOk(){
    final Response response = RestAssured.get(API_ROOT)
    
    assertEquals HttpStatus.OK.value(),response.getStatusCode()
    assertTrue response.as(List.class).size() > 0
}

然后,通过传递我们之前填充的readingTodoId来查找特定任务:

@Test
void whenGetTodoById_thenOk(){
    final Response response = RestAssured.get("$API_ROOT/$readingTodoId")
    
    assertEquals HttpStatus.OK.value(),response.getStatusCode()
    Todo todoResponse = response.as Todo.class
    assertEquals readingTodoId,todoResponse.getId()
}

在这里,我们使用插值来连接URL字符串。

此外,我们使用readingTodoId更新Todo列表中的任务:

@Test
void whenUpdateTodoById_thenOk(){
    Todo todo = new Todo(id:readingTodoId, isCompleted: true)
    final Response response = RestAssured.given()
            .contentType(MediaType.APPLICATION_JSON_VALUE)
            .body(todo)
            .put(API_ROOT)
          
    assertEquals HttpStatus.OK.value(),response.getStatusCode()
    Todo todoResponse = response.as Todo.class
    assertTrue todoResponse.getIsCompleted()
}

然后使用writingTodoId删除Todo列表中的任务:

@Test
void whenDeleteTodoById_thenOk(){
    final Response response = RestAssured.given()
            .delete("$API_ROOT/$writingTodoId")
    
    assertEquals HttpStatus.OK.value(),response.getStatusCode()
}

最后,我们可以保存一个新任务:

@Test
void whenSaveTodo_thenOk(){
    Todo todo = new Todo(task: 'Blogging', isCompleted: false)
    final Response response = RestAssured.given()
            .contentType(MediaType.APPLICATION_JSON_VALUE)
            .body(todo)
            .post(API_ROOT)
    
    assertEquals HttpStatus.OK.value(),response.getStatusCode()
}

5. 总结

在本文中,我们使用Groovy和Spring Boot构建了一个简单的应用程序,并看到了如何将它们集成在一起。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章