前言
上次的文章分享后,有粉丝反馈内容太实践太形象,看不到理论的样子。
因而,我这里就写一篇教程,手把手教你如何把一个 SpringBoot 我的项目部署到 Serverless 并测试胜利。
上面的链接是我发表到官网的文章,但官网的文章会综合思考,所以不会有那么细的步骤。本文是最具体的步骤。
SpringBoot + SCF 最佳实际:实现待办利用
本文章以腾讯云 Serverless 云函数为例,将分为事件函数和 Web 函数两种教程。
事件函数就是指函数是由事件触发的。
Web 函数就是指函数能够间接发送 HTTP 申请触发函数。具体区别能够看这里。
两者在 Spring 我的项目迁徙革新上的区别在于:
- 事件函数须要减少一个入口类。
- Web 函数须要批改端口为固定的 9000。
- 事件函数须要操作更多的控制台配置。
- Web 函数须要减少一个 scf_bootstrap 启动文件,和不一样的打包形式。
事件函数
Spring 我的项目筹备
事件函数示例代码下载地址:https://github.com/woodyyan/scf-springboot-java8/tree/eventfunction
示例代码介绍
@SpringBootApplication
类保持原状不变。
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);
}
}
Controller 类也会依照原来的写法,放弃不变。这里以 todo 利用为例子。
记住此处的/todos
门路,前面会用到。
代码如下:
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);
}
}
减少一个 ScfHandler
类,我的项目构造如下:
Scfhandle
类次要用于接管事件触发,并转发音讯给 Spring application,而后接管到 Spring application 的返回后把后果返回给调用方。
默认端口号为8080
.
其代码内容如下:
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
这里以 gradle 为例,与传统开发不一样的中央次要在于,build.gradle
中须要退出全量打包的 plugin,来保障所有用到的依赖都打入 jar 包中。
- 增加
id 'com.github.johnrengelman.shadow' version '7.0.0'
这个 plugin。 - 增加
id 'application'
- 增加
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
- 指定
mainClass
。
build.gradle
具体内容如下:
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
这里以 maven 为例,与传统开发不一样的点次要在于,pom.xml 须要退出 maven-shade-plugin
,来保障所有用到的依赖都打入 jar 包中。同时须要指定mainClass
,上面代码中的mainClass
须要改为你本人的 mainClass
门路。
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 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>
编译 JAR 包
下载代码之后,到该项目标根目录,运行编译命令:
- Gradle 我的项目运行:
gradle build
- Maven 我的项目运行:
mvn package
编译实现后就能在以后我的项目的输入目录找到打包好的 jar 包。
- Gradle 我的项目:在
build/libs
目录下看到打包好的 jar 包,这里须要抉择后缀是-all
的 JAR 包。如下图。 - Maven 我的项目:在
target
目录下能看到打包好的 jar 包,这里须要抉择前缀 不带orginal-
的 jar 包。
一会部署函数的时候就用这个 JAR 包。
云函数筹备
云函数创立
在函数服务中,点击新建,开始创立函数。
如下图
- 抉择自定义创立
- 抉择事件函数
- 输出一个函数名称
- 运行环境抉择 Java8
- 提交办法抉择本地上传 zip 包
-
执行办法指定为
包名. 类名:: 入口函数名
- 比方此处是:
com.tencent.scfspringbootjava8.ScfHandler::mainHandler
- 比方此处是:
- 上传那里抉择后面编译好的带
-all
后缀的 jar 包。
而后点击实现创立函数。
云函数配置
创立实现之后,选择函数治理 - 函数配置 - 编辑。如下图。
点开编辑之后,在环境配置中:
- 把内存批改为 1024MB
- 把执行超时工夫批改为 15 秒
触发器配置
在触发治理中,创立触发器。
创立触发器时,在下图中:
- 触发形式抉择 API 网关触发。
- 集成响应勾选。
- 而后提交
创立实现之后须要批改一些 API 网关参数。点击 API 服务名进入批改。
点击右侧的编辑按钮批改。
第一个前端配置中,将门路批改为 Spring 我的项目中的默认门路。如下图。
而后点击立刻实现。
而后点击公布服务。
公布实现之后回到云函数控制台。
开始测试
此处咱们就以 Controller 外面写的第一个 GET
办法为例,如下图,咱们将取得所有的 todo items。
在函数治理中,选择函数代码,就能够很不便的进行测试。如下图。
- 测试事件抉择“API Gateway 事件模版”。
- 申请形式抉择
GET
- Path 填
/todos
- 最初就能够点击测试按钮。
测试后果和日志将间接显示在界面的右下方。如下图。
如果想要获取残缺的拜访 URL,能够在触发治理中,找到方才创立的 API 网关触发器,上面有能够拜访的 URL。URL 前面有复制按钮。如下图。
Web 函数
Spring 我的项目筹备
示例代码介绍
Web 函数示例代码下载地址:https://github.com/woodyyan/scf-springboot-java8/tree/webfunction
Web 函数的我的项目代码相比事件函数更简略。代码革新老本简直没有。对原代码的批改只有一个端口号。
Web 函数则不须要 ScfHandler
入口类,我的项目构造如下:
因为 web 函数必须保障我的项目监听端口为9000
,所以须要将 Spring 监听的端口改为 9000。如下图:
代码部署包筹备
代码包编译形式参考下面的“编译 JAR 包”。
而后新建一个 scf_bootstrap 启动文件,文件名字必须是scf_bootstrap
,没有后缀名。
- 第一行需有
#!/bin/bash
。 - java 启动命令必须是绝对路径,java 的绝对路径是:
/var/lang/java8/bin/java
- 请确保你的 scf_bootstrap 文件具备 777 或 755 权限,否则会因为权限有余而无奈执行。
因而启动文件内容如下:
#!/bin/bash
/var/lang/java8/bin/java -Dserver.port=9000 -jar scf-springboot-java8-0.0.2-SNAPSHOT-all.jar
接着,在 scf_bootstrap 文件所在目录执行下列命令来保障 scf_bootstrap 文件可执行。
chmod 755 scf_bootstrap
而后将 scf_bootstrap
文件和方才编译解决的 scf-springboot-java8-0.0.2-SNAPSHOT-all.jar
文件,一起打包成 zip 文件。如下图。
打包好的 zip 文件就是咱们的部署包。
云函数创立
在函数服务中,点击新建,开始创立函数。
如下图
- 抉择自定义创立
- 抉择 Web 函数
- 输出一个函数名称
- 运行环境抉择 Java8
- 提交办法抉择本地上传 zip 包
- 上传那里抉择后面压缩好的
scf_spring_boot.zip
包。
而后在上面的高级配置中,写上启动命令,命令中的 jar 文件应该是你编译进去的 jar 文件的名字。
因为 web 函数必须保障我的项目监听端口为9000
,所以命令中要指定一下端口。
更多对于启动命令的写法能够参考启动文件阐明。
如下图:
而后环境配置那里,把内存改为 512MB。执行超时工夫设置为 15 秒。
其余设置都应用默认的就能够了。而后点击实现。
点击实现之后如果没有反馈,是因为要先期待 ZIP 文件上传,才会开始创立函数。
因为 Web 函数默认会创立 API 网关触发器,因而咱们不须要独自配置触发器。
开始测试
此处咱们就以 Controller 外面写的第一个 GET
办法为例,如下图,咱们将取得所有的 todo items。
在函数控制台的函数代码外面,咱们能够间接测试咱们的云函数。
根据下面的代码,咱们申请形式抉择GET
,path 填写/todos
,而后点击测试按钮,而后就能够在右下角看到咱们的后果了。
如果想在其余中央测试,能够复制下图中的拜访门路进行测试。
最初
本教程没有波及镜像函数,因为镜像部署和原来的部署形式没有差别。我的项目代码也不须要革新。实践上这是最适宜微服务项目的形式。
下一篇文章中,我就会详细分析 Serverless 中上面几个话题了。
- Serverless 中的服务间调用
- Serverless 中的数据库拜访
- Serverless 中的服务的注册与发现
- Serverless 中的服务熔断与降级
- Serverless 中的服务拆分