Spring Framework之再探Core Container(中)

特别说明

这是一个由simviso团队进行的关于Spring Framework 5.2版本内容分享的翻译文档,分享者是Spring Framework 5.2项目leader。
视频第一集:https://www.bilibili.com/vide...java

视频第二集:https://www.bilibili.com/vide...git

视频翻译文字版权归 simviso全部,未经受权,请勿转载
图片描述
参与人员名单:
图片描述
顺带推荐一个专业的程序员后端微信群的圈子:
图片描述程序员

1. GenericApplicationContext

出于多种目的,特别是在GenericApplicationContext这里,咱们专门提供了Kotlin扩展。你不须要特地引入它们(Kotlin支持),不须要额外的步骤,它们已经成为Spring Framework核心中的一部分。so,不管你使用的是 Spring context 5.0、5.一、5. 2 中任何版本,你都会自动得到带有 Kotlin 扩展的 GenericApplicationContext。若是你选择使用 Kotlin 来进行开发,那么它们就会被 Kotlin 编译器检测编译。
图片描述
来看这个GenericApplicationContext,它的处理方式是否是看起来很熟悉,但它是使用Kotlin来写的。能够看到,图中下面的和上面的版本明显有一些区别。举个例子来说,咱们撇开Bar.class,而后来讲这里该如何去建立一个bar实例。咱们只须要拥有一个Supplier实例便可,so,咱们来看registerBean仅仅须要一个基于构造器调用的supplier实例,没有其余。github

理由其实很简单,由于这并非Java里的Lambda表达式,而是一个Kotlin函数。它实际上调用了一个由Kotlin实现的registerBean重载函数。在咱们的 Kotlin 扩展中,Kotlin扩展函数是基于一个元数据模型(T::class.java)
一个反射模型(BeanDefinitionCustomizer)进行设计的。当咱们来问一个Kotlin函数,你会返回什么,它预先已经知道了,而此时Java 8 lambda表达式是没法进行返回类型检查的。基于此,咱们能够很好去使用这个特性,仅须要一个supplier实例(注:无须使用T.class进行类型限定)就能够知道所获得的组件类型。即经过supplier 实例建立的bean的类型。编程

咱们能够经过Kotlin中一些其余函数API来提升开发体验。下面的这个版本基本上只是使用了一点语法糖,有一点点的语法差别。这是另一种应用Kotlin语言特性来实现目标API的方式。这不是一个正式的Gradle风格,只是有一点DSL(领域专用语言)的风格。感受有一点像Gradle构建工具 ,Kotlin风格的Gradle构建工具。后端

so,这里经过一种不同的风格来表达Generic(这里指GenericApplicationContext),但这只是Kotlin语言的一种变体,咱们这里用它来实现咱们的目的,没有什么特别的。微信

2. 性能调优与GraalVM

图片描述
ok,让咱们开始转向另外一个重要的话题。对于Spring Framework 5,咱们一直致力于不断调整并提升它的性能架构

固然咱们也在努力改善提升开箱即用的性能。虽然和咱们的目标相关,但努力的方向有点不同。咱们试图在代码库中避免一些性能不好的东西来减小没必要要的开销。同时咱们也有尝试为你提供带hook的设施,一种你能够用来调整性能的机制。若是你知道的话,你就能够根据它作出最具体的假设,这些假设没法经过通用的框架代码来实现。
图片描述
在5.2中最明显的例子就是注解处理。最初,在5.1版本中,咱们主要经过对现有代码进行修订,但在5.2中咱们选择彻底从新实现。遗憾的是,在java中注解的处理是一个至关复杂的事情。若是你以前有作过这样的事情,你也许就知道个人意思,这也是框架平常所作的绝大多数事情。在一个应用程序的代码中,你几乎不须要去写如何查找一个注解。也就是说你只须要声明注解,框架会负责对它们进行正确的查找。这个过程其实很是复杂,效率又很低。主要缘由在于它们是由Java来实现的。咱们为了作到最好,在这个方面咱们尽可能避免反射,避免经过代理。然而不幸的是,注解实例是经过Java代理实现的,主要也是基于JDK自身。所以,咱们竭尽所能,从开始就避免使用反射。app

关于AnnotationUtils和AnnotatedElementUtils 这两个API基本上是相同的,你在使用SpringBoot的过程当中天然会使用到它们(咱们会使用注解,那就会使用到这些工具类)。你不多会亲自去使用它们,可是基本上它们对你来说是透明的。(也就是你看不到,你只须要关心使用什么注解,不须要关心注解背后的实现)若是你使用了其余的Spring项目例如Spring Integration,Spring Batch,那你就能够从这个透明特性里面获得很明显的好处。框架

在Spring 5.2中有一个名为MergedAnnotations的API,它对Spring声明过的注解层次结构的内省很是有用。你可能感受到Spring的注解模型很复杂了。这里有个元数据注解模型,你能够在此之上覆写它的属性。你不须要作很复杂的事情,你能够很轻易地经过这些选项获取到。所以咱们引入了一种全新的API,它可使全部的内部检查变得很是简单直接,而且十分高效。

咱们的精力更多放在了对于组件中可存在注解和不可存在注解的注册上面。你能够经过编程规范来告诉容器某些注解类型只能存在于特定的组件类里面。换句话讲,在这种特定的组件类和特定的组件包中,根本就不可能有这些注解类型的使用。咱们不须要在这些地方去查找这些注解。在这些地方,你根本就找不到它们的。

这些假设咱们很难在程序中本身经过代码来实现。正常状况下,注解能够被应用于任何地方,这是一个常识。可能因为一些规定,你须要在你或团队的代码库中服从一些约定,即特定的地方只能用一些特定的注解类型。若是你告诉咱们这些约定,咱们就能够在运行代码时减小这些注解产生的性能开销。对此咱们已经在5.2中作了大量的工做,也就是经过这些信息在注解查找的时候尽可能跳过它。以此来整合出一个Java 标准的索引排列。

咱们在启动的时候并无索引,咱们只有这些类文件。在运行时类索引并不指向类文件(指向的是JVM里面的class字节码)。咱们只能经过两种途径对注解进行内省。若是咱们在构建时想要获取额外信息的话,就能够经过像Jandex的索引或者是一个自定义的索引排列同样来达到目的(经过索引来 存储一些关键信息)。就好像你在其余的基础架构中所使用的索引同样。在启动时若是经过加载这样的一个索引来提取信息,这个信息多是Spring ApplicationContext相关的内容,经过这个索引咱们就能立马获取到这个信息。这些在咱们的Bootstrap代码中都有提供配置可进行调整。咱们也会在SpringBoot中会对引用排列进行从新评估,尤为是这个东西它是否是已经能够被SpringBoot自动使用。若是在使用时,发现了一个Jandex索引,SpringBoot会自动识别评估并应用它。

关于这块,接下来的路还很长,但在七月份咱们会将咱们这些想法放到SpringBoot中。最重要的是若是你使用了这些功能(索引排列支持),那么它将会是你整个架构的一个热点(很明显会大量的用到,由于解决了不少痛点)。同时,你能够对这些可用功能进行调整以免没必要要的开销。
图片描述
今天咱们另外一个主题则是GraalVM,它是最近比较火的一个话题。Spring框架对它的支持已经有一段时间了(即将Spring Boot Application封装成一个GraalVM Native Image在上面运行),如今这部分仍然处于实验阶段。目前,咱们在github上已经简单建立了一个基于GraalVM 19GA版本的wiki。经过它,人们能够进行有针对性的讨论,今年晚些时候,咱们也会对其进行专门的讨论。
图片描述
GraalVM Native Image 它究竟是什么东西?它是一个很特别的部署架构。它并非咱们常见的JVM,二者彻底不同。它没有任何动态类加载,也没有任何动态内省。很直接的讲,它并无你想的那么不同凡响。GraalVM一样支持反射。你只须要给Native Image工具提供配置文件,这样它就能预先将正确的信息内置进Native Image。因此,在运行时你不能作任何动态反射,可是你能够将你须要用到反射的地方进行提早配置。在这个基于GraalVM的定制版的Spring Framework application中咱们对prototypes 进行了实验,获得了比索引排列更好的效果。

拿咱们以前很熟悉的函数式Bean Registration(前面ppt中的例子)。经过内联的Supplier注册Bean的过程能够很天然地在GraalVM上运行,你不须要去作什么。在这里咱们须要讨论的是在GraalVM中,基于注解的组件模型须要进行一些额外的工做,你须要提早告诉GraalVM中的Native Image,你所要操做的组件类型以及内省。

咱们当下的目标基本仍是为GraalVM作准备,咱们已经避免了一些没必要要的反射点,同时也重制了一些代码,例如能够自动跳过那些没用的以及对GraalVM没有任何意义的工做,以提高开箱即用的体验。它们其实已经在5.1中出现了很多,在5.2中尤甚。

附带说明(wiki文档)里面也说起如何使用GraalVM 19早期采用版本中的Native Image工具。在咱们Spring Framework 5.3下一次迭代中,主要目标在于提高开箱即用的性能体验。经过整合写开箱即用的配置和构建工具,
你能够很轻易的构建一个用于部署的基于Spring的Application GraalVM Native Image。目前而言,任重道远。毋庸置疑,咱们如今也不清楚这个工具将来会是怎样的,但咱们已经和Oracle团队在GraalVM上紧密合做了至关长的一段时间。自从基于Spring 的 Native Image能够在GraalVM上进行部署,二者结合的优势也已经体现出来了。为了能够在GraalVM上运行,咱们已经作了至关一些改进。在对 GraalVM 19 使用时咱们给出了反馈意见,以后咱们会提供更多的反馈,以指望这些反馈会体如今GraalVM 20上。

从前面所讲的这两种方式来看,咱们能够发现具备开箱即用功能的索引排列在当下多是更优的选择。这个是咱们当前维护的wiki页面。这基本上也是目前现有的状态,我也在wiki上列出了咱们的一些前进方向。

固然,咱们选择将基于Spring的应用程序经过Native Image的方式进行部署的主要理由,就是Native Image基于一种彻底不一样的内存消耗模型以及更加快速的启动方式。经过使用Native Image,咱们能获取大量好处,同时也得进行一些取舍。你会牺牲一些性能,即在构建的的时候,你须要在生成Native Image上花不少时间。对我来讲,这不是个简单的决定。若是你真的须要性能上的调优,从个人观点而言,你能够选择咱们所提供的首选方案(即索引排列)。
图片描述
接下来有一些建议。若是你为了性能提高去选择优化特定的应用或者架构组织,你就必须在你的代码或你的组件模型结构上作出必定的妥协。在此咱们提供了一系列的透明特性来让大家得到好处,同时将它们作的尽量透明。固然你须要去找到你所要用到的配置选项进行配置。例如,若是你使用代码的形式去注册,这样会显得很臃肿。但若是你能明确声明哪些组件你不须要,那么在启动的过程当中就能减小这些组件类的加载,以此来提升性能。这须要你进行不少的微调。相应的在Spring 5.2中,咱们提供了一些特定的设施。例如一些配置类,你能够选择将proxyBeanMethods设定为false来避免在运行时建立CGLIB的子类。若是你的配置类不会出现一个Bean方法调用另外一个Bean方法,不然致使它们会被重定向到容器(会经过代理类进行调用)。若是你的Bean彼此独立,不会互相调用,那么你能够在一个特定的配置类里面设置proxyBeanMethods为false来避免在运行时生成代理子类。咱们已经将上面的功能整合进了Spring Framework5.2和Spring Boot 2中。

另外一个有点奇怪的建议就是,我想我更喜欢基于接口的代理。每一个人都习惯使用代理来映射到目标类,也就意味着在运行时会建立CGLIB的代理子类。可是实际上基于良好的旧的接口代理,在启动时建立的效率更高,而且它们能够在GraalVM上作到开箱即用。Spring老是在基于接口的代理和基于类的代理之间进行默认使用选择(对Spring Boot一样如此)。你可使用任何一种AOP的对应实现,你也能够明确的指出你想要使用的代理类型。若是你的组件模型结构是使用接口代理来实现的,那么这将会对性能产生一个极大的提高。这对转向GraalVM 有特别的意义,由于在这里咱们不须要考虑CGLIB的配置。

相关文章
相关标签/搜索