微服务从小白到专家:Spring Cloud和Kubernetes实战
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.1 Spring Boot的前尘往事

要想掌握并灵活运用Spring Boot,首先需要了解Spring Framework。

2.1.1 Spring Framework

Spring Framework的发明与Java规范的变迁有着千丝万缕的联系,在Java规范的早期版本中,Sun公司针对不同的开发场景创建了不同的规范,以此帮助Java开发者更高效、更专注地开发业务逻辑,这些规范包括J2SE、J2ME及专门为企业级应用打造的J2EE,而EJB规范又是当时J2EE规范中最核心的部分。

EJB 1.0发布于1999年,它最初由IBM提出,之后由Sun公司(Java语言创建公司,现已由Oracle收购)将其吸收进Java官方体系,最后由JCP(Java Community Process,是一个开放的国际组织,主要由Java开发者及被授权者组成)正式将其规范化。由于当时Java在业界风头正劲,各大公司都广泛地使用EJB规范来进行应用开发,在这种环境下,EJB规范几乎成了Java企业级应用开发的代名词。

但是任何技术都不是完美的,EJB自身存在各种问题,要求革新EJB技术的呼声一浪高过一浪,无论是当时的学术界还是企业界都没有符合要求的技术出现。一位名为Rod Johnson的开发者,结合自己扎实的理论知识和丰富的实践经验写出了对当时及后来的Java开发者影响深远的两本著作Expert One-on-One J2EE Design and DevelopmentJ2EE without EJB,他通过这两本书阐述了一种与当时主流EJB截然不同的开发方式,这两本书也可以视为Spring framework的起源。

从设计思想来看,Rod Johnson认为当时的EJB框架太过繁重,程序员需要花费大量的时间和精力来实现EJB规范所要求的各种类,并且还要管理他们的生命周期,这完全违背了EJB提出时的初衷——“让大部分程序员把精力都用在业务功能本身上”。为了解决这种本末倒置的问题,Rod Johnson提出了轻量级框架的概念,并将这种思想完全贯彻于Spring Framework。这也是此框架被称为Spring的原因:传统的J2EE开发让开发者走进了冬天(Winter),而春天(Spring)将会是一个全新的开始。

Spring公开可查的最初版本成型于Rod Johnson的Expert One-on-One J2EE Design and Development一书,其1.0版本于2004年正式开源(其开源许可证为Apache 2.0 license),正式开源版本的Spring Framework以其独特的开发方式和全新的设计理念震撼了整个Java开发者生态圈,它的核心概念如下:

(1)IoC(Inversion of Control,控制反转)和容器

理解IoC的概念对掌握Spring Framework而言是十分重要的,因为所有Spring对象的组装都基于此。在IoC概念中,控制是指依赖者和被依赖者的关系控制。在传统的Java应用开发中,如果A类依赖于B类,那么B类对象的生命周期都由A类对象控制,从而形成A类与B类的强耦合关系。然而,在面向对象的编程体系中,强耦合是应当极力避免的,如果可以将B对象生命周期的控制从A对象中剥离,那么强依赖关系也不再成立,整体系统也更加符合面向对象的设计原则。

IoC的核心思想是,被依赖者不再由依赖者直接创建,而是交由专门的组装者来控制,在组装者创建出被依赖的对象之后,将其注入依赖者。通过这样的设计,可以达到松散耦合和接口与实现分离的目的。如果读者熟悉经典的GoF设计模式,那么在传统的实现中,这种需求都将通过工厂模式来实现。熟悉工厂模式对理解Spring Framework和阅读其源代码都有很好的帮助。因此,笔者建议大家在学习Spring Framework之前,花一点时间了解设计模式中的工厂模式。

从实现的角度来理解IoC,它与依赖注入(DI,Dependency Injection)密不可分。假设A类依赖于B类(B类并不是一个实现类,只是一个实现声明的接口,真正的实现类并不会直接暴露给A类),而且实现类的生命周期也由容器(组装者)控制,B类的实现对象将由容器注入A类对象。则根据面向对象的设计理论,此时的A类依赖于接口B,而非依赖于接口B的实现类,从而使A类获得了不再依赖于实现细节的能力,实现了松耦合的设计目标,A类不再控制B类的生命周期,而且根据业务需求,还可以切换接口B的实现类,以此满足不同功能。

Spring Framework的作用是将普通的Java类经过一系列配置,再利用IoC容器将不同对象组装在一起,最终形成一个可以运行的系统,Spring容器的作用如图2-1所示。

图2-1 Spring容器的作用

(2)AOP(Aspect Oriented Programming,面向切面编程)框架

要深入理解AOP,需要先从面向对象编程谈起。Java是典型的面向对象编程语言,面向对象强调的是继承和封装,也是现实世界在程序的投影。如果有些代码逻辑需要在很多类(class)中反复出现而且又与主干业务逻辑并无强关联(比如应用开发中常见的日志,事务控制和审计功能等),那么应该如何设计呢?例如一台机器肯定不会有打印日志这样的功能,但从应用开发的角度来审视系统,这些功能又是不可或缺的。在AOP出现之前,通常使用经典设计模式来解决这类问题,设计师可以采用代理(Proxy)模式,Java语言也提供了动态代理的实现,尽管这些技术可以解决一部分组件间的强依赖问题,但是解决方案却不够优雅灵活,而且有很强的局限性。比如,最初的原生Java只能代理接口(interface)而无法代理类(class)。了解了面向对象编程和原生Java的短板之后,作为全新的技术,AOP又提供了哪些新思路和新方法呢?在深入细节之前,我们需要先了解AOP的几个关键点。

切面(Aspect)是一组模块化的且可以被多个类(class)复用的逻辑,当目标对象(Target Object)的一段程序在某个执行点(Join-Point)的切入点(Pointcut)判定为真时,切面逻辑(Advice)会被触发执行,并将Aspect和Target Object连接在一起,形成一个真正可执行的切面逻辑,这个过程被称为织入(Weaving)。开发者可以通过AOP的方式将业务逻辑和非业务逻辑进行分离,使二者的代码组织和职责都更集中、更清晰。

我们常用的Spring AOP和AspectJ技术都是AOP理论的一种实现,但是二者的实现目标却是各不相同。Spring AOP只针对Spring Framework需要的AOP功能提供简单有效的实现,而AspectJ是一个大而全的AOP Java实现。不过,Spring Framework也提供了对AspectJ的支持,因此在使用Spring时也可以同时使用AspectJ的功能。此外二者的织入(Weaving)技术也是完全不同的,AspectJ采用了编译(compile)期织入和类装载期(classload)织入,而Spring AOP的织入技术则根据运行期的不同状态可以在Java的动态代理技术或CGLIB代理技术之间切换,其具体实现方式如图2-2所示。

图2-2 Spring AOP的实现方式

基于IoC(DI)和AOP两大强力支柱,Spring Framework提供给Java开发者一种全新的(就当时而言)开发体验。与当时的其他框架都不同,Spring Framework并不只是一种通用框架,更多时候Spring Framework在充当一种类似于胶水的角色,将不同的组件整合在一起最终形成完备的系统。所以,它不仅为开发者提供各种便利性,而且具备将Java生态中的主流开源框架(如Hibernate、iBatis等)和Java语言规范(如JDBC、JMX、JMS等)融合的能力。此外,Spring Framework提倡无侵入式编程,既可以让开发者享受使用框架的好处,又省却了与框架代码过度耦合的烦恼。典型的Spring应用有两部分组成:一部分是与系统功能强相关的业务逻辑,另一部分是与业务无关的框架代码,但此类框架代码大部分已经被Spring简化,开发者只需利用IoC和AOP技术,通过简单的配置(前期以XML为主,在JDK 1.5之后以注释(Annotation)为主)将二者融合在一起形成功能完整的Java应用。Spring Framework的架构如图2-3所示。

图2-3 Spring Framework架构图

Spring Framework的主要组件及其功能列举如下:

(1)Core Container

• Core模块主要提供了最基础的IoC和DI功能。

• Bean模块主要实现了BeanFactory,在Spring语境下所有的Java类都会被注册为Spring容器里的一个Bean,各种Bean的生命周期也都由BeanFactory来控制。所有bean的实例的创建、依赖的识别和Auto-wire都是由Bean模块实现的。

• Context模块基于Core和Bean之上,任何对象在Spring容器内都是Spring Bean,而任意Spring Bean都是定义在context之内的,Context类似于调用Bean和被调用Bean之间的媒介层。

• 在Spring容器的Bean定义中可以采用各种表达式查询和操作容器中的对象,这就是SpEL(Spring Expression Language)提供的主要功能。

(2)Data Access/Integration

• JDBC模块主要功能是封装和简化了JDBC相关的操作,提供了类似template的设计模式的实现。

• ORM模块为各种主流的ORM框架提供了集成方案。例如JPA、Hibernate、iBatis的集成等。

• OXM模块提供了Object和XML之间的映射、转化和数据访问等功能,其主要实现了JAXB、Castor、XStream等的集成。

• JMS模块主要提供了Java体系下主流消息中间件的集成,例如ActiveMQ和RabbitMQ的集成。

• 事务(transaction)模块支持Spring容器内的声明式事务管理和编程式事务管理,主要通过AOP方式使普通的Bean具备了事务能力。

(3)Web

• Web模块提供了最基础的HTTP规范的Spring封装,例如上传下载、通过Servlet Listner实现的Spring容器初始化和WebApplicationContext实例的初始化。

• Web-Servlet封装了Java Servlet规范,同时提供了Spring MVC的实现,Web-Portlet和Servlet从底层来看都是对MVC的实现,二者最大的不同之处是额外提供了Portlet环境下的系统支持,Web-Struts的主要功能是提供了Spring和经典的MVC框架Struts的集成。

• 随着业界发展和互联网时代的来临,Servlet逐渐淡出了大部分的业务开发,Spring MVC和Spring Boot等框架封装了Servlet的底层实现细节,开发者只需要几行代码就可以实现一个简单的RESTful风格的接口。相比早期的Servlet技术而言,Spring MVC和Spring Boot技术大幅提高了构建Web应用的开发效率。尽管如此,也不要忘记Spring MVC的底层实现依赖于Servlet规范,Spring隐藏了很多的实现细节,如果读者希望更好地掌握Spring Web模块,就需要对Servlet相关的规范和实现做一定的了解。

注意:图2-3是Spring官方给出的架构图,但其形成时期是Spring 3.*时代,故而此图与最新的Spring Web有较大差异,例如新的Spring Web版本增添了Reactive、WebSocket等新特性。由于本书并非专注于Spring Web,所以不在此一一详述,请读者自行探索。

(4)其他

• Spring AOP提供AOP的支持。

• Aspects集成了AspectJ。

• Instrumentation模块提供了类植入(Instrumentation)支持和类加载器的实现,可以应用在特定的应用服务器中。该spring-instrument-tomcat模块包含了支持Tomcat的植入代理。

• test模块支持使用JUnit或TestNG对Spring组件进行单元测试和集成测试。它提供了Spring ApplicationContexts的一致加载和上下文的缓存。它还提供可用于独立测试代码的模仿(mock)对象。

在Spring Framework问世之后,因其风格优雅、简洁易用,以及对RESTful等功能的良好支持,迅速地成为Java开发的事实标准,而EJB却被开发者遗忘了。各大Web容器厂商也在重新评估,在Web容器中是否还需要继续支持EJB。总而言之,Spring让广大Java应用开发者从复杂的框架代码中解脱出来,更加专注于应用的业务逻辑,这一点广阔而深远地影响了整个Java生态圈。

虽然Spring公司(由Rod Johnson创建最初名为Interface 21,后改名为SpringSource)经过一系列资本运作已于2009年被VMware收购,而Rod Johnson本人也于2012年正式离开了VMware,但是Rod Johnson对整个Java生态圈的卓越贡献将被永远铭记。

2.1.2 Spring Boot

Spring Framework在成为Java生态的“事实标准”之后,虽然很长时间内热度不减,但是后来没有再推出过任何激动人心的新功能,反而是开发者在处理日益增长的业务需求和管理Spring的配置、依赖和应用部署等方面不断面临新的挑战。特别是RESTful、微服务等概念的流行,以及互联网快速迭代的开发模式的兴起,使开发者们愈发希望可以拥有更敏捷、更高效的开发框架,同时开发者对服务器(或容器)的要求也更加倾向于轻量级。

在这种需求的驱动之下,开发者Mike Youngstrom于2012年在Spring官方的GitHub上提出了一个ID为SPR-9888的需求,他的需求代表了当时Spring Framework使用者的心声,他的需求如下:“如果开发者完全遵循Spring规范构建程序,则这样的程序与原生的Servlet规范差别是非常大的,同时这也让此类程序对Servlet容器的要求大大降低。因此,如果Spring Framework能够提供一个无需直接与Servlet容器交互的框架,那么将会大大地简化开发者的工作”。

在此需求被提交给Spring官方一年之后,Spring Framework的开发者Phil Webb于2013年8月在GitHub上,代表官方正式对该需求做出了回复“我们将会创建一个名为Spring Boot的新项目来解决这些问题”,同时还给出了一个介绍Spring Boot项目的博客,这是Spring Boot第一次出现在大众视野中。

通过前面的简短介绍,我们了解了Spring Boot的来历,现在对我们来说,更加重要的任务是学习Spring Boot的工作原理和它解决问题的能力。

在探讨更多细节之前,我们需要明确Spring Boot并非要取代Spring Framework,它与传统的Spring Framework分别是事物的一体两面。Spring Boot的真正目标是帮助开发者减少传统Spring应用所需的配置文件和复杂的依赖关系,进而加快应用迭代的速度。

回顾第1章开发Spring应用的方法,开发者不仅要正确地引用Spring Framework的模块及版本,还要集成第三方依赖的兼容性,否则会导致应用无法启动,或者发生严重的生产事故。除了依赖问题,传统Spring应用的另外一个烦恼是——大量烦琐的配置文件,虽然从JDK 1.5之后注解(Annotation)式编程被广泛采用,但是在实际项目中,没有配置文件的Spring项目是非常罕见的。因而在Spring Framework发展了十年以后,框架本身的复杂度加上各种业务逻辑交杂在一起,开发者不得不花费大量精力和时间去管理这些配置文件,想要对应用进行一次版本升级更是难上加难,需要大量的回归测试和兼容性测试。在这样的情形下,如果开发团队中程序员对Spring Framework的掌握程度参差不齐,那么维持一个简洁优雅的项目会变得非常困难,并且对新手而言,Spring Framework的学习过程也是相当曲折的。

基于此,Spring Boot为了帮助开发者更加快速地开发各式应用,其设计思想是尽量使用最佳实践和默认配置来自动化装配Spring应用中的各类Bean,从而避免大量的重复性代码和配置文件。更通俗地讲,有很多业界专家知道如何能尽量发挥Spring Framework的优点,同时规避其短处,这些专家的经验被称为最佳实践。对新手而言,能够直接利用这些最佳实践不仅可以避免犯下各种低级错误,还可以节省开发成本。

因此,Spring官方开发者直接将各种最佳实践全部打包进Spring Boot,它以一种自动配置的形式注入应用中,并针对各种依赖的版本管理,设计了全新的Spring Boot Starter框架,各个厂商可以基于Spring Boot Starter规范开发自己的Starter组件,将新的功能模块集成到Spring Boot。此外,Spring Boot还提供了内置的轻量级应用服务器,通过以上创新设计,基于Spring应用的绝大部分基础问题都已经被Spring Boot解决了。

这些全新的特性,让Spring Boot一经推出就立即风靡整个Spring生态圈,也让沉寂许久的Spring开发生态再次喧嚣起来。各种主流开源软件也开始提供自己的Starter,方便开发者适配Spring Boot,更多架构师在设计系统之初就将Spring Boot作为首选,同时还开始将旧项目逐步迁移到Spring boot上。一时间,Spring又回到了舞台的中央。