☰
使用 CloudWatch Events/EventBridge 的 cron 表达式定时调度 AWS Lambda Java 函数:aws-doc-sdk-examples 实战指南
2026/9/25 11:03:29 网站建设 项目流程
  • 示例工程
  • 教程
  • 后端

【免费下载链接】aws-doc-sdk-examples

Welcome to the AWS Code Examples Repository. This repo contains code examples used in the AWS documentation, AWS SDK Developer Guides, and more. For more information, see the Readme.md file below.

项目地址:https://gitcode.com/gh_mirrors/aw/aws-doc-sdk-examples
点击查看免费下载

本指南以 aws-doc-sdk-examples 仓库中 creating_scheduled_events 教程为骨架,完整讲解如何用 Amazon CloudWatch Events(现称 Amazon EventBridge)的 cron 表达式定时触发 AWS Lambda Java 函数。读完本文,你将掌握从 IAM 角色创建、IntelliJ Maven 工程搭建、DynamoDB 增强型客户端扫描、Amazon SNS 短信发送到cron(0 12 ? * MON-FRI *)定时调度配置的全链路实现方法,并能在自己的业务中复现"员工入职一周年自动祝贺短信"这一多服务协作场景。

定时事件的业务场景与整体思路

CloudWatch Events/EventBridge 是 AWS 的事件总线服务,你可以为它配置一条基于 cron 表达式的调度规则(Schedule),让规则在指定时间点自动触发对 AWS Lambda 函数的调用,从而实现"无需人工干预、周期性地执行业务逻辑"。

本教程的典型业务场景如下:企业希望在每个工作日向入职满一整年的员工自动发送一条祝贺短信。整个流程分为两段:

  1. 定时触发:CloudWatch Events 规则按 cron 表达式(例如cron(0 12 ? * MON-FRI *))每周一至周五的 12:00(UTC)调用 Lambda 函数;
  2. 业务执行:Lambda 函数内部读取 Amazon DynamoDB 中的Employee员工表,筛选出startDate(入职日期)恰好为一周年当天的员工,再通过 Amazon SNS 的publish方法向员工手机号发送短信。

在该业务中,Employee表是核心数据源。教程要求创建一个名为Employee的 DynamoDB 表,包含以下字段:

字段含义在代码中的角色
Id员工主键表的分区键(Partition Key)
first员工名用于拼接短信文案
phone员工手机号SNS 短信接收方
startDate入职日期(格式yyyy-MM-dd)周年判定依据,见 Employee.java

创建表后请录入正确的测试数据,尤其是要填入一个真实可接收短信的手机号,以便验证完整链路。

前提条件与重要提示

跟随本教程动手实践,需要准备以下环境:

  • 一个拥有有效凭证的 AWS 账户;
  • Java IDE(教程使用 IntelliJ IDEA,其余支持 Maven 的 IDE 亦可);
  • Java 17 JDK(仓库中的 pom.xml 将maven.compiler.source/target与java.version均声明为 17,与教程正文保持一致);
  • Maven 3.6 或更高版本。

同时请注意三点:

  • 本教程涉及的 Lambda、DynamoDB、SNS 等服务均属于 AWS Free Tier 免费套餐可覆盖范围,但不同服务的免费额度与区域可用性存在差异;
  • 某些 AWS 服务仅在某些特定区域提供,请确认你使用的区域支持所需服务;
  • 运行本教程可能产生 AWS 账户费用,请在体验完成后及时终止(删除)创建的全部资源,以免持续计费。

在进入编码阶段之前,还需要在 DynamoDB 控制台完成Employee表的创建(键为Id,建议同时将startDate设为排序键,以匹配 Employee.java 中@DynamoDbPartitionKey与@DynamoDbSortKey的映射),并填入测试数据。

创建执行 Lambda 所需的 IAM 角色

Lambda 函数在执行时需要调用 DynamoDB 与 SNS 服务,因此必须为其授予一个具备相应权限的 IAM 角色。本教程创建的角色名为lambda-support,具体步骤:

  1. 打开 AWS Management Console,在搜索框输入IAM并进入 IAM 控制台;
  2. 在左侧导航栏选择Roles(角色),点击Create Role(创建角色);
  3. 可信实体选择AWS service,用例选择Lambda;
  4. 在权限策略页面,搜索并勾选AWSLambdaBasicExecutionRole(允许 Lambda 将日志写入 CloudWatch Logs,配合代码中的LambdaLogger使用);
  5. 点击Next(下一步),命名角色为lambda-support并完成创建;
  6. 进入lambda-support角色详情页,选择Attach Policies(附加策略):
    • 搜索并附加AmazonDynamoDBFullAccess(授权 Lambda 扫描Employee表);
    • 搜索并附加AmazonSNSFullAccess(授权 Lambda 调用 SNS 发送短信)。

附加完成后,角色详情页会同时列出上述三条策略。这一步是后续 Lambda 能正常读写 DynamoDB 与发送短信的前提——角色权限缺失时,函数会在运行时抛出权限相关异常。

创建 IntelliJ Maven 项目 LambdaCronFunctions

在 IntelliJ IDEA 中完成以下操作创建 Maven 工程:

  1. 选择File→New→Project;
  2. 在 New Project 对话框中工程类型选择Maven,点击Next;
  3. GroupId填写LambdaCronFunctions;
  4. ArtifactId填写LambdaCronFunctions;
  5. 点击Next→Finish完成创建。

仓库中该教程的完整工程包含三个 Java 类,全部位于com.aws.example包下:

类职责
Handler.javaLambda 函数入口,负责编排整个业务逻辑
ScanEmployees.java扫描 DynamoDB 并调用 SNS 发送短信的核心业务类
Employee.java与 DynamoDB 增强型客户端配合的数据映射类

配置 POM 依赖与打包插件

新建项目后,需要把依赖配置成与仓库 pom.xml 一致。该文件定义了本示例所需的全部依赖,核心部分如下:

<properties> <java.version>17</java.version> <maven.compiler.target>17</maven.compiler.target> <maven.compiler.source>17</maven.compiler.source> </properties> <dependencies> <!-- Lambda Java 运行时核心 API(Context、LambdaLogger 等) --> <dependency> <groupId>com.amazonaws</groupId> <artifactId>aws-lambda-java-core</artifactId> <version>1.2.1</version> </dependency> <!-- DynamoDB 增强型客户端(DynamoDbEnhancedClient、Expression、ScanEnhancedRequest) --> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>dynamodb-enhanced</artifactId> <version>2.21.20</version> </dependency> <!-- DynamoDB Java V2 基础 API(DynamoDbClient、AttributeValue) --> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>dynamodb</artifactId> <version>2.21.20</version> </dependency> <!-- Amazon SNS Java V2 API(SnsClient、PublishRequest) --> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>sns</artifactId> <version>2.21.20</version> </dependency> </dependencies>

除上述依赖外,pom.xml 还引入了gson、log4j等工具库,并额外声明了javax.mail相关依赖,便于后续在类似场景中扩展邮件发送能力。三个 Java 源文件在 metadata.yaml 中被统一标记为lambda服务,用于仓库的自动化检索与校验。

在<build>部分,pom.xml 的关键配置是maven-shade-plugin:

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.2.2</version> <configuration> <createDependencyReducedPom>false</createDependencyReducedPom> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> </execution> </executions> </plugin>

该插件在package阶段将项目及其全部依赖合并进同一个"肥 JAR"(uber JAR)。如果缺少这个插件,打包出的 JAR 不会包含 DynamoDB/SNS 等第三方依赖,部署到 Lambda 后运行时将直接抛出ClassNotFoundException。

编写 Lambda 函数的三要素:Handler、ScanEmployees 与 Employee

Handler:函数入口与 CloudWatch Logs 日志

Handler.java 是 Lambda 的入口类,业务逻辑集中在handleRequest方法中:

package com.aws.example; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.LambdaLogger; /** * This is the entry point for the Lambda function. */ public class Handler { public Void handleRequest(Context context) { LambdaLogger logger = context.getLogger(); ScanEmployees scanEmployees = new ScanEmployees(); Boolean ans = scanEmployees.sendEmployeMessage(); if (ans) logger.log("Messages sent: " + ans); return null; } }

值得注意的实现细节:

  • 方法签名handleRequest(Context context)使用com.amazonaws.services.lambda.runtime.Context,与 Lambda Java 运行时 API 的约定一致;
  • 通过context.getLogger()获取LambdaLogger,调用logger.log(...)即可把日志写入 CloudWatch Logs,便于在控制台排查调度执行结果;
  • 返回值类型为Void,函数执行结果通过日志(而非返回值)对外呈现。

Employee:DynamoDB 增强型客户端的数据映射 Bean

Employee.java 使用@DynamoDbBean注解将普通 Java Bean 映射为 DynamoDB 表结构:

@DynamoDbBean public class Employee { private String Id; private String first; private String phone; private String startDate; @DynamoDbPartitionKey public String getId() { return this.Id; } @DynamoDbSortKey public String getStartDate() { return this.startDate; } // setFirst / getFirst、setPhone / getPhone 等访问器方法略 }

从源码可以清晰看到两个关键映射约定:

  • getId()被标注为@DynamoDbPartitionKey(分区键),对应表中的Id主键;
  • getStartDate()被标注为@DynamoDbSortKey(排序键),因此创建Employee表时建议以Id为分区键、startDate为排序键,与 Java Bean 的注解声明保持一致。

这四个属性(Id、first、phone、startDate)与之前创建的 DynamoDB 表字段一一对应,是增强型客户端自动完成序列化/反序列化的基础。

ScanEmployees:扫描匹配员工并发送短信

ScanEmployees.java 是本示例的核心业务实现,同时使用 DynamoDB Java V2 API 与 SNS Java V2 API,主要包含三个方法。

sendEmployeMessage:扫描并筛选一周年员工

Region region = Region.US_WEST_2; DynamoDbClient ddb = DynamoDbClient.builder() .region(region) .build(); DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder() .dynamoDbClient(ddb) .build(); DynamoDbTable<Employee> table = enhancedClient.table("Employee", TableSchema.fromBean(Employee.class));

这里先用标准的DynamoDbClient构建底层客户端,再包装为DynamoDbEnhancedClient,最后通过enhancedClient.table("Employee", TableSchema.fromBean(Employee.class))绑定EmployeeBean 与真实表名。注意代码中区域被硬编码为US_WEST_2,如果Employee表与 SNS 短信服务不在该区域,请同步调整Region常量。

筛选条件通过增强型客户端的Expression对象构造——这正是本教程示范的"用 Java 逻辑做条件查询"的关键:

AttributeValue attVal = AttributeValue.builder() .s(myDate) // 一年前的今天,格式 yyyy-MM-dd .build(); Map<String, AttributeValue> myMap = new HashMap<>(); myMap.put(":val1", attVal); // 占位符绑定实际日期值 Map<String, String> myExMap = new HashMap<>(); myExMap.put("#startDate", "startDate"); // 表达式名称转义,避免与保留字冲突 Expression expression = Expression.builder() .expressionValues(myMap) .expressionNames(myExMap) .expression("#startDate = :val1") // 只取入职日期等于目标日的员工 .build(); ScanEnhancedRequest enhancedRequest = ScanEnhancedRequest.builder() .filterExpression(expression) .limit(15) // you can increase this value .build(); Iterator<Employee> employees = table.scan(enhancedRequest).items().iterator();

这段代码体现了三个 DynamoDB 增强型客户端的惯用法:

  • 占位符(Placeholder)::val1通过expressionValues绑定具体日期,避免 SQL 注入式的表达式拼接问题;
  • 表达式名称转义(Expression Names):#startDate通过expressionNames映射到真实的属性名startDate,防止属性名与 DynamoDB 保留字冲突;
  • 过滤与限流:filterExpression在服务端完成日期过滤,limit(15)限制单次扫描返回条数(可调大以覆盖更多员工)。

扫描结果通过Iterator<Employee>逐条取出,读取first(姓名)与phone(手机号)后调用sentTextMessage发送短信,并将返回值置为true表示至少成功发送了一条。

sentTextMessage:通过 SNS 发送文本短信

SnsClient snsClient = SnsClient.builder() .region(Region.US_WEST_2) .build(); String message = first + " happy one year anniversary. We are very happy that you have been working here for a year! "; PublishRequest request = PublishRequest.builder() .message(message) .phoneNumber(phone) // 直接向手机号发布,无需先创建 SNS Topic .build(); snsClient.publish(request);

这里使用了 SNS 的手机号直发能力:PublishRequest只需携带message与phoneNumber两个字段,AWS 会自动将消息以 SMS 形式发送到该号码,无需预先创建 Topic 或订阅。

getDate:计算一年前的日期

String DATE_FORMAT = "yyyy-MM-dd"; DateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT); DateTimeFormatter dateFormat8 = DateTimeFormatter.ofPattern(DATE_FORMAT); Date currentDate = new Date(); LocalDateTime localDateTime = currentDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); localDateTime = localDateTime.minusYears(1); String ann = dateFormat8.format(localDateTime); return ann;

该方法取当前时间并minusYears(1)回退一年,输出格式为yyyy-MM-dd的字符串,作为Expression中:val1的比对值。由于 Lambda 默认运行在 UTC 时区,实际调度时间(cron 中的 12:00 也为 UTC)与getDate计算的"今天"保持一致。

异常处理方面:DynamoDbException与SnsException均被捕获并打印错误信息后System.exit(1),便于在 CloudWatch Logs 中快速定位失败原因。

打包项目为可部署的 JAR

确认 pom.xml 配置无误后,在项目根目录执行 Maven 打包命令:

mvn package

打包完成后,JAR 文件位于项目下的target目录(例如target/LambdaCronFunctions-1.0-SNAPSHOT.jar)。再次强调:只有配置了maven-shade-plugin,JAR 才会包含 DynamoDB、SNS、aws-lambda-java-core 等全部运行依赖,否则部署后执行会报ClassNotFoundException。

在 Lambda 控制台部署函数

  1. 打开 Lambda 控制台,选择Create Function(创建函数);
  2. 模板选择Author from scratch(从头创作);
  3. 在 Basic information 中,函数名称填写employeemsg;
  4. Runtime(运行时)选择 Java 运行时(教程正文原选择Java 8;当前仓库源码以 Java 17 为目标编译,请以控制台当前可用的 Java 运行时版本为准,选择与打包产物兼容的版本);
  5. Permissions 选择Use an existing role,并指定此前创建的lambda-support角色;
  6. 点击Create function完成创建;
  7. 进入函数后,Code entry type 选择Upload a .zip or .jar file,点击Upload上传 target 目录中的 JAR;
  8. Handler字段填写函数入口的完整限定名:com.aws.example.Handler::handleRequest——其格式为包名.类名::方法名,Handler是 Lambda 入口类,handleRequest是具体执行方法;
  9. 点击Save保存配置。

配置 CloudWatch Events 规则实现定时触发

部署完成后,为employeemsg函数挂接 CloudWatch Events/EventBridge 定时触发规则:

  1. 打开 Lambda 控制台的 Functions(函数)页面,选择employeemsg;
  2. 在 Designer(设计器)区域点击Add trigger(添加触发器);
  3. 触发器类型选择CloudWatch Events/EventBridge;
  4. Rule 选择Create a new rule(新建规则),并填写 Rule name 与 Rule description;
  5. Rule type选择Schedule expression(调度表达式);
  6. 在Schedule expression输入框中填写 cron 表达式,例如cron(0 12 ? * MON-FRI *);
  7. 点击Add完成添加。

cron 表达式解析与调参建议

以cron(0 12 ? * MON-FRI *)为例,其字段从左至右依次为:分钟(0)、小时(12)、日(?不指定)、月(*每月)、星期(MON-FRI周一至周五)、年(*每年)。整体语义是每周一至周五的 12:00(UTC)触发一次。

Lambda 的定时 cron 表达式使用UTC 时区,因此如需在本地时间(如北京时间 9:00)执行,需要先换算为对应的 UTC 时间。调整字段即可改变调度周期,例如cron(0 9 * * ? *)表示每天 9:00 UTC 触发;cron(0/15 * * * ? *)表示每 15 分钟触发一次。创建规则后,可以稍等数分钟确认函数是否按预期被调用,并在 CloudWatch Logs 中查看Handler通过LambdaLogger写出的执行日志。

资源清理

教程开始部分已强调过:本示例会创建并实际运行 Lambda 函数、DynamoDB 表、SNS 短信与 CloudWatch Events 规则,持续运行可能产生费用。体验完成后,请务必清理以下资源:

  • 删除 Lambda 函数employeemsg(连同其关联的触发器规则);
  • 删除EmployeeDynamoDB 表;
  • 删除 IAM 角色lambda-support。

仓库中的对应资源速查

本教程的全部源码与配置都位于仓库的javav2/usecases/creating_scheduled_events目录下,可直接对照阅读:

  • 教程文档:README.md
  • Maven 构建配置:pom.xml(含依赖版本、shade 插件与 Java 17 编译参数)
  • Lambda 入口类:Handler.java
  • 核心业务类:ScanEmployees.java
  • 数据映射 Bean:Employee.java
  • 示例元数据(服务标签):metadata.yaml
  • 更多 AWS 多服务示例:usecases

至此,你已经掌握了"CloudWatch Events cron 表达式 + Lambda Java + DynamoDB + SNS"的完整组合方案。将Employee表的数据替换为你自己的业务数据、调整ScanEmployees中的过滤表达式与 cron 调度周期,即可把这一模式迁移到批量通知、定时报表、数据清洗等更多定时任务场景。

  • 示例工程
  • 教程
  • 后端

【免费下载链接】aws-doc-sdk-examples

Welcome to the AWS Code Examples Repository. This repo contains code examples used in the AWS documentation, AWS SDK Developer Guides, and more. For more information, see the Readme.md file below.

项目地址:https://gitcode.com/gh_mirrors/aw/aws-doc-sdk-examples
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询