Build a RESTful API using SpringBoot

Keywords: Java Spring Boot

background

Is such a simple thing worth writing an article?

When we started a project last year, because more and more people participated in it, we soon exposed a problem: everyone has his own set of (code) style. Considering the cooperative operation of the team, it is proposed to formulate relevant specifications to make everyone's pace consistent, which is also conducive to the promotion efficiency of the project. During the implementation process, there were particularly big differences (expected). Among them, the most controversial part was the Http API. Two main problems were found:

  • Many students think that RESTful API is good (Advanced), but why is it good?
  • Many students think they know what RESTful API looks like, but they sit down and chat and find that everyone's is different. No one can convince each other?

Finally, because of the project cycle, there is no more entanglement in this matter. Under the guise of business priority, we first promote the project according to our own style and gradually establish norms in the process of practice.

Later, when reviewing the code, I also found a very interesting phenomenon: even for the same student, the RESTful API written by him is actually inconsistent, mainly showing two phenomena:

  • In similar business scenarios, the design of API URLs and parameters are obviously different. The student can't tell the reason. It may be related to the mood when writing specific APIs;
  • In some business scenarios, the API design has obviously deviated from RESTful. The student prevaricated with "if you don't write so, the business logic can't be realized";

After communicating with many students, I came to a conclusion: it is impossible to let these students who think they understand RESTful formulate a set of specifications according to their own understanding to restrict what kind of scenario and how to design the API.

Such a popular thing should be highly standardized (everyone's consensus). Why is there such a phenomenon? Until I saw an article in recent days article:

REST stands for "representational state transfer," described by Roy Fielding in his dissertation. Sadly, that dissertation is not widely read, and so many people have their own idea of what REST is, leading to a lot of confusion and disagreement.

The main idea is that REST was put forward in a foreigner's paper (in 2000); However, many people haven't actually read this paper. Everyone understands it according to "a few words" on the Internet, which leads to a lot of misinterpretation and inconsistency (indicating that they can't agree more).

paper It's really a little long. It's estimated that few people will really read it (including myself). Here I recommend you to read an article from Microsoft article , the discussion is very comprehensive and can basically be used as a guide to the introduction and practice of RESTful API.

Based on this article, the subsequent chapters of this article mainly describe some key technical points of how to use SpringBoot to build RESTful API, which can be ignored by students with relevant experience.

Create a SpringBoot project / module

Create Maven Project with Idea, name: SpringBoot; Create another module, named api, to build a RESTful API. Only pom.xml is listed here.

SpringBoot pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>tech.exchange</groupId>
  <artifactId>springboot</artifactId>
  <packaging>pom</packaging>
  <version>0.1</version>

  <modules>
    <module>api</module>
  </modules>

  <properties>
    <spring-boot.version>2.6.1</spring-boot.version>

    <maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
    <maven-assembly-plugin.version>3.3.0</maven-assembly-plugin.version>
    <java.version>17</java.version>
    <encoding>UTF-8</encoding>
  </properties>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>${spring-boot.version}</version>
        <type>pom</type>
      </dependency>

      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>${spring-boot.version}</version>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>${maven-compiler-plugin.version}</version>
        <configuration>
          <source>${java.version}</source>
          <target>${java.version}</target>
          <encoding>${encoding}</encoding>
        </configuration>
      </plugin>

      <plugin>
        <artifactId>maven-assembly-plugin</artifactId>
        <version>${maven-assembly-plugin.version}</version>
        <configuration>
          <descriptors>
            <!--suppress UnresolvedMavenProperty -->
            <descriptor>
              ${maven.multiModuleProjectDirectory}/src/assembly/package.xml
            </descriptor>
          </descriptors>
        </configuration>
        <executions>
          <execution>
            <id>make-assembly</id>
            <phase>package</phase>
            <goals>
              <goal>single</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

Brief introduction pom.xml Meaning of each part:

  1. groupId/artifactId/packaging/version

      <groupId>tech.exchange</groupId>
      <artifactId>springboot</artifactId>
      <packaging>pom</packaging>
      <version>0.1</version>
    

    Used to declare the organization, name, packaging method and version of the project.

  2. modules

      <modules>
        <module>api</module>
      </modules>
    

    It is used to declare multiple modules within a project (multi module project). There is only one module: api.

  3. properties

      <properties>
        ......
      </properties>
    

    It is used to declare attributes (values) that may be used multiple times or need to be set uniformly in the project pom.xml, such as SpringBoot version number.

  4. dependencyManagement/dependencies

    <dependencyManagement>
        <dependencies>
          ......
        </dependencies>
      </dependencyManagement>
    

    It is used to declare the dependent (Jar) name and version that the project needs to use. The module of the project only needs to declare the specific dependent name, and the version is uniformly specified by the project.

  5. build/plugins

      <build>
        <plugins>
          ......
        </plugins>
      </build>
    

    It is used to declare the plug-ins that need to be used for project or module construction (compilation, packaging or other).

api pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <parent>
    <groupId>tech.exchange</groupId>
    <artifactId>springboot</artifactId>
    <version>0.1</version>
  </parent>

  <modelVersion>4.0.0</modelVersion>

  <artifactId>api</artifactId>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
  </dependencies>
</project>
  1. artifactId

      <artifactId>api</artifactId>
    

    Used to declare the module name.

  2. dependencies/dependency

      <dependencies>
        <dependency>
         ......
        </dependency>
      </dependencies>
    

    It is used to declare the dependency (Jar) that the module needs to use.

Creating RESTful API applications

Create a class named Main:

package tech.exchange.springboot.api;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author yurun
 */
@SpringBootApplication
public class Main {
  public static void main(String[] args) {
    SpringApplication.run(Main.class, args);
  }
}

@SpringBootApplication

@SpringBootApplication is a collection of three annotations:

@SpringBootApplication = @Configuration + @EnableAutoConfiguration + @ComponentScan

Its function is to tell Spring to take Main as the entry, automatically load various beans (components or configurations) into the container, and finally form a SpringBoot application to receive and respond to external requests.

These beans come from three aspects:

@Configuration

Tags the class as a source of bean definitions for the application context.

Load our custom Beans in Main (the custom Beans are not included in this example).

@EnableAutoConfiguration

Tells Spring Boot to start adding beans based on classpath settings, other beans, and various property settings.

Load the required Beans according to the classpath, other Beans or attribute configuration. For example, the example includes a dependency on spring boot starter web, which will automatically load web related Beans.

@ComponentScan

Tells Spring to look for other components, configurations, and services in the tech/exchange/springboot/api package.

Scan Package path: tech/exchange/springboot/api, load the Beans under the Package.

SpringApplication.run

    SpringApplication.run(Main.class, args);

Start the SpringBoot application. By default, you will see the following output:

2021-12-02 14:44:15.537  INFO 58552 --- [           main] tech.exchange.springboot.api.Main        : Starting Main using Java 17.0.1 on bogon with PID 58552 (/Users/yurun/workspace/tech-exchange/springboot/api/target/classes started by yurun in /Users/yurun/workspace/tech-exchange/springboot)
2021-12-02 14:44:15.538  INFO 58552 --- [           main] tech.exchange.springboot.api.Main        : No active profile set, falling back to default profiles: default
2021-12-02 14:44:16.160  INFO 58552 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2021-12-02 14:44:16.170  INFO 58552 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2021-12-02 14:44:16.170  INFO 58552 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.55]
2021-12-02 14:44:16.213  INFO 58552 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2021-12-02 14:44:16.214  INFO 58552 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 633 ms
2021-12-02 14:44:16.436  INFO 58552 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-12-02 14:44:16.443  INFO 58552 --- [           main] tech.exchange.springboot.api.Main        : Started Main in 1.209 seconds (JVM running for 1.584)

Where 8080 (http) means that the port number of the application instance is 8080 and supports HTTP requests.

Rest Controller

At present, the application is still an empty application and cannot actually receive any request or response. HTTP requests or responses in SpringBoot need to be implemented through the Controller. A Controller can support (include) the implementation of one or more HTTP requests or responses, that is, the implementation of one or more API s.

package tech.exchange.springboot.api.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author yurun
 */
@RestController
@RequestMapping("/hello")
public class HelloController {
}

@RestController

@RestController is used to identify that HelloController is a Controller. After the SpringBoot application is started, it will be automatically loaded into the container.

@RequestMapping

HelloController is equivalent to a collection of APIs, which can contain specific implementations of multiple APIs, such as:

/hello/a1
/hello/a2
/hello/a3
......

You can use @ RequestMapping to uniformly identify the parent path of these API request paths, such as: / hello. The internal API request path does not need to contain this parent path. You can use / a1, / a2, / a3.

Get/Post/Put/Patch/Delete

At present, HelloController is still an empty Controller and does not contain any API implementation.

RESTfull API involves five request types: Get/Post/Put/Patch/Delete, and three parameter types: request path parameter, request parameter and request body parameter. Each request type simulates the implementation of an API, which is used to demonstrate the implementation process of the API and the use of each parameter type.

Get

Add Method get in HelloController:

  @GetMapping("/get/{name1}")
  public String get(@PathVariable String name1, @RequestParam(defaultValue = "name2dv") String name2) {
    return "hello " + name1 + " " + name2;
  }

@GetMapping

@GetMapping is used to identify that the get method only responds to HTTP GET requests, and the request path is / hello/get/{name1}, where {name1} is the parameter name of the request path. In the actual request, it needs to be replaced with a specific parameter value, such as value1.

@PathVariable

@PathVariable String name1

@PathVariable is used to identify the request method parameters and receive the request path parameters. Assuming that the request path is / hello/value1, the parameter value value1 will be passed to the parameter name1 of the request method get when the request is executed.

The request path parameter is required by default (modification is not supported). It must be filled in when initiating a request, otherwise the request will fail.

@RequestParam

@RequestParam(defaultValue = "name2dv") String name2

@RequestParam is used to identify request method parameters and receive request parameters. Assuming that the request path is / hello?name2=value2, the parameter value value2 will be passed to the parameter name2 of the request method get when the request is executed; Assuming that the request path is / hello, when executing the request, the parameter name2 of the request method get will be set to the default value name2dv.

Request parameters are required by default (can be modified through the annotation attribute required), which must be filled in when initiating a request; If the default value is set, it can not be filled in when initiating the request, and the default value can be used instead, otherwise the request will fail.

Call example

Request: curl http://localhost:8080/hello/get/value1
 Response: hello value1 name2dv

Request: curl http://localhost:8080/hello/get/value1?name2=value2
 Response: hello value1 value2

The parameter names of the request path parameters and the request method parameters need to be consistent. If they are inconsistent, they need to be additionally specified through the annotation attribute (the same below);
The parameter type filled in when initiating the request must be compatible with the parameter type declared by the request method (the same below);
Zero or more request path parameters and request method parameters can be used (the same below);
There are few application scenarios of request body parameters in the Get scenario, which will not be discussed in this paper.

Post

The use of request path parameters and request parameters in Post is the same as that in Get. It will not be repeated. Only the use of request body parameters is realized. The usage of request body parameters is related to the specific value of HTTP request header content type. This article only discusses the most commonly used type: application/json.

When initiating a request, you need to use JSON to pass the request body parameters, and the request method needs to receive the request body parameter values through the class.

  @PostMapping("/post")
  public String post(@RequestBody PostParams params) {
    return "hello " + params.getName1() + " " + params.getName2();
  }

@PostMapping

@PostMapping is used to identify the method. Post only responds to HTTP POST requests, and the request path is / hello/post.

@RequestBody

@RequestBody PostParams params

@RequestBody is used to identify request method parameters and receive request body parameters (JSON).

Assume request body parameters:

{"name1": "value1", "name2": "value2"}

You need to create a class to receive body parameters:

public class PostParams {
  private String name1;
  private String name2 = "name2dv";

  public String getName1() {
    return name1;
  }

  public void setName1(String name1) {
    this.name1 = name1;
  }

  public String getName2() {
    return name2;
  }

  public void setName2(String name2) {
    this.name2 = name2;
  }
}

When the request is executed, each JSON field is assigned to the class instance (param) field by name. If name1 does not exist in JSON, name1 is null; If name2 does not exist in JSON, name2 is name2dv; If some fields in JSON do not exist in the class, they will be ignored.

Call example

Request: curl -H "Content-Type:application/json" -X POST --data '{"name1": "value1", "name2": "value2"}' http://localhost:8080/hello/post
 Response: hello value1 value2

Request: curl -H "Content-Type:application/json" -X POST --data '{}' http://localhost:8080/hello/post
 Response: hello null name2dv

Request: curl -H "Content-Type:application/json" -X POST --data '{"name1": "value1", "name3": "value2"}' http://localhost:8080/hello/post
 Response: hello value1 name2dv

Request body parameters can only be zero or one (the same below);
The request body parameter field type needs to be compatible with the class field type (the same below);
Request path parameters, request parameters and request body parameters can be mixed (the same below).

Put

  @PutMapping("/put")
  public String put(@RequestBody PostParams params) {
    return "hello " + params.getName1() + " " + params.getName2();
  }

@PutMapping

@PutMapping is used to identify that the put method only responds to HTTP PUT requests, and the request path is / hello/put.

The rest is the same as above and will not be repeated.

Patch

  @PatchMapping("/patch")
  public String patch(@RequestBody PostParams params) {
    return "hello " + params.getName1() + " " + params.getName2();
  }

@PatchMapping

@PatchMapping is used to identify the method. Patch only responds to HTTP PATCH requests, and the request path is / hello/patch.

The rest is the same as above and will not be repeated.

Delete

  @DeleteMapping("/delete")
  public String delete(@RequestBody PostParams params) {
    return "hello " + params.getName1() + " " + params.getName2();
  }

@DeleteMapping

@DeleteMapping is used to identify the method. Delete only responds to HTTP DELETE requests, and the request path is / hello/delete.

The rest is the same as above and will not be repeated.

epilogue

This paper introduces an article about RESTfull API, and on this basis, demonstrates a complete process of building RESTfull API Application using SpringBoot. The core configuration and annotation are also explained, hoping to be helpful to you.

attach

https://github.com/tech-exchange/springboot/blob/master/api/src/main/java/tech/exchange/springboot/api/controller/HelloController.java
https://github.com/tech-exchange/springboot/blob/master/api/src/main/java/tech/exchange/springboot/api/controller/PostParams.java

Posted by mindrage00 on Thu, 02 Dec 2021 15:36:42 -0800