在Java EE领域中,Spring无疑是非常主流的开发框架。Spring是一款集成的开源框架,它整合了很多第三方组件和框架。这些组件和框架应用如此之广泛,以致于大家往往对如何更好地使用Spring自身的功能特性并不是很重视。事实上,Spring自身的功能特性同样非常丰富,而且在使用上也存在一些最佳实践。
本章作为全书的开篇,将对Spring中的基本概念和技术体系做简要介绍,并引出开发人员在使用该框架时所涉及的核心技术组件。本章的最后也会给出全书的组织架构。
本节讨论Spring容器,并给出容器所具备的非常重要的两个功能特性,即依赖注入和面向切面编程。
在介绍Spring容器之前,我们先来介绍一个概念,即控制反转(Inversion of Control,IoC)。试想,如果想有效管理一个对象,就需要知道创建、使用以及销毁这个对象的方法。这个过程显然是繁杂而重复的。而通过控制反转,就可以把这部分工作交给一个容器,由容器负责控制对象的生命周期和对象之间的关联关系。这样,与一个对象控制其他对象的处理方式相比,现在所有对象都被容器控制,控制的方向做了一次反转,这就是“控制反转”这一名称的由来。而Spring扮演的角色就是这里的容器。
可以看到控制反转的重点是在系统运行中,按照某个对象的需要,动态提供它所依赖的其他对象,而这一点可以通过依赖注入(Dependency Injection,DI)实现。Spring会在适当的时候创建一个Bean,然后像使用注射器一样把它注入目标对象中,这样就完成了对各个对象之间关系的控制。
可以说,依赖注入是开发人员使用Spring框架的基本手段,我们可以通过依赖注入获取所需的各种Bean。Spring为开发人员提供了3种不同的依赖注入方式,分别是字段注入、构造器注入和Setter方法注入。
现在,假设我们有如下所示的HealthRecordService接口以及它的实现类:
public interface HealthRecordService { public void recordUserHealthData(); } public class HealthRecordServiceImpl implements HealthRecordService { @Override public void recordUserHealthData () { System.out.println("HealthRecordService has been called."); } }
下面我们来讨论具体如何在Spring中完成对HealthRecordServiceImpl实现类的注入,并分析各种注入类型的优缺点。
首先,我们来看看字段注入,即在一个类中通过字段的方式注入某个对象,如下所示:
public class ClientService { @Autowired private HealthRecordService healthRecordService; public void recordUserHealthData() { healthRecordService.recordUserHealthData(); } }
可以看到,通过@Autowired注解,字段注入的实现方式非常简单而直接,代码的可读性也很高。事实上,字段注入是3种依赖注入方式中最常用、最容易使用的一种。但是,它也是3种注入方式中最应该避免使用的一种。如果使用过IDEA,你可能遇到过这个提示—Field injection is not recommended,告诉你不建议使用字段注入。字段注入的最大问题是对象在外部是不可见的。正如在上面的ClientService类中,我们定义了一个私有变量HealthRecordService来注入该接口的实例。显然,这个实例只能在ClientService类中被访问,脱离了容器环境就无法访问这个实例。
基于以上分析,Spring官方推荐的注入方式实际上是构造器注入。这种注入方式也很简单,就是通过类的构造函数来完成对象的注入,如下所示:
public class ClientService { private HealthRecordService healthRecordService; @Autowired public ClientService(HealthRecordService healthRecordService) { this.healthRecordService = healthRecordService; } public void recordUserHealthData() { healthRecordService.recordUserHealthData(); } }
可以看到构造器注入能解决对象外部可见性的问题,因为HealthRecordService是通过ClientService构造函数进行注入的,所以势必可以脱离ClientService而独立存在。构造器注入的显著问题就是当构造函数中存在较多依赖对象时,大量的构造器参数会让代码显得比较冗长。这时就可以使用Setter方法注入。我们同样先来看一下Setter方法注入的实现代码,如下所示:
public class ClientService { private HealthRecordService healthRecordService; @Autowired public void setHealthRecordService(HealthRecordService healthRecordService) { this.healthRecordService = healthRecordService; } public void recordUserHealthData() { healthRecordService.recordUserHealthData(); } }
Setter方法注入和构造器注入看上去有些类似,但Setter方法比构造函数更具可读性,因为我们可以把多个依赖对象分别通过Setter方法逐一进行注入。而且,Setter方法注入对于非强制依赖注入很有用,我们可以有选择地注入一部分想要注入的依赖对象。换句话说,可以实现按需注入,帮助开发人员只在需要时注入依赖关系。
作为总结,我们用一句话来概括Spring中所提供的3种依赖注入方式:构造器注入适用于强制对象注入;Setter方法注入适用于可选对象注入;而字段注入是应该避免的,因为对象无法脱离容器而独立运行。
所谓Bean的作用域,描述了Bean在Spring容器上下文中的生命周期和可见性。在这里,我们将讨论Spring框架中不同类型的Bean的作用域以及使用上的指导规则。
如果想要通过注解来设置Bean的作用域,可以使用如下所示的代码:
@Configuration public class AppConfig { @Bean @Scope("singleton") public HealthRecordService createHealthRecordService() { return new HealthRecordServiceImpl(); } }
可以看到这里使用了一个@Scope注解来指定Bean的作用域为单例的“singleton”。在Spring中,除了单例作用域之外,还有一个“prototype”,即原型作用域,也可以称为多例作用域来与单例作用域进行区别。在使用方式上,我们同样可以使用如下所示的枚举值来对它们进行设置:
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON) @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
在Spring IoC容器中,Bean的默认作用域是单例作用域,也就是说不管对Bean的引用有多少个,容器只会创建一个实例。而原型作用域则不同,每次请求Bean时,Spring IoC容器都会创建一个新的对象实例。
从两种作用域的效果而言,我们总结一条开发上的结论,即对于无状态的Bean,我们应该使用单例作用域,反之则应该使用原型作用域。
那么,什么样的Bean属于有状态的呢?结合Web应用程序,我们可以明确,对每次HTTP请求而言,都应该创建一个Bean来代表这一次的请求对象。同样,对会话而言,我们也需要针对每个会话创建一个会话状态对象。这些都是常见的有状态的Bean。为了更好地管理这些Bean的生命周期,Spring还专门针对Web开发场景提供了对应的“request”和“session”作用域。
在本小节中,我们将讨论Spring容器的另一项核心功能,即面向切面编程(Aspect Oriented Programming,AOP)。我们将介绍AOP的概念以及实现这些概念的方法。
所谓切面,本质上解决的是关注点分离的问题。在面向对象编程的世界中,我们把一个应用程序按照职责和定位拆分成多个对象,这些对象构成了不同的层次。而AOP可以说是面向对象编程的一种补充,目标是将一个应用程序抽象成各个切面。
举个例子,假设一个Web应用中存在ServiceA、ServiceB和ServiceC这3个服务,而每个服务都需要考虑安全校验、日志记录、事务处理等非功能性需求。这时,就可以引入AOP的思想把这些非功能性需求从业务需求中拆分出来,构成独立的关注点,如图1-1所示。
图1-1 AOP的思想示意
从图1-1可以很形象地看出,所谓切面相当于应用对象间的横切面,我们可以将其抽象为单独的模块进行开发和维护。
为了理解AOP的具体实现过程,我们需要引入一组特定的术语,具体如下。
● 连接点(Join Point):连接点表示应用执行过程中能够插入切面的一个点。这种连接点可以是方法调用、异常处理、类初始化或对象实例化。在Spring框架中,连接点只支持方法的调用。
● 通知(Advice):通知描述了切面何时执行以及如何执行对应的业务逻辑。通知有很多种类型,在Spring中提供了一组注解用来表示通知,包括@Before、@After、@Around、@AfterThrowing和@AfterReturning等。我们会在后续代码示例中看到这些注解的使用方法。
● 切点(Point Cut):切点是连接点的集合,用于定义必须执行的通知。通知不一定应用于所有连接点,因此切点提供了在应用程序中的组件上执行通知的细粒度控制。在Spring中,可以通过表达式来定义切点。
● 切面(Aspect):切面是通知和切点的组合,用于定义应用程序中的业务逻辑及其应执行的位置。Spring提供了@Aspect注解来定义切面。
现在,假设有这样一个代表转账操作的TransferService接口:
public interface TransferService { boolean transfer(Account source, Account dest, int amount) throws MinimumAmountException; }
然后我们提供它的实现类:
package com.demo; public class TransferServiceImpl implements TransferService { private static final Logger LOGGER = Logger.getLogger(TransferServiceImpl.class); @Override public boolean transfer(Account source, Account dest, int amount) throws MinimumAmountException { LOGGER.info("Tranfering " + amount + " from " + source.getAccountName() + " to " + dest.getAccountName()); if (amount < 10) { throw new MinimumAmountException("转账金额必须大于10"); } return true; } }
针对转账操作,我们希望在该操作之前、之后以及执行过程进行切入,并添加对应的日志记录,那么可以实现如下所示的TransferServiceAspect类:
@Aspect public class TransferServiceAspect { private static final Logger LOGGER = Logger.getLogger(TransferServiceAspect.class); @Pointcut("execution(* com.demo.TransferService.transfer(..))") public void transfer() {} @Before("transfer()") public void beforeTransfer(JoinPoint joinPoint) { LOGGER.info("在转账之前执行"); } @After("transfer()") public void afterTransfer(JoinPoint joinPoint) { LOGGER.info("在转账之后执行"); } @AfterReturning(pointcut = "transfer() and args(source, dest, amount)", returning = "isTransferSucessful") public void afterTransferReturns(JoinPoint joinPoint, Account source, Account dest, Double amount, boolean isTransferSucessful) { if (isTransferSucessful) { LOGGER.info("转账成功了"); } } @AfterThrowing(pointcut = "transfer()", throwing = "minimumAmountException") public void exceptionFromTransfer(JoinPoint joinPoint, MinimumAmountException minimumAmountException) { LOGGER.info("转账失败了:" + minimumAmountException.getMessage()); } @Around("transfer()") public boolean aroundTransfer(ProceedingJoinPoint proceedingJoinPoint){ LOGGER.info("方法执行之前调用"); boolean isTransferSuccessful = false; try { isTransferSuccessful = (Boolean)proceedingJoinPoint.proceed(); } catch (Throwable e) { LOGGER.error(e.getMessage(), e); } LOGGER.info("方法执行之后调用"); return isTransferSuccessful; } }
上述代码代表了Spring AOP机制的典型使用方法。使用@Pointcut注解定义了一个切入点,并通过“execution”指示器限定该切入点匹配的包结构为“com.demo”,匹配的方法是TransferService类的transfer()方法。
请注意,在TransferServiceAspect中综合使用了@Before、@After、@Around、@AfterThrowing和@AfterReturning注解用来设置5种不同类型的通知。其中@Around注解会将目标方法封装起来,并执行动态添加返回值、异常信息等操作。这样@AfterThrowing和@AfterReturning注解就能获取这些返回值或异常信息并做出响应,而@Before和@After注解可以在方法调用的前后分别添加自定义的处理逻辑。
Spring框架自2003年由Rod Johnson设计并实现以来,经历了多个重大版本的发展和演进,已经形成了一个庞大的“家族式技术生态圈”。目前,Spring已经是Java EE领域非常流行的开发框架,在全球各大企业中都得到了广泛应用。本节将梳理整个Spring家族中的技术体系,以及各种功能齐全的开发框架。
让我们通过Spring的官方网站来看一下Spring家族技术生态的全景图,如图1-2所示。
可以看到,这里罗列了Spring框架的七大核心技术体系,分别是微服务架构、响应式编程、云原生、Web应用、Serverless架构、事件驱动以及批处理。
一方面,这些技术体系各自独立但也有一定交集,例如微服务架构往往会与基于Spring Cloud的云原生技术结合在一起使用,而微服务架构的构建过程也需要依赖于能够提供RESTful风格的Web应用程序等。
另一方面,除了具备特定的技术特点,这些技术体系也各有其应用场景。例如,如果我们想要实现日常报表等轻量级的批处理任务,而又不想引入Hadoop这套庞大的离线处理平台,那么使用基于Spring Batch的批处理框架是一个不错的选择。再比方说,如果想要实现与Kafka、RabbitMQ等各种主流消息中间件的集成,但又希望开发人员不需要了解这些中间件在使用上的差别,那么使用基于Spring Cloud Stream的事件驱动架构是首选,因为这个框架对外提供了统一的API,从而屏蔽了内部各个中间件在实现上的差异性。
图1-2 Spring家族技术体系(来自Spring官网)
当然,所有我们现在能看到的Spring家族技术体系都是在Spring Framework的基础上逐步演进而来的。在介绍上述技术体系之前,我们先简单对其进行回顾。Spring Framework的整体架构如图1-3所示。
图1-3 Spring Framework的整体架构
Spring从诞生之初就被认为是一种容器,图1-3中的核心容器部分就包含一个容器所应该具备的核心功能,涉及1.1节中介绍的基于依赖注入机制的Bean、AOP、Context,以及Spring自身所提供的表达式工具Instrument等一些辅助功能。
图1-3最上面的就是构建应用程序所需要的两大核心功能组件,也是我们日常开发中最常用的组件,即数据访问和Web服务。这两大功能组件中包含的内容非常多,而且充分体现了Spring Framework的集成性。也就是说,框架内部整合了业界主流的数据库驱动、消息中间件、ORM框架等各种工具,开发人员可以根据需要灵活地替换和调整自己想要使用的工具。
从开发语言上讲,虽然Spring最广泛的一个应用场景是在Java EE领域,但在最新的版本中也支持Kotlin、Groovy以及各种动态开发语言。
本书无意对Spring中的所有七大技术体系做全面的展开。在日常开发过程中,如果要构建单块Web服务,可以采用Spring Boot。如果想要开发微服务架构,那么需要使用基于Spring Boot的Spring Cloud,而Spring Cloud同样内置了基于Spring Cloud Stream的事件驱动架构。同时,在这里应特别强调的是响应式编程技术。响应式编程是Spring 5引入的最大创新,代表了一种系统架构设计和实现的技术方向。因此,本书也将从Spring Boot、Spring Cloud以及Spring响应式编程这三大技术体系进行切入,看看Spring具体能够为我们解决开发过程中的哪些问题。
前面提到Spring家族具备多款开源框架,开发人员可以基于这些开发框架实现各种Spring应用程序。在本节中,我们关注的是基于Spring Boot开发面向Web场景的服务,这也是互联网应用程序最常见的表现形式之一。
在介绍基于Spring Boot的开发模式之前,让我们先将它与传统的Spring MVC进行对比。
在典型的Web应用程序中,前后端通常基于HTTP完成请求和响应,开发过程中需要完成HTTP请求的构建、URL地址的映射、对象的序列化和反序列化以及实现各个服务自身内部的业务逻辑,如图1-4所示。
图1-4 HTTP请求和响应过程
我们先来看基于Spring MVC的Web应用程序开发流程,如图1-5所示。
图1-5 基于Spring MVC的Web应用程序开发流程
图1-5包括使用web.xml定义Spring的DispatcherServlet、完成启动Spring MVC的配置文件、编写响应HTTP请求的控制器(Controller),以及部署服务到Web服务器等流程。事实上,基于传统的Spring MVC框架开发Web应用程序逐渐暴露出一些问题,比较典型的就是配置工作过于复杂和繁重,以及缺少必要的应用程序管理和监控机制。
如果想要优化这一套开发流程,有几个点值得我们去挖掘,比方说减少不必要的配置工作、启动依赖项的自动管理、简化部署并提供应用监控等。这些优化点推动了以Spring Boot为代表的新一代开发框架的诞生。基于Spring Boot的Web应用程序开发流程如图1-6所示。
图1-6 基于Spring Boot的Web应用程序开发流程
从图1-6可以看到,它与基于Spring MVC的Web应用程序开发流程在配置信息的管理、服务部署和监控等方面有明显不同。作为Spring家族新的一员,Spring Boot提供了令人兴奋的特性,这些特性的核心价值就是确保了开发过程的简单性,具体体现在编码、配置、部署、监控等多个方面。
● Spring Boot使编码更简单。
我们只需要在Maven中添加一项依赖并实现一个Controller,就可以提供微服务架构中所推崇的RESTful风格接口。
● Spring Boot使配置更简单。
它把Spring中基于XML的功能配置方式转换为Java Config,同时提供了.yml文件来优化原有的基于.properties和.xml文件的配置方案。.yml文件对配置信息的组织更为直观、方便,语义也更为强大。同时,基于自动配置特性,Spring Boot对常见的各种工具和框架均提供了默认的starter组件来简化配置。
● Spring Boot使部署更简单。
在部署方案上,Spring Boot也创造了一键启动的新模式。相较于传统模式下的war包,Spring Boot部署包既包含业务代码和各种第三方类库,同时也内嵌HTTP容器。这种包结构支持java–jar命令方式的一键启动,它不需要部署独立的应用服务器,通过默认的内嵌Tomcat就可以运行整个应用程序。
● Spring Boot使监控更简单。
相较于传统的Spring框架,Spring Boot的一大亮点是引入了内置的监控机制,这是通过Actuator组件来实现的。基于Actuator组件,我们可以查看包含自动配置在内的应用程序的详细信息。另一方面,也可以实时监控应用程序的运行时健康状态。这部分信息中常见的包括内存信息、JVM信息、垃圾回收信息等。例如,可以通过“/env/{name}”端点获取系统环境变量、通过“/mapping”端点获取所有RESTful服务、通过“/dump”端点获取线程工作状态,以及通过“/metrics/{name}”端点获取JVM性能指标等。
针对一个基于Spring Boot开发的Web应用程序,其代码组织方式需要遵循一定的项目结构。在本书中,如果不做特殊说明,我们都将使用Maven来管理项目中的结构和包依赖。一个典型的Web应用程序的项目结构包括包依赖、启动类、Controller类以及配置文件等4个组成部分。
● 包依赖。
Spring Boot提供了一系列starter组件来简化各种组件之间的依赖关系。以开发Web应用程序为例,我们需要引入spring-boot-starter-web这个组件,而这个组件中并没有具体的代码,而只包含一些依赖,如下所示:
org.springframework.boot:spring-boot-starter org.springframework.boot:spring-boot-starter-tomcat org.springframework.boot:spring-boot-starter-validation com.fasterxml.jackson.core:jackson-databind org.springframework:spring-web org.springframework:spring-webmvc
可以看到,这里包括传统Spring MVC应用程序中会使用到的spring-web和spring-webmvc组件,因此Spring Boot在底层实现上还是基于这两个组件完成对Web请求响应流程的构建的。
在应用程序中引入spring-boot-starter-web组件就像引入一个普通的Maven依赖,如下所示:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
一旦spring-boot-starter-web组件引入完毕,我们就可以充分利用Spring Boot提供的自动配置机制开发Web应用程序。
● 启动类。
使用Spring Boot的非常重要的一个步骤是创建一个Bootstrap启动类。Bootstrap类结构简单且比较固化,如下所示:
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class HelloApplication { public static void main(String[] args) { SpringApplication.run(HelloApplication.class, args); } }
可以看到,这里引入了一个全新的注解@SpringBootApplication。在Spring Boot中,添加了该注解的类就是整个应用程序的入口,一方面会启动Spring容器,另一方面也会自动扫描代码包结构下的@Component、@Service、@Repository、@Controller等注解,并把这些注解对应的类转化为Bean对象,从而全部加载到Spring容器中。
● Controller类。
Bootstrap类为我们提供了Spring Boot应用程序的入口,相当于应用程序已经具备最基本的骨架。接下来我们就可以添加HTTP请求的访问入口,表现在Spring Boot中就是一系列的Controller类。这里的Controller与Spring MVC中的Controller在概念上是一致的。一个典型的Controller类如下所示:
@RestController @RequestMapping(value = "accounts") public class AccountController { @Autowired private AccountService accountService; @GetMapping(value = "/{accountId}") public Account getAccountById(@PathVariable("accountId") Long accountId) { Account account = accountService.getAccountById(accountId); return account; } }
请注意,以上代码中包含@RestController、@RequestMapping和@GetMapping这3个注解。其中,@RequestMapping用于指定请求地址的映射关系,@GetMapping的作用等同于指定了GET请求方法的@RequestMapping注解。而@RestController注解是传统Spring MVC中所提供的@Controller注解的升级版,相当于@Controller和@ResponseEntity这两个注解的结合体,会自动使用JSON实现序列化/反序列化操作。
● 配置文件。
注意到在src/main/resources目录下存在一个application.yml文件,这就是Spring Boot中的默认配置文件。例如,我们可以将如下所示的端口、服务名称以及数据库访问等配置信息添加到这个配置文件中:
server: port: 8081 spring: application: name: orderservice datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/appointment username: root password: root
事实上,Spring Boot提供了强大的自动配置机制,如果没有特殊的配置需求,开发人员完全可以基于Spring Boot内置的配置体系完成诸如数据库访问相关配置信息的自动集成。
Spring Boot构建在Spring Framework的基础之上,是新一代的Web应用程序开发框架。我们可以通过图1-7来了解Spring Boot的全貌。
图1-7 Spring Boot整体架构
通过浏览Spring的官方网站,我们可以看到Spring Boot已经成为Spring中最顶级的子项目之一。自2014年4月发布1.0.0版本以来,Spring Boot俨然已经发展为Java EE领域开发Web应用程序的首选框架。
通过前面的描述,可以看到Spring Boot中一个传统Spring框架所不具备的功能特性,就是支持运行时内嵌容器,包含Tomcat、Jetty等支持Servlet规范的多款传统Web容器可供开发人员选择。而在最新的Spring Boot 2.x中,还提供了对Netty以及集成Servlet 3.1+的非阻塞式容器的支持。基于运行时内嵌容器机制,开发人员只需要使用一行java–jar命令就可以启动Web服务了。
另一个通过前面的示例可以看到的功能特性就是自动配置。前面的示例并没有像以前使用Spring MVC一样指定一大堆关于HTTP请求和响应的XML配置。事实上,Spring Boot的运行过程同样还是依赖于Spring MVC,但是它把原本需要开发人员指定的各种配置项设置了默认值并内置在运行时环境中,例如默认的服务器端口就是8080。如果我们对这些配置项没有定制化需求,就可以不做任何的处理,采用既定的开发约定即可。这就是Spring Boot所倡导的约定优于配置(Convention over Configuration)的设计理念。
在本节中,我们关注的是基于Spring Cloud开发面向微服务的系统,这也是当下非常主流的一种构建分布式系统的技术体系。
Spring Cloud具备一个天生的优势,那就是它是Spring家庭的一员,而Spring在Java EE开发领域的强大地位给Spring Cloud起到了很好的推动作用。同时,Spring Cloud基于Spring Boot,而Spring Boot已经成为Java EE领域中最流行的开发框架之一,Spring Cloud被用来简化Spring应用程序的框架搭建和开发过程。Spring Cloud与微服务架构如图1-8所示。
图1-8 Spring Cloud与微服务架构(来自Spring官网)
在微服务架构中,我们将使用Spring Boot来开发单个微服务。同样作为Spring家族新的一员,Spring Boot提供了令人兴奋的特性。正如我们在1.3节中所讨论的,这些特性主要体现在开发过程的简单化,包括支持快速构建项目、不依赖外部容器独立运行、开发部署效率高以及与云平台天然集成等。而在微服务架构中,Spring Cloud构建在Spring Boot之上,继承了Spring Boot配置简单、开发快速、部署轻松的特点,让原本复杂的架构工作变得相对容易上手。
技术组件的完备性是我们选择Spring Cloud的主要原因。Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了微服务系统基础设施的开发过程,如服务发现注册、API网关、配置中心、消息总线、负载均衡、熔断器、数据监控等都可以基于Spring Boot的开发风格做到一键启动和部署。
在对微服务的各项技术组件进行设计和实现的过程中,Spring Cloud也有自己的一些特色。一方面,它对微服务架构开发所需的技术组件进行了抽象,提供了符合开发需求的独立组件,包括用于配置中心的Spring Cloud Config、用于API网关的Spring Cloud Gateway等。另一方面,Spring Cloud也没有“重复造轮子”,它将目前各家公司现有的比较成熟、经得起实践考验的服务框架组合起来,通过Spring Boot开发风格进行了再次封装。这部分主要指的是Spring Cloud Netflix组件,其中集成了Netflix OSS的Eureka注册中心、Hystrix熔断器、Zuul网关等工具,如图1-9所示。
图1-9 Spring Cloud、Spring Cloud Netflix与Netflix OSS之间的关系
Spring Cloud屏蔽了微服务架构开发所需的复杂配置和实现过程,最终给开发者提供了一套易理解、易部署和易维护的开发工具包。Spring Cloud中的组件非常多,本书无意对所有组件都进行详细展开,而是梳理了开发一个微服务系统所必需的八大核心组件,如图1-10所示。
图1-10 Spring Cloud核心功能组件
可以看到,基于Spring Boot的开发便利性,Spring Cloud巧妙地简化了微服务系统基础设施的开发过程。
无论是电商类系统,还是互联网场景下的智能终端平台,都面临着大流量、高并发的访问请求。在各种请求压力下,系统就可能出现一系列可用性问题,作为系统的设计者,需要保证系统拥有即时的响应性。如何时刻确保系统具有应对请求压力的弹性,成为一个非常现实且棘手的问题。响应式编程技术针对这类问题提供了一种新的解决方案。
经典的服务隔离、限流、降级以及熔断等机制能够在一定程度上实现系统的弹性。通过对比更多可选的技术体系之后,可发现构建系统弹性的一种崭新的解决方案,这就是响应式编程。响应式编程打破了传统的同步阻塞式编程模型,基于响应式数据流和背压机制实现了异步非阻塞式的网络通信、数据访问和事件驱动架构,能够缓解服务器资源的竞争,从而提高服务的响应能力。
比方说,当系统中的服务A需要访问服务B时,在服务A发出请求之后,执行线程会等待服务B的返回,这段时间该线程就是阻塞的,因此整个过程的CPU利用效率低下,很多时间线程被浪费在了I/O阻塞上。更进一步,当执行数据访问时,数据库的执行操作也面临着同样的阻塞式问题。这样,整个请求链路的各个环节都会导致资源的浪费,从而降低系统的弹性。而通过引入响应式编程技术,就可以很好地解决这种类型的问题。
事实上,很多开发框架中已经应用了响应式编程技术。在1.4节中提到的Spring Cloud中存在的Netflix Hystrix组件,就是专门用来实现服务限流的熔断器组件。在这个组件中,实现了一个HealthCountsStream类来提供滑动窗口机制,从而完成对运行时请求数据的动态收集和处理。Hystrix在实现这一机制时大量采用了数据流处理方面的技术以及RxJava响应式编程框架。
再比方说,针对Spring Cloud,Spring家族还专门开发了一个网关工具Spring Cloud Gateway。相比Netflix中提供的基于同步阻塞式模型的Zuul网关,Spring Cloud Gateway的性能得到了显著的提升,因为它采用了异步非阻塞式的实现机制,而这一机制正是借助于响应式编程框架Project Reactor以及Spring 5中所内嵌的相关开发技术实现的。
你可能会问,如何应用响应式编程技术呢?它的开发过程是不是很有难度呢?这点不用担心,因为随着Spring 5的正式发布,我们迎来了响应式编程的全新发展时期。Spring 5中内嵌了与数据管理相关的响应式数据访问、与系统集成相关的响应式消息通信以及与Web服务相关的响应式Web框架等多种响应式组件,从而极大简化了响应式应用程序的开发过程和降低了相应的开发难度。图1-11展示了响应式编程技术栈与传统的Servlet技术栈之间的对比。
图1-11 响应式编程技术栈与传统的Servlet技术栈之间的对比(来自Spring官网)
可以看到,图1-11左侧为基于Spring WebFlux的技术栈,右侧为基于Spring MVC的技术栈。我们知道传统的Spring MVC构建在Java EE的Servlet标准之上,该标准本身就是阻塞式和同步的。而Spring WebFlux基于响应式流,因此可以用来构建异步非阻塞的服务。
在Spring 5中,选取了Project Reactor作为响应式流的实现库。由于响应式编程的特性,Spring WebFlux和Reactor的运行需要依赖于诸如Netty和Undertow等支持异步机制的容器。也可以选择使用较新版本的Tomcat和Jetty作为运行环境,因为它们支持异步I/O的Servlet 3.1。图1-12更加明显地展示了Spring MVC和Spring WebFlux的区别和联系。
在基于Spring Boot以及Spring Cloud的应用程序中,Spring WebFlux和Spring MVC可以混合进行使用。
另一方面,针对数据访问,从Spring Boot 2开始,对于那些支持响应式访问的数据库,Spring Data也提供了响应式版本的Repository支持。我们可以使用MongoDB和Redis等NoSQL数据库来实现响应式数据访问。
图1-12 Spring MVC和Spring WebFlux的区别和联系
图1-13归纳了本书内容的组织架构。本章作为全书的第1章,引入了Spring这款主流的开源开发框架,而剩下的章节在组织上按照Spring Boot篇→Spring Cloud篇→响应式Spring篇来展开。
图1-13 全书组织架构
首先,“Spring Boot篇”介绍Spring Boot所具备的核心功能特性,包括配置体系、数据访问、Web服务、消息通信和系统监控。这些功能是使用Spring框架的基本入口,可以说开发一个典型的Web应用程序离不开这些功能,后续介绍Spring Cloud和响应式Spring时,也需要依赖于这部分功能特性。在本篇的最后,我们从零开始构建一个SpringCSS案例系统,并将这些功能特性应用到具体的开发过程中。
“Spring Cloud篇”中的功能用于构建当下主流的微服务架构。本篇讨论了Spring家族中的Spring Cloud框架在面向分布式环境下的应用场景,详细介绍用于构建一个微服务系统的注册中心、服务网关、服务容错、配置中心、消息通信、服务监控等核心组件。这些组件都是开发一个微服务系统所必备的功能组件。在本篇的最后,我们同样从零开始构建一个SpringHealth案例系统,并将这些功能特性应用到具体的开发过程中。
最后,“响应式Spring篇”的内容关注Spring 5中新引入的响应式编程技术。响应式编程代表一种技术趋势,可以用来构建异步非阻塞的系统架构。这一篇对响应式编程的基础概念、响应式编程框架Project Reactor、响应式WebFlux和RSocket、响应式Spring Data等技术体系全面展开,理论联系实际,阐述Spring框架中针对响应式编程所提供的解决方案。同样,在本篇的最后,我们对SpringCSS案例系统做重构和升级并实现了ReactiveSpringCSS案例系统,用来展示响应式编程技术的具体落地方案和实现技巧。
本章作为全书的第1章,全面介绍了Spring框架的各个方面以及本书对Spring框架所采用的讲解思路,并对Spring Boot的开发模式和所提供的各项技术组件做了介绍。相较于传统的Spring框架,Spring Boot在编码、配置、部署、监控等方面都做了优化。同时,本章正式引入了Spring家族中的微服务开发框架Spring Cloud,明确了Spring Cloud是构建在Spring Boot之上,且提供了一系列的核心组件用来满足微服务系统的开发需求。最后,本章介绍了Spring 5所提供的响应式编程技术,同样也分析了Spring为开发人员准备的各项技术体系。
读者服务:
微信扫码关注【异步社区】微信公众号,回复“e59411”获取本书配套资源以及异步社区15天VIP会员卡,近千本电子书免费畅读。