这是一个由 simviso 团队进行的关于Spring Framework 5.2版本内容分享的视频翻译文档,分享者是Spring Framework 5.2项目leader。方便大家在未来某个时候回顾的时候可以快速定位内容。
【国外前沿技术分享-后端-中文字幕】Spring Framework之再探Core Container 上
顺带推荐一个专业的程序员后端微信群的圈子:
好,我们开始
欢迎回来这个房间,让我们一起进入到Spring 5.2的分享环节中来
在上午进行的Spring过去的15年这个主题分享中我看到你有在
总之,很感谢再次看到你
所以,这很适合接下来要讲的内容,当然,这部分内容是独立存在的
我们将要介绍我们在Spring Framework 5中使用最新技术的状况
特别是在5之后,我经历了我们在Spring Framework 5.0 和 5.1中的主题和所做的设计决策,这与今天整个主题密切相关
标题已经给出了,即Spring Framework之再探Core Container
Spring Framework 作为Spring开源项目中的一个,你可以从github上找到
这个项目包含了整个core web stack,即web Mvc以及webflux
这也是我担任Spring Framework项目负责人平时所做的工作内容
因此,所有Spring Framework项目有关的内容都在我的职责范围内
不仅仅作为技术主管,更少它们的发布管理者,同时也是Spring Framework相关事务的主要协调纽带
ok,目前 Spring 5.2仍处于开发阶段
我接下来会讨论的东西也并未完全成型,我希望我们今天所要讨论的东西能最大程度的保留
不过,Spring Framework 5.2会在七月份推出第一个完整RC版本
so,你们之后很快就能看到了
GA版本(general availability)会在九月份推出
ok
我这里有几个主题,通过对它们进行集中的演示
我们来看看核心容器,我们将从几个不同的角度来看Spring Framework做了什么
首先,让我们看看我们用核心API做什么
针对core API的迭代是我们每年都在做的一件事情
对我们来讲,主版本在我们的版本控制方案里的意义在于,它代表了我们最低也要基于这个标准进行开发 同时需要我们去重新审视整个框架代码库和整个框架API层面
也就是说这些核心API会进行大规模的迭代,实际上,这些大变化也只会发生在 Spring 3, 4, 5 这些主版本中
Spring Framework 5中的核心API 的迭代是以Java 8为基准进行的
整个框架代码库,特别是它的所有API和SPI都在使用Java 8的特性
Java 8做了很多隐式实现的支持(我们不需要刻意去实现),甚至在Spring Framework 4中 特别是在Spring Framework 3中,我们就已经进行了这方面的支持(自动化的隐式实现)
所以你很少有注意到这个框架不是基于Java 8的 整个框架使用起来感觉就像基于Java 8一样(Spring Framework 4就是这样)
但Spring Framework 5真的是基于Java 8的,在少数情况下你会感觉到不一样 我们在内部做了大量代码优化,有趣的是许多优化都来自于社区的贡献
在Java 8代码风格和实现风格的升级上面为我们做了很多贡献
通过Java8可以解决低效问题,也就意味着这个转变过程我们将持续下去 相应的,我也在关注众多关于这方面的代码贡献
我们甚至还有一个来自俄罗斯的贡献者,在大约三个月的时间中向我们发送了50个 Pull Request
所有的这些都与我们遗漏的Java 8的代码风格升级有关
从我们自己设想的角度,我们尽自己所能做了力所能及的改变,当然,过程中我们也使用了趁手的工具
但是依然还有改进空间
很高兴社区能够接受这一点
接着,我们言归正传
在框架自身的API中,我们终于可以基于Java8来设计我们的核心方法
so, 像java.util包下的optional,stream 以及函数式接口(如:supplier,consumer,predicate)等现在可以在核心框架api中使用
你可以在你的代码中直接引用核心框架的api,亦或者你可以实现这些函数式接口
我们来讨论一些像BeanFactory, JdbcTemplate, TransactionSynchronization 这些可能会在我们代码中进行使用的API类型
所以这里进行了大量的修改,我们现在可以直接通过一些函数式接口(java.util.function)进行适配器重载方法的迭代
重载方法接受Java.time类型(例如使用Instant、Duration和Clock来替换java.util.Date或毫秒(long))
在整个代码库中,这些只是表面上的API修订,通常我们是不会单独做这些的 但是我们有尝试在整个核心框架库中找到所有可以进行改进的地方进行迭代升级
除了引用Java 8 API类型之外,还有另一个非常重要的东西 Java 8语言提供了一个新的功能,就是接口中的默认方法(default)
你们可能已经注意到了(或许还没有注意到)这些接口默认方法背后给我们带来怎样的好处
特别是基于现有接口中的许多相应方法实现都可以移除掉
你可能只是对一个特定接口的一个、或两个、或三个、或四个方法的实现感兴趣,现在无需使用适配器模式进行相关实现
你只需要直接实现接口方法就可以了,拿TransactionSynchronization来讲,我们只需要重写afterCommit()方法 同时其他方法直接忽略即可,接口中已经有默认实现了。这也是利用了Java 8 default方法的特性
这对于我们API设计者而言,这确实是我们手中的利器
如果我们想要在接口中引入新的方法,我们就可以将default作为主要实现手段
你没有必要去实现它,我们可以提供一个默认方法以便现有的实现依然可以继续使用
这里,我们只是使用了几个常用的类型(BeanFactory, JdbcTemplate, TransactionSynchronization)来对前面的内容做一个简单的表达
在我们API修订中另外一个同等重要的部分就是可空性处理(处理null的能力,Nullability)
在这之前,我们实现的方式和Java自身是一样的
在没有太多显性声明的时候,一些Java老码农可能会说在这里抛空或者不能抛空 所以相对的,方法的返回值可能为空也可能不为空
这是在Java中处理可空引用的老办法
在Spring Framework 5中展现了一个完全不同的模式
提供了对可空(@Nullable)和非空(@NonNull)的严格声明
在默认情况下可以用在所有的包、核心框架包和异常中,这样就可以在所有可以为NULL的东西上面加上@Nullable注解
@Nullable可用于构造函数参数,方法参数,特别也可以用于返回值(比如ObjectProvider中的getIfAvailable()方法)
在二次迭代时,我们可以将@Nullable应用到我们的字段中,也可以通过针对特定对象的当前状态来验证我们的假设
这是一个很有用的实践
结果证明这是一个对我们非常有用的实践,它可以确保我们在代码中不会遗漏任何潜在的可空性处理
同时可以消除效率低下的问题
同时也可以帮我们找到冗余(重复性防御)代码
在任何一个执行路径中,它会一直对那些永不为Null的对象进行检查
我们该怎么知道?
我们用的最多的工具就是InteliJ IDEA,它支持@Nullable注解,我们只需要将它配置成@Nullable InteliJ会立马知道@Nullable在我们spring中的使用规则,接着InteliJ会为我们提供一些开箱即用的检查
更重要的是,我们称之为 constant conditions &exceptions,即背后代表隐式实现了条件和异常判断
假设你代码中有@Nullable 方面的bug,那IDEA通过这些开箱即用的检查即可告诉我们问题所处位置
当它允许为Null时,你无需进行检查,而另一方面你又对Null做了检查,也就是说,它其实是必须不能为Null
因此,这主要是针对Java层面的事情,因为我们的核心框架就是用Java实现的
我们用的最多的地方就是拿它来校验我们的代码
如果你选择将Spring和Kotlin一起使用,那么效果更加显著 当然Kotlin 同时也是Spring开发支持的推荐语言
从Spring Framework 5.2开始,通过Kotlin的编译器,你将拥有一个更强大的可空性处理模型
在Kotlin类型系统中,如果你没有对基本类型和引用类型进行@Nullable声明,这意味着永远不允许为空
Kotlin编译器会对其使用断言(检查/验证),所做的验证调用和你专门在Java API中所设定的一样
相比之下,在如果在你正在调用Java API中,想要做到这种效果,就要进行特别明确声明,即它永远不会返回另一个值
通过在我们的代码库中使用那些简洁的注解,使得我们框架的API对Kotlin的支持变得比以前更加友好
那么在使用Kotlin开发过程中有一个十分常用的经验 即所有Spring Framework中你可以调用的API都具有这个明确地可空性处理规则
在某些场景中,我们甚至增加和修改了可空性规则
所以在调用Spring Framework 5早期版本中的某些API的时候,你可能会接收到一个空值
但是在Spring Framework 5现在的版本中,我们加强了对它的约束,这种情况现在就不会再发生了
所以在某些场景中,我们对可空性进行了适应性的修正
这对Java而言同样很有用
如果你们的项目代码基于Java和Spring 5,那么你们所能做的是用idea去针对你们的代码进行检查
因为如果你的代码调用的是Spring框架中的代码 那么idea就会理解Spring代码中的@Nullable注解并对其进行可空性判断
idea会对你基于Spring Framework的代码进行条件验证,同时也会指出你设定的那些无意义的条件
所以这对Java开发而言很有价值
Okay
来看几个API修订的例子,ObjectProvider类已经焕然一新 从Spring Framework 4.3到5增加了不少新方法(@Nullable以及增加函数式支持)
这是一个引用(通过它可以获取到目标Bean),即通过它可以对目标依赖进行一些间接处理
你可以通过注解注入或者通过BeanFactory.getBeanProvider()获得这个目标类型的引用
这样你手里就有了一个可以获取目标类型的ObjectProvider对象
你可以通过ObjectProvider来得到目标实例,如果不可用它将返回NULL,就像这里
根据定义在(这个)getIfAvailable()中,如果不可用则返回Null,所以它用@Nullable标注
这个例子很好的阐述了可空性注解(getIfAvailable())
对于getIfUnique()也是一样,只有在有一个唯一的目标实例 而不是多个实例的情况下,你才真正得到一个对象,否则不会
这些方法在Spring Framework4.3之前就已经存在了
在5.2中我们只是对它们添加了注解,这样可以使它的语义非常清晰和美观,由此它们可以返回NULL
同时我们趁机添加了重载方法
为了永不返回Null,我们特别声明了getIfAvailable(Supplier) 这个重载方法 传入的参数类型为java.util.function.Supplier的Java 8 Lambda表达式
这里如果目标对象不可用,那就采用一个默认值 意思就是要么从BeanFactory获得目标对象,要么通过Supplier得到默认值,也就意味着你永远不会得到Null
所以这个方法签名上面我们并没有用@Nullable,返回值也不为空
这里有一个更具函数式风格的ifAvailable(Consumer)方法 如果ObjectProvider可以获取到目标对象,则执行Cunsumer中的操作(Lambda表达式)
在Java中函数式风格真的很nice
通过ObjectProvider这几个重载方法,相信你已经很有感觉了
通过这些风格的改变,你可以告诉core container你要做什么动作(函数式动作)
![1565430004879](Spring Framework之再探Core Container.assets/1565430004879.png)
ObjectProvider也提供了几个用于检索多个匹配实例的方法
这没什么可惊讶的
为此我们集成了java.util.stream来进行流式操作
所以在ObjectProvider中提供了两个新方法 stream()和orderedStream()
从字面上就可以看出它的工作原理类似于Collection.stream()
如果你在Java8风格下对一个集合使用过流式处理
这是一个非常相似的模式,实际上它不是一个集合 它是一个ObjectProvider对象,通过它可以获取到多个目标Bean实例
它虽不是名义上的集合,但它可以像集合一样进行流交互
另外,当你在启动的时候,你会配置一个application context,你也可以在Spring Framework 5中选择这种方式进行编程
在Spring 5中,你就可以做到这种程度
当我们结合stereotype注解(常用的四个有@Component,@Controller,@Repository,@Service) 在classpath中进行组件查找时可以通过这里(指图中)来替换classpath扫描
如图所示
这是百分之百会发生的,没有什么后台工作
当你在Spring中有很明确定义的时候(通俗的讲,就是种瓜得瓜种豆得豆)
如果你不说我们需要扫描某个类,那么我们也不会
同时,作为主要入口点的generic就是用来达到这些目的的 GenericApplicationContext 是一个有着12年历史的一个类
现在,我们可以通过像 registerBean(supplier)这样的方法来获取一个目标类型 同时我们可以设定一个supplier实例作为参数传入
我们在这里使用了些Java 8风格的API
这个registerBean(Foo.class)是基于标准手段来构造一个实例,如果没有其它说明的话,那么就基于默认构造器
你可能对这个registerBean(Bar.class,...)比较感兴趣 我们注册一个Bar.class的组件,里面内联一个Lambda表达式来给我们创建一个新的Bar实例
这个内联的表达式是Java.util.function.Supplier实现,它有点像函数式风格的工厂方法
这里并没有工厂方法
有的只是这个内联的Lambda表达式,没有额外的组件,没有配置类,没有Bean方法,没有注解
甚至于不需要反射,直接通过一个Supplier实例来进行
如果这是一个Scope组件或Prototype组件,每次调用的时候都需要一个新的Bar实例 我们在这个动态指令调用中,我们不需要进行反射,直接通过调用这个Supplier实例来得到
这里有几个变体 如果你想的话,你可以传入一个ObjectProvider
我们假设有这样一个Bar,它是由一个目标类型为Foo.class的Object对象或实例来构造的 它可以通过这种编程方式来绑定(如图所示)
还有些其他的小事情
我们来看下面的变体
它里面有多个Lambda表达式 它不仅仅有Supplier实例(可以用于创建Bar实例)
这里还有一个
实际上可以有更多,我们将它们称之为 BeanDefinitionCustomizer
基于这个,你可以得到一个 BeanDefinition(即我们将要注册的元数据对象) 的回调
这些BeanDefinition同样可以通过注解来创建,我们也可以通过在XML中进行Bean定义创建
但这里我们是用了代码的形式来实现
在它注册到这个容器之前,你可以设定一些特定的标志或限定符之类的 如lazy-init或者是在BeanDefinition中添加一些限定符
这个和你添加Lazy注解或者添加Qualifier注解或者其它一些类似的注解,产生的效果是一样的
这只是给你们留个印象,并不是今天的重点
这一切同样适用于Kotlin,我们在Spring Framework 5中所做的一切都可以在Kotlin中使用