自定义 Gradle 插件进行统一的静态代码分析

静态代码分析是一项了不起的技术, 它能让代码库更易于维护. 但是, 如果你在不同的版本库中拥有多个服务(可能由不同的团队开发), 如何才能让每个人都遵循既定的代码风格呢? 一个好办法是将所有规则封装在一个插件中, 该插件会在每个项目构建时自动执行所需的验证.

因此, 在本文中我将向你展示:

  1. 如何创建带有自定义 PMD 和 Checkstyle 规则的 Gradle 插件.
  2. 如何发布到 [plugins.gradle.org]
  3. 如何使用 GitHub Actions 自动执行发布流程.

你可以查看[本仓库]中的代码示例.

PMD, Checkstyle 和多仓库的难点

[PMD] 是静态分析工具, 可在每次项目构建时检查代码. 通过 [Gradle]“https://medium.com/javarevisited/why-java-developer-should-learn-maven-or-gradle-aefe7ea20a83”), 可以轻松应用它们.

plugins {id 'java'id 'pmd'id 'checkstyle'
}

现在, 你可以按照自己的方式调整每个插件.

checkstyle {toolVersion = '10.5.0'ignoreFailures = falsemaxWarnings = 0configFile = file(pathToCheckstyleConfig)
}pmd {consoleOutput = truetoolVersion = '6.52.0'ignoreFailures = falseruleSetFiles = file(pathToPmdConfig)
}

如果你的整个项目(甚至是公司)都是[单仓库, 那么这样的设置绝对没问题. 你只需将这些配置放入根build.gradle文件中, 就能将这些插件应用到现有的每个模块中. 但如果你选择的是[多仓库]呢?

如果你想在公司内开发人员正在开发的所有项目(以及程序员将来创建的所有项目)中共享相同的代码风格, 该怎么办?

那么, 你可以告诉他们只需[复制并粘贴]插件的配置即可. 无论如何, 这种方法容易出错. 总有人可能会配置错误.

事实上, 我们需要在每个可行的项目中以某种方式重复使用已定义的代码样式配置. 答案很简单. 我们需要一个定制的 Gradle 插件来封装 PMD 和 Checkstyle 规则.

自定义 Gradle 插件

构建配置

请看下面的 build.gradle 声明. 这是 Gradle 插件项目的基本设置.

plugins {id 'java-gradle-plugin'id 'com.gradle.plugin-publish' version '1.1.0'
}group = 'io.github.simonharmonicminor.code.style'
sourceCompatibility = '8'repositories {mavenCentral()
}ext {set('lombokVersion', '1.18.24')
}dependencies {compileOnly "org.projectlombok:lombok:${lombokVersion}"annotationProcessor "org.projectlombok:lombok:${lombokVersion}"testImplementation 'org.junit.jupiter:junit-jupiter:5.7.2'
}gradlePlugin {website = 'https://github.com/SimonHarmonicMinor/gradle-code-style-plugin-example'vcsUrl = 'https://github.com/SimonHarmonicMinor/gradle-code-style-plugin-example'plugins {gradleCodeStylePluginExample {id = 'io.github.simonharmonicminor.code.style'displayName = 'Gradle Plugin Code Style Example'description = 'Predefined Checkstyle and PMD rules'implementationClass = 'io.github.simonharmonicminor.code.style.CodingRulesGradlePluginPlugin'tags.set(['codestyle', 'checkstyle', 'pmd'])}}
}tasks.named('test') {useJUnitPlatform()
}

现在让我们从 plugins 块开始, 一步步解构配置. 请看下面的代码片段.

plugins {id 'java-gradle-plugin'id 'com.gradle.plugin-publish' version '1.1.0'
}

java-gradle-plugin命令会启用常规 Gradle 插件项目的任务. com.gradle.plugin-publish命令允许打包插件并发布到plugins.gradle.org.

我最近正在向你展示整个发布过程.

然后是基本的项目配置.

group = 'io.github.simonharmonicminor.code.style'
sourceCompatibility = '8'repositories {mavenCentral()
}

group定义了groupId, 以符合[Apache Maven 命名规范] sourceCompatibility是目标 Java 二进制文件的版本. 虽然 Java 8 现在已经过时, 但我还是建议你使用公司开发人员使用的最早 JDK 版本构建 Gradle 插件. 否则, 你会阻碍他们遵循你的代码风格指南.

然后是 dependencies 范围.

ext {set('lombokVersion', '1.18.24')
}dependencies {compileOnly "org.projectlombok:lombok:${lombokVersion}"annotationProcessor "org.projectlombok:lombok:${lombokVersion}"testImplementation 'org.junit.jupiter:junit-jupiter:5.7.2'
}

这里没什么特别的. 接下来是发布配置.

website = 'https://github.com/SimonHarmonicMinor/gradle-code-style-plugin-example'vcsUrl = 'https://github.com/SimonHarmonicMinor/gradle-code-style-plugin-example'plugins {gradleCodeStylePluginExample {id = 'io.github.simonharmonicminor.code.style'displayName = 'Gradle Plugin Code Style Example'description = 'Predefined Checkstyle and PMD rules'implementationClass = 'io.github.simonharmonicminor.code.style.CodingRulesGradlePluginPlugin'tags.set(['codestyle', 'checkstyle', 'pmd'])}}
}

websitevcsUrl应指向包含插件源代码的公共 Git 仓库. plugins块定义了项目中Plugin接口的每个实现. 最后,tags只是在注册表中搜索插件的hash标签.

当你将 Gradle 插件发布到 [plugins.gradle.org] 时, 包的名称至关重要. 你的插件代码应该可以在 GitHub 上找到. 如果不是开源的, 发布时可能会遇到问题. 那么, 你可以将软件包名称声明为io.github.your_github_login.any.package.you.like.

但是, 如果你想使用其他名称, 如com.mycompany.my.plugin, 请确保域名mycompany.com. 否则, Gradle 工程师可能会拒绝发布.

注意 Gradle 禁止plugingradle作为标签值. 在gradle publishPlugins任务执行过程中, 这样的构建会失败.

tasks.named('test') {useJUnitPlatform()
}

插件代码

我想向大家展示整个插件的代码. 然后我将向你解释每个细节. 请看下面的代码片段.

public class CodingRulesGradlePluginPlugin implements Plugin<Project> {@Overridepublic void apply(Project project) {project.getPluginManager().apply("checkstyle");project.getExtensions().configure(CheckstyleExtension.class, checkstyleExtension -> {checkstyleExtension.setToolVersion("10.5.0");checkstyleExtension.setIgnoreFailures(false);checkstyleExtension.setMaxWarnings(0);checkstyleExtension.setConfigFile(FileUtil.copyContentToTempFile("style/checkstyle.xml", ".checkstyle.xml"));});project.getPluginManager().apply("pmd");project.getExtensions().configure(PmdExtension.class, pmdExtension -> {pmdExtension.setConsoleOutput(true);pmdExtension.setToolVersion("6.52.0");pmdExtension.setIgnoreFailures(false);pmdExtension.setRuleSets(emptyList());pmdExtension.setRuleSetFiles(project.files(FileUtil.copyContentToTempFile("style/pmd.xml", ".pmd.xml")));});final SortedSet<String> checkstyleTaskNames = project.getTasks().withType(Checkstyle.class).getNames();final SortedSet<String> pmdTaskNames = project.getTasks().withType(Pmd.class).getNames();project.task("runStaticAnalysis",task -> task.setDependsOn(Stream.concat(checkstyleTaskNames.stream(),pmdTaskNames.stream()).collect(Collectors.toList())));}
}

最明显也是最重要的细节是, 每个插件任务都必须实现 Gradle Plugin 接口.

import org.gradle.api.Plugin;
import org.gradle.api.Project;public class CodingRulesGradlePluginPlugin implements Plugin<Project> {@Overridepublic void apply(Project project) { ... }
}

然后我在配置 Checkstyle 任务. 我只需应用 checkstyle 插件, 获取 CheckstyleConfiguration 并覆盖我想要的属性. 请看下面的代码块.

project.getPluginManager().apply("checkstyle");
project.getExtensions().configure(CheckstyleExtension.class, checkstyleExtension -> {checkstyleExtension.setToolVersion("10.5.0");checkstyleExtension.setIgnoreFailures(false);checkstyleExtension.setMaxWarnings(0);checkstyleExtension.setConfigFile(FileUtil.copyContentToTempFile("style/checkstyle.xml", ".checkstyle.xml"));
});

FileUtil.copyContentToTempFile函数需要解释一下. 我把 Checkstyle 配置放到了 src/main/resources/style/checkstyle.xml 文件中. 但是, 如果你直接指向它, 那么人们在他们的项目中应用你的 Gradle 时就会得到奇怪的错误信息. 有一些变通方法, 但最简单的方法是将内容复制到临时文件中.

看看下面的 PMD 配置. 与 Checkstyle 类似.

project.getPluginManager().apply("pmd");
project.getExtensions().configure(PmdExtension.class, pmdExtension -> {pmdExtension.setConsoleOutput(true);pmdExtension.setToolVersion("6.52.0");pmdExtension.setIgnoreFailures(false);pmdExtension.setRuleSets(emptyList());pmdExtension.setRuleSetFiles(project.files(FileUtil.copyContentToTempFile("style/pmd.xml", ".pmd.xml")));
});

现在我们准备就绪. 我们可以将其应用到实际项目中. 虽然也有一点改进. 请看下面的代码片段.

final SortedSet<String> checkstyleTaskNames = project.getTasks().withType(Checkstyle.class).getNames();final SortedSet<String> pmdTaskNames = project.getTasks().withType(Pmd.class).getNames();project.task("runStaticAnalysis",task -> task.setDependsOn(Stream.concat(checkstyleTaskNames.stream(),pmdTaskNames.stream()).collect(Collectors.toList()))
);

runStaticAnalysis任务会触发所有 Checkstyle 和 PMD 任务按顺序运行. 当你想在创建拉取请求前验证整个项目时, 它就派上用场了. 如果直接在build.gradle中添加runStaticAnalysis任务, 它将看起来像这样:

task runStaticAnalysis {dependsOn checkstyleMain, checkstyleTest, pmdMain, pmdTest
}

同样, 我将一次性展示整段代码, 然后指出重要的细节.

class CodingRulesGradlePluginPluginTest {@Testvoid shouldApplyPluginSuccessfully() {final Project project = ProjectBuilder.builder().build();project.getPluginManager().apply("java");assertDoesNotThrow(() -> new CodingRulesGradlePluginPlugin().apply(project));final Task task = project.getTasks().getByName("runStaticAnalysis");assertNotNull(task, "runStaticAnalysis task should be registered");final Set<String> codeStyleTasks =Stream.of("checkstyleMain", "checkstyleTest", "pmdTest", "pmdMain").collect(toSet());assertTrue(task.getDependsOn().containsAll(codeStyleTasks),format("Task runStaticAnalysis should contain '%s' tasks, but actually: %s",codeStyleTasks,task.getDependsOn()));}
}

首先是 Gradle 项目实例化测试. 请看下面的代码片段.

import org.gradle.testfixtures.ProjectBuilder;
import org.gradle.api.Project;final Project project = ProjectBuilder.builder().build();
project.getPluginManager().apply("java");

Gradle 为单元测试提供了一些固定装置. ProjectBuilder创建了一个与 API 兼容的Project接口实现. 因此, 你可以放心地将它传递给 YourPluginClass.apply 方法.

在调用业务逻辑之前, 我们还要手动应用 java 插件. 我们的插件针对 Java 应用程序. 因此, 传递 Java 配置的 Project 实现是很自然的.

然后, 我们只需调用自定义插件方法并传递配置的 Project 实现.

assertDoesNotThrow(() -> new CodingRulesGradlePluginPlugin().apply(project)
);

之后是断言. 我们需要确保 runStaticAnalysis 任务注册成功.

final Task task = project.getTasks().getByName("runStaticAnalysis");
assertNotNull(task, "runStaticAnalysis task should be registered");

如果存在, 我们将根据现有的 Checkstyle 和 PMD 任务验证该任务.

final Set<String> codeStyleTasks =Stream.of("checkstyleMain", "checkstyleTest", "pmdTest", "pmdMain").collect(toSet());
assertTrue(task.getDependsOn().containsAll(codeStyleTasks),format("Task runStaticAnalysis should contain '%s' tasks, but actually: %s",codeStyleTasks,task.getDependsOn())
);

这是我们在将插件推送到 [plugins.gradle.org/]之前应该测试的最基本情况.

使用 GitHub Actions 发布插件

当你在 [plugins.gradle.org/]上注册一个新账户时, 进入你的页面并打开 API Keys 选项卡. 你应该生成新的密钥. 会有两个.

gradle.publish.key=...
gradle.publish.secret=...

然后, 打开版本库的Settings, 转到Secrets and Variables -> Actions项. 你必须把获得的密钥存储为版本库秘密.

最后是 GitHub Actions 的构建配置.

我把自己的文件放在了.github/workflow/build.yml.

请看下面的整个设置. 然后, 我将告诉你特定区块的含义.

name: Java CI with Gradleon:push:branches: [ "master" ]pull_request:branches: [ "master" ]permissions:contents: readjobs:build:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v3- name: Set up JDK 8uses: actions/setup-java@v3with:java-version: '8'distribution: 'temurin'- name: Build with Gradleuses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1with:arguments: buildpublish:needs:- buildif: github.ref == 'refs/heads/master'runs-on: ubuntu-lateststeps:- name: Auto Increment Semver Actionuses: MCKanpolat/auto-semver-action@1.0.5id: versioningwith:releaseType: minorincrementPerCommit: falsegithub_token: ${{ secrets.GITHUB_TOKEN }}- name: Next Release Numberrun: echo ${{ steps.versioning.outputs.version }}- uses: actions/checkout@v3- name: Set up JDK 8uses: actions/setup-java@v3with:java-version: '8'distribution: 'temurin'- name: Publish Gradle pluginuses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1with:arguments: build publishPlugins -Pgradle.publish.key=${{ secrets.GRADLE_PUBLISH_KEY }} -Pgradle.publish.secret=${{ secrets.GRADLE_PUBLISH_SECRET }} -Pversion=${{ steps.versioning.outputs.version }}

文件顶部的声明说明了管道触发的规则.

name: Java CI with Gradleon:push:branches: [ "master" ]pull_request:branches: [ "master" ]

管道会在每次向master分支提出拉取请求和每次构建master分支时运行.

构建由两项工作组成. 第一个工作很简单. 它只是运行 Gradle build 任务. 请看下面的配置.

jobs:build:runs-on: ubuntu-lateststeps:- uses: actions/checkout@v3- name: Set up JDK 8uses: actions/setup-java@v3with:java-version: '8'distribution: 'temurin'- name: Build with Gradleuses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1with:arguments: build

然后是发布的任务本身. 它也包含几个步骤. 第一个步骤是自动增加版本并保存到环境变量中. 这很方便, 因为 Gradle 插件不能以快照的形式发布.

publish:needs:- buildif: github.ref == 'refs/heads/master'runs-on: ubuntu-lateststeps:- name: Auto Increment Semver Actionuses: MCKanpolat/auto-semver-action@1.0.5id: versioningwith:releaseType: minorincrementPerCommit: falsegithub_token: ${{ secrets.GITHUB_TOKEN }}- name: Next Release Numberrun: echo ${{ steps.versioning.outputs.version }}

if: github.ref == 'refs/heads/master'告知 GitHub Actions 只有在master分支在构建的时候才能运行管道线中的任务. 因此, 在拉取请求构建过程中, GitHub Actions 不会触发publish进程.

现在, 我们需要发布打包的插件本身. 请看下面的代码片段.

- uses: actions/checkout@v3
- name: Set up JDK 8uses: actions/setup-java@v3with:java-version: '8'distribution: 'temurin'
- name: Publish Gradle pluginuses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1with:arguments: build publishPlugins -Pgradle.publish.key=${{ secrets.GRADLE_PUBLISH_KEY }} -Pgradle.publish.secret=${{ secrets.GRADLE_PUBLISH_SECRET }} -Pversion=${{ steps.versioning.outputs.version }}

如你所见, GitHub Actions 通过secrets传递了gradle.publish.keygradle.publish.secret属性, 并将新项目版本作为环境变量.

总结一下

正如你所看到的, 在 Gradle 中自动检查代码样式规则并不复杂. 顺便说一句, 你可以通过包含 id 'io.github.simonharmonicminor.code.style' version '0.1.0' 来应用项目中描述的插件.

如果你看到了这里,觉得文章写得不错就给个赞呗?
更多Android进阶指南 可以扫码 解锁更多Android进阶资料


在这里插入图片描述
敲代码不易,关注一下吧。ღ( ´・ᴗ・` )

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/12890.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

最新版rancher环境配置安装和集群搭建详细教程记录

&#x1f680; 作者 &#xff1a;“二当家-小D” &#x1f680; 博主简介&#xff1a;⭐前荔枝FM架构师、阿里资深工程师||曾任职于阿里巴巴担任多个项目负责人&#xff0c;8年开发架构经验&#xff0c;精通java,擅长分布式高并发架构,自动化压力测试&#xff0c;微服务容器化k…

BGP学习三:BGP路由优选12条规则,闪亮登场啦啦啦啦啦

目录 一.BGP策略工具 &#xff08;1&#xff09;Router-policy作用 &#xff08;2&#xff09;组成部分 &#xff08;3&#xff09;router-policy注意事项 二.优选规则 ①丢弃下一跳不可达 (1)优选prefered-value值大的路由 1.首选优先级 (2)优选local-preference(本地…

Fast-Poisson-Image-Editing代码介绍(二)

目录 2.fpei文件下 2.6 number_solver.py 2.7 process.py 2.8 taichi_solver.py 3. 算法总结 4. 代码运行 4.1 测试 4.2 基于GUI后端自定义框输出编辑图像结果 4.2.1 下载open-cv 4.2.2 输入命令 4.2.3 自定义框 4.2.4 按ESC退出 接续Fast-Poisson-Image-Editing代码…

企业研发必备网络:这些关键特性,你get了吗?

对于以研发为核心的企业&#xff0c;如软件开发、生物制药、智能汽车等&#xff0c;安全、稳定的研发网络可是他们业务发展不可或缺的。那么&#xff0c;这些研发网络究竟有哪些独特之处&#xff0c;又能为企业带来哪些价值呢&#xff1f; 首先&#xff0c;我们知道企业研发常常…

开放式耳机哪款具有高性价比?5款高分开放式耳机倾力推荐

作为多年的耳机发烧友&#xff0c;强烈给你们安利开放式耳机&#xff0c;真的是舒适耐用&#xff0c;性价比高。开放式耳机以其独特的不入耳设计&#xff0c;给用户带来了最舒适的佩戴感受。如果小白还不知道怎么选择高性价比的开放式耳机那就看看我的总结吧&#xff01;下面就…

前端面试题(二十三)(答案版)

面试形式&#xff1a;线上电话面试&#xff1a;一面&#xff1a;时长30分钟 面试评价&#xff1a;精准考察项目所需技术理论工作实践 面试官的提问大纲&#xff1a;本公司项目要求本人简历 工作经验&#xff1a;2-4年 公司名称&#xff1a;深圳XX&#xff08;想知道的就滴喔…

冯喜运:5.15黄金原油晚盘分析:鲍威尔再放鹰,降息悬念重重

【黄金消息面分析】&#xff1a;在全球经济动荡和通胀预期不断上升的背景下&#xff0c;黄金作为传统的避险资产&#xff0c;再次成为投资者关注的焦点。当前&#xff0c;黄金价格交投于2370美元/盎司左右&#xff0c;连续两日日线呈现上涨趋势&#xff0c;而白银价格也在连续三…

超级数据查看器 教程合集 整理版本 pdf格式 1-31集

点击下载 超级数据查看器 教程合集整理版本 pdf格式https://download.csdn.net/download/qq63889657/89311725?spm1001.2014.3001.5501

更新Windows 11 后遇到的一些问题(更新中...)

目录 插入U盘后读取不到 在磁盘中新建文件夹需要管理员权限 导致不能安装一些软件 插入U盘后读取不到 解决方法&#xff1a;点击我的电脑或者是此电脑、选择管理、找到设备管理器、选择通用串行总线控制器、右键、选择启动。 第一步&#xff1a;点击我的电脑或者是此电脑、选…

Java学习48-Java 流(Stream)、文件(File)和IO - 复习章节

1.File类的使用 File类的一个实例对应着磁盘上的文件或文件目录。(必须熟悉)File的实例化(新建一个对象)&#xff0c;常用的方法File类中只有新建&#xff0c;删除&#xff0c;获取路径等方法&#xff0c;不包含读写文件的方法&#xff0c;此时需要使用使用下面说的IO流 IO流…

论文阅读:基于改进 YOLOv5算法的密集动态目标检测方法

目录 概要 Motivation 整体架构流程 技术细节 小结 论文地址&#xff1a;基于改进YOLOv5算法的密集动态目标检测方法 - 中国知网 (cnki.net) 概要 目的&#xff1a;提出一种基于 YOLOv5改进的检测算法&#xff0c;解决密集动态目标检测精度低及易漏检的问题。 方法&…

Linux虚拟主机cPanel重置密码

我使用的Hostease的Linux虚拟主机产品默认带普通用户权限的cPanel面板&#xff0c;这边自购买后一直未重新设置过cPanel面板的密码&#xff0c;但是了解到要定期重置一下cPanel面板的密码&#xff0c;以确保主机数据安全&#xff0c;因此想要进行重置cPanel面板的密码&#xff…

SpringBoot上传文件到服务器(跨服务器上传)

目录 &#xff08;一&#xff09;上传文件到本地&#xff08;windows&#xff09; &#xff08;二&#xff09;上传文件到linux服务器 &#xff08;三&#xff09;跨服务器上传文件 &#xff08;一&#xff09;上传文件到本地&#xff08;windows&#xff09; 1.新建一个文件…

第十四届蓝桥杯大赛软件赛国赛C/C++ 大学 B 组 AB路线

//bfs 1000100010不会超时 #include<bits/stdc.h> using namespace std; #define int long long const int n1e311; int a,b,c,h[n][n][12],k[4][2]{0,1,0,-1,1,0,-1,0}; char t[n][n]; struct s {int x,y,z,w; }; signed main() {ios::sync_with_stdio(false);cin.t…

(规格参考)ADP5360ACBZ-1-R7 电量计 电池管理IC,ADP5072ACBZ 双通道直流开关稳压器,ADL5903ACPZN 射频检测器

1、ADP5360ACBZ-1-R7&#xff1a;具有超低功耗电量计、电池保护功能的先进电池管理PMIC 功能&#xff1a;电池保护 电池化学成份&#xff1a;锂离子/聚合物 电池数&#xff1a;1 故障保护&#xff1a;超温&#xff0c;过压 接口&#xff1a;I2C 工作温度&#xff1a;-40C ~ 85…

Spring Security入门教程:实现自定义用户配置

在上一篇文章中&#xff1a;Spring Security入门教程&#xff1a;利用Spring Security实现安全控制 我们学会了使用Spring Security实现安全控制&#xff0c;学会了他的基础使用&#xff0c;这节课我们来学习一下它的自定义的功能&#xff0c;更深入的了解和使用Spring Securit…

OpenHarmony 实战开发——ArkUI canvas组件

canvas 是 ArkUI 开发框架里的画布组件&#xff0c;常用于自定义绘制图形。因为其轻量、灵活、高效等优点&#xff0c;被广泛应用于 UI 界面开发中。本期&#xff0c;我们将为大家介绍 ArkUI 开发框架中 canvas 组件的使用。 一、canvas 介绍 1.1 什么是 canvas&#xff1f; …

rocketmq的存储和检索

messageId是rocketmq自动生成的。

Java的response返回Json格式

问题 今天开发过程中&#xff0c;写了个拦截器&#xff0c;对于所以请求进行一个token的工作&#xff0c;对于不合标准的token返回错误&#xff0c;在网上找了个拦截器进行二次开发。 package com.maizhiyu.yzt.handle;import org.springframework.beans.factory.annotation.…

AWS Lambda配置CloudWatch日志

Hello example&#xff1a;AWS Lambda 第一个例子Hello (JAVA)-CSDN博客 创建lambda函数&#xff0c;测试&#xff0c;然后点击CloudWatch日志 CloudWatch日志组不存在 创建CloudWatch日志组 1) CloudWatch -> 日志组 -> 创建日志组 2) 填写名称&#xff0c;创建 添加权…