本指南详细介绍如何将 Spring Boot 应用部署到 CloudBase HTTP 云函数。
📋 前置要求:如果您还没有创建 Spring Boot 项目,请先阅读 Spring Boot 项目创建指南。
HTTP 云函数适合以下场景:
- 轻量级 API:RESTful API 服务、微服务
- 间歇性访问:不需要持续运行的应用
- 成本敏感:按请求次数和执行时间计费
- 快速部署:无需容器化配置
| 特性 | 说明 |
|---|---|
| 计费方式 | 按请求次数和执行时间 |
| 启动方式 | 冷启动,按需启动 |
| 端口要求 | 固定 9000 端口 |
| 扩缩容 | 自动按请求扩缩 |
| Java 环境 | 预配置 Java 运行时 |
创建 scf_bootstrap 文件(无扩展名):
#!/bin/bash
export PORT=9000
export JAVA_OPTS="-Xms256m -Xmx512m"
cd /var/user
java $JAVA_OPTS -jar target/cloudrun-springboot-1.0-SNAPSHOT.jar为启动脚本添加执行权限:
chmod +x scf_bootstrap确保 src/main/resources/application.properties 支持云函数环境:
# 服务器配置
server.port=${PORT:8080}
server.servlet.context-path=/
# 应用信息
spring.application.name=cloudrun-springboot
management.endpoints.web.exposure.include=health,info
# 日志配置
logging.level.com.tencent.cloudrun=INFO
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n
# 编码配置
server.servlet.encoding.charset=UTF-8
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true
# 云函数优化配置
spring.main.lazy-initialization=true
server.tomcat.threads.max=10
server.tomcat.threads.min-spare=2# 清理并构建项目
mvn clean package
# 确保 JAR 文件存在
ls -la target/cloudrun-springboot-1.0-SNAPSHOT.jar确保 pom.xml 包含必要依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>cloudrun-springboot/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── tencent/
│ │ │ └── cloudrun/
│ │ │ ├── CloudrunApplication.java
│ │ │ ├── controller/
│ │ │ ├── entity/
│ │ │ └── dto/
│ │ └── resources/
│ │ └── application.properties
├── target/
│ └── cloudrun-springboot-1.0-SNAPSHOT.jar # 🔑 构建后的 JAR 文件
├── pom.xml # Maven 配置文件
└── scf_bootstrap # 🔑 云函数启动脚本
💡 说明:
scf_bootstrap是 CloudBase 云函数的启动脚本- 设置
PORT=9000环境变量确保应用监听云函数要求的端口- 重要:HTTP 云函数部署时需要包含构建后的 JAR 文件
- 使用云函数运行时环境的 Java 解释器启动应用
💡 注意:
- java 程序启动较慢,需要调整函数执行超时时间,默认为 3s, 建议调整 10s
- 登录 CloudBase 控制台
- 选择您的环境,进入「云函数」页面
- 点击「新建云函数」
- 选择「HTTP 云函数」
- 填写函数名称(如:
cloudrun-springboot-app) - 选择运行时:Java 8(或其他支持的版本)
- 提交方法选择:本地上传文件夹
- 函数代码选择
cloudrun-springboot目录进行上传 - 自动安装依赖:关闭此选项(Java 项目使用预构建的 JAR)
- 点击「创建」按钮等待部署完成
如果需要手动打包:
# 构建项目
mvn clean package
# 创建部署包(包含 JAR 文件和启动脚本)
zip -r cloudrun-springboot-app.zip . -x ".git/*" "src/*" "*.log" "Dockerfile" ".dockerignore" "target/classes/*" "target/test-classes/*"部署成功后,您可以参考通过 HTTP 访问云函数设置自定义域名访问 HTTP 云函数。
访问地址格式:https://your-function-url/
- 根路径:
/- Spring Boot 欢迎页面 - 健康检查:
/health- 查看应用状态 - 监控端点:
/actuator/health- Spring Boot Actuator 健康检查 - 用户列表:
/api/users- 获取用户列表 - 用户详情:
/api/users/1- 获取特定用户 - 创建用户:
POST /api/users- 创建新用户
# 健康检查
curl https://your-function-url/health
# Spring Boot Actuator 健康检查
curl https://your-function-url/actuator/health
# 获取用户列表
curl https://your-function-url/api/users
# 分页查询
curl "https://your-function-url/api/users?page=1&limit=2"
# 创建新用户
curl -X POST https://your-function-url/api/users \
-H "Content-Type: application/json" \
-d '{"name":"测试用户","email":"test@example.com"}'A: CloudBase HTTP 云函数要求应用监听 9000 端口,这是平台的标准配置。通过在 scf_bootstrap 中设置 PORT=9000 环境变量来控制端口,本地开发时默认使用 8080 端口。
A: 可以通过以下方式优化启动时间:
- 启用懒加载:
spring.main.lazy-initialization=true - 减少自动配置:排除不需要的自动配置类
- 优化 JVM 参数:设置合适的堆内存大小
- 使用 Spring Boot 的 CDS(Class Data Sharing)功能
A: 在 Spring Boot 中配置 CORS:
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOriginPattern("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}A: HTTP 云函数部署时需要包含构建后的 JAR 文件。Spring Boot 的 Fat JAR 包含了所有依赖,无需额外处理依赖问题。
A: 在 CloudBase 控制台的云函数页面,点击函数名称进入详情页查看运行日志。
A: CloudBase 支持 Java 8、Java 11 等版本,建议使用 Java 8 以获得最佳兼容性。
# application.properties
server.port=${PORT:8080}
spring.profiles.active=${SPRING_PROFILES_ACTIVE:prod}
# 数据库配置
spring.datasource.url=${DATABASE_URL:jdbc:h2:mem:testdb}
spring.datasource.username=${DATABASE_USERNAME:sa}
spring.datasource.password=${DATABASE_PASSWORD:}
# 日志级别
logging.level.com.tencent.cloudrun=${LOG_LEVEL:INFO}增强 scf_bootstrap 脚本:
#!/bin/bash
export PORT=9000
export SPRING_PROFILES_ACTIVE=prod
export JAVA_OPTS="-Xms256m -Xmx512m -XX:+UseG1GC -XX:MaxGCPauseMillis=200"
# 检查 JAR 文件
if [ ! -f "target/cloudrun-springboot-1.0-SNAPSHOT.jar" ]; then
echo "JAR file not found"
exit 1
fi
# 启动应用
cd /var/user
java $JAVA_OPTS -jar target/cloudrun-springboot-1.0-SNAPSHOT.jar@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<Object>> handleException(Exception e) {
logger.error("Unhandled exception: ", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error("内部服务器错误"));
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ApiResponse<Object>> handleIllegalArgumentException(IllegalArgumentException e) {
logger.warn("Invalid argument: {}", e.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ApiResponse.error(e.getMessage()));
}
}@Component
public class LoggingInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
long startTime = (Long) request.getAttribute("startTime");
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
logger.info("{} {} - {} - {}ms",
request.getMethod(),
request.getRequestURI(),
response.getStatus(),
duration);
}
}@RestController
@RequestMapping("/health")
public class HealthController {
@Value("${spring.application.name}")
private String applicationName;
@GetMapping
public Map<String, Object> health() {
Map<String, Object> response = new HashMap<>();
response.put("status", "healthy");
response.put("application", applicationName);
response.put("framework", "Spring Boot");
response.put("version", "2.7.18");
response.put("deployment", "CloudBase HTTP 云函数");
response.put("timestamp", LocalDateTime.now().toString());
response.put("java_version", System.getProperty("java.version"));
response.put("memory_usage", getMemoryUsage());
return response;
}
private Map<String, Object> getMemoryUsage() {
Runtime runtime = Runtime.getRuntime();
Map<String, Object> memory = new HashMap<>();
memory.put("total", runtime.totalMemory() / 1024 / 1024 + " MB");
memory.put("free", runtime.freeMemory() / 1024 / 1024 + " MB");
memory.put("used", (runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024 + " MB");
return memory;
}
}-
scf_bootstrap文件存在且有执行权限 - 端口配置为 9000
- 项目已构建(JAR 文件存在)
- 包含构建后的 JAR 文件
- 排除不必要的文件(如
src、Dockerfile) - 测试本地构建和启动是否正常
- 检查启动脚本语法是否正确
- JVM 参数配置合理
# application.properties
# 启用懒加载
spring.main.lazy-initialization=true
# 减少 Tomcat 线程
server.tomcat.threads.max=10
server.tomcat.threads.min-spare=2
# 禁用不需要的自动配置
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration# scf_bootstrap 中的 JVM 参数
export JAVA_OPTS="-Xms256m -Xmx512m \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:+UseStringDeduplication \
-XX:+OptimizeStringConcat"<!-- pom.xml 中的构建优化 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>@Component
public class MemoryMonitor {
private static final Logger logger = LoggerFactory.getLogger(MemoryMonitor.class);
@EventListener
public void handleContextRefresh(ContextRefreshedEvent event) {
logMemoryUsage("Application started");
}
private void logMemoryUsage(String event) {
Runtime runtime = Runtime.getRuntime();
long totalMemory = runtime.totalMemory() / 1024 / 1024;
long freeMemory = runtime.freeMemory() / 1024 / 1024;
long usedMemory = totalMemory - freeMemory;
logger.info("{} - Memory usage: {}MB used, {}MB free, {}MB total",
event, usedMemory, freeMemory, totalMemory);
}
}