实现企业集成模式第 0 部分

工程 | Iwein Fuld | 2008 年 5 月 19 日 | ...

在我关于 Spring Integration 的演讲之后,我收到了一些关于澄清和示例的问题。为了满足需求,我将开始一个关于使用 Spring Integration 实现不同集成模式的小系列。第一篇文章将重点介绍基础知识。它将向您展示如何启动和运行,并逐步完成其中一个示例。

如果您以前从未听说过 Spring Integration,最好阅读Mark Fisher 撰写的介绍性博客或浏览项目网站来熟悉它。一般来说

让我先声明一下:

为什么我应该关心 OSGi?

工程 | Adrian Colyer | 2008 年 5 月 15 日 | ...

InfoQ 有一个讨论主题,总结了对 SpringSource Application Platform 发布的反应。Michael Burke 在该主题上提出了一个很好的问题,可以概括为“忘记围绕 OSGi 的炒作,如果我将当前打包为 EAR 的应用程序移植到 OSGi 捆绑包,我能期望看到什么好处?”

我开始在 InfoQ 主题上回答这个问题,但我的答案对于评论来说太长了,所以我将在此处解答它。

这是一个很好的问题。您将在基于 OSGi 的应用程序与传统的基于 JEE EAR 的应用程序中看到的最大区别是模块化得到了改进。因此,问题就变成了,这种改进的模块化是否能给我带来任何好处,如果能,好处是什么?这本书《设计规则:模块化的力量》对这个问题进行了非常全面的论述。这是一个很好的背景资料,但我感觉 Michael 可能正在寻找比那本书中更不那么理论化的一些东西……

使用 SpringSource Application Platform 的供应库

工程 | Andy Wilkinson | 2008 年 5 月 9 日 | ...

SpringSource Application Platform 的主要优势之一是其能够根据需要提供依赖项。这样做的好处是双重的:它确保平台的内存占用尽可能小,并允许部署应用程序而无需将所有依赖项封装在一个整体部署单元(例如 WAR 文件)中。为了利用这些功能,您需要了解平台的供应库,而本博客旨在提供这一点。

供应库在哪里以及它是如何工作的?

默认情况下,平台的供应库可以在安装根目录下的repository目录中找到:供应库的目录结构 如您所见,主要有三个目录:bundlesinstalledlibrariesinstalled 用于平台的内部使用,因此我们将重点关注bundleslibraries目录。每个目录都包含许多子目录,以区分不同类型的依赖项
  • ext 包含与平台一起提供的外部依赖项,但不是平台本身的一部分。
  • subsystems 包含构成平台的所有子系统。
  • usr 最初为空,旨在包含用户添加的依赖项,即您的应用程序依赖的任何内容,而这些内容并非平台已提供。
平台在初始启动期间搜索repository目录结构中的捆绑包和库。我将在本文后面讨论如何配置此搜索。当在存储库中找到捆绑包和库时,它们的符号名称、导出的包等的详细信息将添加到存储库的内存中索引。完成扫描后,内存中索引将缓存到磁盘。在开发过程中,最大限度地减少平台的启动时间是我们的优先事项。此缓存允许平台在启动期间节省一些时间:除非它检测到存储库的内容已更改,否则它可以跳过扫描。

运行时配置

在普通的 OSGi 环境中,捆绑包的依赖项只能由已安装在环境中的其他捆绑包满足。例如,安装并启动导入org.apache.commons.dbcp包的捆绑包将会失败,除非已安装导出该包的捆绑包。这对用户来说可能是一个真正的难题,因为他们必须手动安装捆绑包的所有依赖项。值得庆幸的是,SpringSource Application Platform 通过根据需要动态安装依赖项来显著改进这一点。

当平台启动已部署的应用程序时……

可移植性、炸鱼薯条

工程 | Rod Johnson | 2008 年 5 月 9 日 | ...

很高兴听到关于SpringSource Application Platform的很多讨论,无论是线上的还是在 JavaOne 现场的。最具见解的评论之一来自 WebSphere 事务架构师 Ian Robinson

这是否会影响 WebSphere?好吧,核心 Spring 框架没有任何变化。无论 SpringSource Application Platform 的未来如何,核心 Spring 框架项目都仍然与 WebSphere 互补。就像炸鱼薯条一样。
Ian 完全正确。SpringSource Application Platform 是 Spring 部署的另一种选择。没有任何变化……

SpringSource Application Platform 清单头

工程 | Glyn Normington | 2008 年 5 月 8 日 | ...

SpringSource Application Platform 由 OSGi 捆绑包构成,并支持同样由 OSGi 捆绑包构成的应用程序。该平台支持 OSGi 的标准功能,但也支持一些附加的清单头。一些人问过“SpringSource 为什么添加专有头?”和“新头的语义是什么?”,因此这篇文章解释了Import-LibraryImport-Bundle的背景动机和语义。

标准 OSGi 捆绑包支持

该平台基于OSGi R4.1标准,或者如果您更喜欢的话,是JSR 291,并使用Equinox作为其 OSGi 实现。结果是,您可以使用平台的工具开发标准 OSGi 捆绑包,并将这些捆绑包部署到平台上,正如许多用户自平台推出以来一直在做的那样。

因此,精通 OSGi 的开发人员可以使用该平台作为标准 OSGi 容器,并受益于平台功能,例如

  • 能够使用 Admin Console 或通过将捆绑包放入平台的pickup目录来部署捆绑包,
  • 诊断,例如解析失败诊断、特定于应用程序的跟踪和自动死锁检测,
  • 与 Spring 和 Spring Dynamic Modules 的强大集成,适用于想要使用这些框架的开发人员,以及
  • 从存储库自动配置依赖项。
但是,该平台还旨在使几乎没有或根本没有接触过 OSGi 的企业应用程序开发人员也能轻松地从 OSGi 中受益,这对该平台提出了一些额外的要求。

企业应用程序的其他要求

正如 Sam 最近关于平台部署选项的博客所解释的那样,您可以将现有的整体式 WAR 文件部署到平台上,而无需了解 OSGi——平台会为您处理所有事情。但是,为了受益于共享库、共享服务以及最终的 PAR 文件范围,必须将整体式 WAR 文件分解成 OSGi 捆绑包。这有多难?

好吧,该过程中的某些步骤相对容易,尤其是在遵循良好的软件工程实践并且代码已组织成服务、领域和基础架构组件的情况下。这些组件可以转换为捆绑包,并且它们之间的依赖关系可以使用META-INF/MANIFEST.MF中的标准 OSGi Import-Package 和 Export-Package 头来表示。

更困难的一步是表达对企业框架(如 Spring 和 Hibernate)的依赖。完全可以使用标准 OSGi Import-Package 和 Require-Bundle 头来表达这些依赖关系,如果您目标是创建将在其他 OSGi 容器中运行的 OSGi 捆绑包,这正是您应该做的,但这方法有一些隐藏的成本。

首先,开发人员必须精确地确定构成给定框架的哪些包。仅仅导入应用程序代码使用的包是不够的,因为当加载应用程序时,一些企业框架会将进一步的依赖项编织到应用程序的字节码中。开发人员必须通过反复试验来发现还需要导入哪些附加的实现包,以确保编织应用程序的正确行为。

然后是迁移到框架的下一个版本的苦差事,其中构成框架的精确包集已更改。编织所需的附加包通常不由公共契约定义,因此可能会发生更改。

此外,生成的包导入没有正确捕获设计意图,这使得将来维护或扩展应用程序更加困难。

我们真的不想给用户带来这些负担,因此我们创建了一些额外的 SpringSource Application Platform 特定的清单头,Import-LibraryImport-Bundle,作为表达对企业框架依赖的便捷方法。如下所示,这些头实际上只是语法糖,它们用标准 OSGi 包导入来表示。

导入库

基本语法与其他清单头类似
    Import-Library: <librarySymbolicName>;version=<versionRange>
其中<librarySymbolicName>是库的“符号名”,<versionRange>是使用OSGi版本范围表示法的可接受库版本的范围。库定义指定了库的符号名和版本,这两者共同唯一地标识了平台上的库。

如果您不熟悉OSGi版本范围表示法,最常用的形式是最低版本范围,例如2,表示“版本2或更高版本”,以及“半开”范围,例如[2.2.1,2.2.2),表示2.2.1(包含)到2.2.2(不包含)之间的任何版本。如果省略version=<versionRange>(当然也包括分号分隔符),则默认范围包括所有版本。

对于每个库导入,平台都会选择在平台存储库中具有给定符号名和给定版本范围内最高版本的库。然后,平台将库导入替换为一组包导入,这些包导入与库的bundle导出的所有包匹配。平台检测到bundle导入两个或多个导出公共包的库的情况,会发出相应的日志消息,并阻止安装导入bundle。

例如,以下标题导入2.5.4(包含)到2.5.5(不包含)之间某个版本的Spring框架库

    Import-Library: org.springframework.spring;version="[2.5.4,2.5.5)"

可选库导入

您可以使用以下语法指示库导入是可选的。请注意特殊的分割符:=,它表示修改清单头语义的“指令”,而不是表示“匹配属性”(如version)的分割符=
    Import-Library: <librarySymbolicName>;version=<versionRange>;resolution:=optional

如果未指定resolution,或将其指定为mandatory,则如果不存在具有给定符号名和给定范围内的版本的库,则包含导入库头的bundle将无法安装。但是,如果指定了resolution:=optional,则如果没有合适的库可用,则将忽略库导入。

例如,以下标题从2.5版开始导入某个版本的Spring框架库,但如果没有合适的库可用,则会被忽略。

    Import-Library: org.springframework.spring;version="2.5";resolution:=optional

导入多个库

如果需要导入多个库,则在一个Import-Library清单头中指定以逗号分隔的库导入列表,如下例所示。
    Import-Library: org.foo.p;version="[1,2)",org.bar.q;version="[2,3)"

导入Bundle

Import-Bundle是针对库仅包含单个bundle且创建库定义不方便的情况的另一种便捷方法。其语法与Import-Library非常相似,只是它引用的是bundle的符号名和版本,而不是库的符号名和版本。

正如您所料,对于每个导入的bundle,平台都会选择在平台存储库中具有给定符号名和给定版本范围内最高版本的bundle。然后,平台将bundle导入替换为一组与bundle导出的包匹配的包导入。

例如,以下标题导入Hibernate 对象关系映射器 bundle。

    Import-Bundle: com.springsource.org.hibernate;version="[3.2.6,3.2.7)"

为什么不重载Require-Bundle

如果您熟悉OSGi,您可能会问自己,为什么我们没有重载Require-Bundle,而是引入了Import-Bundle

嗯,我们希望Require-Bundle保留其标准语义,包括将拆分包的各个部分组合在一起的能力。但是我们希望Import-LibraryImport-Bundle具有与Import-Package相同的底层语义,以避免拆分包的复杂性。

我们还预计,随着平台的不断发展,我们将需要向Import-LibraryImport-Bundle添加更多指令,这些指令不适合添加到Require-Bundle中。

接下来是什么?

平台测试版计划正在进行中,我们将倾听所有关于平台功能(包括新的清单头)的反馈。

对于希望利用平台头但需要生成可在其他OSGi容器上运行的bundle的用户,我们计划开发一个工具来替换Import-LibraryImport-Bundle……

SpringSource 应用平台部署选项

工程 | Sam Brannen | 2008年5月6日 | ...

自从我们上周三发布SpringSource应用平台以来,许多开发人员已经下载了1.0.0测试版,并开始试用该平台。因此,人们开始问:“如何在平台上部署我的应用程序,以及我有哪些部署和打包选项?”此外,开发人员热切地要求查看有效的示例。作为回应,S2AP团队将在未来几周内发布几个示例应用程序来演示这些功能以及更多功能,但在您动手操作这些示例之前,我想先向您介绍一个高级……

使用SpringSource应用平台在OSGi上运行Spring应用程序

工程 | Rob Harrop | 2008年5月2日 | ...

许多人都一直在问,SpringSource应用平台究竟为Spring应用程序做了什么才能使其在OSGi下良好运行,这超出了您使用OSGi和Spring动态模块开箱即用所能获得的功能。Adrian昨天的文章重点介绍了一些普遍存在的问题,现在让我们来看一些细节。

在OSGi上运行Spring应用程序最具挑战性的三个方面是

  • 加载时编织
  • 类路径扫描
  • 线程上下文类加载器管理

其余不太重要的问题包括:JSP支持、TLD扫描、注释匹配和资源查找。总的来说,为了使应用程序顺利部署,需要解决相当多数量的问题。

加载时编织

加载时编织是最难以可靠支持的功能之一。在基本层面上,它需要连接到Equinox ClassLoader,以便可以在defineClass调用期间附加和使用标准ClassFileTransformers。最重要的是,许多LTW的使用都需要访问一个临时的ClassLoader,该加载器可用于检查类型以决定在编织过程中需要发生什么,而不会影响实际的ClassLoader

这个基本级别的支持实际上相当容易实现。困难在于,当编织由一个bundle中的类驱动时,需要编织另一个bundle中的类。这在企业应用程序中很常见,其中一个bundle包含域实体,另一个bundle包含使用JPA EntityManager的类型。平台通过确保应用程序中的所有bundle都可以使用适当的ClassFileTransformers进行编织来处理这种复杂性。

当您开始将编织传播到其他bundle时,您确实需要知道何时停止。如果您只是将编织应用于所有bundle,那么应用程序将相互干扰。平台通过显式地限定编织范围以使其仅应用于应用程序中的模块来防止这种情况发生。

LTW的另一个问题是它使刷新变得复杂。当刷新bundle时,OSGi将刷新所有依赖它的bundle。这意味着,在我上面给出的示例中,刷新域bundle将导致EntityManager bundle被刷新。但是,刷新EntityManager **不会**刷新域bundle,这意味着编织可能不同步。平台通过将刷新传播到受编织影响的其他bundle来处理此问题。

类路径扫描

对于类路径扫描,主要问题是Equinox不公开标准的jar:file:资源。平台在中间放置一个适配器,以便库可以看到它们期望的资源协议。这有一个很好的副作用,那就是使许多第三方库都能工作——它不仅仅是针对类路径扫描的修复。

线程上下文类加载器管理

许多第三方库使用线程上下文ClassLoader来访问应用程序类型和资源。OSGi中的每个bundle都有自己的ClassLoader,因此,任何时候都只能将一个bundle公开为线程上下文ClassLoader。这意味着如果第三方库需要查看分布在多个bundle中的类型,它将无法按预期工作。

平台通过创建一个导入应用程序中每个模块的所有导出包的ClassLoader来解决此问题。然后将此ClassLoader公开为线程上下文ClassLoader,使第三方库能够查看应用程序中的所有导出类型。

这只是平台解决问题的几个例子,但希望它能让你了解平台对Spring框架用户的意义。

补充说明:Spring、OSGi和SpringSource应用平台

工程 | Adrian Colyer | 2008年5月1日 | ...

**5月2日更新,增加了案例研究:- 请参阅本文底部的详细信息**我相信大多数阅读此博客的人都会看到昨天发布的SpringSource应用平台的公告。如果没有,请务必查看Rob的博客文章,其中描述了一些动机、编程模型和路线图。

现在我想在此文中直接解答几个常见问题。之后,我将介绍另外两个令人兴奋的公告,它们补充了SpringSource应用平台本身,但昨天并没有成为头条新闻:…

介绍SpringSource应用平台

工程 | Rob Harrop | 2008年4月30日 | ...

经过数月的紧张编码,我很高兴地宣布SpringSource应用平台1.0的测试版发布。

在2007年初,我们开始讨论替代与企业Java成为同义词的单片式和重量级应用服务器的可能性。客户正在寻找一个轻量级、模块化且灵活的平台,以满足他们的开发和部署需求。

Spring和Tomcat的组合证明了开发人员和运营商可以在生产中成功使用轻量级平台。尽管这种组合取得了成功,但缺乏模块化和对非Web应用程序的显式支持限制了其适用性和灵活性。

我们着手构建SpringSource应用平台来满足这些要求并消除这些限制。

平台的核心是动态模块内核 (DMK)。DMK 是一个基于 OSGi 的内核,它充分利用了 OSGi 平台的模块化和版本控制功能。DMK 基于 Equinox 并扩展了其在供应和库管理方面的能力,同时也为平台提供了核心功能。

SpringSource Application Platform Architecture

为了保持最小的运行时占用空间,OSGi 捆绑包由 DMK 供应子系统按需安装。这允许将应用程序安装到正在运行的平台中,并从外部存储库满足其依赖关系。这不仅消除了手动安装所有应用程序依赖项的需要(这将非常繁琐),而且还将内存使用量保持在最低限度。

DMK 本身只需要最少的捆绑包即可运行,并使用配置文件来精确控制加载的附加模块集。例如,DMK 不需要 Tomcat,但默认的平台配置文件包含 Tomcat 以允许部署 Web 应用程序。如果您想在没有 Tomcat 的情况下运行平台,只需编辑配置文件即可,它不会被安装。(如果您尝试这样做——请记住,删除 Web 支持意味着 Web 模块将不再部署,因此请删除 pickup 目录的内容,以便平台启动时不会尝试安装 Admin 和启动画面应用程序。)安装了管理控制台的默认平台配置仅占用 15MB 的内存。

我一直对企业级 Java 感到沮丧的一点是,应用程序经常被塞进人为编造的孤岛中,并且缺乏对不同应用程序类型的明确支持。考虑一个在线商店的应用程序。此应用程序具有 Web 前端、消息驱动的订单处理模块、批处理驱动的库存重新排序模块和 B2B Web 服务模块。如今,许多这样的应用程序将被打包为 WAR 或 EAR,并且模块看起来非常相似,对模块类型差异的支持很少。有趣的是,许多人会将此称为 Web 应用程序,而不是具有 Web 模块的应用程序。

在 SpringSource Application Platform 中,应用程序是模块化的,每个模块都有一个个性来描述它是什么类型的模块:Web、批处理、Web 服务等。平台以特定于个性的方式部署每个个性的模块。例如,Web 模块在 Tomcat 中使用 Web 上下文进行配置。应用程序中的每个模块都可以独立于其他模块进行更新,同时保留作为更大应用程序一部分的身份。无论您构建的是哪种类型的应用程序,编程模型仍然是标准的 Spring 和 Spring DM。

在 1.0 平台版本中,我们支持Webbundle个性,这使您可以构建复杂的 Web 应用程序。以后的版本将支持更多个性,详情将在后面介绍。

构建应用程序

平台支持三种形式的应用程序包

  1. Java EE WAR
  2. 原始 OSGi 捆绑包
  3. 平台存档 (PAR)

平台直接支持标准 WAR 文件。在部署时,WAR 文件将转换为 OSGi 捆绑包并安装到 Tomcat 中。所有标准 WAR 契约都将得到遵守,并且您现有的 WAR 文件应该可以直接部署而无需更改。

任何符合 OSGi 标准的捆绑包都可以直接部署到平台中,并且可以充分利用 Import-PackageRequire-Bundle 所引用的任何依赖项的即时供应。

PAR 格式是为平台打包和部署应用程序的推荐方法。PAR 只是一个标准 JAR 文件中分组在一起的 OSGi 捆绑包 (模块) 集合,以及一个唯一标识应用程序的名称和版本。PAR 文件作为单个单元部署到平台中。平台将提取 PAR 中的所有模块并安装它们。第三方依赖项将根据需要即时安装。

与直接将捆绑包部署到平台相比,PAR 格式具有三个主要优点。首先,它更容易。一个中等规模的企业应用程序可能包含 12 个以上的捆绑包——手动部署这些捆绑包将过于繁琐。其次,PAR 文件在应用程序中的所有捆绑包周围形成了一个明确的范围,这可以防止使用重叠类型或服务的应用程序相互冲突。此范围还被一些高级功能(如加载时编织)使用,以确保一个应用程序的编织不会干扰另一个应用程序的编织。最后,PAR 形成一个逻辑分组,以定义哪些模块是应用程序的一部分,以及应用程序有哪些第三方依赖项。管理工具使用此分组来提供应用程序的详细视图。典型的 PAR 应用程序如下所示

PAR File Structure

应用程序中模块之间的依赖关系通常使用 Import-PackageExport-Package 来表示。对第三方库的依赖可以使用相同的方式来表示,但是对于许多库来说,这可能会容易出错且费时。当使用像 Hibernate 这样的库时,您通常需要导入比您最初预期的更多包。为了解决这个问题,您可以使用 Require-Bundle,但这有一些语义上的粗糙边缘,例如拆分包,其中逻辑包跨越两个或多个类加载器,从而导致运行时问题。平台引入了两种新的机制来引用第三方依赖项:Import-BundleImport-LibaryImport-BundleRequire-Bundle 类似,但它可以防止拆分包以及 Require-Bundle 的其他问题。Import-Library 提供了一种机制来引用由一组捆绑包导出的所有包,例如 Spring Framework 中的所有捆绑包,在一个声明中。

Bundle-SymbolicName: com.myapp.dao.jdbc
Bundle-Version: 1.0.0
Import-Bundle: org.apache.commons.dbcp;version="1.2.2.osgi"
Import-Library: org.springframework.spring;version="2.5.4.A"

这里我有一个模块捆绑包,它依赖于 Commons DBCP 捆绑包和 Spring Framework 库。Spring Framework 库包含在您的应用程序中使用 Spring 所需的所有捆绑包。

Import-LibraryImport-Bundle 在底层扩展到 Import-Package,因此与标准 OSGi 语义一致。

平台理解模块的个性,并由此可以推断如何配置模块的执行环境。在部署 Web 模块时,为典型的 Spring MVC 应用程序所需的所有 servlet 基础结构都将自动创建,从而无需跨应用程序重新创建此样板代码。在 1.0 正式版中,将向 Web 模块个性添加更多智能,以支持 Spring Web Flow 等其他技术。

无论您选择哪种打包格式,编程模型都只是 Spring Framework 和 Spring Dynamic Modules,其他 Spring 产品组合产品在其之上运行。

可服务性

可服务性是整个工程团队的一个关键考虑因素。我们花费了大量时间来关注日志消息的格式和堆栈跟踪的大小,以确保诊断应用程序问题尽可能容易。每当我们发现一项重复且耗时的任务时,我们都会寻找一种方法来自动化它或将其完全删除。

为了帮助诊断问题,平台在日志消息和跟踪消息之间进行了严格的区分。日志消息旨在供最终用户使用,并允许您访问最重要的故障信息,而无需浏览数 GB 的跟踪内容。所有应用程序故障都将显示在日志输出中并进行编码——这些代码可以用作访问知识库或支持内容的便捷方式。为了理解这为什么如此有用,请考虑以下来自平台启动的输出

[2008-04-29 12:12:01.124] main                     <SPKB0001I> Platform starting.
[2008-04-29 12:12:04.037] main                     <SPKE0000I> Boot subsystems installed.
[2008-04-29 12:12:06.013] main                     <SPKE0001I> Base subsystems installed.
[2008-04-29 12:12:07.396] platform-dm-1            <SPPM0000I> Installing profile 'web'.
[2008-04-29 12:12:07.674] platform-dm…

Web 应用程序和 OSGi

工程 | Costin Leau | 2008年4月29日 | ...

自从 Spring Dynamic Modules 的第一个里程碑以来,关于在 OSGi 中运行 Web 应用程序的请求就开始涌现。这可能是最受请求的功能之一,难怪 1.0 正式版发布后,Web 支持成为 1.1 分支的主要焦点。我很高兴地报告,正如刚刚发布的 M2 那样,正如Juergen暗示的,Spring-DM 不仅支持普通 WAR(自 1.1.0 M1 起可用),还支持在 OSGi 内部运行的 Spring-MVC 应用程序。在本篇文章中,我想简要讨论典型的 OSGi Web 场景和 Spring-DM 的方法。但首先,

为什么要在 OSGi 中部署 WAR?

简单的问题:OSGi *原生*地提供了版本控制、包连接和热重载。想象一下在您的应用程序中利用这些功能:您可以停止在WEB-INF/lib下嵌入库,并开始在 Web 应用程序之间共享它们,避免标签库重复(同时保持多个版本运行)并在运行时仅更新应用程序的某些部分。这尤其有用,因为 Web 应用程序往往是分层的,因此在其生命周期中会发生大量变化。

为什么 OSGi 中的 Web 应用程序存在问题?

Servlet 规范围绕*Web 容器*的概念展开:Web 组件的运行时环境,它提供标准服务,例如生命周期管理(对象创建和处置、线程分配)、并发、HTTP 请求处理等等。另一方面,OSGi 平台也充当受管环境,具有其服务注册表、包连接和版本控制(仅举几例)。为了解决这个问题,OSGi 委员会设计了纲要规范的一部分,即Http 服务

获取 Spring 简报

通过 Spring 简报保持联系

订阅

领先一步

VMware 提供培训和认证,以加快您的进度。

了解更多

获取支持

Tanzu Spring在一个简单的订阅中提供对 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部