引言
写这篇博文的原因是想做出改变,做自己想做的事情,找回真正的自己,希望能帮助到屏幕前的你。
这篇学习笔记我是围绕《Spring实战》第4版这本书展开的,作者是 Craig Walls,是Pivotal公司的高级工程师,是Spring Social和Spring Sync的项目领导者,这本书是最畅销的Spring教科书,也是我选择这本书学习Spring的原因。好了,我们进入正题。
第一部分 Spring core
第一章Spring基础
1.1简化java开发
首先,spring是一个开源的java框架(作者是Rod Johnson,请记住他~技术的进步离不开这些伟大的先行者),诞生的目的就是简化企业级应用开发,利用Spring可以让简单的javabean实现以前只有EJB(Enterprise Java Bean)才能完成的任务。并且spring不仅仅可以应用在服务器端,它可以使任何java应用都变得简单、可测试和松耦合。所以请记住一句话:Spring最根本的使命就是简化java开发
那spring是如何做到这一点的?
spring采取了四种关键策略:
- 基于pojo的轻量级和最小入侵性编程
- 通过依赖注入和面向接口实现松耦合
- 基于切面和惯例进行声明式编程
- 通过切面和模板减少样板式代码
我们逐个分析
如果你是一个资深的java工程师,那么你一定经历过一段难熬的时间,那个时候很多java框架都会强迫java应用去继承它们的类或者实现它们的接口,导致的问题就是应用和框架绑死,java应用无法再应用其他更好用的框架。
Spring的优势就在于它会竭力避免自身的API弄乱你的code,并且不会强迫应用者实现Spring规范的接口或继承Spring规范的类,当我们应用spring的时候就会发现,基本没有任何痕迹表明我们应用了spring,最明显的场景是我们在类中使用spring注解,但是类依旧是POJO(plain old java object),举个例子
package com.bandiaoqian.spring
public class HelloWorldBean(){
public String sayHello(){
return "Hello World";
}
}
这是一个简单普通的java类--POJO,没有任何地方表明它是一个Spring组件,看起来很简单,但是POJO一样可以有所作为,Spring通过DI(Dependency injected)来装配POJO。
DI:依赖注入,目的是降低对象间的耦合性
接下来我们分析下DI是如何帮助应用对象间彼此实现松耦合的
耦合是什么:任何一个有实际意义的应用,都是由多个类相互协作完成特定的业务逻辑实现的,按照传统的做法,每个对象负责管理与自己相互协作的对象的引用(也就是所依赖的对象),这会使对象间高度依赖,就是少个对象引用应用都无法正常工作,这种依赖性就是耦合。传统的应用基本都是高度耦合且难以测试的。
举个例子:
package com.bandiaoqian.knights;
public class DamselRescuingKnight implements Knight(){
private RescueDamselQuest quest;
public DamselRescuingKnight(){
this.quest = new RescueDamselQuest();
}
public void embarkOnQuest(){
quest.embark();
}
}
可以发现DamselRescuingKnight 的构造函数中创建了RescueDamselQuest(一个探险任务:营救公主)这个对象,使得DamselRescuingKnight和RescueDamselQuest紧密耦合,这样极大地限制了骑士能够完成的探险任务,也就是只能救公主,并且进行单元测试也极为困难。
耦合是一把双刃剑,我们既需要适当的耦合保障业务逻辑的通顺,又需要尽力降低耦合,总之耦合时必须的,但我们需要谨慎处理。
Spring就较好的解决了这个问题,请看下图:
我们用一个容器管理所有对象,当A类发起请求,容器就会直接将依赖(就是A类所需要的对象)注入到A类中
我们看一个依赖注入的例子:
package com.bandiaoqian.knights;
public class BraveKnight implements Knight(){
private Quest quest;
public DamselRescuingKnight(Quest quest){
this.quest = quest;
}
public void embarkOnQuest(){
quest.embark();
}
}
这次没有在构造方法中创建另一个对象,而是将一个接口Quest作为参数传入(这是依赖注入的方式之一:构造器注入)
所有探险任务都必须实现Quest接口,这样一来BraveKnight 这个类就可以响应任何Quest实现(就是构造器中可以接收任何Quest实现作为参数),也就是BraveKnight 这个类没有与任何一个特定的Quest对象耦合,所以只要被要求挑战的任务类型实现了Quest接口,那么具体是哪种类型的挑战任务就不重要了,这就是DI所带来的最大收益--松耦合。
如果一个对象只通过接口(而不是具体实现或初始化过程)来表明依赖关系,那么这种依赖就能在对象毫不知情的情况下用不同的具体实现进行替换。
对依赖进行替换最常用的方法就是在测试的时候使用mock实现,对于BraveKnight,我们只需传给他一个Quest的mock实现即可
package com.bandiaoqian.knights;
import static org.mockito.Mockito.*;
import org.junit.Test;
public class BraveKnightTest(){
@Test
public void knightShouldEmbarkOnQuest(){
Quest mockQuest = mock(quest.class);
BraveKnight knight = new BraveKnight(quest);
mockQuest.embarkOnQuest();
verify(mockQuest,times(1).embark());
}
}
讲解一下上面的代码:
我们可以用mock框架Mockito去创建一个Quest接口的mock实现,然后我们把它注入BraveKnight的构造器,构建一个BraveKnight对象,接着调用embarkOnQuest方法,然后正常流程就是mockQuest会调用一次embark方法,我们就可以要求mock框架验证Quest接口的mock实现(mockQuest)的embark方法仅被调用了一次,如果验证通过,就说明测试通过了,整个流程是正确的。
装配(wiring)
创建应用组件之间的协作行为称之为装配,spring有多中装配bean的方式(自动装配、Java、XML)
不管使用哪种装配方式,DI所带来的收益是不变的,尽管BraveKnight依赖于Quest,但是他不知道传给他的是什么类型的Quest,也不知道这个Quest来自哪。只有Spring通过它的配置,能够了解这些组成部分是如何装配起来的,这样就能在不改变所依赖的接口的情况下,修改依赖关系。
应用切面(AOP)
面向切面编程(aspect-oriented programming,AOP),允许你把遍布应用各处的功能分离出来形成可重用的组件,就是去掉一切重复关注点,只关注组件本身必须关注的。
日志、事务管理和安全是系统服务,通常被称为横切关注点,因为它们会跨越系统多个组件,如果把这些关注点分散到各个组件中,代码将十分冗余:
实现关注点功能的代码将重复出现在各个组件,,这意味着如果要修改关注点逻辑,那就要深入到各个组件中去修改相关实现,即使把关注点抽象为一个独立模块,各个组件还是要重复调用其方法。
组件会因为与自身业务无关的关注点代码而变得混乱,比如一个向地址簿添加地址的功能只应该关注如何添加地址,而不应该关注它是否安全或者是否需要支持事务。
AOP能解决这个问题
AOP能够使这些服务模块化,并以声明的方式将他它们应用到需要它们影响的组件中去 ,所以AOP能确保POJO的简单性。
我们用具体例子说明,回到骑士的例子,为骑士添加一个切面
骑士需要吟游诗人来吟唱他们的英勇事迹
吟游诗人类:
正如上面代码所示,吟游诗人会在骑士执行探险任务前调用singBeforeQuest方法;在骑士执行完任务后调用singAfterQuest方法;如果不用AOP,让吟游诗人工作的代码是这样的:
但是似乎有点不对,骑士不仅要执行任务,还要负责管理吟游诗人类,这超出了骑士的职责范围,骑士的关注点应该在执行任务,而无需关注吟游诗人。而且把吟游诗人注入BraveKnight类使其代码复杂化了,所以这样不行。
我们需要利用AOP,我们在AOP中声明吟游诗人必须吟唱骑士的英勇事迹,而骑士本身无需直接访问吟游诗人类的方法,代码如下:
吟游诗人类可以被应用到BraveKnight类中,而BraveKnight类无需显示的调用它,事实上BraveKnight根本不知道吟游诗人类的存在。
在这个例子中,骑士类就相当于应用组件,吟游诗人类就相当于系统服务(横切关注点),我们不把吟游诗人分散到各个骑士中,那样会造成代码冗余复杂化并且维护困难。我们声明一个吟游诗人切面(把吟游诗人这个服务模块化),然后把它应用到真正需要它去影响的骑士类(应用组件)中去。