起首,我们来澄清一下 aot.factories 和 spring.factories 之间的区别。这两个文件不仅名称不同,而且在功能上也存在显着差别。接下来,我们将深入探讨这两个文件的具体作用以及它们各自的应用场景。让我们一起来揭开它们的神秘面纱吧!
在我们上一次讨论 Spring Boot 3 版本时,我们关注了它的加载机制并注意到了一些小的变化。严格来说,这些变化重要体现在文件名称的调整上:原本的 META-INF/spring.factories 文件已经迁移至新的位置,即 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports。
想要了解更多详细信息,欢迎查阅这篇文章:https://www.cnblogs.com/guoxiaoyu/p/18384642
题目来了
要深入了解 Spring Boot 的加载机制,起首必要认识到每个第三方依赖包现实上都包罗自己独特的 META-INF/spring.factories 文件。正如图中所示。
这些文件在应用程序启动时扮演着告急的角色,它们定义了主动配置的类和其他相关设置,资助 Spring Boot 在运行时主动识别和加载相应的配置。
然而,当我们试图查看某个第三方依赖包时,大概会发现找不到相应的 META-INF/spring.factories 文件,甚至没有 *.imports 文件,这时该怎么办呢?不要慌张!并不是全部的项目都具备主动配置功能。比方,ZhiPuAiAutoConfiguration 的主动配置现实上已经包罗在 Spring Boot 的核心库中。- @AutoConfiguration(after = { RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class })
- @ConditionalOnClass(ZhiPuAiApi.class)
- @EnableConfigurationProperties({ ZhiPuAiConnectionProperties.class, ZhiPuAiChatProperties.class,
- ZhiPuAiEmbeddingProperties.class, ZhiPuAiImageProperties.class })
- public class ZhiPuAiAutoConfiguration {
- }
复制代码 可以简单理解为,一旦你引用了相应的依赖包,它们的配置便会立刻生效。然而,在查找配置的 *.imports 文件时,我发现了一个有趣的现象:很多依赖包下也存在 aot.factories 文件。这是用来做什么的呢?考虑到 Spring Boot 自身也包罗此类文件,这表明这个概念并非无的放矢。
因此,带着这个疑问,我决定深入探究其背后的机制与作用。
一探究竟
经过了一番 AI 问答和上网搜刮,我大抵了解了 aot.factories 文件的用途:它现实上是为打包和编译服务的。这个文件可以资助将 Java 项目打包成可实行的 EXE 文件(在 Windows 系统下,其他操作系统则有不同的打包方式),如许就无需依赖 Java 运行情况即可直接运行。不过,这与 Spring Boot 的主动配置机制并没有直接关系。
那么,为什么会发明如许的东西呢?我知道你很着急,但是你先别着急!听我一点一点讲,你就更明确了!
Java当前痛点
有过 Java 开发经验的朋侪们应该都知道,以前的 Java 应用通常都是单体架构,这意味着启动一个项目每每必要耗费几分钟的时间,尤其是大型项目,启动时间更是让人头疼。因此,随着技术的发展,微服务架构应运而生,不仅显着缩短了启动时间,而且将业务逻辑进行了合理的切分。
然而,微服务架构也并非没有缺点。只管启动速度更快,项目在启动后每每无法立刻达到最佳的运转状态,也就是说,应用必要一段时间才能进入高效的运行峰值。
因为 Java 的底层通常使用的是 HotSpot 假造机,HotSpot 的运行机制是将常用的代码部门编译为本地(即原生)代码。这意味着在程序启动之初,HotSpot 并不知道哪些代码会成为“热点”代码,因此无法立刻将这些代码转换为呆板可以或许直接理解和实行的形式。
在这个过程中,HotSpot 会不断分析和监测代码的实行情况,以快速识别出哪些部门是频繁被调用的。只有在识别出热点代码并将其编译为本地代码之后,我们的项目才能实现最佳的吞吐量。
要想让 Java 像 Python 那样实现瞬时启动,几乎是不大概的。这一现象使得 Java 在很多情况下更适适用于企业级服务,重要原因在于其所追求的稳定性和可靠性。在企业情况中,系统的稳定性每每是主要考虑的因素。
然而,Java 也面临着一系列挑战,这些挑战在云计算时代尤为突出。
云时代
以 Serverless 为例,Serverless 是一种在云计算情况中日益成为主流的部署模式。它通过将底子设施的管理和运维任务抽象化,使开发者可以或许更加专注于业务逻辑的实现,而不必过多关注底层的资源配置和管理。
我就不提以前那种必要自己部署物理机的老年代的情况了。如今,绝大多数公司都已经采用了 Kubernetes(K8s)作为集群管理的办理方案。在各大云服务提供商处购买服务器后,企业通常会自行管理其集群服务。运维团队则负责监控和优化资源配置,及时进行扩展以满足需求。
别的,随着技术的发展,Server Mesh 和边车模式也渐渐兴起,这些都是值得深入了解的概念。归根结底,这些改进的目的就是为了显着节省公司内部的开发时间,从而让团队可以或许更专注于核心业务。
现在的 Serverless 架构显着提高了资源利用的效率,因为全部的底子设施管理工作都由云服务提供商负责。现实上,云厂商的底子设施本身并没有发生根本变化,变化的重要是架构计划,使得客户的使用体验更加便捷和高效。在这种模式下,无论是运维人员还是开发人员,都只需关注函数的部署,而无需深入了解服务器的细节信息。
开发者不再必要关心函数的运行方式、底层有多少容器或服务器在支撑这些服务。对他们而言,这一切都被抽象化为一个简单的接口,只需确保参数对接得当即可。
但是,你敢用 Java 来部署 Serverless 函数吗?当系统的吞吐量急剧上升,必要迅速启动一个新节点来支撑额外的负载时,这位 Java 大哥大概还在忙着启动或者进行预热,这可真是耽误事啊!以是Java作为牛马届的老大哥怎么大概会愿意当小弟?
GraalVM 简介
假如你还不熟悉 GraalVM,但一定听说过 OpenJDK。现实上,它们都是完整的 JDK 发行版本,可以或许运行任何面向 JVM 的语言开发的应用。不过,GraalVM 不仅限于此,它还提供了一项独特的功能——Native Image 打包技术。这项技术的强盛之处在于,它可以或许将应用程序打包成可以独立运行的二进制文件,这些文件是自包罗的,完全可以脱离 JVM 情况运行。
换句话说,GraalVM 答应你创建雷同于常见的可实行文件(如 .exe 文件)的应用程序,这使得部署和分发变得更加轻便和灵活。
如上图所示,GraalVM 编译器提供了两种模式:即时编译(JIT)和提前编译(AOT)。AOT全称为Ahead-of-Time Processing。
对于 JIT 模式,我们都知道,Java 类在编译后会生成 .class 格式的文件,这些文件是 JVM 可以识别的字节码。在 Java 应用运行的过程中,JIT 编译器会将一些热点路径上的字节码动态编译为呆板码,以实现更快的实行速度。这种方法充分利用了运行时信息,可以或许根据现实的实行情况进行优化,从而提高了性能。
而对于 AOT 模式,GraalVM 则在编译期间就将字节码转换为呆板码,完全省去了运行时对 JVM 的依赖。由于省去了 JVM 加载和字节码运行期预热的时间,AOT 编译和打包的程序具有非常高的运行时效率。这意味着在启动时,应用程序可以几乎瞬间相应,极大地提高了处置惩罚哀求的能力。
那么,这种 AOT 编译到底有多快?它是否会成为 Serverless 函数的一种常用方案,逾越 Python 等其他语言的应用呢?为了验证其性能优势,我们可以进行现实测试。
安装GraalVM
我们大家根本上都在本地安装了 IntelliJ IDEA 开发工具,使用起来非常方便。在这里,我们可以直接通过 IDEA 的内置功能下载 GraalVM,省去了在官方网站上寻找和下载的时间。只需简单几步,我们就可以快速获取到最新的 GraalVM 版本,随时预备进行开发。
下载完成后,我们只需配置项目的 JDK 为 GraalVM。由于我现在使用的是 JDK 17,因此必要选择与之兼容的 GraalVM 17 版本。这种配置过程相对简单,只需在项目设置中更改 JDK 路径即可。
我们将继续使用之前研究过的 Spring AI 项目,在此底子上,我们必要添加一些相关的 Spring Boot 插件。- <plugin>
- <groupId>org.graalvm.buildtools</groupId>
- <artifactId>native-maven-plugin</artifactId>
- </plugin>
复制代码 在我们顺遂完成全部配置后,预备进行编译时,却不测地遇到了错误提示,显示 JAVA_HOME 指向的是我们原本的 JDK 1.8。这一题目的出现重要是由于其中某个工具并不依赖于 IntelliJ IDEA 的启动变量,而是直接读取了 JAVA_HOME 的情况变量。
为了办理这个题目,我们必要确保 JAVA_HOME 情况变量精确指向我们新安装的 GraalVM 版本。因此,我们必须在本地系统中下载并安装 GraalVM,确保其版本与我们项目中所需的 JDK 版本相匹配。
起首我们找到官网:https://www.graalvm.org/downloads/
因为我是windows版本,以是自己请选择好相应的操作系统。等候下载完毕,解压完成后,将配置情况变量指向改目录后重启生效,再次编译即可。
运行后,还是报错如下:
Error: Please specify class (or /) containing the main entry point method. (see --help)
内容就是找不到启动类的意思,以是必要加一些配置。找了半天修改如下,切记前后顺序别变,否则还是会有点题目。- <build>
- <plugins>
- <plugin>
- <groupId>org.graalvm.buildtools</groupId>
- <artifactId>native-maven-plugin</artifactId>
- <configuration>
-
- <imageName>${project.artifactId}</imageName>
-
- <mainClass>com.example.demo.DemoApplication</mainClass>
- <buildArgs>
- --no-fallback
- </buildArgs>
- </configuration>
- <executions>
- <execution>
- <id>build-native</id>
- <goals>
- <goal>compile-no-fork</goal>
- </goals>
- <phase>package</phase>
- </execution>
- </executions>
- </plugin>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- <configuration>
- <excludes>
- <exclude>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- </exclude>
- </excludes>
- </configuration>
- </plugin>
- </plugins>
- </build>
复制代码 接下来,我们将继续使用常用的 Maven 命令,如 mvn clean package,来进行项目的打包。这个过程大概会显得有些漫长,尤其是与以前相比,打包速度似乎下降了不止一个级别。这次,我的打包过程持续了大约十分钟,这确实比我之前的体验慢了不少。

然后非常激动的点击了生成好的demo.exe文件,结果还是在报错:- . ____ _ __ _ _
- /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
- ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
- \\/ ___)| |_)| | | | | || (_| | ) ) ) )
- ' |____| .__|_| |_|_| |_\__, | / / / /
- =========|_|==============|___/=/_/_/_/
- :: Spring Boot :: (v3.3.1)
- Application run failed
- org.springframework.boot.AotInitializerNotFoundException: Startup with AOT mode enabled failed: AOT initializer com.example.demo.DemoApplication__ApplicationContextInitializer could not be found
- at org.springframework.boot.SpringApplication.addAotGeneratedInitializerIfNecessary(SpringApplication.java:443)
- at org.springframework.boot.SpringApplication.prepareContext(SpringApplication.java:400)
- at org.springframework.boot.SpringApplication.run(SpringApplication.java:334)
- at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363)
- at org.springframework.boot.SpringApplication.run(SpringApplication.java:1352)
- at com.example.demo.DemoApplication.main(DemoApplication.java:10)
复制代码 然后经过仔细查询,必要指定profile为native,因为之前打的包没有Aot信息。
好的,又履历了15分钟打包完毕,这次,我们终于成功了,我们可以直观的看下AOT方式打包后的启动时间与jar包方式的启动时间对比。简直是天壤之别。
哦?确实,现在的启动时间已经缩短到毫秒级别,真令人惊讶!通过 GraalVM 的 Native Image 技术,我们不仅实现了 Java 项目的快速启动,还有效去除了传统 Java 应用中的预热时间。这一切看似都非常理想,然而,题目也随之而来。
GraalVM 缺点
说完了 GraalVM 能办理 Java 原来的题目后,我们也必须认识到,它并非没有缺点。假如没有这些不足,大家对 GraalVM 的了解程度肯定会超过对 OpenJDK 的熟悉度。毕竟,既然它如此出色,为什么大多数人却没有广泛使用呢?
起首,兼容性题目是一个显着的挑战。很多老旧版本的 JDK 项目根本无法与 GraalVM 兼容,这无疑限制了大部门企业的使用范围。对于那些依赖于较旧 JDK 的企业而言,迁移到 GraalVM 大概必要耗费大量时间和资源,甚至面临重构代码的风险。
其次,即便是使用新版本 JDK 的项目,开发者们也每每对使用 GraalVM 感到犹豫。原因在于,GraalVM 对某些动态特性的支持相对较弱。比方,反射机制、资源加载、序列化以及动态代理等功能的限制,大概会对现有代码的运行产生庞大影响。这些动态行为在很多应用程序中都是核心部门,任何对它们的削弱都大概导致功能缺失或性能题目。
有人大概会产生疑问:Spring 框架本身依赖于工厂模式和各种动态代理功能,若 GraalVM 不支持这些高级特性,岂不是意味着 Spring 的运行将受到致命影响?假如动态代理无法正常使用,Spring 的很多核心功能将会受到制约,那刚才提到的打包成功又是怎么回事呢?
现实上,这一切的背后得益于 GraalVM 提供的 AOT(Ahead-of-Time)元数据文件功能。这个特性使得开发者可以或许在编译阶段明确哪些类和方法将会使用到动态代理,GraalVM 会在编译时将这些信息整合到最终的可实行文件中。
RuntimeHints与aot.factories
GraalVM 的 API —— RuntimeHints 负责在运行时收集反射、资源加载、序列化和 JDK 代理等功能的需求。这一特性为我们理解 GraalVM 如何支持动态特性提供了告急线索。现实上,大家在这里就可以猜到 aot.factories 文件的作用。没错,这个文件的存在正是为了在 GraalVM 编译时,确保可以或许加载 Spring 框架所需的 JDK 代理相关需求。我们看下文件:- org.springframework.aot.hint.RuntimeHintsRegistrar=\
- org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider.FreeMarkerTemplateAvailabilityRuntimeHints,\
- org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider.GroovyTemplateAvailabilityRuntimeHints,\
- org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration.JacksonAutoConfigurationRuntimeHints,\
- org.springframework.boot.autoconfigure.template.TemplateRuntimeHints
- org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\
- org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingProcessor
- org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
- org.springframework.boot.autoconfigure.flyway.ResourceProviderCustomizerBeanRegistrationAotProcessor
- org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter=\
- org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer
复制代码 我们可以注意到 RuntimeHintsRegistrar 的存在,它的重要作用是识别并加载全部实现了相关接口的类,从而进行解析和处置惩罚。必要夸大的是,GraalVM 默认并不会主动查找 aot.factories 文件,因为这属于 Spring 的特定机制。这就意味着,假如没有显式的指引,GraalVM 是无法主动识别和利用这些动态特性。
在 RuntimeHintsRegistrar 下面,我们还可以看到很多 AotProcessor 的实现。这个布局和我们之前讨论的 beanFactoryProcessor 有些相似,但这里我们不深入探讨具体的细节。本日我们只聚焦于表面现象,以便理解其根本功能。
Spring 现实上已经为我们办理了加载相关信息的题目,使得动态特性可以在编译时得到得当处置惩罚。然而,这并不意味着一切都已经预备就绪。第三方组件同样必要提供相应的实现,以确保与 Spring 的兼容性。假如你的依赖库使用了某些高级功能,但没有实现 Spring 的 aot.factories 扫描机制,那么这些功能在编译后将无法生效。
因此,仍然有很多工作必要进行,以确保整个生态系统的兼容性和功能性。
总结
在探索 aot.factories 和 spring.factories 的过程中,我们不仅显现了这两个文件的本质差别,还深入探讨了它们在 Spring Boot 3 中的作用及其应用场景。这一探索之旅引领我们进入了当代 Java 应用开发的前沿,尤其是在 Serverless 和微服务架构的配景下。随着云计算的发展,应用程序的性能与启动速度已成为开发者的核心关注点。在此配景下,GraalVM 的出现提供了一种新颖的办理方案,通过其 Native Image 功能,Java 应用的启动时间得以大幅度缩短,这为开发者们带来了巨大的便利。
然而,我们也意识到,虽然 GraalVM 提供了诸多优势,但它并非没有挑战。兼容性题目仍然是一个重要障碍,很多老旧 JDK 项目大概难以迁移到新平台。别的,某些动态特性在 GraalVM 中的支持仍显不足,这大概会影响到开发者在使用 Spring 框架时的灵活性与功能实现。尤其是在复杂的企业应用中,这种影响大概更加显着。
借助 Spring 框架与 GraalVM 的结合,开发者可以或许享受更快的应用启动速度和更好的资源利用率,但同时也要做好充分的预备,以应对兼容性带来的潜在题目。这意味着,随着新技术的不断涌现,我们必要不断地学习、适应和优化自己的开发流程。
我是积极的小雨,一名 Java 服务端码农,潜心研究着 AI 技术的奥秘。我热爱技术交流与分享,对开源社区充满热情。同时也是一位腾讯云创作之星、阿里云专家博主、华为云云享专家、掘金良好作者。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |