Building a Simple Web Application with Spring Boot and Groovy – 用Spring Boot和Groovy构建一个简单的Web应用

最后修改: 2020年 4月 26日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Groovy has a number of capabilities we might want to use in our Spring web applications.

Groovy有许多我们可能想在Spring Web应用程序中使用的功能。

So, in this tutorial, we’ll build a simple todo application with Spring Boot and Groovy. Also, we’ll explore their integration points.

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

2. Todo Application

2.Todo应用程序

Our application will have the following features:

我们的应用程序将有以下特点。

  • Create task
  • Edit task
  • Delete task
  • View specific task
  • View all tasks

It’ll be a REST-based application and we’ll use Maven as our build tool.

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

2.1. Maven Dependencies

2.1.Maven的依赖性

Let’s include all the dependencies required in our pom.xml file:

让我们在我们的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>

Here, we’re including spring-boot-starter-web to build REST endpoints, and importing the groovy dependency to provide Groovy support to our project.

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

For the persistence layer, we’re using spring-boot-starter-data-jpa, and h2 is the embedded database.

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

Also, we’ve got to include gmavenplus-plugin with all the goals in the pom.xml:

此外,我们还得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 Entity Class

2.2.JPA实体类

Let’s write a simple Todo Groovy class with three fields – id, task, and isCompleted:

让我们写一个简单的TodoGroovy类,有三个字段 – id, task,isCompleted

@Entity
@Table(name = 'todo')
class Todo {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Integer id
    
    @Column
    String task
    
    @Column
    Boolean isCompleted
}

Here, the id field is the unique identifier of the task. task contains the details of the task and isCompleted shows whether the task is completed or not.

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

Notice that, when we don’t provide access modifiers to the field, then the Groovy compiler will make that field as private and also will generate getter and setter methods for it.

请注意,当我们没有为字段提供访问修饰符时,那么Groovy编译器将使该字段成为私有字段,也将为其生成getter和setter方法

2.3. The Persistence Layer

2.3.持久层

Let’s create a Groovy interface – TodoRepository which implements JpaRepository. It’ll take care of all the CRUD operations in our application:

让我们创建一个Groovy接口 – TodoRepository ,它实现了JpaRepository。它将负责我们应用程序中的所有CRUD操作。

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

2.4. The Service Layer

2.4.服务层

The TodoService interface contains all the abstract methods required for our CRUD operation:

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

interface TodoService {

    List<Todo> findAll()

    Todo findById(Integer todoId)

    Todo saveTodo(Todo todo)

    Todo updateTodo(Todo todo)

    Todo deleteTodo(Integer todoId)
}

The TodoServiceImpl is an implementation class which implements all the methods of TodoService:

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. The Controller Layer

2.5.控制器层

Now, let’s define all the REST APIs in the TodoController which is our @RestController:

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

@RestController
@RequestMapping('todo')
public 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
    }
}

Here, we’ve defined five endpoints which user can call to perform CRUD operations.

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

2.6. Bootstrapping the Spring Boot Application

2.6.引导Spring Boot应用程序

Now, let’s write a class with the main method that will be used to start our application:

现在,让我们写一个带有main方法的类,它将被用来启动我们的应用程序。

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

Notice that, in Groovy, the use of parenthesis is optional when calling a method by passing arguments – and this is what we’re doing in the example above.

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

Also, the suffix .class is not needed for any class in Groovy that’s why we’re using the SpringBootGroovyApplication directly.

另外,Groovy中的任何类都不需要后缀.class这就是为什么我们要直接使用SpringBootGroovyApplication

Now, let’s define this class in pom.xml as start-class:

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

<properties>
    <start-class>com.baeldung.app.SpringBootGroovyApplication</start-class>
</properties>

3. Running the Application

3.运行应用程序

Finally, our application is ready to run. We should simply run the SpringBootGroovyApplication class as the Java application or run the Maven build:

最后,我们的应用程序已经可以运行了。我们应该简单地将SpringBootGroovyApplication类作为Java应用程序运行,或者运行Maven构建。

spring-boot:run

This should start the application on http://localhost:8080 and we should be able to access its endpoints.

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

4. Testing the Application

4.测试应用程序

Our application is ready for testing. Let’s create a Groovy class – TodoAppTest to test our application.

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

4.1. Initial Setup

4.1.初始设置

Let’s define three static variables – API_ROOT, readingTodoId, and writingTodoId in our class:

让我们在类中定义三个静态变量–API_ROOTreadingTodoIdwritingTodoId

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

Here, the API_ROOT contains the root URL of our app. The readingTodoId and writingTodoId are the primary keys of our test data which we’ll use later to perform testing.

这里,API_ROOT包含了我们应用程序的根URL。readingTodoIdwritingTodoId是我们测试数据的主键,我们稍后将用它来进行测试。

Now, let’s create another method – populateDummyData() by using the annotation @BeforeClass to populate the test data:

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

@BeforeClass
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()
}

We’ll also populate variables – readingTodoId and writingTodoId in the same method to store the primary key of the records we’re saving.

我们还将在同一方法中填充变量–readingTodoIdwritingTodoId,以存储我们所保存的记录的主键。

Notice that, in Groovy we can also initialize beans by using named parameters and the default constructor like we’re doing for beans like readingTodo and writingTodo in the above snippet.

注意,在Groovy中,我们也可以通过使用命名参数和默认构造函数来初始化Bean,就像我们在上面的片段中对readingTodowritingTodo等Bean所做的那样。

4.2. Testing CRUD Operations

4.2.测试CRUD操作

Next, let’s find all the tasks from the todo list:

接下来,让我们从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
}

Then, let’s find a specific task by passing readingTodoId which we’ve populated earlier:

然后,让我们通过传递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()
}

Here, we’ve used interpolation to concatenate the URL string.

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

Furthermore, let’s try to update the task in the todo list by using readingTodoId:

此外,让我们尝试通过使用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()
}

And then delete the task in the todo list by using writingTodoId:

然后通过使用writingTodoId来删除todo列表中的任务。

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

Finally, we can save a new task:

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

@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. Conclusion

5.总结

In this article, we’ve used Groovy and Spring Boot to build a simple application. We’ve also seen how they can be integrated together and demonstrated some of the cool features of Groovy with examples.

在这篇文章中,我们使用Groovy和Spring Boot构建了一个简单的应用程序。我们还看到了如何将它们整合在一起,并通过实例展示了Groovy的一些很酷的功能。

As always, the full source code of the example is available over on GitHub.

一如既往,该示例的完整源代码可在GitHub上获得over