本系列的文章参考《spring实战》第四版,由个人总结记录而得。
Spring的诞生:
由Rod Johnson创建,并在《Expert One-on-One:J2EE Design and Development》做了介绍。
使命:
解决企业级应用开发的复杂性,简化java开发。
为了达成使命所采取的策略:
- 基于JOPO的轻量级和最小侵入性编程;
- 通过依赖注入和面向接口实现松耦合;
- 基于切面和惯例进行声明式编程;
- 通过切面和模板减少样板式代码。
Spring的两个核心特性:
- 依赖注入(DI):
传统劣势:任何一个有实际应用的类,都会有二至多个类所组成,如果让每个类负责管理与自己相互协作的对象的引用,这将会导致高度耦合和难以测试。
看下面的代码:
package com.springinaction.knights; public class DamselRescuingKnight implements Knight{ private RescueDamselQuest quest; public DamselRescuingKnight(){ this.quest = new RescueDamselQuest(); } public void embarkOnQuest(){ quest.embark(); } }
DamselRescuingKnight 在构造函数之中自行创建了RescueDamselQuest对象,二者的耦合度是很高的,使得DamselRescuingKnight 与RescueDamselQuest牢牢的绑定在了一起,骑士只具有embark()的能力,不能进行其他探险。
对这个类进行测试的时候,还需要保证在执行embarkOnQuest()方法的时候,embark()方法也要被调用。
然后耦合具有两面性,不存在耦合的代码什么也做不了,过高的耦合会导致代码难以维护、测试与理解。
通过DI,对象之间的调用创建维护,将由系统中的第三方组件来完成。只需要把Bean注入到组件中。
再看改良后的代码:
package com.springinaction.knights; public class BraveKnight implements Knight{ private Quest quest; //Quest被注入进来 public BraveKnight(Quest quest){ this.quest = quest; } public void embarkOnQuest(){ quest.embark(); } }
在这个类中,没有执行具体的任务,而是通过构造器将探险任务传入(构造器注入)。BraveKnight并不知道具体的任务是什么,所有的任务都将实现Quest接口,然后通过构造器传递进来。
可以看出,BraveKnight只与Quest接口存在耦合关系,不与任何的Quest实现存在耦合。
- 面向切面编程(AOP)
AOP:分离应用功能,形成可重用的组件,促使软件系统实现关注点分离的一项技术。
系统由不同的组件组成,这些组件中除了本身所必须包含的功能之外还经常承担一些额外的职责。比如学生服务、课程服务和计费服务三者,不仅要实现各自的功能,还要有日志、安全以及事务等服务,而这些往往不是模块的核心业务。
AOP能够使这些服务模块化,并以声明的方式将它们应用到它们需要影响的组件中去。所以,组件会具有更高的内聚性,只需关注自身业务,无需关心复杂的系统服务是如何实现的。使用AOP,就像是把这些服务包裹在各个模块之上。
看以下代码,吟游诗人将骑士的英勇事迹编写成诗歌进行传唱。
package com.springinaction.Knight;
import java.io.PrintStream;
public class Minstrel{
private PrintStream stream;
public Minstrel (PrintStream stream){
this.stream = stream;
}
//探险之前使用
public void singBeforeQuest(){
stream.println("Fa la la,the knight is so brave!");
}
//探险之后使用
public void singAfterQuest(){
stream.println("Tee hee hee,the brave knight did embark on a quest!");
}
}
package com.springinaction.knights;
public class BraveKnight implements Knight{
private Quest quest;
private Minstrel minstrel;
//Quest被注入进来
public BraveKnight(Quest quest,Minstrel minstrel){
this.minstrel = minstrel;
this.quest = quest;
}
public void embarkOnQuest(){
minstrel.singBeforeQuest();
quest.embark();
minstrel.singAfterQuest();
}
}
think:传唱事迹是勇士需要关心去做的事吗?(如果他想出名,看来是需要的)要是有哪个勇士就想一心探险默默无闻不被世人所熟知,那还要判断一下minstrel是否为null,简单的BraveKnight类变的复杂化了。勇士只需要关心quest,不该把Minstrel的维护工作交给勇士来做,这是吟游诗人的工作。
我们把Minstrel声明成一个切面,把embarkOnQuest声明为一个切点,需要做以下配置:
...
...
<bean id="minstrel" class="com.springinaction.Knight.Minstrel">
//构造器注入,spEL表达式
<constructor-arg value="#{T(System).out}"/>
</bean>
<aop:config>
//切面
<aop:aspect ref="minstrel">
//定义切点
<aop:pointcut id="embark"
expression="execution(**.embarkOnQuest(..))"/>
//方法调用之前
<aop:before pointcut-ref="embark" method="singBeforeQuest" />
//方法调用之后
<aop:after pointcut-ref="embark" method="singBeforeQuest" />
</aop:aspect>
</aop:config>
...
BraveKnight类中的代码没有做任何改动,通过配置切点、切面后,已经实现了吟游诗人(minstrel)传唱勇士的功能。
Spring容器(container)
在使用spring框架的应用中,应用对象生存在容器之中。spring容器负责创建、装配和配置对象,管理他们的生命周期。
容器是spring框架的核心,spring自带了多个容器实现,可分为以下两种类型:bean工厂(由org.springframework.beans.factory.BeanFactory接口定义)是最简单的容器,提供基本的DI支持。应用上下文(由org.springframework.context.ApplicationContext接口定义)基于BeanFactory构建,提供应用框架级别的服务(更受欢迎)。
使用应用上下文:
- AnnotationConfigApplicationContext:从一个或多个基于java的配置类中加载Spring应用上下文。
- AnnotationConfigWebApplicationContext:从一个或多个基于java的配置类中加载Spring Web应用上下文。
- ClassPathXMLApplicationContext:从类路径下的一个或多个XML配置文件中加载上下文定义,把应用上下文的定义文件作为类资源。
- FileSystemXmlApplicationContext:从文件系统下的一个或多个XML配置文件中加载上下文定义。
- XmlWebApplicationContext:从Web应用下的一个或多个XML配置文件中加载上下文定义。
FileSystemXmlApplicationContext:
ApplicationContext context = new FileSystemXmlApplicationContext("c:/Knight.xml");
ClassPathXmlApplicationContext:
ApplicationContext context = new ClassPathXmlApplicationContext("Knight.xml");
二者的区别在于,FileSystemXmlApplicationContext是从指定的文件系统路径下查找Knight.xml文件;而ClassPathXMLApplicationContext是在所有的类路径(包括JAR文件)下查找knight.xml文件。
上下文准备就绪后,就可以调用上下文的getBean()方法从Spring容器中获取bean。
其余几种获取上下文的方式待后续。