持续集成Jenkins (五)Jenkins API的使用(更新中)

news2024/11/17 13:48:06

 前言

目前,我们将要上线DevOps系统,面对未来可能需要对系统的二次开发和处于对系统的深入理解,我需要对Jenkins API有个比较熟悉的了解。在DevOps中,jenkins总是作为其中的一环集成到里面,它提供了几种语言的API,可以很方便的进行调用集成。

一.Jenkins Rest API简单介绍

在githup上,jenkins-rest项目地址:jenkins-rest ,接口定义和接口测试代码都可以详细看到。以下是使用REST API的前置条件:

  1. 需要运行着的jenkins环境

  1. 需要一个管理员权限的账号

  1. CSRF功能需要关闭掉,不然会影响接口的请求(在API使用JenkinsClient连接后,基于Jenkins API进行请求是没有问题的,也可以不关闭。我是因为需要通过访问post接口创建凭据,所以需要关闭CSRF功能,不然会403。低版本的jenkins可以在  Configure Global Security功能

  1. 重点:config.xml的创建,通过REST API创建job,则需要动态传入和修改Job对应的config.xml,目前主流未提供完整的config.xml生成工具。根据查询到的资料,大部分人都是用固定的xml字符串去修改后再拼接到一起,来构成所需要的config.xml,这并不规范,而且在多种Job类型的场景下,无法根据任务类型生成所需的config.xml。

这里提供一个博主自己在做的开源项目 xml-jenkins,博主会持续维护Jenkins常用的 config.xml。

二.关闭CSRF保护

高版本jenkins,页面因为无关闭CSRF选项了,需要在Manage Jenkins —>Script Console中手动禁用CSRF.

禁用CSRF,命令如下:

 hudson.security.csrf.GlobalCrumbIssuerConfiguration.DISABLE_CSRF_PROTECTION = true

启用CSRF,命令如下:

  hudson.security.csrf.GlobalCrumbIssuerConfiguration.DISABLE_CSRF_PROTECTION = false

三.Jenkins Rest API使用

1.引入 jenkins-rest.jar 包

引入以下依赖:

 <dependency>
            <groupId>com.cdancy</groupId>
            <artifactId>jenkins-rest</artifactId>
            <version>0.0.28</version>
        </dependency>

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>28.1-jre</version>
  </dependency>

2.创建客户端

2.1JenkinsClient

@Configuration
public class JenkinsClient {

    @Autowired
    private JenkinsProperties jenkinsProperties;

    @Bean(name = "jenkinsClient")
    @Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON)
    public JenkinsClient getJenkinsClient() {
        return JenkinsClient.builder()
                .endPoint(jenkinsProperties.getServerUri())
                .credentials(jenkinsProperties.getUsername() + ":" + jenkinsProperties.getPassword())
                .build();
    }
}

2.2JenkinsProperties

@Data 
@Component
@ConfigurationProperties("jenkins")
public class JenkinsProperties {
    private String serverUri;
    private String username;
    private String password;
}

2.3application.yaml

jenkins:
  # jenkins的访问url
  server-uri: http://127.0.0.1:8080
  username: jenkins
  password: jenkins

3.API介绍   

底层代码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.cdancy.jenkins.rest;

import com.cdancy.jenkins.rest.features.ConfigurationAsCodeApi;
import com.cdancy.jenkins.rest.features.CrumbIssuerApi;
import com.cdancy.jenkins.rest.features.JobsApi;
import com.cdancy.jenkins.rest.features.PluginManagerApi;
import com.cdancy.jenkins.rest.features.QueueApi;
import com.cdancy.jenkins.rest.features.StatisticsApi;
import com.cdancy.jenkins.rest.features.SystemApi;
import com.cdancy.jenkins.rest.features.UserApi;
import java.io.Closeable;
import org.jclouds.rest.annotations.Delegate;

public interface JenkinsApi extends Closeable {
    @Delegate
    CrumbIssuerApi crumbIssuerApi();

    @Delegate
    JobsApi jobsApi();

    @Delegate
    PluginManagerApi pluginManagerApi();

    @Delegate
    QueueApi queueApi();

    @Delegate
    StatisticsApi statisticsApi();

    @Delegate
    SystemApi systemApi();

    @Delegate
    ConfigurationAsCodeApi configurationAsCodeApi();

    @Delegate
    UserApi userApi();
}

由代码可以看出一共提供了6个API类,如下表所示:

API类

说明

pluginManagerApi

插件API:提供插件操作接口,一共就俩方法,查看和安装插件

crumbIssuerApi

系统哈希值信息(用于防御CSRF攻击)

jobsAPi

任务(项目管理),提供任务的创建、修改、查询、删除等操作,主要使用接口

queueApi

任务队列相关

statisticsApi

jenkins统计相关

systemApi

jenkins系统信息,如版本

API 二次封装:

@Component
public class JenkinsTemplate {
    
    @Autowired
    private JenkinsClient jenkinsClient;
    
    /**
     * 任务管理(任务信息、创建、修改)API
     */
    private JobsApi jobsApi;

    /**
     * 插件管理(插件信息、安装插件)API
     */
    private PluginManagerApi pluginManagerApi;
    
    /**
     * 任务队列相关(队列状态)API
     */
    private QueueApi queueApi;
    
    /**
     * Jenkins统计信息 API
     */
    private StatisticsApi statisticsApi;
    
    /**
     * 系统哈希值信息(用于防御CSRF攻击)API
     */
    private CrumbIssuerApi crumbIssuerApi;
    
    /**
     * Jenkins系统状态(版本、路径)API
     */
    private SystemApi systemApi;

    /**
     * jenkins用户信息API
     */
    private UserApi userApi;

    public JenkinsTemplate() {

    }

    /**
     * 后置处理,JenkinsTemplate比JenkinsProperties先初始化,所以不能在构造器里获取api
     */
    @PostConstruct
    public void initApi(){
        this.jobsApi = jenkinsClient.api().jobsApi();
        this.pluginManagerApi = jenkinsClient.api().pluginManagerApi();
        this.queueApi = jenkinsClient.api().queueApi();
        this.statisticsApi = jenkinsClient.api().statisticsApi();
        this.crumbIssuerApi = jenkinsClient.api().crumbIssuerApi();
        this.systemApi = jenkinsClient.api().systemApi();
    }
}

4.JobsApi-任务API

4.1任务列表查询
    /**
     * @Title: fetchJobInfo   
     * @Description:  任务列表查询   
     * @param: optionalFolderPath  文件夹名称(对应的是 folder 类型的item)
     * @return: JobInfo      
     * @throws
     */
    public JobList getJobInfo(String optionalFolderPath) {
        return jobsApi.jobList(optionalFolderPath);
    }
4.2获取任务信息
   /**
     * @Title: fetchJobInfo   
     * @Description:  获取任务信息   
     * @param: optionalFolderPath  文件夹名称(对应的是 folder 类型的item)
     * @param: jobName  任务名
     * @return: JobInfo      
     * @throws
     */
    public JobInfo getJobInfo(String optionalFolderPath, String jobName) {
        
        return jobsApi.jobInfo(optionalFolderPath, jobName);
    }
4.3 使用 XML 文件创建任务
    /**
     * @Title: create   
     * @Description: 使用 XML 文件创建任务  
     * @param: @param optionalFolderPath 文件夹名称(对应的是 folder 类型的item)
     * @param: @param jobName 任务名
     * @param: @param configXML  任务配置
     * @param: @return      
     * @return: RequestStatus      
     * @throws
     */
    public RequestStatus createJob(String optionalFolderPath, String jobName, String configXML) {
        return jobsApi.create(optionalFolderPath, jobName, configXML);
    }
    
4.4删除任务
   /**
     * @Title: deleteJob   
     * @Description: 删除任务
     * @param: @param optionalFolderPath 文件夹名称(对应的是 folder 类型的item)
     * @param: @param jobName 任务名
     * @param: @param configXML 任务配置
     * @param: @return      
     * @return: RequestStatus      
     * @throws
     */
    public RequestStatus deleteJob(String optionalFolderPath, String jobName, String configXML) {
        return jobsApi.delete(optionalFolderPath, jobName);
    }
4.5启用任务
    /**
     * @Title: deleteJob   
     * @Description: 启用任务
     * @param: @param optionalFolderPath 文件夹名称(对应的是 folder 类型的item)
     * @param: @param jobName 任务名
     * @param: @param configXML 任务配置
     * @param: @return      
     * @return: RequestStatus      
     * @throws
     */
    public boolean enableJob(String optionalFolderPath, String jobName, String configXML) {
        return jobsApi.enable(optionalFolderPath, jobName);
    }
4.6禁用任务
    /**
     * @Title: disableJob   
     * @Description: 禁用任务 
     * @param: @param optionalFolderPath 文件夹名称(对应的是 folder 类型的item)
     * @param: @param jobName 任务名
     * @param: @param configXML 任务配置
     * @param: @return      
     * @return: boolean      
     * @throws
     */
    public boolean disableJob(String optionalFolderPath, String jobName, String configXML) {
        return jobsApi.disable(optionalFolderPath, jobName);
    }
4.7获取任务配置文件内容
    /**
     * @Title: getConfig   
     * @Description: 获取任务配置文件内容
     * @param: @param optionalFolderPath 文件夹名称(对应的是 folder 类型的item)
     * @param: @param jobName 任务名
     * @param: @return      
     * @return: String      
     * @throws
     */
    public String getJobConfig(String optionalFolderPath, String jobName) {
        return jobsApi.config(optionalFolderPath, jobName);
    }
4.8更新任务配置文件内容
    /**
     * @Title: updateConfig   
     * @Description: 更新任务配置文件     
     * @param: @param optionalFolderPath 文件夹名称(对应的是 folder 类型的item)
     * @param: @param jobName 任务名
     * @param: @param configXML 任务配置
     * @param: @return      
     * @return: boolean      
     * @throws
     */
    public boolean updateJobConfig(String optionalFolderPath, String jobName, String configXML) {
        return jobsApi.config(optionalFolderPath, jobName, configXML);
    }
4.9获取任务描述内容
    /**
     * @Title: getJobDescription   
     * @Description: 获取任务描述内容  
     * @param: @param optionalFolderPath 文件夹名称(对应的是 folder 类型的item)
     * @param: @param jobName 任务名
     * @param: @return      
     * @return: String      
     * @throws
     */
    public String getJobDescription(String optionalFolderPath, String jobName) {
        return jobsApi.description(optionalFolderPath, jobName);
    }
4.10设置任务描述内容
    /**
     * @Title: setJobDescription   
     * @Description: 设置任务描述   
     * @param: @param optionalFolderPath 文件夹名称(对应的是 folder 类型的item)
     * @param: @param jobName 任务名 
     * @param: @param configXML 任务配置
     * @param: @return      
     * @return: boolean      
     * @throws
     */
    public boolean setJobDescription(String optionalFolderPath, String jobName, String configXML) {
        return jobsApi.description(optionalFolderPath, jobName, configXML);
    }
4.11构建任务
    /**
     * @Title: buildJob   
     * @Description: 构建任务   
     * @param: @param optionalFolderPath 文件夹名称(对应的是 folder 类型的item)
     * @param: @param jobName 任务名
     * @param: @return      
     * @return: IntegerResponse      
     * @throws
     */
    public IntegerResponse buildJob(String optionalFolderPath, String jobName) {
        return jobsApi.build(optionalFolderPath, jobName);
    }
4.12构建带参数任务
    /**
     * @Title: buildJobwithParams   
     * @Description: 构建带参数任务   
     * @param: @param optionalFolderPath 文件夹名称(对应的是 folder 类型的item)
     * @param: @param jobName 任务名
     * @param: @param configXML 任务配置
     * @param: @param properties
     * @param: @return      
     * @return: IntegerResponse      
     * @throws
     */
    public IntegerResponse buildJobwithParams(String optionalFolderPath, String jobName, Map<String,List<String>> properties) {
        return jobsApi.buildWithParameters(optionalFolderPath, jobName, properties);
    }
4.13获取Job上次构建序号
    /**
     * @Title: lastBuildNumber   
     * @Description: 获取Job上次构建序号  
     * @param: @param optionalFolderPath 文件夹名称(对应的是 folder 类型的item)
     * @param: @param jobName 任务名
     * @param: @return      
     * @return: Integer      
     * @throws
     */
    public Integer getLastBuildNumber(String optionalFolderPath, String jobName) {
        return jobsApi.lastBuildNumber(optionalFolderPath, jobName);
    }
4.14 获取Job上次构建时间戳
    /**
     * @Title: getLastBuildTimestamp   
     * @Description: 获取Job上次构建时间戳  
     * @param: @param optionalFolderPath 文件夹名称(对应的是 folder 类型的item)
     * @param: @param jobName 任务名
     * @param: @return      
     * @return: String      
     * @throws
     */
    public String getLastBuildTimestamp(String optionalFolderPath, String jobName) {
        return jobsApi.lastBuildTimestamp(optionalFolderPath, jobName);
    }
4.15获取构建信息
    /**
     * @Title: fetchJobInfo   
     * @Description:  获取构建信息   
     * @param: optionalFolderPath 文件夹名称(对应的是 folder 类型的item)
     * @param: jobName 任务名
     * @param: buildNumber 构建序号
     * @return: JobInfo      
     * @throws
     */
    public BuildInfo getBuildInfo(String optionalFolderPath, String jobName, int buildNumber) {
        return jobsApi.buildInfo(optionalFolderPath, jobName, buildNumber);
    }
4.16任务构建对应的控制台输出内容
    /**
     * @Title: getLogText   
     * @Description: 任务构建对应的控制台输出内容
     * @param: @param optionalFolderPath 文件夹名称(对应的是 folder 类型的item)
     * @param: @param jobName 任务名
     * @param: @param start
     * @param: @return      
     * @return: ProgressiveText      
     * @throws
     */
    public ProgressiveText getLogText(String optionalFolderPath, String jobName, int start) {
        return jobsApi.progressiveText(optionalFolderPath, jobName, start);
    }
4.17 获取指定构建控制台输出
 /**
     * @Title: getLogTextofBuildNumber   
     * @Description: 获取指定构建控制台输出    
     * @param: @param optionalFolderPath 文件夹名称(对应的是 folder 类型的item)
     * @param: @param jobName 任务名
     * @param: @param buildNumber 构建序号
     * @param: @param start
     * @param: @return      
     * @return: ProgressiveText      
     * @throws
     */
    public ProgressiveText getLogTextofBuildNumber(String optionalFolderPath, String jobName, int buildNumber, int start) {
        return jobsApi.progressiveText(optionalFolderPath, jobName, buildNumber, start);
    }
4.18 任务重命名
    /**
     * @Title: renameJob   
     * @Description: 任务重命名
     * @param: @param optionalFolderPath 文件夹名称(对应的是 folder 类型的item)
     * @param: @param currentJobName 任务当前名称)
     * @param: @param newJobName 任务新名称
     * @param: @return      
     * @return: boolean      
     * @throws
     */
    public boolean renameJob(String optionalFolderPath,String currentJobName, String newJobName) {
        return jobsApi.rename(optionalFolderPath, currentJobName, newJobName);
    }
4.19任务某次构建的步骤信息
    /**
     * @Title: workflow   
     * @Description: 任务某次构建的步骤信息
     * @param: @param optionalFolderPath 文件夹名称(对应的是 folder 类型的item)
     * @param: @param jobName (任务名)
     * @param: @param buildNumber (任务新名称)
     * @param: @return      
     * @return: Workflow       
     * @throws
     */
    public Workflow workflow(String optionalFolderPath,String JobName, int buildNumber) {
        return jobsApi.workflow(optionalFolderPath, JobName, buildNumber);
    }
4.20 获取pipelineNode信息
   /**
     * @Title: getPipelineNode   
     * @Description: 获取pipelineNode信息
     * @param: @param optionalFolderPath 文件夹名称(对应的是 folder 类型的item)
     * @param: @param jobName (任务名)
     * @param: @param buildNumber (任务新名称)
     * @param: @return      
     * @return: ProgressiveText      
     * @throws
     */
    public PipelineNode  getPipelineNode(String optionalFolderPath,String JobName, int buildNumber) {
        return jobsApi.pipelineNode(optionalFolderPath, JobName, buildNumber);
    }

5.systemApi-系统环境API

未完待续……

6.pluginManagerApi-插件API

7.statisticsApi-统计信息API

8.queueApi-任务队列API

9.crumbIssuerApi-用于CSRF

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

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

相关文章

如何在Wordpress中使用wp_nav_menu()在<li>及a标记中添加Class

我正在使用wp_nav_menu($args),我想将my_own_classCSS类名添加到<li>元素中以获得以下结果:<li classmy_own_class><a href>Link</a>怎么做&#xff1f;wp_nav_menu()在<li>标记中添加Class方法一&#xff1a;只需使用其他参数并为nav_menu_css_…

熟悉GC常用算法,熟悉常见垃圾收集器,具有实际JVM调优实战经验

程序的栈和堆 栈先进后出&#xff0c;且里面的数据自动释放&#xff0c; 堆内的空间则需要手动释放 java python go 只管创建&#xff0c;不用像c,c需要手动释放空间&#xff0c; 因为他们都会开一个进程GC&#xff08;Garbage Collector&#xff09;&#xff0c;由垃圾回收…

从早吃到晚,才是我对旅行目的地最大的尊重

点击文末“阅读原文”即可收听本期节目剪辑、音频 / 卷圈 编辑 / SandLiu 卷圈 监制 / 姝琦 文案 / 粒粒 封面 / midjourney 产品统筹 / bobo 场地支持 / 声湃轩北京录音间摊牌吧&#xff0c;承认吧&#xff0c;有些地方&#xff0c;你就是冲着吃东西才去的&#xff0c;旅…

【python】main方法教程

嗨害大家好鸭&#xff01; 我是小熊猫~ 首先 if name "main": 可以看成是python程序的入口&#xff0c; 就像java中的main&#xff08;&#xff09;方法&#xff0c; 但不完全正确。 事实上python程序是从上而下逐行运行的&#xff0c; 在.py文件中&#xff0c; 除…

CSS数据类型以及符号

css数据类型定义的是css属性中具有代表性的值&#xff0c;在规范的语法格式中&#xff0c;使用关键字外加一对 <和>表示&#xff0c;例如数值类型<number>、色值类型<color>等。 举个例子&#xff1a;background-image这个css属性语法结构如下&#xff1a; …

【汇编】二、预备知识(一只 Assember 的成长史)

嗨~你好呀&#xff01; 我是一名初二学生&#xff0c;热爱计算机&#xff0c;码龄两年。最近开始学习汇编&#xff0c;希望通过 Blog 的形式记录下自己的学习过程&#xff0c;也和更多人分享。 这篇文章主要讲述学习汇编所需的基础知识。 话不多说~我们开始吧&#xff01; 目…

电信网上用户自管理系统的设计与实现

技术&#xff1a;Java等摘要&#xff1a;当今时代随着科技的飞速发展&#xff0c;用户信息的收集处理变得非常重要&#xff0c;因此用户自管理系统模式正迅猛发展并深入到各行各业中。在这个新的时代下&#xff0c;要求程序设计员能根据不同行业、不同需求的特点&#xff0c;来…

Spring MVC 源码- RequestToViewNameTranslator 组件

RequestToViewNameTranslator 组件RequestToViewNameTranslator 组件&#xff0c;视图名称转换器&#xff0c;用于解析出请求的默认视图名。就是说当 ModelAndView 对象不为 null&#xff0c;但是它的 View 对象为 null&#xff0c;则需要通过 RequestToViewNameTranslator 组件…

Flink高手之路1一Flink的简介

文章目录一、Flink简介1. Fink的引入2.Flink简介3.支持的编程语言4.Flink的特性5.Flink四大基石6.批处理和流处理二、Flink的架构1.Flink的角色2.编程模型一、Flink简介 1. Fink的引入 大数据的计算引擎&#xff0c;发展过程有四个阶段 第一代&#xff1a;Hadoop的MapReduce…

如何确定RocketMQ中消费者的线程大小

背景 随着物联网行业的发展、智能设备数量越来越多&#xff0c;随着设备活跃量过大&#xff0c;常常存在一些高并发的请求&#xff0c;形成了流量尖峰&#xff0c;过多的请求会压垮服务器&#xff0c;影响其他服务运行。因此&#xff0c;为了保护云端服务&#xff0c;需要对请求…

KALOS.art AI 作品每周精选 006

KALOS.art —— AI 和 数字艺术作品展示及销售平台。创作者们可以在这创建自己的主页和画廊&#xff0c;收取充电打赏、以图库模式出售作品。爱好者们可以在这里探索发现&#xff0c;购买作品图片&#xff08;带商用授权&#xff09;&#xff0c;跟艺术家们开启私信通道交流。具…

在外包公司熬了 3 年终于进了字节,竭尽全力....

其实两年前校招的时候就往字节投了一次简历&#xff0c;结果很明显凉了&#xff0c;随后这个理想就被暂时放下了&#xff0c;但是这个种子一直埋在心里这两年除了工作以外&#xff0c;也会坚持写博客&#xff0c;也因此结识了很多优秀的小伙伴&#xff0c;从他们身上学到了特别…

云镜CVE-2021-44983复现

CVE-2021-44983复现漏洞信息漏洞复现读取flag&#x1f349; shell来源&#xff1a;https://yunjing.ichunqiu.com/cve/detail/967?type1&pay2漏洞信息 漏洞名称taocms 3.0.1 登陆后台后文件管理处存在任意文件下载漏洞漏洞编号CVE-2021-44983危害等级中危漏洞类型任意文…

大学毕业后,送了2个月外卖,哭了一整晚

先简单介绍一下自己&#xff0c;我来自湛江&#xff0c;大学学的的物流管理专业&#xff0c;现在就职于一家互联网公司&#xff0c;从事软件测试工作。 我来自湛江的一个偏远农村&#xff0c;家里兄弟姐妹多&#xff0c;父母无力负担我的学费&#xff0c;很多时候学费都是靠姐…

戴尔Latitude 3410电脑 Hackintosh 黑苹果efi引导文件

原文来源于黑果魏叔官网&#xff0c;转载需注明出处。硬件型号驱动情况主板戴尔Latitude 3410处理器英特尔酷睿i7-10510U已驱动内存8GB已驱动硬盘SK hynix BC511 NVMe SSD已驱动显卡Intel UHD 620Nvidia GeForce MX230(屏蔽)无法驱动声卡Realtek ALC236已驱动网卡Realtek RTL81…

哈希表以及哈希冲突

目录 哈希表 哈希冲突 1. 冲突发生 2. 比较常见的哈希函数 3. 负载因子调节(重点) 散列表的载荷因子概念 负载因子和冲突率的关系 冲突-解决-闭散列 线性探测 二次探测 冲突-解决-开散列 结尾 我们在前面讲解了TerrMap&#xff08;Set&#xff09;的底层是一个搜索…

雅思经验(十四)

剑10 test3 阅读p3这篇阅读比较难做下来&#xff0c;主要是这个题材我们不太熟悉&#xff0c;介绍了一种成为拉皮塔人&#xff0c;他们在太平洋上航行&#xff0c;很多岛屿上都有他们足迹&#xff0c;后来人们发掘、探索他们的历史的故事。1.derelict 与 abandoned 主要是前面的…

Mysql 语句优化 (Explain)

Mysql 语句优化 &#xff08;Explain&#xff09; 1. 概述 ​ 在 select 语句之前增加 explain 关键字&#xff0c; mysql 会在查询上设置一个标记&#xff0c;返回查询执行计划信息&#xff0c;而不是执行这条sql 字段formatjson时的名称含义idselect_id该语句的唯一标识sel…

图形编辑器:拖拽阻塞优化

大家好&#xff0c;我是前端西瓜哥。在图形编辑器中&#xff0c;想象这么一个场景&#xff0c;我们撤销了一些重要的操作&#xff0c;然后想选中一个图形&#xff0c;看看它的属性。你点了上去&#xff0c;然后你发现你再也无法重做了。 你以为你点了一下&#xff0c;但其实你…

Java知识复习(七)常见的设计模式(装饰、代理、观察、策略、建造)

前言 参考书籍&#xff1a;《秒懂设计模式》 1、装饰器模式&#xff08;Decorator&#xff09; 1、装饰器模式&#xff1a;对原始对象动态地进行“包装”&#xff0c;是对类实例“装饰”的结果&#xff1b;类似于继承的效果&#xff0c;但这个过程是动态的&#xff0c;是可设…