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:
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.
- Add the plugin
id 'com.github.johnrengelman.shadow' version '7.0.0'
- Add
id 'application'
- Add
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
- 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 packageorginal-
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
- Choose custom creation
- Select event function
- Enter a function name
- Select Java8 for the operating environment
- The submission method chooses to upload the zip package locally
The execution method is specified as the
package name. Class name:: entry function name
- For example, here is:
com.tencent.scfspringbootjava8.ScfHandler::mainHandler
- For example, here is:
- Upload there and select the previously compiled jar package
-all
Then click Finish to create the function.
Cloud function configuration
After the creation is complete, select Function Management-Function Configuration-Edit. As shown below.
After clicking Edit, in the environment configuration:
- Modify the memory to 1024MB
- Modify the execution timeout time to 15 seconds
Trigger configuration
In trigger management, create triggers.
When creating a trigger, in the following figure:
- Select the trigger method to trigger on the API gateway.
- Integrated response check.
- Then submit
After the creation is complete, some API gateway parameters need to be modified. Click the API service name to enter the modification.
Click the edit button on the right to modify.
In the first front-end configuration, modify the path to the default path in the Spring project. As shown below.
Then click Finish now.
Then click Publish Service.
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.
In the function management, you can easily test by selecting the function code. As shown below.
- Select "API Gateway Event Template" for test events.
- Request method selection
GET
- Path fill in
/todos
- Finally, you can click the test button.
The test results and logs will be displayed directly at the bottom right of the interface. As shown below.
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.
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:
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:
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.
#!/bin/bash
required on the first line.- The java start command must be an absolute path, the absolute path of java is:
/var/lang/java8/bin/java
- 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.
Cloud function creation
In the function service, click New to start creating the function.
As shown below
- Choose custom creation
- Select Web function
- Enter a function name
- Select Java8 for the operating environment
- The submission method chooses to upload the zip package locally
- Upload there and select the previously compressed
scf_spring_boot.zip
package.
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:
Then in the environment configuration, change the memory to 512MB. The execution timeout period is set to 15 seconds.
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.
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.
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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。