头图

Preface

After the last article was shared, some fans responded that the content was too theoretical and too abstract to see the actual appearance.

Therefore, I will write a tutorial here to teach you how to deploy a SpringBoot project to Serverless and test it successfully.

The link below is the official article I published, but the official article will be considered comprehensively, so there will be no such detailed steps. This article is the most detailed steps.

SpringBoot + SCF Best Practice: Implement To-Do Application

This article takes Tencent Cloud Serverless cloud function as an example, and will be divided into two tutorials: event function and web function.

Event function means that the function is triggered by an event.

Web function means that the function can directly send the HTTP request to trigger the function. The specific difference can be seen in here .

The difference between the two in the Spring project migration and transformation is:

  • The event function needs to add an entry class.
  • Web functions need to modify the port to a fixed 9000.
  • Event functions need to manipulate more console configuration.
  • Web functions need to add a scf_bootstrap startup file, which is different from the packaging method.

Event function

Spring project preparation

Event function sample code download address: https://github.com/woodyyan/scf-springboot-java8/tree/eventfunction

Sample code introduction

@SpringBootApplication category remains unchanged.

package com.tencent.scfspringbootjava8;

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

@SpringBootApplication
public class ScfSpringbootJava8Application {

    public static void main(String[] args) {
        SpringApplication.run(ScfSpringbootJava8Application.class, args);
    }
}

The Controller class will also remain unchanged according to the original writing. Take todo application as an example here.

Remember the /todos path here, you will use it later.

code show as below:

package com.tencent.scfspringbootjava8.controller;

import com.tencent.scfspringbootjava8.model.TodoItem;
import com.tencent.scfspringbootjava8.repository.TodoRepository;
import org.springframework.web.bind.annotation.*;

import java.util.Collection;

@RestController
@RequestMapping("/todos")
public class TodoController {
    private final TodoRepository todoRepository;

    public TodoController() {
        todoRepository = new TodoRepository();
    }

    @GetMapping
    public Collection<TodoItem> getAllTodos() {
        return todoRepository.getAll();
    }

    @GetMapping("/{key}")
    public TodoItem getByKey(@PathVariable("key") String key) {
        return todoRepository.find(key);
    }

    @PostMapping
    public TodoItem create(@RequestBody TodoItem item) {
        todoRepository.add(item);
        return item;
    }

    @PutMapping("/{key}")
    public TodoItem update(@PathVariable("key") String key, @RequestBody TodoItem item) {
        if (item == null || !item.getKey().equals(key)) {
            return null;
        }

        todoRepository.update(key, item);
        return item;
    }

    @DeleteMapping("/{key}")
    public void delete(@PathVariable("key") String key) {
        todoRepository.remove(key);
    }
}

Add a ScfHandler category, the project structure is as follows:
截屏2021-11-09 21.31.31.png

Scfhandle class is mainly used to receive event triggers, forward the message to the Spring application, and then return the result to the caller after receiving the return from the Spring application.

The default port number is 8080 .

The code content is as follows:

package com.tencent.scfspringbootjava8;

import com.alibaba.fastjson.JSONObject;
import com.qcloud.services.scf.runtime.events.APIGatewayProxyRequestEvent;
import com.qcloud.services.scf.runtime.events.APIGatewayProxyResponseEvent;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;

public class ScfHandler {
    private static volatile boolean cold_launch;

    // initialize phase, initialize cold_launch
    static {
        cold_launch = true;
    }

    // function entry, use ApiGatewayEvent to get request
    // send to localhost:8080/hello as defined in helloSpringBoot.java
    public String mainHandler(APIGatewayProxyRequestEvent req) {
        System.out.println("start main handler");
        if (cold_launch) {
            System.out.println("start spring");
            ScfSpringbootJava8Application.main(new String[]{""});
            System.out.println("stop spring");
            cold_launch = false;
        }
        // 从api geteway event -> spring request -> spring boot port

        // System.out.println("request: " + req);
        // path to request
        String path = req.getPath();
        System.out.println("request path: " + path);

        String method = req.getHttpMethod();
        System.out.println("request method: " + method);

        String body = req.getBody();
        System.out.println("Body: " + body);

        Map<String, String> reqHeaders = req.getHeaders();
        // construct request
        HttpMethod httpMethod = HttpMethod.resolve(method);
        HttpHeaders headers = new HttpHeaders();
        headers.setAll(reqHeaders);
        RestTemplate client = new RestTemplate();
        HttpEntity<String> entity = new HttpEntity<>(body, headers);

        String url = "http://127.0.0.1:8080" + path;

        System.out.println("send request");
        ResponseEntity<String> response = client.exchange(url, httpMethod != null ? httpMethod : HttpMethod.GET, entity, String.class);
        //等待 spring 业务返回处理结构 -> api geteway response。
        APIGatewayProxyResponseEvent resp = new APIGatewayProxyResponseEvent();
        resp.setStatusCode(response.getStatusCodeValue());
        HttpHeaders responseHeaders = response.getHeaders();
        resp.setHeaders(new JSONObject(new HashMap<>(responseHeaders.toSingleValueMap())));
        resp.setBody(response.getBody());
        System.out.println("response body: " + response.getBody());
        return resp.toString();
    }
}

Gradle

Take gradle as an example. The main difference from traditional development is that a build.gradle to ensure that all the dependencies used are included in the jar package.

  1. Add the plugin id 'com.github.johnrengelman.shadow' version '7.0.0'
  2. Add id 'application'
  3. Add id 'io.spring.dependency-management' version '1.0.11.RELEASE'
  4. Specify mainClass .

build.gradle specific content of 0619e3e6caf0a0 is as follows:

plugins {
    id 'org.springframework.boot' version '2.5.5'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java-library'
    id 'application'
    id 'com.github.johnrengelman.shadow' version '7.0.0'
}

group = 'com.tencent'
version = '0.0.2-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
    mavenCentral()
}

dependencies {
    api 'org.springframework.boot:spring-boot-starter-web'
    api group: 'com.tencentcloudapi', name: 'tencentcloud-sdk-java', version: '3.1.356'
    api group: 'com.tencentcloudapi', name: 'scf-java-events', version: '0.0.4'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
    useJUnitPlatform()
}

application {
    // Define the main class for the application.
    mainClass = 'com.tencent.scfspringbootjava8.ScfSpringbootJava8Application'
}

Maven

Take maven as an example here. The main difference from traditional development is that pom.xml needs to add maven-shade-plugin to ensure that all the dependencies used are included in the jar package. At the same time you need to specify mainClass , the following code mainClass need to change your own mainClass path.

pom.xml specific content of 0619e3e6caf16c is as follows:

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>1.0</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
      <!-- Build an executable JAR -->
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-jar-plugin</artifactId>
      <version>3.1.0</version>
      <configuration>
        <archive>
          <manifest>
            <addClasspath>true</addClasspath>
            <classpathPrefix>lib/</classpathPrefix>
            <mainClass>com.mypackage.MyClass</mainClass>
          </manifest>
        </archive>
      </configuration>
    </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <dependencies>
                    <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-maven-plugin</artifactId>
                        <version>2.1.1.RELEASE</version>
                    </dependency>
                </dependencies>
                <configuration>
                    <keepDependenciesWithProvidedScope>true</keepDependenciesWithProvidedScope>
                    <createDependencyReducedPom>true</createDependencyReducedPom>
                    <filters>
                        <filter>
                            <artifact>*:*</artifact>
                            <excludes>
                                <exclude>META-INF/*.SF</exclude>
                                <exclude>META-INF/*.DSA</exclude>
                                <exclude>META-INF/*.RSA</exclude>
                            </excludes>
                        </filter>
                    </filters>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                    <resource>META-INF/spring.handlers</resource>
                                </transformer>
                                <transformer
                                        implementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer">
                                    <resource>META-INF/spring.factories</resource>
                                </transformer>
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                    <resource>META-INF/spring.schemas</resource>
                                </transformer>
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Compile the JAR package

After downloading the code, go to the root directory of the project and run the compilation command:

  • Gradle project running: gradle build
  • Maven project running: mvn package

After compiling, you can find the packaged jar package in the output directory of the current project.

  • Gradle project: build/libs directory. Here you need to select the JAR package with the -all As shown below.
  • Maven project: target can see the packaged jar package in the 0619e3e6caf32f directory. Here you need to select the prefix without the jar package orginal-

Use this JAR package when deploying the function later.

Cloud function preparation

Cloud function creation

In the function service, click New to start creating the function.

As shown below

  1. Choose custom creation
  2. Select event function
  3. Enter a function name
  4. Select Java8 for the operating environment
  5. The submission method chooses to upload the zip package locally
  6. The execution method is specified as the package name. Class name:: entry function name

    1. For example, here is: com.tencent.scfspringbootjava8.ScfHandler::mainHandler
  7. Upload there and select the previously compiled jar package -all
    截屏2021-11-09 21.39.35.png

Then click Finish to create the function.

Cloud function configuration

After the creation is complete, select Function Management-Function Configuration-Edit. As shown below.
Untitled.png

After clicking Edit, in the environment configuration:

  1. Modify the memory to 1024MB
  2. Modify the execution timeout time to 15 seconds
    截屏2021-11-10 14.25.57.png

Trigger configuration

In trigger management, create triggers.
1235.png

When creating a trigger, in the following figure:

  1. Select the trigger method to trigger on the API gateway.
  2. Integrated response check.
  3. Then submit
    5325.png

After the creation is complete, some API gateway parameters need to be modified. Click the API service name to enter the modification.
235.png

Click the edit button on the right to modify.
23.png

In the first front-end configuration, modify the path to the default path in the Spring project. As shown below.
55.png

Then click Finish now.

Then click Publish Service.
截屏2021-11-09 21.54.29.png

After the release is complete, return to the cloud function console.

start testing

Here we take the first GET method written in the Controller as an example, as shown in the figure below, we will get all todo items.
2345.png

In the function management, you can easily test by selecting the function code. As shown below.

  1. Select "API Gateway Event Template" for test events.
  2. Request method selection GET
  3. Path fill in /todos
  4. Finally, you can click the test button.
    4443.png

The test results and logs will be displayed directly at the bottom right of the interface. As shown below.
2222.png

If you want to get the complete access URL, you can find the API gateway trigger you just created in the trigger management. The accessible URL is below. There is a copy button behind the URL. As shown below.
111.png


Web function

Spring project preparation

Sample code introduction

Web function sample code download address: https://github.com/woodyyan/scf-springboot-java8/tree/webfunction

The project code of the web function is simpler than the event function. The cost of code modification is almost nothing. There is only one port number for the modification of the original code.

Web functions do not need the ScfHandler entry class, the project structure is as follows:
666.png

Because the web function must ensure that the project listening port is 9000 , it is necessary to change the Spring listening port to 9000. As shown below:
111235.png

Code deployment package preparation

For the code package compilation method, refer to " compile JAR package " above.

Then create a new scf_bootstrap startup file , the file name must be scf_bootstrap , without a suffix.

  1. #!/bin/bash required on the first line.
  2. The java start command must be an absolute path, the absolute path of java is: /var/lang/java8/bin/java
  3. Please make sure your scf_bootstrap file has 777 or 755 permissions, otherwise it will not be executed due to insufficient permissions.

Therefore, the content of the startup file is as follows:

#!/bin/bash
/var/lang/java8/bin/java -Dserver.port=9000 -jar scf-springboot-java8-0.0.2-SNAPSHOT-all.jar

Then, execute the following command in the directory where the scf_bootstrap file is located to ensure that the scf_bootstrap file is executable.

chmod 755 scf_bootstrap

Then scf_bootstrap file and just compile process scf-springboot-java8-0.0.2-SNAPSHOT-all.jar files, packaged together into a zip file. As shown below.

The packaged zip file is our deployment package.
截屏2021-11-11 13.38.02.png

Cloud function creation

In the function service, click New to start creating the function.

As shown below

  1. Choose custom creation
  2. Select Web function
  3. Enter a function name
  4. Select Java8 for the operating environment
  5. The submission method chooses to upload the zip package locally
  6. Upload there and select the previously compressed scf_spring_boot.zip package.
    截屏2021-11-11 13.40.28.png

Then in the following advanced configuration, write the startup command, the jar file in the command should be the name of the jar file you compiled.

Because the web function must ensure that the project's listening port is 9000 , the port must be specified in the command.

For more information about how to write the startup command, please refer to startup file description .

As shown below:
13ewd.png

Then in the environment configuration, change the memory to 512MB. The execution timeout period is set to 15 seconds.
23we.png

Use the default settings for other settings. Then click Finish.

If there is no response after clicking Finish, it is because you have to wait for the ZIP file to upload before starting to create the function.

Because Web functions create API gateway triggers by default, we don't need to configure triggers separately.

start testing

Here we take the first GET method written in Controller as an example, as shown in the figure below, we will get all todo items.
awdz.png

In the function code of the function console, we can directly test our cloud functions.

According to the above code, we request the method to choose GET , fill in the path /todos , and then click the test button, and then we can see our results in the lower right corner.

If you want to test in other places, you can copy the access path in the figure below to test.
aeawe.png

finally

This tutorial does not involve the mirror function, because the mirror deployment is no different from the original deployment method. The project code does not need to be modified. In theory, this is the most suitable method for microservice projects.

In the next article, I will analyze the following topics in Serverless in detail.

  • Inter-service calls in Serverless
  • Database Access in Serverless
  • Registration and discovery of services in Serverless
  • Service fusing and degradation in Serverless
  • Service split in Serverless

Woody
843 声望62 粉丝

ThoughtWorks|高级咨询师