Maven实战(四)- 生命周期和插件

news2024/11/15 8:50:14

Maven实战(四)- 生命周期和插件

1.何为生命周期

在Maven出现之前,项目构建的生命周期存在着各式各样的方式,不同公司、不同项目往往采用自己独特的构建方式。手工执行编译、测试和部署的方法被广泛使用,同时也有一些项目编写了自动化脚本来执行这些任务。尽管手工方式和自动化脚本都在不断地进行构建工作,但由于缺乏统一的构建工具,每个项目的方式都略有差异。

这样的情况带来了一些挑战和问题。新加入项目的开发人员需要耗费时间来学习并适应项目的构建流程和脚本。随着时间的推移,构建脚本和流程可能变得复杂且难以维护。当不同项目需要进行集成时,由于构建方式的不一致,可能需要解决一些兼容性和配置问题。

幸运的是,Maven的出现解决了这些问题,它提供了一种标准化的项目构建和管理方式。Maven的生命周期就是为了对所有的构建过程进行抽象和统一。Maven从大量项目和构建工具中学习和反思,然后总结了一套高度完善的、易扩展的生命周期。这个生命周期包含了项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等几乎所有构建步骤。也就是说,几乎所有项目的构建,都能映射到这样一个生命周期上。

Maven的生命周期是抽象的,这意味着生命周期本身不做任何实际的工作,在Maven的设计中,实际的任务(如编译源代码)都交由插件来完成。这种思想与设计模式中的模板方法非常相似。模板方法模式在父类中定义算法的整体结构,子类可以通过实现或者重写父类的方法来控制实际的行为,这样既保证了算法有足够的可扩展性,又能够严格控制算法的整体结构。

2.生命周期

Maven的生命周期指的是Maven构建过程中的一系列阶段和插件目标的顺序执行流程。它定义了一组固定的阶段,每个阶段对应一个或多个插件目标。

Maven的生命周期包括三个固定的阶段:clean、default和site。

2.1.Clean生命周期

该生命周期用于清理项目,删除先前构建生成的文件。包括以下三个阶段:

  • pre-clean: 在清理之前执行一些准备工作。
  • clean: 清理生成的文件目录,将项目恢复到初始状态。
  • post-clean: 在清理之后执行一些额外的工作。

2.1.Default生命周期

该生命周期是Maven的主要构建生命周期,用于构建和部署项目。它包括了项目的编译、测试、打包等一系列阶段,其中一些阶段是必需的,一些是可选的。默认生命周期包括以下阶段:

  • validate: 对项目进行验证,确保项目正确。
  • initialize: 执行一些初始化的任务,准备构建环境。
  • generate-sources: 根据项目的配置和注解处理器(如lombok、JPA等)生成源代码。
  • process-sources: 在这个阶段,Maven会处理源代码,如对生成的代码进行一些额外的操作或处理。其他插件可以在这个阶段执行额外的源代码处理任务,例如静态代码分析、代码生成或替换。
  • generate-resources: 生成项目中的一些资源,如配置文件、属性文件等。
  • process-resources: 处理项目的资源文件,例如复制、替换等操作。
  • compile: 编译项目的源代码。
  • process-classes: 对编译生成的字节码进行一些额外的处理。这可能包括字节码增强、字节码转换、资源文件的处理等。
  • generate-test-sources:
  • process-test-sources:
  • generate-test-resources:
  • process-test-resources:
  • test-compile:
  • process-test-classes:
  • test: 运行项目的单元测试。
  • package: 将编译后的代码打包成可分发的格式,如jar或war。
  • pre-intergration-test: 集成测试前。
  • intergration-test: 执行项目的集成测试。集成测试是指将各个模块或组件整合在一起进行测试,以验证它们之间的协作和交互是否正常。
  • post-intergration-test: 集成测试后。
  • verify: 对集成测试的结果进行验证,以保证质量。
  • install: 将包安装到本地仓库,供其他项目使用。
  • deploy: 将最终的包复制到远程仓库,供其他开发人员或项目使用。

2.3.Site生命周期

该生命周期用于生成项目的站点文档和报告。包括以下阶段:

  • site: 生成项目文档的站点。
  • post-site: 在站点生成之后执行一些额外的工作。
  • site-deploy: 将生成的站点发布到远程服务器。

每个生命周期阶段都与插件目标关联,这些目标在执行阶段时被调用。当执行某个阶段时,Maven会依次调用与该阶段关联的插件目标。 通过定义和配置插件目标和生命周期,开发人员可以利用Maven的生命周期来管理项目的构建过程,自动执行各种构建任务,提高开发效率和项目质量。

3.Maven插件

3.1.插件目标

在进一步讨论插件和生命周期的绑定关系之前,有必要先了解插件目标(Plugin Goal)的概念。Maven的核心仅定义了抽象的生命周期,具体的任务是由插件完成的,并且插件以独立的构件形式存在。因此,Maven核心的分发包大小仅为不到3MB。在需要时,Maven会下载并使用插件。

插件本身往往能够完成多个任务,以便复用代码。例如,maven-dependency-plugin可以基于项目的依赖关系执行多个任务,如分析项目依赖并帮助找出潜在的无用依赖、列出项目的依赖树以帮助分析依赖来源、列出项目所有已解析的依赖等等。如果为每个功能编写独立的插件将是不可取的,因为这些任务背后有很多可复用的代码。因此,这些功能被集成在一个插件中,每个功能对应一个插件目标。

3.2.插件绑定

3.2.1.内置插件

为了能让用户几乎不用任何配置就能构建Maven项目,Maven在核心为一些主要的生命周期阶段绑定了很多插件的目标,当用户通过命令行调用生命周期阶段的时候,对应的插件目标就会执行相应的任务。
clean生命周期仅有pre-clean、clean和post-clean三个阶段,其中的clean与maven-clean-plugin:clean绑定。maven-clean-plugin仅有clean这一个目标,其作用就是删除项目的输出目录。

生命周期阶段插件目标
pre-clean
cleanmaven-clean-plugin:clean
post-clean

site生命周期有pre-site、site、post-site 和site-deploy四个阶段,其中,site和maven-site- plugin:site相互绑定,site-deploy和maven-site-plugin:depoy相互绑定。maven-site-plugin有很多目标,其中,site目标用来生成项目站点,deploy目标用来将项目站点部署到远程服务器上。

生命周期阶段插件目标
pre-site
sitemaven-site-plugin:site
post-site
site-deploymaven-site-plugin:deploy

相对于clean和site生命周期来说,default生命周期与插件目标的绑定关系就显得复杂一些。这是因为对于任何项目来说,例如jar项目和war项目,它们的项目清理和站点生成任务是一样的,不过构建过程会有区别。例如jar项目需要打成JAR包,而war项目需要打成WAR包。

生命周期阶段插件目标
process-resourcesmaven-resources-plugin:resources
compilemaven-compile-plugin:compile
process-test-resourcesmaven-resources-plugin:testResources
test-compilemaven-compile-plugin:testCompile
testmaven-surefire-plugin:test
packagemaven-jar-plugin:jar
installmaven-install-plugin:install
deploymaven-deploy-plugin:deploy
3.2.2.自定义插件

除了内置绑定以外,用户还能够自己选择将某个插件目标绑定到生命周期的某个阶段上,这种自定义绑定方式能让Maven项目在构建过程中执行更多更富特色的任务。

一个常见的例子是创建项目的源码jar包,内置的插件绑定关系中并没有涉及这一任务,因此需要用户自行配置。maven-source-plugin可以帮助我们完成该任务,它的jar-no-fork目标能够将项目的主代码打包成jar文件,可以将其绑定到default生命周期的verify阶段上,在执行完集成测试后和安装构件之前创建源码jar包。

<build>
	<plugins>
		<plugin>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-source-plugin</artifactId>
			<version>3.2.1</version>
			<executions>
				<execution>
					<id>attach-sources</id>
					<phase>verify</phase>
					<goals>
						<goal>jar-no-fork</goal>
					</goals>
				</execution>
			</executions>
		</plugin>
	</plugins>
</build>

在POM的build元素下的plugins子元素中声明插件的使用,该别中用到的是maven-source-plugin。其groupId为org.apache.maven.plugins,这也是Maven官方插件的groupId,紧
接着artifactId为maven-source-plugin,version为2.1.1。对于自定义绑定的插件,用户总是
应该声明一个非快照版本,这样可以避免由于插件版本变化造成的构建不稳定性。

上述配置中,除了基本的插件坐标声明外,还有插件执行配置,executions下每个execution子元素可以用来配置执行一个任务。该例中配置了一个id为attach-sources的任务,通过phrase配置,将其绑定到verify生命周期阶段上,再通过godals配置指定要执行的插件目标。至此,自定义插件绑定完成。运行mvn verify就能看到如下输出。

在这里插入图片描述

可以看到,当执行verify生命周期阶段的时候,maven-source-plugin: jar-no-fork得以执行,它会创建一个以-sources.jar结尾的源码文件包。

有时候,即使不通过phase元素配置生命周期阶段,插件目标也能够绑定到生命周期中去。例如,可以尝试删除上述配置中的phase一行,再次执行mvn verify,仍然可以看到maven-source-plugin:jar-no-fork得以执行。出现这种现象的历原因是,有很多插件的目标在编写时已经定义了默认绑定阶段。可以使用maven-help-plugin查看插件详细信息,了解插件目标的默认绑定阶段。运行命令如下:

mvn help:describe -Dplugin=org.apache.maven.plugins:maven-source-plugin:3.2.1 -Ddetail

该命令输出对应插件的详细信息。在输出信息中,能够看到关于目标jar-no-fork的如下信息。

在这里插入图片描述

Bound to phase: package表示默认的绑定生命周期阶段为package。

4.插件配置

在完成插件和生命周期的绑定之后,用户还可以配置插件目标的参数,进一步调整插件目标执行的任务,以满足项目的需求。几乎所有的Maven插件目标都具有一些可配置的参数,用户可以通过命令行或者POM配置等方式来进行参数配置。

4.1.命令行配置

许多插件目标的参数都支持从命令行进行配置,用户可以在Maven命令中使用-D参数,并携带参数键=参数值的形式来配置插件目标的参数。 例如,maven-surefire-plugin提供了一个名为maven.test.skip的参数,当其值为true时,就会跳过执行测试。

mvn install -Dmaven.test.skip=true

参数-D是Java自带的,其作用是通过命令行设置一个Java系统属性。Maven简单地重用了该参数,在准备插件时检查系统属性的值,从而实现了插件参数的配置。

4.2.POM中全局配置

并不是所有的插件参数都适合从命令行配置,有些参数的值从项目创建到项目发布都不会改变,或者说很少改变,对于这种情况,在POM文件中一次性配置就显然比重复在命令行输入要方便。

用户可以在声明插件的时候,对此插件进行一个全局的配置。也就是说,所有该基于该插件目标的任务,都会使用这些配置。例如,我们通常会需要配置maven-compiler-plugin告诉它编译Java8版本的源文件,生成与JVM8兼容的字节码文件。

<build>
	<plugins>
		<plugin>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-compiler-plugin</artifactId>
			<version>3.2.1</version>
			<configuration>
        <source>8</source>
        <target>8</target>
      </configuration>
		</plugin>
	</plugins>
</build>

这样,不管绑定到compile阶段的maven-compiler-plugin:compile任务,还是绑定到test-compiler阶段的maven-compiler-plugin:testCompiler任务,就都能够使用该配置,基于Java8版本进行编译。

4.3.POM中任务配置

除了为插件配置全局的参数,用户还可以为某个插件任务配置特定的参数。以 maven-antrumn-plugin 为例,它有一个目标 run,可以用来在 Maven 中调用 Ant 任务。用户将 maven-antrun-plugin:run 绑定到多个生命周期阶段上,再加以不同的配置,就可以让 Maven 在不同的生命阶段执行不同的任务。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-antrun-plugin</artifactId>
    <version>1.8</version>
    <executions>
        <execution>
            <id>ant-validate</id>
            <phase>validate</phase>
            <goals>
                <goal>run</goal>
            </goals>
            <configuration>
                <tasks>
                    <echo>i am bound to validate phase</echo>
                </tasks>
            </configuration>
        </execution>
        <execution>
            <id>ant-verify</id>
            <phase>verify</phase>
            <goals>
                <goal>run</goal>
            </goals>
            <configuration>
                <tasks>
                    <echo>i am bound to verify phase</echo>
                </tasks>
            </configuration>
        </execution>
    </executions>
</plugin>

在上述代码片段中,首先,maven-antrun-plugin:run 与 validate 阶段绑定,从而构成一个 id 为 ant-validate 的任务。插件全局配置中的 configuration 元素位于 plugin 元素下面,而这里的 confguration 元素则位于 execution 元素下,表示这是特定任务的配置,而非插件整体的配置。这个 ant-validate 任务配置了一个echo Ant 任务,向命令行输出一段文字,表示该任务是绑定到 validate 阶段的。第二个任务的 id 为 ant-verify,它绑定到了 verify 阶段,同样它也输出一段文字到命令行,告诉该任务绑定到了verify 阶段。

5.插件解析机制

为了方便用户使用和配置插件,Maven不需要用户提供完整的插件坐标信息,就可以解析得到正确的插件,Maven的这一特性是一把双刃剑,虽然它简化了插件的使用和配置,可一旦插件的行为出现异常,用户就很难快速定位到出问题的插件构件。例如mvn help:system这样一条命令,它到底执行了什么插件?该插件的groupld、artifactId和version分别是什么?这个构件是从哪里来的?

5.1.插件仓库

与依赖构件一样,插件构件同样基于坐标存储在Maven仓库中。在需要的时候,Maven会从本地仓库寻找插件,如果不存在,则从远程仓库查找。找到到插件之后,再下载到本地仓库使用。

不同于repositories及其repository子元素,插件的远程仓库使用pluginRepositories和pluginRepository配置。例如,Maven内置了如下的插件远程仓库配置。

<pluginRepositories>
  <pluginRepository>
    <id>central</id>
    <name>Central Repository</name>
    <url>https://repo.maven.apache.org/maven2</url>
    <layout>default</layout>
    <snapshots>
      <enabled>false</enabled>
    </snapshots>
    <releases>
      <updatePolicy>never</updatePolicy>
    </releases>
  </pluginRepository>
</pluginRepositories>

除了pluginRepositories和pluginRepository标签不同之外,其余子元素表达的含义与之前《Maven实战(三)- Maven仓库》中讲解的完全一致。

5.2.插件默认的groupId

在POM中配置插件的时候,如果该插件是Maven的官方插件(即如果其groupld为org.apache.maven.plugins),就可以省略groupId配置。

5.3.解析插件版本

同样是为了简化插件的配置和使用,在用户没有提供插件版本的情况下,Maven会自动解析插件版本。解析过程与《Maven实战(三)- Maven仓库》中依赖解析基本一致。

5.4.解析插件前缀

插件前缀与groupld:artifactId是一一对应的,这种匹配关系存诸在仓库元数据中。与之前提到的groupId/artifactld/maven-metadata.xml不同,这里的仓库元数据为groupld/maven-metadata.xml

基本上所有主要的Maven插件都来自Apache和Codehaus。

Maven在解析插件仓库元数据的时候,会默认使用org.apache.maven.plugins和org.codehaus.mojo两个groupId。也可以通过配置 settings.xml 让Maven检查其他groupld上的插件仓库元数据。

<settings>
  <pluginGroups>
    <plugin>com.xxx.plugins</plugin>
  </pluginGroups>
</settings>

基于该配置,Maven就不仅仅会检查org/apache/maven/plugins/maven-metadata.xml
org/codehaus/mojo/maven-metadata.xml,还会检查 com/your/plugkins/maven-metadata. xml

通过这个链接https://repo1.maven.org/maven2/org/apache/maven/plugins/可以查看插件仓库元数据的内容。

<metadata>
	<plugins>
    <plugin>
			<name>Apache Maven Clean Plugin</name>
			<prefix>clean</prefix>
			<artifactId>maven-clean-plugin</artifactId>
		</plugin>
    <plugin>
			<name>Apache Maven Compiler Plugin</name>
			<prefix>compiler</prefix>
			<artifactId>maven-compiler-plugin</artifactId>
		</plugin>
  </plugins>
</metadata>

上述内容是从中央仓库的org.apache.maven.plugins groupld下插件仓库元数据中截取的一些片段,从这段数据中就能看到maven-clean-plugin的前缀为clean,maven-compiler-plugin的前缀为compiler。




>注:内容源于《Maven实战》(许晓斌著)

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

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

相关文章

算法学习day28

一、寻找右区间(二分法) 题意&#xff1a;题目很容易理解 但是转换为二分法有点晦涩 给你一个区间数组 intervals &#xff0c;其中 intervals[i] [starti, endi] &#xff0c;且每个 starti 都 不同 。区间 i 的 右侧区间 可以记作区间 j &#xff0c;并满足 startj > e…

gptpdf深度解析:开源文档处理技术全攻略

目录 一、引言二、gptpdf 是什么&#xff1f;三、gptpdf 的功能特性1. 精准的 PDF 元素解析能力2. 对复杂文档结构的处理示例3. 高效的处理速度4. 低成本的优势 四、gptpdf 应用场景1. 学术研究与文献处理2. 企业文档管理3. 软件开发中的文档转换 五、gptpdf 代码示例1. 基本的…

【Vue3】组件通信之v-model

【Vue3】组件通信之v-model 背景简介开发环境开发步骤及源码总结 背景 随着年龄的增长&#xff0c;很多曾经烂熟于心的技术原理已被岁月摩擦得愈发模糊起来&#xff0c;技术出身的人总是很难放下一些执念&#xff0c;遂将这些知识整理成文&#xff0c;以纪念曾经努力学习奋斗的…

图像梯度与几种算子

“滤波器”也可以称为“卷积核”&#xff0c;“掩膜”&#xff0c;“算子”等。 1、Sobel算子 Sobel算子是一个33的卷积核&#xff0c;利用局部差分寻找边缘&#xff0c;计算得到梯度的近似值。x和y方向的Sobel算子分别为&#xff1a; 梯度有方向&#xff0c;对于一个图像&a…

电子元器件—三极管(一篇文章搞懂电路中的三极管)(笔记)(面试考试必备知识点)

三极管的定义及工作原理 1. 定义 三极管&#xff08;Transistor&#xff09;是一种具有三层半导体材料&#xff08;P-N-P 或 N-P-N&#xff09;构成的半导体器件&#xff0c;用于信号放大、开关控制和信号调制等应用。三极管有三个引脚&#xff1a;发射极&#xff08;Emitter…

SpringBoot智慧旅游在线平台的设计与实现(源码+论文+部署讲解等)

博主介绍&#xff1a;✌全网粉丝10W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围&#xff1a;SpringBoot、Vue、SSM、HLM…

从地铁客流讲开来:地铁客运量特征

1.数据来源 数据来源&#xff1a;MetroWatch地铁观察 | 地铁客流量数据 在做城市地铁客流数据的整理及可视化这块其实国内已经有很多大牛一直在做无偿免费的更新&#xff0c;其中覆盖多城市且每日更新数据的主要有两个&#xff1a;一个是地铁数据库 | 地铁客流量查询 (metrod…

数字农业农村云平台整体规划建设方案PPT

数字农业农村云平台的规划建设方案是一个全面而复杂的项目&#xff0c;涉及到多个方面的整合与创新。根据搜索结果&#xff0c;以下是一些关键点&#xff1a; 资料下载方式&#xff0c;请看每张图片右下角信息 1. 组织领导与政策支持&#xff1a;加强组织领导&#xff0c;确保…

如何在联络中心使用人工智能驱动的语音分析?

人工智能驱动的语音分析是一种使用自然语言处理和机器学习技术的语音识别软件。借助呼叫中心的语音分析&#xff0c;您可以将实时语音转换为文本。之后&#xff0c;程序会评估此文本以揭示有关客户需求、偏好和情绪的详细信息。 在联络中心&#xff0c;语音分析工具有助于&…

OpenCV函数

1&#xff0c;cv2.imread cv2.imread:这个函数可以直接用cv2.imread(filename, cv2.IMREAD_GRAYSCALE)直接将图片以黑白图像输入&#xff0c;也可以通过cv2.imread(img, 0)来将图片以黑白图像输入。其实这两者是一样的&#xff0c;如下图所示&#xff0c;可以将特定的颜色通道…

C语言程序设计之结构体篇2

程序设计之结构体2 问题2_1的代码2_1结果2_1 问题1_2代码1_2结果1_2 问题1_3代码1_3结果1_3 问题1_4代码1_4结果1_4 问题2_1的 函数 f u n fun fun 的功能是&#xff1a; 对 N N N 名学生的学习成绩&#xff0c;按从高到低的顺序找出前 m m m &#xff08; m < 10 m<1…

『 C++11 』模板可变参数包,Lambda表达式与 function 包装器

文章目录 模板可变参数模板可变参数包的展开可变参数包与STL容器中的emplace函数关系 Lambda 表达式function 包装器function 包装器对成员函数的包装bind 绑定 模板可变参数模板 可变参数模板是C11引入的一个特性,允许模板接收任意数量的参数; 该特性增加了C的泛型编程能力; 可…

搭建jenkins一键部署java项目

一、搭建jenkins 链接: https://pan.baidu.com/s/1jzx15PiyI8EhLd_vg7q8bw 提取码: ydhl 复制这段内容后打开百度网盘手机App&#xff0c;操作更方便哦 直接使用docker导入镜像&#xff0c;运行就好 docker run -di --name jenkins -p 8080:8080 -v /home/jenkins_home:/var/je…

黑神话:悟空

《黑神话&#xff1a;悟空》是由游戏科学公司制作的以中国神话为背景的动作角色扮演游戏&#xff0c;将于2024年8月20日发售 [9] [14]&#xff0c;简体中文PC标准版售价268人民币,数字豪华版售价328人民币。 [27] [34] 游戏中&#xff0c;玩家将扮演一位“天命人”&#xff0c…

洗袜子的小洗衣机哪款好?小户型洗衣机推荐!懒人洗袜子神器分享

市面上的那些迷你的小型洗衣机可以洗袜子&#xff0c;洗涤空间够一次性洗5-6双左右的袜子&#xff01;这种不仅不会因为清洗的衣物数量少而浪费水浪费电&#xff0c;同时使用也很便利&#xff0c;小小个的放在家的任意角落就可以进行清洗&#xff0c;不仅是清洗袜子这些&#x…

jquery.ajax + antd.Upload.customRequest文件上传进度

前情提要&#xff1a;大文件分片上传&#xff0c;需要利用Upload的customRequest属性自定义上传方法。也就是无法通过给Upload的action属性赋值上传地址进行上传&#xff0c;所以Upload组件自带的上传进度条&#xff0c;也没法直接用了&#xff0c;需要在customRequest中加工一…

GraphSAGE (SAmple and aggreGatE)知识总结

1.前置知识 inductive和transductive 模型训练&#xff1a; Transductive learning在训练过程中已经用到测试集数据&#xff08;不带标签&#xff09;中的信息&#xff0c;而Inductive learning仅仅只用到训练集中数据的信息。 模型预测&#xff1a; Transductive learning只能…

6.前端怎么做一个验证码和JWT,使用mockjs模拟后端

流程图 创建一个发起请求 创建一个方法 getCaptchaImg() {this.$axios.get(/captcha).then(res > {console.log(res);this.loginForm.token res.data.data.tokenthis.captchaImg res.data.data.captchaImgconsole.log(this.captchaImg)})}, captchaImg: "", 创…

【数据结构】排序基本概念、插入排序、希尔排序(详解)

Hi~&#xff01;这里是奋斗的明志&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f331;&#x1f331;个人主页&#xff1a;奋斗的明志 &#x1f331;&#x1f331;所属专栏&#xff1a;数据结构、LeetCode专栏 &#x1f4da;本系…

java学习--泛型

前言 当我们将dog类放入集合List中想要遍历通过一下手段可实现遍历名字和年龄&#xff0c;但是当我们要加入一个新的Cat类时&#xff0c;他并不会报错&#xff0c;只有编译了才会报错&#xff0c;因为在这一步的时候注定了只能是Dog类&#xff0c;但这是非常不方便的 此时我们…