【免费下载链接】aws-cdk-examples
Example projects using the AWS CDK
AWS CDK 的自定义资源(Custom Resource)是在 CloudFormation 原生资源之外,将任意业务逻辑(如外部系统调用、托管 API 操作、参数校验)接入 IaC 编排的关键手段。本文以本仓库aws-cdk-examples中的 java/custom-resource 示例为完整骨架,讲解如何用 Java 编写 CDK 应用、用 Lambda 实现自定义资源处理逻辑,并覆盖从mvn compile构建、CDK Toolkit 合成/部署到 Lambda 代码更新的完整工作流。读完本文,你将掌握在 Java 工程中拆分构造(Construct)、注册自定义资源 Provider、读取资源返回属性并输出到 CloudFormation 输出的完整实战方案。
为什么需要自定义资源:把架构拆成可复用组件
CloudFormation 原生的资源类型覆盖了绝大多数 AWS 服务,但总有“官方资源覆盖不到”的场景:比如在部署时调用第三方 API、初始化外部系统数据、或者编排多个服务之间的依赖关系。自定义资源正是为这类场景设计的——你可以把一段 Lambda 函数注册成资源 Provider,然后在 CDK 应用里像使用普通资源一样声明它,CloudFormation 会在资源创建、更新、删除的生命周期内自动调用你的处理器。
示例 README 明确指出,这个示例的价值在于构造(Construct)的设计与架构组件拆分:
This example shows adding a custom resource to a CDK app in Java. This custom resource is useful when considering design of constructs, or separating your architecture into components.
也就是说,自定义资源不仅是一种“补位”技术,更是把“有状态的外部逻辑”封装成可复用、可组合的 CDK 构造单元的一种工程实践。本示例属于 Stable 稳定性等级,仅依赖 CDK 核心库,没有任何基础设施前置条件(不需要预先创建 VPC、密钥等),开箱即可构建。
工程结构与多模块 Maven 布局
示例采用标准的 Maven 多模块工程,共三个模块,各自职责清晰:
java/custom-resource/ ├── asset/ │ └── lambda-1.0.0-jar-with-dependencies.jar # Lambda 处理器 jar(已构建产物) ├── cdk/ # CDK 应用模块 │ ├── cdk.json │ ├── pom.xml │ └── src/main/java/software/amazon/awscdk/examples/ │ ├── CustomResourceApp.java # 应用入口 │ ├── CustomResourceStack.java # Stack 定义 │ └── MyCustomResource.java # 自定义构造(核心) ├── lambda/ # Lambda 处理器模块 │ ├── cdk.json │ ├── pom.xml │ └── src/main/java/software/amazon/awscdk/examples/ │ ├── CustomResourceHandler.java # 资源生命周期处理器 │ └── CustomResourceIsCompleteHandler.java # 完成轮询处理器 ├── cdk.json ├── pom.xml # 聚合父 POM └── README.md根目录的 pom.xml 是聚合父工程,声明了lambda与cdk两个子模块,并配置了maven-compiler-plugin(source/target 1.8)与maven-assembly-plugin(产出jar-with-dependencies形式的可运行 jar)。
cdk模块的 cdk/pom.xml 声明了 CDK 核心依赖,版本区间是工程当前实际使用的:
software.amazon.awscdk:aws-cdk-lib:[2.262.0, 3.0.0)software.constructs:constructs:[10.5.0, 11.0.0)junit:junit4.13.1(test 作用域,供编写单元测试)
lambda模块的 lambda/pom.xml 则使用maven-shade-plugin打 fat jar,依赖aws-lambda-java-core1.2.1 与aws-lambda-java-events3.10.0,同时通过dependencyManagement引入 AWS SDK v2 BOM。
构建:一条命令拉起整个工程
README 给出的构建命令只有一条:
$ mvn compile这条命令会在根工程执行,Maven 会自动解析lambda、cdk两个子模块并按依赖顺序编译,同时下载所需的全部依赖。编译之后,工程结构就已就绪,可以进入下一步——用 CDK Toolkit 合成与部署。
提示:你也可以用 IDE 编写代码和单元测试,但合成(synthesize)与部署(deploy)必须通过 CDK Toolkit完成,IDE 无法替代这一环节。
CDK Toolkit 集成:cdk.json 与入口配置
CDK Toolkit 如何知道“该怎样执行你的 Java 程序”?答案在 cdk.json:
{ "app": "mvn exec:java -pl cdk -Dexec.mainClass=software.amazon.awscdk.examples.CustomResourceApp" }app字段指示 Toolkit 使用mvn exec:java作为应用入口命令,主类是software.amazon.awscdk.examples.CustomResourceApp。-pl cdk指定只在cdk模块内执行。这意味着:修改 Java 代码后,无需手动重新编译——CDK Toolkit 每次执行时 Maven 会按需自动重编译,随后即可照常运行所有 Toolkit 命令:
$ cdk ls <list all stacks in this program> $ cdk synth <cloudformation template> $ cdk deploy <deploy stack to your account> $ cdk diff <diff against deployed stack>cdk ls:列出本程序包含的所有 Stack;cdk synth:把 CDK 应用合成成 CloudFormation 模板;cdk deploy:将 Stack 部署到你的账号;cdk diff:对比当前代码与已部署 Stack 的差异。
结合 java/README.md 中 Java 示例的统一运行方式,首次部署前的全局准备是安装 CDK CLI:
$ npm install -g aws-cdk $ cd java/custom-resource $ cdk deployStack 使用完毕后,用cdk destroy即可清理全部资源。
源码拆解:三层结构如何协作
整个示例由“应用入口 → Stack → 自定义构造 → Lambda 处理器”四个层次构成,下面逐层剖析。
1. 应用入口:CustomResourceApp
CustomResourceApp.java 是所有 CDK 应用的入口,负责创建App、注入资源属性、注册 Stack 并触发合成:
public class CustomResourceApp { private static Map<String, Object> props = new HashMap<String, Object>(); public static void main(final String args[]) { App app = new App(); props.put("Message", "AWS CDK"); new CustomResourceStack(app, "cdk-custom-resource-example2", props); app.synth(); } }这里的关键设计是:把业务参数(Message: "AWS CDK")以Map<String, Object>形式从入口传入,后续会一路透传到自定义资源构造与 Lambda 处理器。Stack 的 id 为cdk-custom-resource-example2,app.synth()最终生成 CloudFormation 模板。
2. Stack 层:CustomResourceStack
CustomResourceStack.java 的职责很单纯:实例化自定义构造,并把构造暴露出来的返回属性输出为 CloudFormation 输出:
public class CustomResourceStack extends Stack { public String response = ""; public CustomResourceStack(final Construct scope, final String id, final Map<String, ? extends Object> props) { super(scope, id); try { MyCustomResource resource = new MyCustomResource(this, "DemoResource", props); CfnOutput.Builder.create(this, "ResponseMessage") .description("The message that came back from the Custom Resource") .value((resource.response)) .build(); } catch (Exception e) { e.printStackTrace(); } } }Stack 里有一个公开字段response,它接收MyCustomResource从自定义资源返回的Response属性值;随后CfnOutput(逻辑 idResponseMessage)把它暴露为 CloudFormation 输出,部署后可以在控制台或 CLI 中直接看到“自定义资源回传的消息”。
3. 核心构造:MyCustomResource
MyCustomResource.java 是本例的“灵魂”,它继承software.constructs.Construct,把“Provider + 自定义资源”整体封装成一个可复用组件。整个构造按三步搭建:
第一步:创建事件处理 Lambda(onEvent)
final SingletonFunction onEvent = SingletonFunction.Builder.create(this, "Singleton1") .uuid(UUID.randomUUID().toString()) .code(Code.fromAsset("./asset/lambda-1.0.0-jar-with-dependencies.jar")) .handler("software.amazon.awscdk.examples.CustomResourceHandler") .runtime(Runtime.JAVA_21).memorySize(1024) .timeout(Duration.minutes(5)) .build();要点:
- 使用
SingletonFunction复用同一个 jar(./asset/lambda-1.0.0-jar-with-dependencies.jar),并配一个随机uuid保证函数资源唯一; - 处理器入口是
CustomResourceHandler; - 运行时
JAVA_21、内存 1024 MB、超时 5 分钟——这些参数保证即使onEvent逻辑较重也不会被超时打断。
第二步:创建完成轮询 Lambda(isComplete)
final SingletonFunction isComplete = SingletonFunction.Builder.create(this, "Singleton2") .uuid(UUID.randomUUID().toString()) .code(Code.fromAsset("./asset/lambda-1.0.0-jar-with-dependencies.jar")) .handler("software.amazon.awscdk.examples.CustomResourceIsCompleteHandler") .runtime(Runtime.JAVA_21).memorySize(1024) .timeout(Duration.minutes(5)) .build();它复用同一个 jar,但入口换成了CustomResourceIsCompleteHandler——这是 AWS CDKcustom-resources模块的“异步 Provider”模式:CDK 框架会先用onEvent触发业务逻辑,再反复调用isComplete轮询,直到返回IsComplete: true才认为资源就绪。
第三步:组装 Provider 并声明自定义资源
final Provider myProvider = Provider.Builder.create(this, "MyProvider") .onEventHandler(onEvent) .isCompleteHandler(isComplete) .logRetention(RetentionDays.ONE_DAY) .build(); final CustomResource resource = CustomResource.Builder.create(this, "Resource1") .serviceToken(myProvider.getServiceToken()) .properties(props) .build(); response = resource.getAttString("Response");Provider是software.amazon.awscdk.customresources包提供的框架构造,负责封装响应上报、完成轮询等底层机制;logRetention设为ONE_DAY,控制 Provider 框架 Lambda 的 CloudWatch 日志保留时间;CustomResource通过serviceToken指向 Provider,并把入口传入的props(含Message)作为资源属性下发;- 最后用
resource.getAttString("Response")读取自定义资源返回的Response属性——这个值会在合成期被解析,最终进入 Stack 的CfnOutput。
从源码结构看,MyCustomResource对外只暴露一个response字段,内部细节(两个 Lambda、Provider、资源声明)全部被封装起来——这正是 README 强调的“构造设计”与“组件化拆分”的直观体现:使用方(Stack)完全不需要关心自定义资源内部的 Lambda 与轮询机制。
4. 生命周期处理器:CustomResourceHandler
CustomResourceHandler.java 实现了RequestHandler<Map<String, Object>, Map<String, Object>>,按 CloudFormation 事件中的RequestType分发到三种生命周期操作:
@Override public Map<String, Object> handleRequest(Map<String, Object> event, Context context) { String requestType = (String) event.get("RequestType"); switch (requestType) { case "Create": return onCreate(event); case "Update": return onUpdate(event); case "Delete": return onDelete(event); default: throw new RuntimeException("Invalid request type: " + requestType); } }Create 分支是业务核心:从ResourceProperties里取出Message,构造一个Data属性Response返回:
private Map<String, Object> onCreate(Map<String, Object> event) { Map<String, Object> props = (Map<String, Object>) event.get("ResourceProperties"); String message = (String) props.get("Message"); Map<String, String> attributes = new HashMap<String, String>(); attributes.put("Response", String.format("Resource message %s", message)); Map<String, Object> response = new HashMap<String, Object>(); response.put("Data", attributes); return response; }这个返回 Map 会被 CDK 的 Provider 框架转换成标准 CloudFormation 自定义资源响应:Data.Response正是前面getAttString("Response")读到的那个值。当入口传入Message = "AWS CDK"时,最终输出就是Resource message AWS CDK。
Update / Delete 分支则相对轻量:从事件中读取PhysicalResourceId并原样返回,确保 CloudFormation 能正确标识资源、完成响应上报:
private Map<String, Object> onUpdate(Map<String, Object> event) { String physicalId = (String) event.get("PhysicalResourceId"); Map<String, Object> response = new HashMap<String, Object>(); response.put("PhysicalResourceId", physicalId); return response; } private Map<String, Object> onDelete(Map<String, Object> event) { String physicalId = (String) event.get("PhysicalResourceId"); Map<String, Object> response = new HashMap<String, Object>(); response.put("PhysicalResourceId", physicalId); return response; }真实项目中,onDelete通常需要执行清理逻辑(如删除外部系统数据),本示例保持最简,只做物理 ID 的回传。
5. 完成轮询处理器:CustomResourceIsCompleteHandler
CustomResourceIsCompleteHandler.java 是整个链路的“收尾哨兵”,逻辑极简:
public class CustomResourceIsCompleteHandler implements RequestHandler<Map<String, Object>, Map<String, Object>> { @Override public Map<String, Object> handleRequest(Map<String, Object> event, Context context) { Map<String, Object> response = new HashMap<String, Object>(); response.put("IsComplete", true); return response; } }它永远返回IsComplete: true,即“onEvent 一执行完就算完成”。对于需要异步等待外部系统就绪的真实场景,可以在这里检查实际状态,未就绪时返回false,CDK 的 Provider 框架会持续轮询直至返回true或超时。
Lambda 代码更新流程:改完怎么生效
README 特别强调了 Lambda 处理器的部署更新流程。由于 CDK 构造引用的是asset/目录下预构建的 jar,因此每当你修改了 Lambda 处理器代码,都必须重新打包并拷贝 jar,才能让下一次cdk deploy携带最新代码:
$ mvn package -f lambda/pom.xml <generates jar with lambda handler code> $ cp lambda/target/lambda-1.0.0-jar-with-dependencies.jar asset/ <copies lambda handler jar to asset dir>- 第一步用
-f lambda/pom.xml只对lambda模块执行package,maven-shade-plugin会生成包含全部依赖的 fat jar(lambda-1.0.0-jar-with-dependencies.jar); - 第二步把生成的 jar 覆盖拷贝到
asset/目录——这正是MyCustomResource中Code.fromAsset("./asset/...")引用的位置(注意该相对路径以java/custom-resource/cdk为基准解析)。
完成这两步后,再执行cdk deploy即可把新的处理器代码部署到 Lambda。
运行与清理:完整实操闭环
综合 README 与 java/README.md,完整实操闭环如下:
# 1. 安装 CDK CLI(如未安装) $ npm install -g aws-cdk # 2. 进入示例目录 $ cd java/custom-resource # 3. 编译工程(下载依赖) $ mvn compile # 4. 查看 Stack $ cdk ls # 5. 部署 $ cdk deploy # 6. 部署后在输出中查看 ResponseMessage(值为 Resource message AWS CDK) # 7. 清理资源 $ cdk destroy若需要提前查看将要生成的 CloudFormation 模板或与线上对比,可随时执行cdk synth与cdk diff。
小结
通过这个 Java 示例,可以提炼出自定义资源在 CDK 工程中的完整落地模式:
- 入口层(
CustomResourceApp)负责组装参数并触发合成; - Stack 层(
CustomResourceStack)把自定义构造的输出暴露为 CloudFormation 输出; - 构造层(
MyCustomResource)用SingletonFunction+Provider+CustomResource封装完整机制,对外只暴露业务字段——这是构造化设计、组件化拆分的最佳示范; - 处理器层(
CustomResourceHandler、CustomResourceIsCompleteHandler)按Create/Update/Delete处理生命周期,并以Data属性向 CDK 回传业务结果。
当你需要把“CloudFormation 覆盖不到的业务逻辑”接入 IaC 编排,或想把一段有状态的外部操作封装成可复用组件时,可以直接套用本示例的三层骨架。相关代码全部可在仓库中继续研读:CustomResourceApp.java、CustomResourceStack.java、MyCustomResource.java,以及两个 Lambda 处理器 CustomResourceHandler.java 与 CustomResourceIsCompleteHandler.java。
【免费下载链接】aws-cdk-examples
Example projects using the AWS CDK
相关推荐
QGroundControl 资源覆盖(Resource Overrides)机制全解析:从 URL 拦截器到 Custom 前缀的自定义构建实践
QGroundControl 资源覆盖(Resource Overrides)机制全解析:从 URL 拦截器到 Custom 前缀的自定义构建实践 导读 本文面
无人机智能硬件TensorFlow Serving 自定义算子(Custom Op)接入实战:从源码链接到模型部署
TensorFlow Serving 自定义算子(Custom Op)接入实战:从源码链接到模型部署 本文面向需要在 TensorFlow Serving 中部
模型推理服务人工智能后端aws-doc-sdk-examples:使用 AWS CDK 与 CloudFormation 部署 DynamoDB Item Tracker 资源
aws doc sdk examples:使用 AWS CDK 与 CloudFormation 部署 DynamoDB Item Tracker 资源 本指南
示例工程教程后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考