ramostear.comramostear.com 谭朝红的技术分享博客

格言 编程是一门技术,也是一门艺术 !

开端-Java设计模式导读

开端-Java设计模式导读

开端-Java设计模式导读

和往常一样,本篇文章依旧采用“3W”顺序(即What,Why和Where)来回答软件工程中的设计模式是什么,为什么需要设计模式以及在什么地方使用设计模式这三个问题。

​ 本篇文章时Java设计模式系列技术文章的开篇,作为导读文章,将快速地对设计模式的基本概念、模式分类和适用范围进行解读。在后续的章节中,将对每一种类别的设计模式进行详细的讲解,讲解的内容包括每种设计模式的基本原理、适用范围和实战案例剖析三个部分。

1 模式的基本概念

​ 模式是指解决某个特定领域问题,实现既定目标的方法或思想。具体来说,模式是那些身处于某个行业的从业人员根据实际的工作经验总结出的,具有通用性的且被行业公认的解决问题的方法或流程。模式并非只在软件工程中被应用,其在日常的生产活动中被广泛地使用,如制造业,餐饮业,建筑设计、医疗卫生、教育培训以及软件工程等都有模式的身影。

2 什么是设计模式?

​ 首先,设计模式是一种模式。在软件工程中,设计模式是一种通用的、可重复使用的用于解决既定范围内普遍发生的重复性问题的软件设计方法。使用成熟可靠的设计模式,可以提高代码复用性,节省开发时间,从而实现功能更强大、高度可维护的代码。这有助于降低软件产品的总体拥有成本,即TCO(Total Cost of Ownership)。另一方面,由于采用了统一的标准设计方法(思想或理论知识),可以显著提升开发团队的生产效率和协作能力。

3 Java设计模式的分类

​ 在Java编程语言中,常用的设计模式可分为三种类型:

  • 建造类设计模式:主要用于定义和约束如何创建一个新的对象
  • 结构类设计模式:主要用于定义如何使用多个对象组合出一个或多个复合对象
  • 行为类设计模式:主要用于定义和描述对象之间的交互规则和限定对象的职责边界线

图3-1 设计模式分类

3.1 建造类设计模式

​ 建造类共包括五(5)种基本设计模式:单例模式,工厂模式,抽象工厂模式,建造器模式和原型模式,如图3-2所示:

图3-2 建造类设计模式

3.2 结构类设计模式

​ 结构类共包括八(8)种基本设计模式:适配器模式,组合模式,代理模式,享元模式,过滤器模式,桥接模式,修饰模式和外观模式,如图3-3所示:

图3-3 结构类设计模式

3.3 行为类设计模式

​ 行为类共包括十一(11)种基本设计模式:模板方法模式,解释器模式,责任链模式,观察者模式,战略模式,命令模式,状态模式,访客模式,转义模式,迭代器模式和备忘录模式,如图3-4所示:

图3-4 行为类设计模式

​ 设计模式不仅仅只有上述描述的这三大类,除此之外还有许多的设计模式。现已知的设计模式还有100多种,如DAO模式,依赖注入模式和MVC模式等。

4 快速理解设计模式

​ 在接下来的内容中,将快速对Java中常见的24中设计模式的基本概念进行梳理,以求对各种设计模式的原理和适用范围有一个大致的认识。

4.1 建造类

​ 建造类设计模式提供了对创建对象的基本定义和约束条件,以寻求最佳的实例化Java对象解决方案。

4.1.1 单例模式-Singleton

​ 单例模式限制类的实例化过程,以确保在Java虚拟机(JVM)中有且只有一个类的实例化对象。单例模式是Java中最常用,也是最简单的设计模式之一。单例模式通常需具备如下的几个特征:

  • 单例模式限制类的实例化,且Java虚拟机中只能存在一个该类的示例化对象
  • 单例模式必须提供一个全局可用的访问入口来获取该类的实例化对象
  • 单例模式常被用于日志记录,驱动程序对象设计,缓存以及线程池
  • 单例模式也会被用于其他的设计模式当中,如抽象工厂模式,建造者模式,原型模式等

单例模式的Java类的内部结构如图4-1所示:

图4-1 单例模式类图

下面是单例模式的一份示例代码清单:

4.1.2 工厂模式-Factory

​ 在Java程序设计过程中,当一个超类(super class)具有多个子类(sub class),且需要频繁的创建子类对象时,我们可以采用工厂模式。工厂模式的作用是将子类的实例化工作统一交由工厂类来完成,通过对输入参数的判断,工厂类自动实例化具体的子类。实现工厂模式需要满足三个条件:

  • 超类(super class):超类是一个抽象类
  • 子类(sub class): 子类需继承超类
  • 工厂类(factory class):工厂类根据输入参数实例化子类

图4-2为Java工厂模式的类图:

图4-2 工厂模式UML类图

下面是工厂模式的一份示例代码清单:

4.1.3 抽象工厂模式-Abstract Factory

​ 抽象工厂模式与工厂模式很类似,抽象工厂模式可以简单的理解为“工厂的工厂”。在工厂模式中,根据提供的输入参数返回产品类的实例化对象,这个过程需要通过if-else或者switch这样的逻辑判断语句来完成具体子类的判定。而在抽象工厂模式中,每种产品都有具体的工厂类与之对应,从而避免在编码过程中使用大量的逻辑判断代码。抽象工厂模式会根据输入的工厂类型以返回具体的工厂子类。抽象工厂类只负责实例化工厂子类,不参与商品子类的实例化工作。图4-3是抽象工厂模式的UML类图:

图4-3 抽象工厂模式

4.1.4 建造器模式-Builder

​ 建造者模式通常被用于需要多个步骤创建对象的场景中。建造者模式的主要意图是将类的构建逻辑转移到类的实例化之外,当一个类有许多的属性,当在实例化该类的对象时,并不一定拥有该实例化对象的全部属性信息,便可使用建造者模式通过逐步获取实例化对象的属性信息,来完成该类的实例化过程。而工厂模式和抽象工厂模式需要在实例化时获取该类实例化对象的全部属性信息。图4-4展示了建造器模式的基本逻辑关系:

图 4-4 建造器模式UML类图

4.1.5 原型模式-Prototype

​ 原型模式的主要作用是可以利用现有的类通过复制(克隆)的方式创建一个新的对象。当示例化一个类的对象需要耗费大量的时间和系统资源时,可是采用原型模式,将原始已存在的对象通过复制(克隆)机制创建新的对象,然后根据需要,对新对象进行修改。原型模式要求被复制的对象自身具备拷贝功能,此功能不能由外界完成。图4-5展示了原型模式的基本逻辑:

图4-5 原型模式UML类图

4.2 结构类

​ 结构类设计模式主要解决如何通过多个小对象组合出一个大对象的问题,如使用继承和接口实现将多个类组合在一起。

4.2.1 适配器模式-Adapter

​ 适配器模式的主要作用是使现有的多个可用接口能够在一起为客服端提供新的接口服务。在适配器模式中,负责连接不同接口的对象成为适配器。在现实生活中,我们也能够找到很多实际的案例来理解适配器的工作原理,例如常用的手机充电头,在手机和电源插座之间,手机充电头就扮演一个适配器的角色,它能够同时适配220V,200V,120V等不同的电压,最终将电转换成手机可用的5V电压为手机进行充电。图4-6展示了适配器的基本原理:

图 4-6 适配器模式UML类图

4.2.2 组合模式-Composite

​ 组合模式的主要作用是让整体与局部之前具有相同的行为。例如我们需要绘制一个图形(正方形,三角形,圆形或其他多边形),首先需要准备一张空白的纸,然后是选择一种绘制图案的颜色,再次是确定绘制图案的大小,最后是绘制图案。不管是绘制正方形还是三角形,都需要按照这个步骤进行。在软件设计过程中,组合模式的最大意义在于保证了客户端在调用单个对象与组合对象时,在其操作流程上是保持一致的。图4-7展示了组合模式的基本原理:

图 4-7 组合模式UML类图

4.2.3 代理模式-Proxy

​ 代理模式的主要作用是通过提供一个代理对象或者一个占位符来控制对实际对象的访问行为。代理模式通常用于需要频繁操作一些复杂对象的地方,通过使用代理模式,可以借由代理类来操作目标对象,简化操作流程。图4-8展示了代理模式的基本原理:

图 4-8 代理模式UML类图

4.2.4 享元模式-Flywight

​ 享元模式的主要作用是通过共享来有效地支持大量细粒度的对象。例如当需要创建一个类的很多对象时,可以使用享元模式,通过共享对象信息来减轻内存负载。如果在软件设计过程中采用享元模式,需要考虑以下三个问题:

  • 应用程序需要创建的对象数量是否很大?
  • 对象的创建对内存消耗和时间消耗是否有严格的要求?
  • 对象的属性是否可以分为内在属性和外在属性?对象的外在属性是否支持有客户端定义?

图4-9展示了享元模式的基本原理:

图 4-9 享元模式UML类图

4.2.5 外观模式-Facade

​ 外观模式的主要作用是为子系统中的一组接口提供一个统一的接口,以便客户端更容易去使用子系统中的接口。简单的理解是外观模式为众多复杂接口定义了一个更高级别的接口。外观模式的目的是让接口更容易被使用,图4-10展示了外观模式的基本原理:

图 4-10 外观模式UML类图

4.2.6 桥接模式-Bridge

​ 桥接模式的主要用途是将抽象类与抽象类的具体实现相分离,以实现结构上的解耦,使抽象和实现可以独立的进行变化。桥接模式的实现优先遵循组合而不是继承,当使用桥接模式时,在一定程度上可以在客户端中因此接口的内部实现。图4-11展示了桥接模式的基本原理:

图 4-11 桥接模式UML类图

4.2.7 修饰模式-Decorator

​ 修饰模式的主要作用是在运行时动态的组合类的行为。通常,你会添加一些新的类或者新的方法来扩展已有的代码库,然而,在某些情况下你需要在程序运行时为某个对象组合新的行为,此时你可以采用修饰模式。图4-12展示了修饰模式的基本原理:

图 4-12 修饰模式UML类图

4.2.8 过滤器模式-Filter

​ 过滤器模式是使用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式将对象组合起来。图 4-13展示了过滤器模式的基本原理:

图 4-13 过滤器模式

4.3 行为类

​ 行为类设计模式主要用于定义和描述对象之间的交互规则和职责边界,为对象之间更好的交互提供解决方案。

4.3.1 模板方法模式-Template Method

​ 模板方法模式的主要作用是在一个方法里实现一个算法,可以将算法中的的一些步骤抽象为方法,并将这些方法的实现推迟到子类中去实现。例如建造一栋房子,我们需要设计图纸,打地基,构筑墙体,安装门窗和内部装修。我们可以设计不同的房屋样式(别墅,高楼,板房等),不同的门窗和不同的装修材料和风格,但是其顺序不能颠倒。在这种情况下,我们可以定义一个模板方法,规定方法的执行顺序,而将方法的实现推迟到子类中完成。图4-14展示了模板方法模式的基本原理:

图 4-14 模板方法模式UML类图

4.3.2 解释器模式-Mediator

​ 解释器(中介)模式的主要设计意图是定义一个中间对象,封装一组对象的交互,从而降低对象的耦合度,避免了对象间的显示引用,并可以独立地改变对象的行为。解释器(中介)模式可以在系统中的不同对象之间提供集中式的交互介质,降低系统中各组件的耦合度。图 4-15展示了解释器(中介)模式的基本原理:

图 4-15 解释器(中介)模式UML类图

4.3.3 责任链模式-Chain of Responsibility

​ 责任链模式主要作用是让多个对象具有对同一任务(请求)的处理机会,以解除请求发送者与接收者之间的耦合度。try-catch就是一个典型的责任链模式的应用案例。在try-catch语句中,可以同时存在多个catch语句块,每个catch语句块都是处理该特定异常的处理器。当try语句块中发生异常是,异常将被发送到第一个catch语句块进行处理,如果第一个语句块无法处理它,它将会被请求转发到链中的下一个catch语句块。如果最后一个catch语句块仍然不能处理该异常,则该异常将会被向上抛出。图4-16展示了责任链模式的基本原理:

图 4-16 责任链模式UML类图

4.3.4 观察者模式-Observer

​ 观察者模式的目的是在多个对象之间定义一对多的依赖关系,当一个对象的状态发生改变时,观察者会通知依赖它的对象,并根据新状态做出相应的反应。简单来说,如果你需要在对象状态发生改变时及时收到通知,你可以定义一个监听器,对该对象的状态进行监听,此时的监听器即为观察者(Observer),被监听对象称为主题(Subject)。Java消息服务(JMS)即采用了观察者设计模式(同时还使用了中介模式),允许应用程序订阅数据并将数据发布到其他应用程序中。图4-17展示了观察者模式的基本原理:

图 4-17 观察者模式UML类图

4.3.5 策略模式-Strategy

​ 策略模式的主要目的是将可互换的方法封装在各自独立的类中,并且让每个方法都实现一个公共的操作。策略模式定义了策略的输入与输出,实现则由各个独立的类完成。策略模式可以让一组策略共存,代码互不干扰,它不仅将选择策略的逻辑从策略本身中分离出来,还帮助我们组织和简化了代码。一个典型的例子是Collections.sort()方法,采用Comparator作为方法参数,根据Comparator接口实现类的不同,对象将以不同的方式进行排序。图 4-18 展示了策略模式的基本原理:

图 4-18 策略模式UML类图

4.3.6 命令模式-Command

​ 命令模式的设计意图是将请求封装在对象的内部。直接调用是执行方法的通常做法,然而,在有些时候我们无法控制方法被执行的时机和上下文信息。在这种情况下,可以将方法封装到对象的内部,通过在对象内部存储调用方所需要的信息,就可以让客户端或者服务自由决定何时调用方法。图 4-19 展示了命令模式的基本原理:

图 4-19 命令模式UML类图

4.37 状态模式-State

​ 状态模式的设计意图是更具对象的状态改变其行为。如果我们必须根据对象的状态改变对象的行为,可以在对象中定义一个状态变量,并使用逻辑判断语句块(如if-else)根据状态执行不同的操作。图4-20展示了状态模式的基本原理:

图 4-20 状态模式UML类图

4.3.8 访客模式-Visitor

​ 访客模式的设计意图是在不改变现有类层次结构的前提下,对该层次结构进行扩展。例如在购物网站中,我们将不同的商品添加进购物车,然后支付按钮时,它会计算出需要支付的总金额数。我们可以在购物车类中完成金额的计算,也可以使用访客模式,将购物应付金额逻辑转移到新的类中。图 4-21展示了访客模式的基本原理:

图 4-21 访客模式UML类图

4.3.9 转义(翻译)模式-Interpreter

​ 转义(翻译)模式的设计意图是让你根据事先定义好的一系列组合规则,组合可执行的对象。实现转义(翻译)模式的一个基本步骤如下:

  • 创建执行解释工作的上下文引擎
  • 根据不同的表达式实现类,实现上下文中的解释工作
  • 创建一个客户端,客户端从用户那里获取输入,并决定使用哪一种表达式来输出转义后的内容

图4-22展示了转义(翻译)模式的基本原理:

图 4-22 转义(翻译)模式UML类图

4.3.10 迭代器模式-Iterator

​ 迭代器模式为迭代一组对象提供了一个标准的方法。迭代器模式被广泛的应用于Java Collection框架中,Iterator接口提供了遍历集合元素的方法。迭代器模式不仅仅是遍历集合,我们还可以根据不同的要求提供不同类型的迭代器。迭代器模式通过集合隐藏内部的遍历细节,客户端只需要使用对应的迭代方法即可完成元素的遍历操作。图4-23 展示了迭代器的基本原理:

图 4-23 迭代器模式UML类图

4.3.11 备忘录模式-Memento

​ 备忘录模式的设计意图是为对象的状态提供存储和恢复功能。备忘录模式由两个对象来实现-Originator和Caretaker。Originator需要具有保存和恢复对象状态的能力,它使用内部类来保存对象的状态。内部内则称为备忘录,因为它是对象私有的,因此外部类不能直接访问它。图4-24展示了备忘录模式的基本原理:

图 4-24 备忘录模式UML类图

小节

​ 在本篇文章中,说明了模式是指解决某个特定领域问题,实现既定目标的方法或思想;设计模式是一种通用的、可重复使用的用于解决既定范围内普遍发生的重复性问题的软件设计方法。同时,对Java中常见的设计模式进行了分类,设计模式分为建造、结构和行为三种类型,并对每种类型的设计模式的基本概念和原理进行了介绍,在后续的章节中,将详细的介绍每种设计模式的原理、使用方式和适用范围,并给出相应的实战源码。

附件

​ 在文章最后,为了便于大家阅读,提供了本文的电子版文件

​ 百度网盘下载地址:https://pan.baidu.com/s/1OREFj5QmLsHcKNbaJGLTmw

​ 文件提取码:jqgf

​ 文件提取二维码:

技术精讲:Java 8 Stream API

技术精讲:Java 8 Stream API

技术精讲:Java 8 Stream API

Stream(流)是在Java 8中新增的新特性,首先需要为Java 8 Stream正名:Java 8中的Stream跟Java I/O Stream(例如:InputStream,OutputStream等)没有任何的关系。Stream是Java中数据源的包装器,通过Stream我们可以快速的对数据源进行操作(例如:过滤,排序、求和等等),且Stream不对任何数据进行存储,所以Stream也不是数据结构。

​ 在Java 8中,Stream增强了Array,List等对象操作数据的能力,Stream提供了一些列的方法使的这些原有的集合类可以轻松创建Stream对象实例。通过Stream API提供的静态方法,可以快速的生成有限/无限的数据流。特别指出,Stream只是增强了原有集合类的能力,并未对底层的源码做任何的修改。

1.Stream的工作流程

​ 接下来,将介绍使用Java Stream的基本步骤。在Java 8中,Stream的生命周期一共有三个阶段:

  • 1.获取数据源并创建Stream实例。数据源可以是数组、列表、对象或者I/O流
  • 2.执行中间操作(Intermediate Operations)。中间操作可以是过滤、排序、类型转换等操作
  • 3.执行终端操作(Terminal Operation)。终端操作主要是对最终结果进行计数、求和、创建新集合等操作

图1-1展示了Java 8 Stream的工作工作流程。

Stream工作流程

图 1-1 Stream工作流程

2.Stream 操作管道是什么?

​ 所谓的Stream操作管道是指“中间操作”和“终端操作”的组合。在Java 8 Stream API中,许多的操作方法都将当前的Stream作为最终结果返回,这就允许开发人员以连式编程的方式,组合出更大的Stream操作管道。Java 8 Stream API位于java.util.stream包下,其组织结构如图2-1所示:

java.util.stream包

图2-1 java.util.stream包

接下来,将详细介绍“中间操作”和“终端操作”各自的方法和特性。

3.Stream中间操作(Intermediate Operations)是什么?

​ Stream的中间操作将返回一个Stream,它允许开发者以查询的方式调用多个其他的操作。特别注意的是,在“终端操作”方法未被调用之前,“中间操作”的方法不会被执行。Stream的“中间操作”一共包括七个:Stream.filter,Stream.map,Stream.flatmap,Stream.peek,Stream.sorted和Stream.limit。

3.1.Stream.filter

​ Stream.filter将返回一个包含与之谓词相匹配的元素的新的Stream。下面将通过一个示例,演示Stream.filter的使用方法。

Stream.filter

执行结果:

此示例使用“中间操作”Stream.filter过滤以“j”开头的字符串,并使用“终端操作”Stream.count统计以“j”开头的字符串数量。

3.2.Stream.map

​ Stream.map将使用java.util.function.Function提供的方法转换Stream中的元素。Stream.map操作通常用于转换集合对象。下面是使用Stream.map的示例代码:

Stream.map

执行结果:

3.3.Stream.flatmap

​ Stream.flatmap通常将Stream中的每一个元素转换成0个或多个元素。下面将通过flatmap来统计一个文本中单词的出现次数(不重复)。示例代码如下:

Stream.flatmap

执行结果:

source.txt内容:

java 8 stream example by ramostear

3.4.Stream.peek

​ Stream.peek在调试期间非常有用,它允许您在操作Stream之前查看Stream内部的数据。下面是示例代码:

执行结果:

Java
C#
C++
GO
result size = 4

3.5.Stream.distinct

​ Stream.distinct是根据其内部的equals方法在Stream中对元素进行去重操作。下面是使用示例:

执行结果:

6
1
8
11

3.6.Stream.sorted

​ Stream.sorted方法用于将Stream中的数据元素进行排序。下面是使用示例:

执行结果:

1
6
8
11

3.7.Stream.limit

​ Stream.limit方法用于限定Stream中元素的个数。下面是使用示例:

执行结果:

0
1
2
3
4
5
11
618

4.Stream 终端操作(Terminal Operations)是什么?

​ 终端操作用于生产最终的数据结果,如对象,数组或者列表。在终端操作方法被执行前,中间操作方法将不会被执行。终端操作方法一共有十二个,它们是:forEach,toArray,reduce,collect,min,max,count,anymatch,allMatch,noneMatch,findFirst和findAny。表4-1列举了这十二个终端操作的使用方法:

Stream终端操作方法

图 4-1 Stream终端操作方法

5.如何创建Stream实例?

​ Stream支持多种数据源创建Stream实例,如Array,List,Object和I/O流。接下来,通过简单的示例来演示如何创建Stream。

5.1 使用数组创建Stream

​ 首先,我们定义一个静态的数组users,并初始几条用户数据,代码如下:

private static User[] users = {new User("user1"),new User("user2"),new User("user3")};

接下来,将利用users来创建一个Stream对象实例,示例代码如下:

public static void fromArray() {
    Stream<User> uStream = Stream.of(users);
    System.out.println("create stream from array.");
    uStream.forEach(u->System.out.println(u.getUsername()));
}

5.2 使用对象创建Stream

​ 沿用上面定义的users数组,通过数组下标获得用户对象,并利用这些对象创建一个Stream实例,代码如下:

public static void fromObjects() {
    Stream<User> uStream = Stream.of(users[0],users[1],users[2]);
    System.out.println("create stream from objects.");
    uStream.forEach(u->System.out.println(u.getUsername()));
}

5.3 使用List创建Stream

​ 接下来,我们将users数组转换成List,并使用此List来创建Stream实例。代码如下:

public static void fromList() {
    List<User> list = Arrays.asList(users);
    Stream<User> uStream = list.stream();
    System.out.println("create stream from list.");
    uStream.forEach(u->System.out.println(u.getUsername()));
}

5.4 使用builder方法创建Stream

​ 最后,我们将演示使用Stream内置的builder()静态方法创建Stream实例。代码如下:

public static void byBuilder() {
    Stream.Builder<User> userStream = Stream.builder();
    userStream.accept(users[0]);
    userStream.accept(users[1]);
    userStream.accept(users[2]);

    System.out.println("create stream by builder.");
    userStream.build().forEach(u->System.out.println(u.getUsername()));

}

​ 以上就是通过数据源创建Stream实例的几种不同方式。

6.为什么要使用Stream?

​ 在命令式编程中,我们必须逐行编写代码,才能完成相关的计算。例如,计算1~10000的数的总和,我们需要使用for(int i=1;i<=10000;i++){….}循环语句来迭代求值。在这个语句中,我们需要花费额外的精力来维护loop变量的值。而在声明式编程中,我们只需要关注想要实现的目标,而不是其内部重复的循环逻辑。Stream API通过使用内部迭代器和lambda表达式,帮助我们完成了那些重复性的逻辑。Stream API的作用不仅仅用于数据迭代,它还能通过中间操作和终端操作实现数据的排序、类型转换、求和、计数、求最大值、最小值以及模式匹配等操作。

​ 接下来,表6-1展示了命令式编程和声明式编程的优劣:

命令式编程与声明式编程

总结

​ 在本文中,介绍了Java Stream的基本工作流程,并详细列举了中间操作和终端操作的细节。通过使用Stream,我们可以将传统的命令式编程代码进行简化,以提高编程效率。

Ehcache:Another CacheManager with same name exception

Ehcache:Another CacheManager with same name exception

Ehcache:Another CacheManager with same name异常

​ 在Spring Boot中开启热部署功能后,整合Shiro和Ehcache缓存时发生如下的异常信息:

Caused by: net.sf.ehcache.CacheException: Another CacheManager with same name 'shiro-cache' already exists in the same VM. Please provide unique names for each CacheManager in the config or do one of following:
1. Use one of the CacheManager.create() static factory methods to reuse same CacheManager with same name or create one if necessary
2. Shutdown the earlier cacheManager before creating new one with same name.

导致此错误的原因是针对新版本(2.5以后)的Ehcache,CacheManager使用了单例模式,当使用Spring Boot devtools开启热加载功能时,在加载SecurityManager Bean 对象实例时,会创建多个CacheManager实例,从而导致上述的异常。

​ 在此之前,网上已给出很多的解决方案,如降低Ehcache版本,设置缓存shared属性等。那么,如何在不改变当前框架版本的情况下修复上述异常呢?下面是笔者的完整解决方案。

1.改造前的代码

​ 下面的代码片段是改造前的示例:

在此示例中,只是简单的创建了ehcacheManager对象实例,并设置了缓存文件的存放地址。而新版本中,CacheManager要求是单例模式,因此需要使用EhCachManagerFactoryBean对象来限定cacheManager为共享模式。EhCachManagerFactoryBean是Spring提供的ehcache实现类。接下来我们看看如何改造。

2. 改造后的代码

​ 修改的地方一共有三处,它们是:配置EhcacheManagerFactoryBean并设置其为共享模式,配置DefaultAdvisorAutoProxyCreator,让Spring 来管理Shiro的Bean生命周期,配置lifecycleBeanPostProcessor,并让DefaultAdvisorAutoProxyCreator依赖于此对象。

2.1 添加EhcacheManagerFactoryBean配置项

​ 在原有的配置文件中添加EhcacheManagerFactoryBean配置项,并将shared属性设置为true,示例代码如下:

2.2 添加LifecycleBeanPostProcessor配置项

​ 添加LifecycleBeanPostProcessor配置项,LifecycleBeanPostProcessor将统一管理Initializable和Destroyable的实现类,从而达到统一管理Shiro Bean的生命周期的目的。示例代码如下:

2.3 添加DefaultAdvisorAutoProxyCreator配置项

DefaultAdvisorAutoProxyCreator的作用是动态代理Shiro的事务,最终将事务交由Spring进行统一管理。此配置项需要依赖于LifecycleBeanPostProcessor。示例代码如下:

通过添加上述的三个配置项,在Spring Boot中开启热加载功能就可以避免Another CacheManager with same name的异常出现。

首席技术官(CTO)在IT创业公司的角色转变

首席技术官(CTO)在IT创业公司的角色转变

在大多数的IT创业公司中,大多是技术驱动型的公司。面对不断涌现的行业新技术,企业需要快速的掌握并采用这些创新型技术,以确保企业在激烈的竞争中获得相对的竞争优势。以技术为驱动力的公司,为了能快速的处理公司所有的技术问题,从而催生了一个新的职位——首席技术官,英文简称CTO。如果你的公司是一家以技术为驱动力的创业公司,或者你是一位准备转型做CTO的开发人员,那你应该先充分了解首席技术官在IT创业公司中所扮演的角色以及该角色需要承担的责任。

​ 本文以首席技术官的职责和技能为主题,分析首席技术官在IT创业公司的各个发展阶段所扮演的角色,帮助你快速地对首席技术官(CTO)这个职位有一个全面的了解。

一、IT创业公司的CTO在扮演什么角色?

​ 首席技术官,从字面意思上看,主要负责与公司内部技术相关的工作,但这个定义太过于模糊和笼统。在国内外的IT创业公司中,对于CTO该干什么,不该干什么没有明确的定义和统一的标准,不同的IT创业公司因其业务的不同,对CTO的职能要求也不尽相同。虽然不同IT创业公司对CTO有着不同的职能划分,但是从CTO的工作本质上看,可以将CTO划分成技术领导型和技术管理型两类。

1-1、技术领导型

​ 技术领导型的首席技术官都拥有这超高的编码水平,在软件编码、架构设计、基础设施配置等领域都拥有丰富的专业知识和实践经验。他们自身具备的丰富的专业知识和开发经验,帮助其有能力对技术团队中其他的开发人员的代码进行评审,同时能为公司的工程项目作出风险评估。

1-2、技术管理型

​ 这一类型的首席技术官主要的工作任务是监督和管理工程项目的各个项目经理。技术管理型的首席技术官也具备较高的编码水平,但他们通常不参与具体的编码工作,而是将主要的精力放在公司技术战略走向并管理技术团队内部的工程。

​ 随着IT创业公司的不断发展,首席技术官的工作重心将从技术领导过渡到技术管理。在下面的内容中,将逐步分析IT创业公司在各个阶段中,首席技术官角色的演变过程。

二、IT创业公司的CTO角色会发生什么变化?

​ 所有IT企业的首席技术官都有一个贯穿公司始末的伟大使命:确保公司的技术战略完全服务于公司的业务战略。下面是一个IT创业公司从零(0)到一(1)的过程中CTO角色发生的转变。

2-1、创业早期

​ 在创业早期,所有的工作都是围绕着一个商业创意开始的。在这个阶段,公司可能还没有成立,所有的创始人在一起思想碰撞,一场又一场的头脑风暴,目的是完善创业,起草商业计划书。此时的CTO(通常是创始人)往往是一个拥有强大技术专长的人,他们的主要工作职责是验证商业方案的技术可行性,并提出实施软件产品开发的技术解决方案。

2-2、创业初期

​ 如果创业团队的商业方案得到了投资人和消费者的关注,创业团队会注册公司并开始着手构建第一个版本的软件产品。此时的CTO是技术团队的第一个(大多数情况下也是唯一一个)开发人员,他们需要在有限的时间内开发出一个最小化的可使用的产品——MVP(MVP是Minimum Viable Product的缩写)。

​ 在这个阶段,CTO的主要工作如下:

  1. 选择并确定公司的技术栈
  2. 设计软件架构
  3. 配置可扩展的基础架构(硬件和网络)
  4. 软件源代码编写
  5. 测试应用软件

​ 这些初期的工作需要CTO具备强大编码能力(有时是全栈开发),经验和相关的开发流程知识。可能一些创业初始的公司会聘用技术人员,CTO不需要单挑所有的工作,但他们依旧需要在早期为产品原型编写大量的源代码。

2-3、创业成长期

​ 公司进入快速成长期时,软件产品开始投放市场,CTO的工作重点转向软件的升级工作。软件升级让初始版本的的产品更具功能性和可用性,以便吸引更多的用户使用软件。为了加快软件开发速度,企业通常在这个阶段加快扩充技术团队人数的速度,CTO的角色也相应发生了转变,技术团队的管理事务开始进入CTO的工作日程表:

  1. 优化应用程序的部署流程,以提高开发人员的工作效率
  2. 确保应用程序能安全、可靠的运行
  3. 提出更经济、可扩展的基础设施配置方案

​ 在这个阶段,CTO除了参与编码工作外,还要负责管理不断扩大的技术队伍,其管理工作主要体现在以下两个方向:

  1. 负责监督招聘新职员的工作,需要对招聘技术人员的工作做最后的把关
  2. 建立公司的技术文化,如开发工具集、SDK版本、编码风格、书写规范、命名规范、注释规范、文档规范等等,良好的技术文化可以确保所有的开发人员能够遵循同一个标准和最佳实践开展编码工作,从而提高生产效率。

2-4、创业成熟期

​ 公司进入创业成熟期,技术团队的人数将由原来的几人增加到几十人甚至几千人,在这个时期,CTO必须要带领大批的开发人员按照既定的开发规范和工作流程开展编码工作。此时CTO的工作重点将完全转向管理,CTO可能会同时协调并监督多个开发团队,完成不同的软件工程任务。

​ 在此阶段,CTO需要花时间和精力去构建软件产品的愿景,跟踪并分析行业中最新的技术动向,并给出在下一个软件版本中实施相关技术的可行性方案。为了做出合理的、正确的技术选择,CTO在这个时期需要进一步了解新技术的详细流程和行业竞争者的技术状态,需要采用相关的创新技术,确保相比于竞争对手有一定的技术竞争优势。

​ 在本小节中,分析了在IT创业公司中,随着公司的发展,CTO从技术领导型向技术管理型转变的原因。在下面的章节中,将介绍IT创业公司的CTO都需要具备什么样的技能。

三、IT创业公司的CTO需要哪些必杀技?

​ 前面我们分析了IT创业公司在不同阶段中CTO工作职责的转变,CTO需要在软件开发和团队管理上都有着优于常人的能力。接下来,将介绍IT创业公司在各个发展阶段CTO需要具备的一些技能。

3-1、软件架构师

​ 在创业初期,CTO是公司唯一的技术权威和技术实现者,这就要求CTO需要拥有以下的技战水平:

  1. 设计并实现软件架构
  2. 选择并确定技术栈
  3. 设计并配置基础架构(硬件和网络)
  4. 选择并调试开发工具
  5. 设计并实现数据建模
  6. 优化并升级应用程序
  7. 确保应用程序的安全性和可用性
  8. 确保基础架构的经济适用性
  9. 跟踪并分析行业新技术,并对是否采用新技术作出决策

3-2、系统管理员

​ 在创业初期,CTO大多数都兼任系统管理员一职,负责对基础架构和应用程序进行管理,需要具备以下的工作能力:

  1. 软硬件的安全防护管理
  2. 域名、SSL证书和第三方服务管理
  3. 内部开发沟通渠道管理
  4. 基础架构性能监控管理
  5. 数据备份和日志审查管理

随着公司的壮大,这一部分工作会交由首席信息官(CIO)来管理,但在早期,大多数工作还是由CTO来完成。

3-3、项目组长

​ 当技术团队开发人员数量增加时,CTO开始转向团队管理,编码工作的份额开始减少,此时的CTO开始承担一个项目组长的所有职责:

  1. 根据以往的最佳实践和新的编码方法定义和实施开发标准
  2. 制定开发流程
  3. 审查软件源代码
  4. 分配开发任务
  5. 监督招聘工作
  6. 培训新职工
  7. 与客户展开沟通
  8. 与决策层沟通
  9. 评估开发人员的KPI(Key Performance Indicatro,即关键绩效指标)
  10. 监督开发人员的工作进度
  11. 评估技术风险

​ 成为一个优秀的技术管理者不仅需要强大的专业技术知识,还需要有良好的沟通能力,需要随时与开发人员沟通,并解决开发中遇到的问题。

3-4、运维工程师

​ 除了推动公司的开发工作向前发展,管理好技术团队之外,CTO在公司的发展期,还需要成为一名不错的运维工程师,在公司进入创业发展期,CTO必须要负责建立安全、可靠、易用的软件部署通道,为开发人员提供良好的软件升级环境,此时的CTO需要建立起高效的运维流程:

  1. 构建易用、安全、稳定的软件部署通道
  2. 规划软件产品的版本
  3. 选择版本管理工具并管理软件产品版本
  4. 制定并实施软件产品版本的发布周期

3-5、企业高管

​ 在本文一开始提到,IT创业公司的CTO通常是公司的创始人之一,是公司软件工程的最高执行官。除了编码、运维等技术类工作,他们还需要参与公司的上层管理:

  1. 参与公司战略规范和重大决策工作
  2. 参与公司的高层管理工作
  3. 跟踪和分析竞争对手,提出技术创新建议
  4. 为公司的业务提供可行性的技术方案
  5. 监督并协调外部劳动力(外包团队)
  6. 跟踪行业内的技术趋势并识别出公司的技术竞争优势
  7. 评估并监督公司在技术领域的资金预算
  8. 优化基础设施配置方案,降低产品运营成本

​ 技术类的企业高管,不仅要具备强大的专业知识,还需要对企业的管理流程有深入的了解。单纯的技术达人,不是一个合格的IT企业CTO。

四、总结

​ 随着IT创业公司的发展,首席技术官所扮演的角色也在发生改变,总的来说,CTO需要处理公司内部所有和技术有关的问题,可能是微观上的,也可能是宏观上的,但其核心使命不变:确保公司的技术战略完全贴合公司的业务战略。一个成功的IT创业公司(什么样子才算成功?所有的IT创业公司都想要成为行业的领头羊或者独角兽,也许这样才算成功!),一定有一个优秀的首席技术官伴随着企业的成长,他们了解企业在不同的发展背景下应该做什么事、需要具备什么样的技术能力和需要思考什么样的问题。

​ 最后,如果需要用一句话来概括一名出色的CTO所具备的能力和特点,那可能是这样的:出色的CTO都是嚼着嘴里的,夹着碗里的,看着锅里的,想着地里长着的

总结:实施SaaS模型的11个关键步骤

总结:实施SaaS模型的11个关键步骤

总结:实施SaaS模型的11个关键步骤

前言:在SaaS模型中,应用程序是通过网络向用户提供服务。用户只需要通过网络访问应用程序便可完成自身的业务活动,而不需要安装和维护软件。任何的SaaS模型的实施,都应具备以下三个特征:

  1. 多租户架构:SaaS模型设定了多个用户共享一套应用程序源码,此源码被服务提供商统一托管
  2. 自定义可配置:不同用户共享一套应用程序源码,这就要求SaaS模型须具备一定的自定义和可配置功能,以便可以管理和维护用户的自定义业务
  3. 可访问:SaaS模型依赖于网络为用户提供服务,所以必须确保在任何的时间点都能为用户提供数据访问服务。

为了能够打造一款成功的SaaS软件,在实施SaaS模型的过程中,应该遵循以下的步骤:

1. 提前了解业务

​ 在考虑技术和架构的问题之前,清楚的了解业务非常重要。没有了解业务,任何应用程序的设计和开发工作都无从谈起。为了得到更好的SaaS模型实施效果,必须在很早的阶段就开展需求调研工作,明确软件的业务和设计目标。调研的范围应该是充足的、广泛的,以便更好的识别出SaaS模型的业务边界和核心内容。在调研时,应该包括以下的内容:

  1. 采用什么样的架构进行设计?
  2. 用户类型和用户规模分别是什么?
  3. 如何处理应用程序的可扩展性、安全性和稳定性这三个问题?

​ 业务先于设计和开发,在非常早的阶段便开展调研和需求分析工作非常重要,它有助于评估项目的成本、周期和风险。除此之外,还应该重视现有系统在改造工程中所面临的挑战。

2.选择能胜任的团队

​ 物尽其用,人尽其才,无论技术和架构如何,选择适合SaaS模型的技术团队尤为重要。在实施SaaS模型之前,决策者应该选择一支由经验丰富的开发人员组成的团队,他们必须对SaaS模型的概念有深入的了解,且该团队应该拥有多种专业技术的人员,同时对SaaS行业中应该遵循的最佳实践有充分的了解。

3.设计可扩展的基础架构

一旦技术团队了解完业务需求,下一步的重点就是构建可扩展的基础架构组件:

  1. 数据中心
  2. 网络设施:网络安全(如HTTPS加密、认证和鉴权)
  3. 硬件设施:操作系统和存储
  4. 数据备份和系统监控工具

​ 除了上述之外,内部的评审工作也应该同步进行,以便在构建基础架构是评估应用系统的总体成本,在最终决定实施SaaS模型前,还需考虑一下因素(成本驱动):

  1. SaaS软件的成熟度模型
  2. 可伸缩性和可用性
  3. 客户端兼容性
  4. 数据备份和恢复
  5. 网络带宽和存储空间
  6. 安全管理(加密级别和安全证书)

4.网络带宽和应用服务器

​ 这点非常重要,为了达到良好的用户体验效果,用户规模和类型决定了带宽的大小和应用服务的数量。因此需要综合考虑SaaS模型的带宽大小和应用服务器的数量,合适的搭配能够消除数据中心的网络瓶颈问题。如果不自行构建数据中心,选择外包托管(如阿里云,腾讯云,亚马逊)方式,则应该考虑以下因素:

  1. 数据中心是否高可用(7x24x365)?
  2. 是否具备监控和灾备能力(容灾和数据恢复)?
  3. 是否具备数据安全防护能力(防攻击,防破解)?
  4. 是否具备可测试能力(完备的API测试接口,沙盒测试环境,SDK开发包)?

5.构建基础架构组件

​ 基础设施设计完成后,我们须准备可靠的、稳定的、经过实际验证的功能组件,此过程对于确保SaaS模型的高可用性能指标尤为重要。在评估这些硬件指标时,我们还需要确保其能够在项目交付日期范围内交付所有的硬件组件并确保调试正常。

6.向基础设施中持续部署SaaS应用

​ 一旦基础设施可用,开发团队就应该持续构建和部署SaaS应用。此时应对服务器进行统一的管理和配置,根据不同的业务需要安装对应的操作系统,分配指定的IP,设置用户访问策略以及配置防火墙等。

7.容灾和数据恢复

​ 现在,SaaS应用程序已经准备就绪,下一步应该考虑的问题是如何容灾和数据恢复的问题。面对容灾和数据恢复,需要考虑一下几点:

  1. 如何应对灾难的发生(不可抗拒因素不在考虑范围)
  2. 如何缩小受灾范围以及如何减小受灾带来的影响
  3. 如何在灾难发生时,应用程序还可以局部可用
  4. 如何在有限的时间范围内完成数据恢复

8.系统监控

​ 系统监控子系统对于SaaS模型特别重要,好的应用监控子系统有助于更早的发现问题,及时的避免灾难的发生。在SaaS模型中,监控子系统应该对一下的参数进行监控:

  1. 内存和CPU的使用率
  2. 磁盘的I/O读写速率
  3. 操作系统和应用程序日志
  4. 其他应用组件(数据库、Web容器、TCP等)性能参数

9.建立客服中心

​ 一旦SaaS应用成功投放市场,它就需要有一个客户支持服务中心(面向业务或者技术)。客户支持服务中心应该具备适当的票据处理和在线沟通能力,一个优秀的SaaS模型,客户支持服务是必不可少的,它有助于提升SaaS软件的服务质量。客户支持服中心应当适时地提供邮件、短信、电话、票据等服务能力,如果SaaS模型中有需要技术团队继续关注的问题,还应该提供工单处理服务,以便在发生技术故障时,能够以最短的时间将故障信息以邮件或者短信的形式发送给相关的技术人员。

10.SaaS成熟度模型

​ 在实施SaaS模型时,选择何种等级的成熟度模型,直接决定了你需要为此付出的资金成本,同时也影响着软件的交付周期和开发风险。因此,在面对此问题时,可以先考虑第二级或者第三级SaaS软件成熟度模型,然后根据业务量和用户规模,逐步满足高性能、可配置、自定义等更高级别成熟度模型的要求,以降低实施SaaS模型的风险。SaaS软件成熟度模型评估对照表如下图所示:

11.规范化的文档

​ 在完成上述10个关键步骤后,需对整个SaaS模型做出详细的记录。如需求文档,设计文档,组件文档以及接口文档等。这些文档将帮助运维人员在运维过程中快速定位系统故障。如果SaaS模型在后期有任何的修改和升级操作,这些文档也可以帮助工作人员了解SaaS模型。

正确甄别API & REST API & RESTful API & Web Service之间的差异与联系

正确甄别API & REST API & RESTful API & Web Service之间的差异与联系

​ 看到API你会想起什么?是接口、第三方调用、还是API文档?初看你可能会觉得这太熟悉了,这不是系统开发日常系列吗?但你仔细想一想,你会发现API的概念在你脑海里是如此的模糊。如何你通过搜索引擎检索API,你会看到类似这样的信息:API——Application Programming Interface(应用程序编程接口),这太抽象了。接下来,我将结合在开发中总结的一些经验,以通俗的方式聊聊API、REST API、RESTful API以及Web Service这四者之间的联系与区别。

1、API 与 REST API

​ 什么是API?这里引述维基百科给出的定义:应用程序接口(英语:Application Programming Interface,缩写:API;又称为应用编程接口)是软件系统不同组成部分衔接的约定。这个对API的定义太过于广泛和抽象,而通俗的讲,API是一段应用程序与另一段应用程序相互“交流”的方式(协议)。在Web应用程开发中,API是我们通过网络进行数据检索的一种主要方式,API文档将告知你检索数据的URL列表、查询参数、请求方式以及响应状态,其目的是降低Web应用程序开发难度,共享两个应用程序之间的数据(文本、音频、视频、图片等),而屏蔽其内部复杂的实现细节。

​ REST是Representational State Transfer的缩写,直译过来就是:表述状态的转移。REST API是一组关于如何构建Web应用程序API的架构规则、标准或指导,或者说REST API是遵循API原则的一种架构风格。REST是专门针对Web应用程序而设计的,其目的在于降低开发的复杂度,提高系统的可伸缩性。下面是设计REST风格的系统架构时需要满足或者遵循的一些基本条件和原则:

  • 1、在REST架构中,Web中所有的事物(文本、音频、视频、图片、链接)都可以被统一的抽象为资源(resource)
  • 2、在REST架构中,每一个资源都有与之对应的唯一资源标识符(resource identifier),当资源的状态发生改变时,资源标识符不会发生改变
  • 3、在REST架构中,所有的操作都是无状态的。REST架构遵循CRUD原则,所有的资源都可以通过GET、POST、PUT和DELETE这四种行为完成对应的操作。
  • 4、可缓存(可选项),在REST架构中需要缓存来有效的处理大批量的请求
  • 5、接口一致

​ 现在,了解了API和REST API的基本概念,那这两者之间有什么异同?如果按照数学上集合的概念来解释API与REST API之间的联系与区别,API是REST API的超集,REST API 是API的子集;所有的REST API都是API,但不是所有的API都是REST API。更通俗的解释是:所有的男人都是人,但不是所有的人都是男人。

2、REST API 与RESTful API

​ 在第一小节中,了解了什么是REST API,接下来聊聊REST API与RESTful API之间的异同。很多初学者很容易将这两者等同起来,认为RESTful API就是REST API,这可能是单纯的从字面上去理解了,当你深入的去了解两者的本质后,你会发现其实不然。REST API是Web API设计的一种规范或者指导原则,而RESTful API则是这中架构设计原则或者规范的一种具体实现方式。也就是说,RESTful API是REST API的非正式实现方式,因为实现REST API的方式有很多,RESTful API只是其中一种,且没有完全满足REST API的所有设计原则,每个开发者在实现REST 架构时的则重点都会有差别。

​ 很多初学者容易将REST API与RESTful API两者的概念搞混淆,我想可能只是看字面意思,而没有关注它们本身的含义(就像认识中文字一样,有边读边,无边读中间,断章取义了)。这就好比很多人会把变性人等同于女人,变性人可能五官的表象看起来和女人一样,但变性人不能生育,它只是满足了定义一个女性的大多数条件(实现),但本质上不是女人。

​ 接下来,通过一个简单的例子以加深对REST API和RESTful API的理解。下面将给出一个执行CURD操作的RESTful API设计案例:

说明:resource代指某种资源的名称,可以时student(学生)、teacher(老师)、book(书籍)等等,通常使用名词来表示;{id}则代指某种资源的唯一标识符(resource identifier)

下面是一个具体的小例子,以学生管理为例,设计学生管理的API。学生资源包括ID,姓名和所学课程信息,学生资源信息如下:

​ 现在,我们需要将学生数据保存到数据库,然后执行查询、修改和删除学生数据的操作。学生管理API的使用者调用的API如下:

​ 前面的内容说到,API共享数据资源,而屏蔽内部实现,API的使用者(客户端)关注的是资源(读懂数据),并不需要了解API内部构造;API的提供者(服务端)只关注自己的内部实现,而不关系API使用者(客户端)的状态。为了加深对这一概念的理解,下面给出学生管理API的内部实现示例:

说明:

示例代码是基于Spring MVC进行实现的。

除了上述的内容之外,你还可以通过提供键值对的方式对查询数据进行过滤,如获取所有的学生数据时,只想获取性别为女性的学生数据,则可以通过这样的方式来完成:

[GET] http://www.example.com/students?gender=female

Tip:如果API拥有对数据过滤的功能,对应服务端的API实现代码也需要做调整。

​ 在前面的内容中,我们提到RESTful API是REST API的非正式实现方式或规范。为什么这么说呢?因为在RESTful API的设计中,我们完全可以通过GET的方式完成CURD操作,也可以通过DELETE行为来创建资源,通过POST行为来修改资源,它的实现方式并不严谨或者说并没有严格按照REST API提出的约束条件来进行。所以说RESTful API是REST API的非正式实现方式

3、REST与Web Service

3-1、什么是Web Service?

​ 如万维网联盟(W3C)所述,Web Service提供了在各种平台和/或框架上运行的不同软件应用程序之间可以进行互操作的标准方法。Web Service的特点是具有良好的互操作性和可扩展性,以及由于使用XML而可以对程序处理过程进行描述。它们可以以松散耦合的方式组合不同的服务以实现复杂的操作。提供简单服务的程序可以通过相互交互,以提供复杂的增值服务。

​ 两个Web Service之间主要通过HTTP网络协议进行通信,如我们熟知的SOA(面向服务的体系架构),主要依赖于XML-RPC和SOAP(Simple Object Access Protocol,即简单对象访问协议)。

Tip:千万不要将SOA(面向服务体系架构)和SOAP(简单对象访问协议)搞混,前者是一种架构设计形式,后者是一种数据交换协议。

​ 简单的一个示例:假设一个Web Service A提供允许其他应用程序通过URL获取用户信息的功能:[GET] http://www.abc.com/{id}。id是用户的唯一标识符,请求此URL将获得用户信息。现在假设浏览器、手机、桌面应用程序的用户都要获取服务A提供的用户信息,这三者只需要请求服务A提供的URL地址,并输入用户id信息即可。至于者三个不同客户端的实现方式(编程语言)是什么与服务A 没有任何关系,只要能够解析出服务A返回的XML文档即可。这样,应用程序之间交换数据就可以不用依赖于具体的语言和环境。这就好比不同国家不同语言的人,只要能够知晓对方语言的语法结构,两个人就可以进行交流。

3-2、Web Service的优点

​ 使用Web Service有如下的几个优点:

  • 1、互操作性:Web Service允许应用程序之间进行通信,交换数据和共享服务。
  • 2、可用性:Web Service的功能可以从简单的信息查找到复杂的算法计算。
  • 3、可重用性:Web Service之间可以相互组合,以提供更为复杂的服务,由于其互操作性的特点,可以轻松的在其他的服务中重用Web Service组件,提高了服务的重用率。
  • 4、易于部署:Web Service可以部署在基于Internet标准的容器中,如Apache、Axis2等,以提供HTTP或者WSDL(网络服务定义语言)驱动的服务。
  • 5、成本低:Web Service是通过打包成Web服务组件进行部署,从而降低了使用的成本。

3-3、Web Service的类型

​ 目前,Web Service主要有两大流派:

  • 1、基于SOAP的Web Service : SOAP(简单对象访问协议)是一种基于XML的协议,用以访问Web Service。其接口以机器可处理的格式进行描述,称为WSDL(Web服务定义语言)文档。通过使用标准的的XML文档来描述Web Service,在XML文件中,会详细记录接口的信息,如消息的格式、传输协议以及交互的位置等信息。
  • 2、基于REST的Web Service :REST(Representational State Transfer)是一种软件架构,它使用JSON来描述数据格式,最重要的是HTTP传输协议对REST来说是非必须的。

3-4、REST与SOAP的区别和联系

​ 下面,通过一张表格来对比REST与SOAP之间的异同:

总结

如上所述,我们了解了什么是API,什么是REST API,什么是RESTful API以及Web Service的相关概念。API代表应用程序编程接口,是一种较为宽泛的定义或者说是一种协议,作为软件程序之间相互通信的接口而存在。REST API是API的一个子集,所有的REST API都是API;RESTful API是对REST API架构风格的一种非正式实现方式。API与Web Service都是服务提供者和服务消费者之间的通信手段。最后,为了能够快速的识别API与Web Service之间的差异,将这两种手段的不同之处整理成对照表如下:

鱼与熊掌得兼:Hibernate与Mybatis共存

鱼与熊掌得兼:Hibernate与Mybatis共存

鱼与熊掌得兼:Hibernate与Mybatis共存

很长一段时间,网上有很多关于Hibernate与Mybatis孰优孰劣的争论,两个阵营的人谁也不能说服谁,每个人的理由都很有道理。今天,我分享的主题是:在一个项目中同时使用Hibernate和Mybatis两个ORM框架。

​ 作为一个开发者,没有必要花费过多的时间去证明技术无用论,当你开始指责某个框架垃圾,另外一个框架最好时,隐性的暴露出你对某个框架没有深入的研究,无知的指责对于技术的提升没有任何的帮助。框架本身没有对错一说,只有适合和更适合项目的选择。任何框架都有自身的能力范围,就拿Hibernate和Mybatis这两个ORM框架来说,Hibernate封装了很多有用的API给开发者,降低了操作数据库的难度和复杂度,同时也减少了模板代码的数量,但Hibernate留给开发者可操作的空间相对Mybatis少了很多;Mybatis框架使用起来很灵活,开发者可以自定义查询语句,但增加了模板代码的数量,看起来没有Hibernate那么便捷。两种框架在便捷与灵活两个指标上做出了取舍与妥协,这不能说是框架的错。对于一个框架而言,需要有自身专注的领域和设计愿景,不可能面面俱到。

​ 使用任何一种技术框架,都需要贴合现实的业务需求以及自身的技术能力。当你还没有深入的去了解一门技术或者当前业务需求无法与框架契合时,不要盲目的批判框架的好坏。今天,我不再去对比Hibernate与Mybatis两者之间的优劣,而是给出一个比较中庸的放方案,将两个ORM框架同时整合在一个项目中。

一、准备开发环境

​ 如果你想成功运行本文中的源代码,需要满足一下的几个条件:

  • 1、JDK : JDK 1.8.x及以上版本
  • 2、Maven : Maven 3.x或更高版本
  • 3、Git:版本控制工具,选择一个你喜欢的
  • 4、IDE : 选择你比较喜欢的一个代码编辑器,如STS、IntelliJ IDEA。笔者使用的是IntelliJ IDEA
  • 5、Databases : 选择一个你熟练使用的数据库系统。笔者在本文中使用的是MySQL 5.1.x版本的数据库系统

如需获取本次分享内容的源代码进调试,可以到文章末尾找到源代码仓库连接

二、搭建项目

2-1、引入依赖

​ 为了快速构建项目,笔者采用Spring Boot来构建项目,同时使用加入Spring Data JPA和Mybatis两个ORM框架的依赖包。在此需要特别说明,Hibernate是一个JPA标准的实现,尔Spring Data JPA是一个JPA数据访问抽象,通过Spring Data JPA,可以轻松使用Hibernate框架。

​ 你可以通过Spring Initializer来初始化项目,也可以通过IDEA自带的Spring Initializer功能构建项目,项目构建完成之后,pom.xml文件中的配置如下(包含但不限于文中给出的依赖项):

2-2、定义实体类-User.java

​ 为了演示同时使用Hibernate和Mybatis操作数据库,需要提供一个实体类User.java,代码如下所示:

说明:

在本次演示的项目中,使用到了Lombok插件,它可以让开发者减少模板代码的书写,提高开发速度。@Data注解可以自动生成类属性的getter、setter和toString方法。@NoArgsConstructor会自动为类生成无参构造函数,@AllArgsConstructor则会生成带全部属性的构造函数。

2-3、定义数据持久化接口

​ 在本次课程中,将使用Spring Data JPA来完成写操作,如新增、修改、删除;使用Mybatis来完成读操作,如根据用户ID查询、查询所有的用户等。Spring Data JPA和MyBatis的持久化接口都位于com.ramostear.hm.orm包下,Spring Data JPA的持久化接口相对比较简单,之间继承JpaRepository类即可,代码如下:

说明:因为JPA只负责写操作,所以直接继承并使用JpaRepository提供的API即可,不需要额外的定义其他的接口方法。

​ 下面是Mybatis的映射接口,定义了两个方法:根据ID查询用户信息和查询所有的用户信息。代码如下所示:

说明:

此接口需要注意的地方是@Component@Mapper注解,@Component注解标注此接口后,Spring会自动扫描并配置此类;@Mapper注解是把这个mapper的DAO交由Spring进行管理。

定义完Mybatis 映射接口后,需要提供一个进行数据库查询的xml配置文件。该文件位于resources/mapper文件夹中,UserMapper.xml完整代码如下:

2-4、定义UserService

​ 在UserService接口中,提供三个方法:保存用户信息、根据ID查询用户、查询所有的用户。UserService接口代码如下:

在UserService接口的实现类中,需要同时注入UserRepository和UserMapper两个依赖。我们使用构造函数的方式来注入这两个依赖。代码如下:

说明:

@Transactional注解用于设置每个方法的事务控制方式。@Service注解声明该类是一个服务提供类,且设置了该类被Spring初始化时Bean对象的名称为“userService”。

2-5、定义控制器

​ 最后,提供一个控制器,用于处理客户端的相关请求。在控制器中,提供了三个请求处理方法,分别处理客户端新增用户、根据ID查询用户和查询所有用户的请求。控制器代码如下:

说明:

在本次教程中,为了编码IDEA报警告,所有的依赖注入都采用构造函数的方式注入相关的依赖。

三、配置Hibernate和Mybatis

​ 网络上有很多关于在Spring Boot项目中配置Hibernate和Mybatis的教程,但同时配置Hibernate和Mybatis的文章很少,有一些是通过Java代码的方式对这两个ORM框架进行配置,采用的是多数据源的方法来整合两个框架。其实整合这两个框架没有想象中的那么难,只需要在application.yml或者application.properties配置文件中加入几行代码,就可以完成两个框架的整合。以application.yml配置文件为例,配置代码如下:

是不是很简单,并为没有太多复杂的配置,这是一种较为简单的整合方式。Hibernate和Mybatis共用一个数据源,如果是JPA的忠实粉丝,现在想要使用Mybatis,只需要额外加入mybatis的配置即可。

四、测试

​ 通过以上的几个步骤,整个项目已经搭建完毕,接下来将使用Postman测试工具对Controller的三个方法进行测试,验证两个ORM框架在同一个项目中是否能共存。

​ 首先测试 POST http://localhost/users ,验证Hibernate是否能够成功将用户信息持久化。打开Postman工具,在地址栏输入http://localhost/users请求地址,请求方式选择POST,在Body栏输入如下的信息:

{
    "username":"谭朝红",
    "alias":"ramostear",
    "age":28
}

点击“Send”按钮发送请求,观察服务端响应信息,测试结果如下图所示:

可以看到,服务端成功返回用户信息,且用户ID=3。接下来,我们请求 GET http://localhost/users/3 ,验证Mybatis是否能够成功查询出用户信息,测试结果如下:

通过测试,服务端成功返回了用户ID=3的用户信息:

{
  "id": 3,
  "username": "谭朝红",
  "alias": "ramostear",
  "age": 28
}

由此证明,在同一个项目中,Hibernate和Mybatis均能正常工作,整合方案有效,解决了在同一项目中Hibernate与Mybatis共存的问题。

五、总结

​ 本次课程验证了同时使用Hibernate和Mybatis两个ORM框架的方案可行,且采用了一种比较简单的方式来整合两个框架,摒弃了多数据源的复杂配置,快速实现两个框架并用的需求。

​ 在一个项目中同时使用两个ORM框架有没有实际的意义呢?我的答案是肯定的。同时使用两个ORM框架,两者之间可以相互弥补自身的不足,以达到灵活性和便捷性同时兼顾,另外一方面,在单独使用Mybatis时,开发者需要手动或者借助其他的工具生成数据库表信息,而采用本文的整合方案,Mybatis可以借助JPA自动生成数据库表的能力,从而简化使用Mybatis的步骤。最后,对于一些读多于写的系统,完全可以将这两个框架同时使用,写操作少的模块,可以使用Spring Data JPA快速完成相关功能的实现,对于读操作部分,则可以利用Mybatis来优化查询语句。两者之间的优势互补,能进一步的提升开发效率和系统性能。

本次分享内容的源代码仓库地址:

https://github.com/ramostear/orm-hm.git

5个Spring Event奇技淫巧,你GET到了吗?

5个Spring Event奇技淫巧,你GET到了吗?

5个Spring Event奇技淫巧,你GET到了吗?

前言:

谈到事件,接触过前端或GUI编程(JavaScript,Swing)的同学应该有较深刻印象。如事件源、事件的监听、回调等概念应该耳熟能详。而在Web应用程序中则很少用到事件。但在Web应用程序中,也可以轻松实现面向事件的编程。

1、为什么需要面向事件编程

​ 在本文中,将基于Spring框架来谈谈关于面向事件编程的5个奇技淫巧。在开始主要内容之前,先了解一下为什么需要面向事件编程。首先看一个生活中的一个场景来帮助我们快速的了解面向事件编程所带来的好处。以客户到银行柜台取现为例,在较早以前,银行柜台取现需要一个接一个的排长龙等待柜台业务员处理取现业务,在此期间,客户不能离开队伍去做其他的事情,否则需要重新排队。这好比常规的面向过程的编程,需要依次执行每条逻辑语句,直到所有的语句都执行完毕,方法才返回结果。现在,银行柜台取现多了一台叫号机,需要办理取现业务的客户先通过叫号机领取一张业务号,如果等待人数过多的时候,客户可以先处理自身的事情,直到柜台叫到自己的号时,才到柜台办理取现业务。这就是一个典型(不太严谨)面向事件的处理过程。首先,客户通过叫号机注册一个取现的事件,此时的叫号机,相当于事件发布器(事件注册中心),客户相当于事件源,当柜台收到有客户需要取现的消息时,就会广播提示客户办理取现业务,柜台就相当于一个事件监听器。

​ 如上所述,面向事件的编程(也称作事件驱动编程)是基于对消息信号的反应的编程。面向事件编程需要满三个条件:

  • 1、事件源:消息的源头,如点击一个按钮、访问某个页面、新增一条数据等。
  • 2、事件注册中心:事件注册中心用于存储和发布事件。
  • 3、事件监听器:用于接收特定的消息,并作出对应的反应。

下面通过一张图更为直观的了解面向事件编程的基本原理:

图 1-1 事件处理机制

2、Spring Events

​ Spring Events 是Spring Framework的一部分,但在应用程序中并不经常使用,甚至被忽略。然而,Spring的Application拥有强大的事件发布并注册事件监听器的能力,它拥有一套完整的事件发布与处理机制。Spring 4.1开始引入@EventListener注解来监听具体类型的事件,它的出现,消除了使用Spring定义一个事件监听器的复杂操作。仅仅通过一个简单的注解,就可以完成一个监听器的定义,你不需要额外的进行其他的配置;这简直太棒了。在Spring Framework中,事件的发布是由ApplicationContext提供的,如果想完成一个完整的面向事件(也称为事件驱动编程)编程,你需要遵循以下三个基本的原则:

  • 1、定义一个事件,且扩展ApplicationEvent
  • 2、事件发布者应该注入一个ApplicationEventPublisher对象
  • 3、监听器实现ApplicationListener接口或者使用@EventListener注解

在开始介绍面向事件编程的具体实施细节之前,先通过一张类图了解一下Spring的事件机制设计类图:

图 2-1 Spring Event 类图

​ 通过类图,先快速对Spring Event事件类图做一个简单的介绍:

  • 1、ApplicationEventPublisher是Spring的事件发布接口,所有的事件都是通过该接口提供的publishEvent方法发布事件的。
  • 2、ApplicationEventMulticaster是Spring事件广播器,它将提供一个SimpleApplicationEventMulticaster实现类,如果没有提供任何可用的自定义广播器,Spring将采用默认的广播规则。
  • 3、ApplicationListener是Spring事件监听接口,所有的事件监听器都需要实现此接口,并通过onApplicationEvent方法处理具体的逻辑。
  • 4、在Spring Framework中,ApplicationContext本身就具备监听器的能力,我们也可以通过ApplicationContext发布先关的事件。

​ 通过分析,可以看到:当一个事件通过ApplicationEventPublisher发布出去之后,ApplicationEventMulticaster广播器就会开始工作,它将去ApplicationContext中寻找对应的事件监听器(ApplicationListener),并执行对应监听器中的onApplicationEvent方法,从而完成事件监听的全部逻辑。

3、自定义Spring Event

​ 本次内容将使用Spring Boot 2.1.5快速搭建所需要的环境。首先将演示如何自定义一个事件,并监听该事件且对监听到的事件做出反应。

​ 我们将使用IntelliJ IDEA创建一个Web项目,并在pom.xml文件中添加如下的依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.ramostear</groupId>
    <artifactId>spring-boot-event</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-event</name>
    <description>Demo project for Spring events</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

说明:

这将是一个完整的演示Spring Event事件处理机制的项目,因为期间需要演示事务管理的特性,所以加入了MySql数据库依赖。

​ 本次的内容将依据这样一个场景展开:当成功添加一条用户信息后,将发送一条邮件到用户的邮箱账户。首先,需要定义两个实体,用户实体和邮件详情实体。具体代码如下:

接下来是邮件详情实体类:

持久化类通过扩展JPA的JpaRepository类快速获得CRUD能力。用户持久化类和邮件详情持久化类分别如下:

UserRepository.java

package com.ramostear.repository;

import com.ramostear.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * Created by Ramostear on 2019/5/31 0031.
 */
public interface UserRepository extends JpaRepository<User,Long> {
}

EmailDetailRepository.java

package com.ramostear.repository;

import com.ramostear.domain.EmailDetail;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * Created by Ramostear on 2019/5/31 0031.
 */
public interface EmailDetailRepository extends JpaRepository<EmailDetail,Long> {
}

接下来,将定义一个用户业务接口,提供创建和查找用户两个方法,代码如下:

package com.ramostear.service;

import com.ramostear.domain.User;

/**
 * Created by Ramostear on 2019/5/31 0031.
 */
public interface UserService {

    User created(User user);


    User findOne(Long id);
}

基本的环境已经准备就绪,现在开始定义定义第一个事件类:SendEmailEvent.java,其代码如下所示

如之前所述,自定义事件需要扩展ApplicationEvent类,并提供自己的实现细节。这里的事件需要为其提供一个用户ID,在接下来的监听器中,通过此ID来甄别具体的用户,并发送邮件。

接下来,我们将使用@EventListener注解实现一个事件监听器,其源码如下:

说明:

在此类中,我们注入了EmailDetailRepository和UserRepository两个依赖项,并使用@EventListener注解对sendEmail方法进行了标注。在方法中,使用了Thread.sleep(1000*3)让程序休眠3秒,以模拟发送邮件时所耗费的时间。@Slf4j注解是Lombok提供的快速使用Logger的一个快捷方式。@Component注解用于标注此类是一个普通的组件,将被Spring自动扫描并管理。

​ 准备好了事件源以及事件监听器,现在来看看如何发布一个事件。发布事件将在用户业务实现类中进行操作。UserServiceImpl.java类的实现细节如下:

​ 如前面章节所讲,如果使用Spring发布一个事件,需要在具体的类中注入一个ApplicationEventPublisher对象。在用户业务实现类中,通过构造函数的方式注入ApplicationEventPublisher对象和UserRepository持久化对象。在created(User user)方法中,当成功保存用户数据后,通过发布一个SendEmailEvent事件,将邮件发送给用户的邮箱。与此同时,我们记录了程序执行完所耗费的事件,在下一小节中我们将使用到这个数据。

​ 最后,我们定义一个控制器,提供一个处理添加用户请求的方法,验证发送邮件事件是否被监听器成功监听。控制器代码如下:

启动项目,并使用PostMan工具对POST /users API进行测试:

图 3-1 Postman测试结果

经测试,成功返回了用户信息,且耗时3530ms。我们在看看控制台的日志信息:

图 3-2 日志记录信息

​ 至此,第一个基于Spring Event的面向事件的编程案例完成。在这个案例中,虽然我们解决了事件的发送和监听的编码实现问题,但可能会有同学会想,这样子的处理方式和将发布事件的代码替换成直接调用邮件持久化类保存邮件信息没有任何区别,如同一开始说的案例,虽然有叫号机叫号,解决排队的问题,但等待的时间依旧没有改变,这和直接排队在时间耗费上没有根本的变化。那么针对这样一个问题,我们在接下来的内容中将讲解如何实现异步事件。异步事件就好比你现在从普通储蓄客户变成了VIP会员,你可以不去银行柜台叫号,而是通过电话或者其他手段预约一个时间去办理业务,银行客户经理会给你预留相应的时间,当预约时间到达之后,你才去银行柜台办理业务。

4、Spring 异步事件

​ 在该小节的基础上,将原有的事件改造成异步事件,以节省客户端等待服务端响应的时间。使用Spring实现异步事件有两种方式,第一种方式是使用@Async注解对监听器进行标注,这是最为简单的一种实现方式;第二种是通过配置类对Spring的事件广播器进行配置,设置Multicaster对异步操作的支持,此方法比第一种方法稍微复杂一些,但可以拥有对事件广播器更多的控制权。接下来,将分别介绍这两种实现方式。

4-1、使用@Async注解实现异步事件

​ 在现有的工程中,我们需要对两个类进行改造。第一处改造是在项目主类上加入@EnableAsync注解,开启对异步操作的支持。代码如下:

package com.ramostear;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
public class SpringBootEventApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootEventApplication.class, args);
    }
}

第二步是改造监听器,在原来的方法上加入@Async注解,其余部分保持不变,代码如下:

    @Async
    @EventListener
    public void sendEmail(SendEmailEvent event){
       ...
    }

通过上述两个步骤,我们就将Spring默认的同步事件改造为异步事件。重新启动项目,依旧使用Postman对接口POST /users进行测试,结果如下:

图 4-1 @Async异步事件

通过上图我们可以看到,服务端成功返回用户信息,注意观察,从客户端发起请求到服务端响应结束总共耗时480ms,而在第一个案例中,请求和响应的总耗时为3530ms。最后我们再来看看控制台的日志信息,确定一下数据库是否成功保存用户邮件:

图 4-2 @Async异步事件控制台日志信息

通过上述的测试结果,我们可以看到,异步事件已经生效,在相同的环境下,同样的业务请求,使用同步事件和异步事件,服务端响应时间存在巨大的差异。同步事件耗时3530ms,异步事件耗时480ms,这就是异步事件带来的巨大优势。

4-2 、使用配置文件开启对异步事件的支持

​ 在4-1中,我们学会了如何使用@Async定义异步事件,那本小节将展示另外一种方式获取对异步事件的支持。首先需要定义一个配置类,该类用于配置SimpleApplicationEventMulticaster类对于异步事件的支持。其源码如下:

package com.ramostear.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.core.task.SimpleAsyncTaskExecutor;

/**
 * Created by Ramostear on 2019/5/31 0031.
 */
@Configuration
public class AsyncSpringEventConfig {

    @Bean(name = "applicationEventMulticaster")
    public ApplicationEventMulticaster applicationEventMulticaster(){
        SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
        multicaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
        return multicaster;
    }
}

​ 在此配置文件中,applicationEventMulticaster方法将返回一个SimpleApplicationEventMulticaster对象。在返回此事件发布器之前,我们为此事件发布器设置了一个任务执行器:SimpleAsyncTaskExecutor,当应用程序中有新的异步事件时,将会调用此异步任务执行器的submitListenable(Callable task)方法,发布异步事件任务,并选择相应的监听器处理事件。定义好配置类后,还需要将主类中的@EnableAsync和监听器中的@Async两个注解注释掉,其余部分保持不变。最后重启项目,并测试API接口。

图 4-3 异步事件发布器配置测试

如图4-3所示,测试成功返回用户信息,其请求和响应总耗时为492ms。通过配置的方式开启Spring对异步事件的支持已生效。

5、事件过滤

​ 如果我们想对用户事件进行限制,某一部分满足条件的用户才发送邮件,可以使用condition属性来指定限制的条件。这就好比银行柜台提供的VIP预约取现业务,它分不同等级的VIP,只有达到某个条件时,才享有预约服务。在原来的基础上,我们做如下的改动,当用户ID大于5时,才发送邮件给用户。代码如下:

    @EventListener(condition = "#event.userId > 5")
    public void sendEmail(SendEmailEvent event){
       ...
    }

现在,数据库中用户ID最大为3,我们先添加两个用数据,看邮件详情表是否会新增数据。启动项目,测试API接口:

图 5-1 条件过滤测试一

服务端成功返回结果,用户ID=4,现在看看数据库是否有用户邮件数据被添加:

图 5-2 数据库查询结果一

从查询结果可以看到,并未给ID=4的用户发送邮件。控制台也只输出了插入用户数据的SQL语句:

Hibernate: 
    insert 
    into
        t_user
        (email, first_name, last_name) 
    values
        (?, ?, ?)
2019-06-03 06:14:19.334  INFO 4652 --- [p-nio-80-exec-2] c.r.service.impl.UserServiceImpl         : total times :58

接下来我们再新增两条用户数据,观察当用户ID=6时,是否会为用户发送邮件。图5-3是当用户ID=6时,数据库的查询结果以及控制台的输出:

图 5-3 数据库查询结果二

此时,控制台的输出信息如下所示:

图 5-4 控制台日志信息

由此可以看出,我们的事件过滤条件已生效。

6、事务处理

​ 现在,Spring Event的相关使用技巧以及介绍了大部分,最后让我们来考虑这样一个问题,如果在保存用户的时候发生错误,事件将会怎么样?在监听器处理事件时,保存用户的事务还未提交会怎么样?按照常规的逻辑,当用户持久化事务未提交,或者保存过程中发生异常时,不应该给用户发送邮件。试想一下,如果本次案例的场景是发生在用户注册的业务中,如果用户尚未注册成功,就给用户发送一封邮件,那是多么尴尬的事情。这就涉及到事件的事务控制。在Spring中,可以使用@TransactionalEventListener注解对监听器进行标注,一旦使用此注解,被标注的方法将被纳入到事务管理的范围。此注解是@EventListener的扩展,它允许你将事件的监听绑定到某个事务阶段。可以通过phase属性对事务阶段进行设置。下面是phase可设置的所有事务阶段:

  • 1、AFTER_COMMIT(默认值),用于在事务成功完成后出发事件
  • 2、AFTER_ROLLBACK,用于事务回滚是出发事件
  • 3、AFTER_COMPLETION,用于事务完成后出发事件(AFTER_COMMIT和AFTER_ROLLBACK的一种模糊表述)
  • 4、BEFORE_COMMIT,用于事务提交前出发事件

最后给出一个简单的示例:

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT,condition = "#event.userId >=5")
    public void sendEmail(SendEmailEvent event){
        Optional<User> optional = userRepository.findById(event.getUserId());
        if(optional.isPresent()){
            User user = optional.get();
            log.info("User Detail:{ id:{},firstName:{},email:{}",user.getId(),user.getFirstName(),user.getEmail());
            try {
                Thread.sleep(1000*3);
            } catch (InterruptedException e) {
                log.error(e.getLocalizedMessage());
            }
            emailDetailRepository.save(EmailDetail.builder().
                    userId(user.getId()).
                    email(user.getEmail()).
                    sendTime(new Date()).
                    build());
            log.info("User email already send.");
        }
    }

其余代码保持不变。

总结

以上就是关于Spring Event的5个实用技巧,如果你想了解更多细节内容,我已将全部源码上传至Github仓库,你可以通过此链接获取全部源码。如有问题,请在文章最后给我留言。

(七讲)Spring Boot Annotations 全家桶快速通

(七讲)Spring Boot Annotations 全家桶快速通

本次课程将对Spring Boot 中所涉及到的注解进行全部的梳理,将开发过程中常用的Annotation进行介绍并给出相应的代码示例。帮助开发者统一的、规范的、全面的学习并掌握Spring Framework Annotations的用法。

1、Spring Web MVC 与Spring Bean 注解

1-1、Spring Web MVC 注解

1-1-1、@RequestMapping

@RequestMapping注解的主要用途是将Web请求与请求处理类中的方法进行映射。Spring MVC和Spring WebFlux都通过RquestMappingHandlerMappingRequestMappingHndlerAdapter两个类来提供对@RequestMapping注解的支持。

@RequestMapping注解对请求处理类中的请求处理方法进行标注;@RequestMapping注解拥有以下的六个配置属性:

  • value:映射的请求URL或者其别名
  • method:兼容HTTP的方法名
  • params:根据HTTP参数的存在、缺省或值对请求进行过滤
  • header:根据HTTP Header的存在、缺省或值对请求进行过滤
  • consume:设定在HTTP请求正文中允许使用的媒体类型
  • product:在HTTP响应体中允许使用的媒体类型

提示:在使用@RequestMapping之前,请求处理类还需要使用@Controller@RestController进行标记

下面是使用@RequestMapping的两个示例:

@RequestMapping还可以对类进行标记,这样类中的处理方法在映射请求路径时,会自动将类上@RequestMapping设置的value拼接到方法中映射路径之前,如下:

1-1-2、@RequestBody

@RequestBody在处理请求方法的参数列表中使用,它可以将请求主体中的参数绑定到一个对象中,请求主体参数是通过HttpMessageConverter传递的,根据请求主体中的参数名与对象的属性名进行匹配并绑定值。此外,还可以通过@Valid注解对请求主体中的参数进行校验。下面是一个使用@RequestBody的示例:

1-1-3、@GetMapping

@GetMapping注解用于处理HTTP GET请求,并将请求映射到具体的处理方法中。具体来说,@GetMapping是一个组合注解,它相当于是@RequestMapping(method=RequestMethod.GET)的快捷方式。下面是@GetMapping的一个使用示例:

1-1-4、@PostMapping

@PostMapping注解用于处理HTTP POST请求,并将请求映射到具体的处理方法中。@PostMapping@GetMapping一样,也是一个组合注解,它相当于是@RequestMapping(method=HttpMethod.POST)的快捷方式。下面是使用@PostMapping的一个示例:

1-1-5、@PutMapping

@PutMapping注解用于处理HTTP PUT请求,并将请求映射到具体的处理方法中,@PutMapping是一个组合注解,相当于是@RequestMapping(method=HttpMethod.PUT)的快捷方式。下面是使用@PutMapping的一个示例:

1-1-6、@DeleteMapping

@DeleteMapping注解用于处理HTTP DELETE请求,并将请求映射到删除方法中。@DeleteMapping是一个组合注解,它相当于是@RequestMapping(method=HttpMethod.DELETE)的快捷方式。下面是使用@DeleteMapping的一个示例:

1-1-7、@PatchMapping

@PatchMapping注解用于处理HTTP PATCH请求,并将请求映射到对应的处理方法中。@PatchMapping相当于是@RequestMapping(method=HttpMethod.PATCH)的快捷方式。下面是一个简单的示例:

1-1-8、@ControllerAdvice

@ControllerAdvice@Component注解的一个延伸注解,Spring会自动扫描并检测被@ControllerAdvice所标注的类。@ControllerAdvice需要和@ExceptionHandler@InitBinder以及@ModelAttribute注解搭配使用,主要是用来处理控制器所抛出的异常信息。首先,我们需要定义一个被@ControllerAdvice所标注的类,在该类中,定义一个用于处理具体异常的方法,并使用@ExceptionHandler注解进行标记。此外,在有必要的时候,可以使用@InitBinder在类中进行全局的配置,还可以使用@ModelAttribute配置与视图相关的参数。使用@ControllerAdvice注解,就可以快速的创建统一的,自定义的异常处理类。下面是一个使用@ControllerAdvice的示例代码:

1-1-9、@ResponseBody

@ResponseBody会自动将控制器中方法的返回值写入到HTTP响应中。特别的,@ResponseBody注解只能用在被@Controller注解标记的类中。如果在被@RestController标记的类中,则方法不需要使用@ResponseBody注解进行标注。@RestController相当于是@Controller@ResponseBody的组合注解。下面是使用该注解的一个示例:

1-1-10、@ExceptionHandler

@ExceptionHander注解用于标注处理特定类型异常类所抛出异常的方法。当控制器中的方法抛出异常时,Spring会自动捕获异常,并将捕获的异常信息传递给被@ExceptionHandler标注的方法。下面是使用该注解的一个示例:

1-1-11、@ResponseStatus

@ResponseStatus注解可以标注请求处理方法。使用此注解,可以指定响应所需要的HTTP STATUS。特别地,我们可以使用HttpStauts类对该注解的value属性进行赋值。下面是使用@ResponseStatus注解的一个示例:

1-1-12、@PathVariable

@PathVariable注解是将方法中的参数绑定到请求URI中的模板变量上。可以通过@RequestMapping注解来指定URI的模板变量,然后使用@PathVariable注解将方法中的参数绑定到模板变量上。特别地,@PathVariable注解允许我们使用value或name属性来给参数取一个别名。下面是使用此注解的一个示例:

​ 模板变量名需要使用“{ }”进行包裹,如果方法的参数名与URI模板变量名一致,则在@PathVariable中就可以省略别名的定义。下面是一个简写的示例:

提示:如果参数是一个非必须的,可选的项,则可以在@PathVariable中设置require = false

1-1-13、@RequestParam

@RequestParam注解用于将方法的参数与Web请求的传递的参数进行绑定。使用@RequestParam可以轻松的访问HTTP请求参数的值。下面是使用该注解的代码示例:

该注解的其他属性配置与@PathVariable的配置相同,特别的,如果传递的参数为空,还可以通过defaultValue设置一个默认值。示例代码如下:

1-1-14、@Controller

@Controller@Component注解的一个延伸,Spring会自动扫描并配置被该注解标注的类。此注解用于标注Spring MVC的控制器。下面是使用此注解的示例代码:

1-1-15、@RestController

@RestController是在Spring 4.0开始引入的,这是一个特定的控制器注解。此注解相当于@Controller@ResponseBody的快捷方式。当使用此注解时,不需要再在方法上使用@ResponseBody注解。下面是使用此注解的示例代码:

1-1-16、@ModelAttribute

​ 通过此注解,可以通过模型索引名称来访问已经存在于控制器中的model。下面是使用此注解的一个简单示例:

@PathVariable@RequestParam注解一样,如果参数名与模型具有相同的名字,则不必指定索引名称,简写示例如下:

特别地,如果使用@ModelAttribute对方法进行标注,Spring会将方法的返回值绑定到具体的Model上。示例如下:

在Spring调用具体的处理方法之前,被@ModelAttribute注解标注的所有方法都将被执行。

1-1-17、@CrossOrigin

@CrossOrigin注解将为请求处理类或请求处理方法提供跨域调用支持。如果我们将此注解标注类,那么类中的所有方法都将获得支持跨域的能力。使用此注解的好处是可以微调跨域行为。使用此注解的示例如下:

1-1-18、@InitBinder

@InitBinder注解用于标注初始化WebDataBinider的方法,该方法用于对Http请求传递的表单数据进行处理,如时间格式化、字符串处理等。下面是使用此注解的示例:

1-2、Spring Bean 注解

​ 在本小节中,主要列举与Spring Bean相关的4个注解以及它们的使用方式。

1-2-1、@ComponentScan

@ComponentScan注解用于配置Spring需要扫描的被组件注解注释的类所在的包。可以通过配置其basePackages属性或者value属性来配置需要扫描的包路径。value属性是basePackages的别名。此注解的用法如下:

1-2-2、@Component

@Component注解用于标注一个普通的组件类,它没有明确的业务范围,只是通知Spring被此注解的类需要被纳入到Spring Bean容器中并进行管理。此注解的使用示例如下:

1-2-3、@Service

@Service注解是@Component的一个延伸(特例),它用于标注业务逻辑类。与@Component注解一样,被此注解标注的类,会自动被Spring所管理。下面是使用@Service注解的示例:

1-2-4、@Repository

@Repository注解也是@Component注解的延伸,与@Component注解一样,被此注解标注的类会被Spring自动管理起来,@Repository注解用于标注DAO层的数据持久化类。此注解的用法如下:

2、Spring Dependency Inject 与 Bean Scops注解

2-1、Spring DI注解

2-1-1、@DependsOn

@DependsOn注解可以配置Spring IoC容器在初始化一个Bean之前,先初始化其他的Bean对象。下面是此注解使用示例代码:

2-1-2、@Bean

@Bean注解主要的作用是告知Spring,被此注解所标注的类将需要纳入到Bean管理工厂中。@Bean注解的用法很简单,在这里,着重介绍@Bean注解中initMethod和destroyMethod的用法。示例如下:

2-2、Scops注解

2-2-1、@Scope

@Scope注解可以用来定义@Component标注的类的作用范围以及@Bean所标记的类的作用范围。@Scope所限定的作用范围有:singleton、prototype、request、session、globalSession或者其他的自定义范围。这里以prototype为例子进行讲解。当一个Spring Bean被声明为prototype(原型模式)时,在每次需要使用到该类的时候,Spring IoC容器都会初始化一个新的改类的实例。在定义一个Bean时,可以设置Bean的scope属性为prototype:scope=“prototype”,也可以使用@Scope注解设置,如下:

@Scope(value=ConfigurableBeanFactory.SCOPE_PROPTOTYPE)

下面将给出两种不同的方式来使用@Scope注解,示例代码如下:

2-2-2、@Scope 单例模式

​ 当@Scope的作用范围设置成Singleton时,被此注解所标注的类只会被Spring IoC容器初始化一次。在默认情况下,Spring IoC容器所初始化的类实例都为singleton。同样的原理,此情形也有两种配置方式,示例代码如下:

3、容器配置注解

3-1、@Autowired

@Autowired注解用于标记Spring将要解析和注入的依赖项。此注解可以作用在构造函数、字段和setter方法上。

3-1-1、作用于构造函数

​ 下面是@Autowired注解标注构造函数的使用示例:

3-1-2、作用于setter方法

​ 下面是@Autowired注解标注setter方法的示例代码:

3-1-3、作用于字段

@Autowired注解标注字段是最简单的,只需要在对应的字段上加入此注解即可,示例代码如下:

3-2、@Primary

​ 当系统中需要配置多个具有相同类型的bean时,@Primary可以定义这些Bean的优先级。下面将给出一个实例代码来说明这一特性:

输出结果:

this is send DingDing method message.

3-3、@PostConstruct@PreDestroy

​ 值得注意的是,这两个注解不属于Spring,它们是源于JSR-250中的两个注解,位于common-annotations.jar中。@PostConstruct注解用于标注在Bean被Spring初始化之前需要执行的方法。@PreDestroy注解用于标注Bean被销毁前需要执行的方法。下面是具体的示例代码:

3-4、@Qualifier

​ 当系统中存在同一类型的多个Bean时,@Autowired在进行依赖注入的时候就不知道该选择哪一个实现类进行注入。此时,我们可以使用@Qualifier注解来微调,帮助@Autowired选择正确的依赖项。下面是一个关于此注解的代码示例:

4、Spring Boot注解

4-1、@SpringBootApplication

@SpringBootApplication注解是一个快捷的配置注解,在被它标注的类中,可以定义一个或多个Bean,并自动触发自动配置Bean和自动扫描组件。此注解相当于@Configuration@EnableAutoConfiguration@ComponentScan的组合。在Spring Boot应用程序的主类中,就使用了此注解。示例代码如下:

@SpringBootApplication
public class Application{

    public static void main(String [] args){
        SpringApplication.run(Application.class,args);
    }

}

4-2、@EnableAutoConfiguration

@EnableAutoConfiguration注解用于通知Spring,根据当前类路径下引入的依赖包,自动配置与这些依赖包相关的配置项。

4-3、@ConditionalOnClass@ConditionalOnMissingClass

​ 这两个注解属于类条件注解,它们根据是否存在某个类作为判断依据来决定是否要执行某些配置。下面是一个简单的示例代码:

@Configuration
@ConditionalOnClass(DataSource.class)
class MySQLAutoConfiguration {
    //...
}

4-4、@ConditionalOnBean@ConditionalOnMissingBean

​ 这两个注解属于对象条件注解,根据是否存在某个对象作为依据来决定是否要执行某些配置方法。示例代码如下:

@Bean
@ConditionalOnBean(name="dataSource")
LocalContainerEntityManagerFactoryBean entityManagerFactory(){
    //...
}

@Bean
@ConditionalOnMissingBean
public MyBean myBean(){
    //...
}

4-5、@ConditionalOnProperty

@ConditionalOnProperty注解会根据Spring配置文件中的配置项是否满足配置要求,从而决定是否要执行被其标注的方法。示例代码如下:

@Bean
@ConditionalOnProperty(name="alipay",havingValue="on")
Alipay alipay(){
    return new Alipay();
}

4-6、@ConditionalOnResource

​ 此注解用于检测当某个配置文件存在使,则触发被其标注的方法,下面是使用此注解的代码示例:

@ConditionalOnResource(resources = "classpath:website.properties")
Properties addWebsiteProperties(){
    //...
}

4-7、@ConditionalOnWebApplication@ConditionalOnNotWebApplication

​ 这两个注解用于判断当前的应用程序是否是Web应用程序。如果当前应用是Web应用程序,则使用Spring WebApplicationContext,并定义其会话的生命周期。下面是一个简单的示例:

@ConditionalOnWebApplication
HealthCheckController healthCheckController(){
    //...
}

4-8、@ConditionalExpression

​ 此注解可以让我们控制更细粒度的基于表达式的配置条件限制。当表达式满足某个条件或者表达式为真的时候,将会执行被此注解标注的方法。

@Bean
@ConditionalException("${localstore} && ${local == 'true'}")
LocalFileStore store(){
    //...
}

4-9、@Conditional

@Conditional注解可以控制更为复杂的配置条件。在Spring内置的条件控制注解不满足应用需求的时候,可以使用此注解定义自定义的控制条件,以达到自定义的要求。下面是使用该注解的简单示例:

@Conditioanl(CustomConditioanl.class)
CustomProperties addCustomProperties(){
    //...
}

总结

本次课程总结了Spring Boot中常见的各类型注解的使用方式,让大家能够统一的对Spring Boot常用注解有一个全面的了解。由于篇幅的原因,关于Spring Boot不常用的一些注解,将在下一次分享中进行补充和说明。

架构师必备技能指南:SaaS(软件即服务)架构设计

架构师必备技能指南:SaaS(软件即服务)架构设计

架构师必备技能指南:SaaS(软件即服务)架构设计

SaaS(软件即服务)平台架构设计指南

1、介绍

从计算机诞生开始,就伴随着计算机应用程序的演变。简短的回顾历史,我们可以清楚的看到应用程序发生的巨大变化。上世纪70年代中期,随着个人PC机的爆炸式增长以及程序员的崛起,让计算机的计算能力得到了大跨越的提升,个人PC机上可以运行非常复杂的应用程序。

进入上世纪80年代,随着Bulletin Board System(简称:BBS)电子公告板系统的兴起,它可以为广大PC机用户提供基本的在线服务,如在线聊天、电子邮件、消息发送和文件下载。由于受到那个时代计算机网络传输速度的限制,在线服务的响应速度慢,交互体验差是最大的通病。

进入90年代中后期,随着万维网的出现,计算机的计算能开始进入快速提升阶段,加之网络基础设施的持续完善,计算机网络技术也随之发展起来,这让Web网站可以提供功能多元化和更为复杂的在线服务,直到今天,我们所看到的互联网(或云)开发的在线服务应用程序。

在这段计算机技术快速成长的时间里,计算机软件到底发生了哪些变化?从历史的发展中,我们可以看到,应用程序本身没有发生本质的变化(程序=数据结构+算法),变化的是软件的供需方式发生了改变。现在,应用程序消费者不需要再在他们的PC机上下载和安装特定的应用程序,即可获得软件所提供的计算服务。在云计算技术的支持下,消费者(企业或个人)只需要使用Web工具(浏览器)访问并登录软件提供商的Web系统,通过简单的配置,就可以获得自己所需应用程序服务。这种通过网络即可使用软件的服务,即使SaaS(软件即服务)。

图 1-1 2015中国SaaS生态

在本篇文章中,我们将着重介绍SaaS架构设计,并围绕WHAT(是什么?)、WHY(为什么?)、WHERE(在哪里?)和HOW(怎么样?)这四个问题,对以下的几点进行阐述:

图 1-2 文章结构
  • 1、什么是SaaS平台?
  • 2、为什么需要使用SaaS平台架构?
  • 3、SaaS平台主要的特性和优势有哪些?
  • 4、SaaS平台适合在什么领域进行实施?
  • 5、SaaS平台有哪些先天性的缺陷?
  • 6、SaaS平台有哪些核心的组件?
  • 7、实施SaaS架构设计时的注意事项有哪些?

2、什么是SaaS平台?

图 2-1 SaaS组成结构

在你决定实施SaaS品台架构设计前,你有必要先了解SaaS平台是什么。从宏观的角度来看,SaaS是一种软件应用程序交付方式,软件提供商集中化托管一个或多个软件应用程序,并通过互联网向租户体用这些软件应用程序。从分类上看,SaaS(软件即服务)也是云计算重要的一部分。目前国内主流的云服务提供商如阿里云、百度云、腾讯云等,为广大用户提供了不同业务需求的云服务,它们大致可以分为以下几类:

  • 1、基础设施即服务:如CPU、Network、Disk和Memory等
  • 2、平台即服务:如阿里云服务器和云数据库等
  • 3、软件即服务:阿里短信、阿里邮箱等
  • 4、数据即服务:如阿里云对象存储,七牛云存储等
  • 5、其他软件服务:机器学习、人工智能等

SaaS应用程序的任何更新或者修复漏洞操作都是由软件提供商负责实施和处理的,由于租户是通过互联网获取软件服务,所以租户端无需下载任何的升级包或者修复补丁,是一种开箱即获取最新软件产品的服务方式。

通过对什么是SaaS的介绍,接下来,我们了解一下选择SaaS作为软件架构来设计产品的一些理由。

3、为什么选择SaaS?

我们将从不同的角度来阐述几个为什么选择SaaS的理由。透过对这些因素的分析,为你是否需要将自己的软件SaaS化提供一定的参考依据。

3.1、消费者角度

获取软件服务的方式足够简单,SaaS也许是迄今为止使用软件最简单的方式之一,租户只需要动动鼠标和键盘,即可在几小时甚至几分钟内获得一个大型的软件服务。相比于传统使用软件的方式,租户省去了研发、部署、运维等一系列繁复的过程,且获得软件的时间和费用成本都大幅度降低。

3.2、商业角度

SaaS可以体用跨地域、跨平台的软件服务。与此同时,软件服务商可以统一对软件进行版本管理,这将带来以下几点好处(包括但不限于):

  • 1、缩短产品上线时间:多端适配,统一版本,统一更新
  • 2、降低维护成本:不需要同时维护多个版本的软件实例,运维压力减小
  • 3、容易升级:由于版本得到有效控制,一次升级,即可覆盖所有租户端

4、SaaS的特性和优势

我们将SaaS应用程序与传统的桌面应用程序做一个水平的对比,部署一个SaaS产品将可以获得以下的几点优势。

4.1、简单

SaaS化的产品通过互联网向租户提供软件服务,随着Web技术(如jQuery、Node.js)的进步,Web页面的交互体验度大幅度提升,交互更流畅、更人性化。与传统的桌面应用程序的人机交互效果相差无几。

4.2、经济实惠

SaaS化产品可以为租户提供弹性的付费方案,如按日、按月、按年、按使用人数或者按使用量进行计费,它将给租户提供更经济的使用软件的财务预算表。

4.3、安全

使用SaaS产品无需担心数据安全问题,这好比将钱存入银行一样安全。相较于企业内部部署的软件系统而言,SaaS产品具备更高的安全保障能力,因为软件提供商具有更多软件安全防护的技术资源、人力资源和财政资源。

4.4、兼容性

与传统软件相比、SaaS软件的兼容性更好,它没有传统软件的多本版维护问题和操作系统兼容问题。在SaaS软件中,租户用户在使用软件的过程中,几乎上感觉不到软件发生了改变。当租户用户登录到系统上时,就已经获得了最新版本的软件。

5、SaaS软件的适用范围

SaaS产品具有广泛的适应范围,特别是与其他云产品(如IaaS(基础设施即服务)和PaaS(平台即服务))配合使用时这种能力表现尤为突出,例如阿里云之类的云计算技术允许你配置可托管的Web站点、数据库服务器等。你只需要打开浏览器并登录到阿里云控制台,通过操作对应的控制面板,即可获得相关的软件服务。

从理论上讲,SaaS可以将任何的软件SaaS,下面列举一些通用的分类供大家参考:

  • 1、Office在线办公类SaaS产品
  • 2、电子邮件和即时消息类SaaS产品
  • 3、社交媒体类SaaS产品
  • 4、第三方API类SaaS产品
  • 5、安全和访问控制类SaaS产品
  • 6、机器学习类SaaS产品
  • 7、人工智能类SaaS产品
  • 8、地理位置服务类SaaS产品
  • 9、数据流和数据检索类SaaS产品

6、SaaS产品的天生缺陷

图 6-1 SaaS产品的缺点

从上图我们可以直观的看到,SaaS产品与生俱来的几个缺陷,接下来我们将逐一进行描述。

6.1、软件控制权

与企业内部部署的软件不同,由于SaaS软件被击中托管在服务提供商的Web服务器中,所以租户无法控制所有的软件应用程序,SaaS化的软件比企业自行部署的软件获得的控制权更少,租户可操作的自定义控制权极度有限。

6.2、消费者基数小

由于SaaS软件是将一套应用程序共享给一个或者多个租户共同使用,这种共享的消费方式还未被大多数的消费者所接受。同时,受制于市场环境的影响,目前还有大多数的软件还未SaaS化。

6.3、性能瓶颈

共享应用程序必然会带来服务器性能的下降、如计算速度、网络资源、I/O读写等都将面临严峻的考验。在性能方面,企业内部部署的“独享模式”的应用程序比SaaS软件的“共享模式”略胜一筹。

6.4、安全问题

当租户在选择一款SaaS产品时,产品的安全性将会被放置在第一位进行考虑。如数据的隔离、敏感数据的加密、数据访问权限控制、个人隐私等问题。在2018年5月25日,GDPR(General Data Protection Regulation)《通用数据保护条例》出现之后,越来越多的人开始重视数据安全问题。如何最大程度的打消租户的这一顾虑,需要服务提供商加强对自身信誉度的提升,以赢得租户的信赖。

7、SaaS产品的核心组件

不同类型的SaaS产品,由于要面对不同的用户愿景,可能在功能和业务上会有所不同,但任何一个SaaS产品,都具备以下几个共同的核心组件。

图 7-1 SaaS 核心组件

7.1、安全组件

在SaaS产品中,系统安全永远是第一位需要考虑的事情,如何保障租户数据的安全,是你首要的事情。这如同银行首选需要保障储户资金安全一样。安全组件就是统一的对SaaS产品进行安全防护,保障系统数据安全。

7.2、数据隔离组件

安全组件解决了用户数据安全可靠的问题,但数据往往还需要解决隐私问题,各企业之间的数据必须相互不可见,即相互隔离。在SaaS产品中,如何识别、区分、隔离个租户的数据时你在实施SaaS平台架构设计时需要考虑的第二个问题。

7.3、可配置组件

尽管SaaS产品在设计之初就考虑了大多数通用的功能,让租户开箱即用,但任然有为数不少的租户需要定制服务自身业务需求的配置项,如UI布局、主题、标识(Logo)等信息。正因为无法抽象出一个完全通用的应用程序,所以在SaaS产品中,你需要提供一个可用于自定义配置的组件。

7.4、可扩展组件

随着SaaS产品业务和租户数量的增长,原有的服务器配置将无法继续满足新的需求,系统性能将会与业务量和用户量成反比。此时,SaaS产品应该具备水平扩展的能力。如通过网络负载均衡其和容器技术,在多个服务器上部署多个软件运行示例并提供相同的软件服务,以此实现水平扩展SaaS产品的整体服务性能。为了实现可扩展能力,就需要SaaS展示层的代码与业务逻辑部分的代码进行分离,两者独立部署。例如使用VUE+微服务构建前后端分离且可水平进行扩展的分布式SaaS应用产品。对于可扩展,还有另外一种方式,即垂直扩展,其做法比较简单,也比较粗暴:通过增加单台服务器的配置,如购买性能更好的CUP、存储更大的内存条、增大带宽等措施,让服务器能够处理更多的用户请求。但此做法对于提升产品性能没有质的改变,且成本很高。

7.5、0停机时间升级产品

以往的软件在升级或者修复Bug是,都需要将运行的程序脱机一段时间,等待升级或修复工作完成后,再重新启动应用程序。而SaaS产品则需要全天候保障服务的可用性。这就需要你考虑如何实现在不重启原有应用程序的情况下,完成应用程序的升级修复工作。

7.6、多租户组件

要将原有产品SaaS化,就必须提供多租户组件,多租户组件是衡量一个应用程序是否具备SaaS服务能力的重要指标之一。SaaS产品需要同时容纳多个租户的数据,同时还需要保证各租户之间的数据不会相互干扰,保证租户中的用户能够按期望索引到正确的数据,多租户组件是你必须要解决的一个问题。其余的组件都将围绕此组件展开各自的业务。

总结

本文将软件应用程序的发展历程作为切入点,并围绕WHAT(是什么?)、WHY(为什么?)、WHERE(在哪些领域实施?)和HOW(怎么样?)这四个问题对SaaS展开了介绍。文中详细的阐述了基于SaaS架构的软件设计需要注意的问题,并分析了SaaS产品的特性、有点、缺点。最后还介绍了基于SaaS架构的软件产品应该具备的几个核心组件以及他们各自的作用。希望本次能够让你对SaaS平台架构有一个全面的了解,并且在你准备实施SaaS平台架构设计前能够提供一些价值的参考信息。