当前位置: 首页 > Linux

重温SpringFramework的CoreContainer(中)

时间:2023-04-06 23:10:44 Linux

特别说明这是simviso团队分享的关于SpringFramework5.2版本内容分享的翻译文档,分享者为SpringFramework5.2项目负责人。第一集视频:https://www.bilibili.com/vide...第二集视频:https://www.bilibili.com/vide...视频翻译文字版权归属tosimviso,未经授权,请勿转载:参与人员名单:顺便推荐一个专业程序员后台微信群:1.GenericApplicationContext服务于多种用途,特别是在GenericApplicationContext中,我们提供了Kotlin扩展。您不需要导入它们(Kotlin支持),没有额外的步骤,它们已经是SpringFramework核心的一部分。因此,无论您使用的是Springcontext5.0、5.1、5.2的任何版本,您都会自动获得带有Kotlin扩展的GenericApplicationContext。如果您选择使用Kotlin进行开发,它们将被Kotlin编译器检测并编译。看这个GenericApplicationContext,它的处理是不是似曾相识,不过是用Kotlin写的。正如您所看到的,图中低版本和高版本之间存在一些明显的差异。比如我们先把Bar.class放在一边,再说说这里如何创建bar实例。我们只需要有一个Supplier实例,那么,让我们看看registerBean只需要一个基于构造函数调用的供应商实例,没有别的。原因其实很简单,因为这不是Java中的Lambda表达式,而是Kotlin函数。它实际上调用了一个由Kotlin实现的重载的registerBean函数。在我们的Kotlin扩展中,Kotlin扩展功能是基于元数据模型(T::class.java)和反射模型(BeanDefinitionCustomizer)设计的。当我们询问一个Kotlin函数,你会返回什么时,它已经提前知道了,而此时Java8lambda表达式无法进行返回类型检查。基于此,我们可以很好地利用这个特性,只需要一个供应商实例(注意:不需要使用T.class进行类型限定)就可以知道获取到的组件类型。即,供应商实例创建的bean类型。我们可以通过Kotlin中的一些其他函数式API来提升开发体验。下面的版本基本上只是一些语法糖,有一点语法差异。这是应用Kotlin语言特性实现目标API的另一种方式。这不是官方的Gradle风格,只是有点DSL(领域特定语言)风格。感觉有点像Gradle构建工具,一个Kotlin风格的Gradle构建工具。所以,Generic在这里是通过不同的风格来表达的(这里指的是GenericApplicationContext),但这只是Kotlin语言的一种变体,我们在这里使用它来达到我们的目的,并没有什么特别之处。2.性能调优和GraalVMok,我们转到另一个重要的话题。对于SpringFramework5,我们一直致力于不断调优和提升其性能,当然我们也在努力提高开箱即用的性能。虽然与我们的目标有关,但努力的方向有点不同。我们试图通过避免代码库中一些性能不佳的东西来减少不必要的开销。同时,我们也尝试为您提供钩子设施,一种您可以用来调整性能的机制。如果你知道它,你可以基于它做出通用框架代码无法实现的最具体的假设。5.2中最明显的例子是注解处理。最初,在5.1版中,我们主要通过修改现有代码来工作,但在5.2版中,我们选择了完全重新实现。不幸的是,在Java中处理注释是一件相当复杂的事情。如果您以前做过类似的事情,您可能会明白我的意思,这是框架日常工作的绝大部分。在应用程序的代码中,您很少需要编写如何查找注释。也就是说,你只需要声明注解,框架会负责正确地找到它们。这个过程实际上是非常复杂和低效的。主要原因是它们是由Java实现的。为了做到最好,我们在这方面尽量避免反思和代理。然而不幸的是,注解实例是通过Java代理实现的,主要是基于JDK本身。所以我们从一开始就尽力避免反思。关于AnnotationUtils和AnnotatedElementUtils这两个API基本相同,大家在使用SpringBoot的过程中自然会用到它们(我们会用到注解,那我们就用到这些工具类)。你自己很少使用它们,但基本上它们对你来说是透明的。(也就是看不到,只需要关心用的是什么注解,不需要关心注解背后的实现)如果使用其他Spring项目如SpringIntegration,SpringBatch,那么你可以从这个透明的特性中得到很多。明显的好处。在Spring5.2中,有一个名为MergedAnnotations的API,可用于内省Spring的声明性注释层次结构。你可能会觉得Spring的注解模型很复杂。有一个元数据注释模型,您可以在其上覆盖其属性。您不需要做任何复杂的事情,您可以通过这些选项轻松获得它。因此,我们引入了一个全新的API,使所有内部检查变得非常简单和高效。我们的重点更多地放在组件中可以存在和不可以存在的注解的注册上。你可以通过编程规范告诉容器某些注解类型只能存在于特定的组件类中。也就是说,在这个特定的组件类和特定的组件包中,根本不可能使用这些注解类型。我们不需要在这些地方寻找这些注解。您根本不会在这些地方找到它们。我们很难通过程序中的代码来实现这些假设。众所周知,注解在正常情况下可以应用在任何地方。也许由于某些规定,您需要在您或您团队的代码库中遵守一些约定,即在某些地方只能使用某些注解类型。如果你告诉我们这些约定,我们就可以在运行代码时减少这些注解的性能开销。5.2我们在这方面做了很多工作,就是通过这个资料找注解的时候尽量跳过。这样就集成了一个Java标准的索引排列。我们在启动时没有索引,我们只有这些类文件。类索引在运行时不指向类文件(它指向JVM中的类字节码)。我们只能通过两种方式内省注解。如果我们想在构建过程中获取额外的信息,可以通过像Jandex这样的索引或者自定义索引排列(通过索引存储一些关键信息)来达到目的。就像您在其他基础设施中使用的索引一样。如果在启动时通过加载这样一个索引来提取信息,这个信息可能与SpringApplicationContext有关,我们可以通过这个索引立即获取到这些信息。这些在我们的Bootstrap代码中提供并且可以调整。我们也会重新评估SpringBoot中的引用安排,尤其是这个东西能不能被SpringBoot自动使用。如果在使用过程中发现有Jandex索引,SpringBoot会自动识别评估并应用。关于这块,还有很长的路要走,不过七月份我们会把我们的想法放到SpringBoot里面。最重要的是,如果你使用了这些功能(indexpermutationsupport),那么它就会成为你整个架构中的一个热点(显然会被大量使用,因为它解决了很多痛点)。同时,您可以调整这些可用功能以避免不必要的开销。我们今天的另一个话题是GraalVM,这是最近很火的一个话题。Spring框架已经支持了一段时间(就是将SpringBootApplication封装成GraalVMNativeImage运行在上面),现在这部分还在实验阶段。目前,我们在github上简单的创建了一个基于GraalVM19GA版本的wiki。通过它,人们可以有针对性地进行讨论,今年晚些时候,我们也将对此进行专门的讨论。GraalVMNativeImage到底是什么?它是一种非常特殊的部署架构。不是我们常见的JVM,两者是完全不同的。它没有任何动态类加载,也没有任何动态内省。坦率地说,它并不像您想象的那么与众不同。GraalVM也支持反射。您只需要向NativeImage工具提供配置文件,它就可以将正确的信息预构建到NativeImage中。所以,你不能在运行时做任何动态反射,但是你可以预先配置你需要使用反射的地方。在这个基于GraalVM的自定义版本的SpringFramework应用程序中,我们对原型进行了试验,并获得了比索引排列更好的结果。就拿我们之前熟悉的函数式BeanRegistration(上一个ppt中的例子)来说吧。通过内联Supplier注册bean的过程在GraalVM上很自然地工作,你不需要做任何事情。这里需要讨论的是,在GraalVM中,基于注解的组件模型需要做一些额外的工作。你需要提前告诉GraalVM中的NativeImage,你要操作的组件类型,自省。我们目前的目标基本上是为GraalVM做准备。我们避免了一些不必要的反射点并重新编写了一些代码。例如,我们可以为GraalVM自动跳过那些无用和无意义的工作,以改善开箱即用的体验。他们在5.1,尤其是5.2,其实出现过不少。随附的说明(wiki文档)也提到了如何在早期采用的GraalVM19版本中使用NativeImage工具。在我们的SpringFramework5.3的下一个迭代中,主要目标是提高开箱即用的性能体验.通过集成开箱即用的配置和构建工具,您可以轻松构建基于Spring的ApplicationGraalVMNativeImage进行部署。目前,还有很长的路要走。不用说,我们现在不知道这个工具的未来会怎样,但我们已经与Oracle团队在GraalVM上密切合作了很长一段时间。由于基于Spring的NativeImage可以部署在GraalVM上,两者结合的优势也得到了体现。我们做了很多改进以使其在GraalVM上运行。我们对GraalVM19的使用进行了反馈,未来我们会提供更多的反馈,希望这个反馈能在GraalVM20中得到体现。从上面提到的两种方式,我们可以发现,索引的排列与out-of-the-box函数目前可能是更好的选择。这是我们目前维护的维基页面。这基本上也是目前的状态,我已经在wiki上列出了我们的一些前进方向。当然,我们选择通过NativeImage部署基于Spring的应用程序的主要原因是NativeImage基于完全不同的内存消耗模型和更快的启动方式。通过使用NativeImage,我们可以获得很多好处,但也不得不做出一些取舍。你会牺牲一些性能,就是在构建的时候,你需要花费大量的时间来生成NativeImage。对我来说,这不是一个容易的决定。如果你真的需要性能调优,从我的角度来看,你可以选择我们提供的首选解决方案(即索引排列)。这里有一些建议。如果您选择优化特定应用程序或架构组织以提高性能,则必须在您的代码或组件模型结构中做出某些妥协。为了您的利益,我们在这里提供了一组透明度功能,同时使它们尽可能透明。当然,您需要找到要用于配置的配置选项。比如你用代码的形式去注册,就会显得臃肿。但是如果你能清楚地声明哪些组件你不需要,那么你就可以在启动时减少这些组件类的加载,从而提高性能。这需要您进行大量微调。相应的在Spring5.2中,我们提供了一些特定的便利。比如一些配置类,可以选择将proxyBeanMethods设置为false,避免在运行时创建CGLIB子类。如果你的配置类没有出现一个bean方法调用另一个bean方法,否则它们将被重定向到容器(将通过代理类调用)。如果你的bean是相互独立的,不互相调用,那么你可以在具体的配置类中将proxyBeanMethods设置为false,避免在运行时生成代理子类。我们已经将上述功能集成到SpringFramework5.2和SpringBoot2中。还有一点奇怪的建议,我想我更喜欢基于接口的代理。大家习惯使用代理映射到目标类,也就是说在运行时创建一个CGLIB的代理子类。但实际上基于良好的旧接口代理,在启动时创建效率更高,并且可以在GraalVM上开箱即用。Spring在基于接口的代理和基于类的代理之间总是有默认的使用选择(SpringBoot也是如此)。您可以使用任何相应的AOP实现,也可以指定要使用的代理类型。如果您的组件模型结构是使用接口代理实现的,那么这将有巨大的性能提升。这对迁移到GraalVM有特别的影响,因为在这里我们不需要担心CGLIB配置。