Spring(1)_基础

Introduction

Spring 框架是一个开源的 Java 平台,它为容易而快速的开发出耐用的 Java 应用程序提供了全面的基础设施。Spring最基本的使命是:简化Java开发。Spring 框架的核心特性可以用于开发任何 Java 应用程序,但是在 Java EE 平台上构建 web 应用程序是需要扩展的。 Spring 框架的目标是使 J2EE 开发变得更容易使用,通过启用基于 POJO 编程模型来促进良好的编程实践。

为了降低Java开发的复杂性,Spring采取了一下4种关键策略:

  • 基于POJO(bean)的轻量级和最小侵入性编程;
  • 通过依赖注入和面向接口实现松耦合;
  • 基于切面和管理进行声明式编程;
  • 通过切面和模板减少样板式代码。

Spring最重要的两个特性是依赖注入基于切面编程

依赖注入

在面向对象编程中,我们将不可避免的耦合对象,也就在一个对象的功能中需要依赖与其他一些对象。耦合具有两面性,一方面耦合的代码难以测试,难以复用。另一方面耦合是必须的—完全没有的耦合的代码什么也做不了。

1
2
3
4
5
6
7
8
9
public class DamselRescuingKnight implements Knight {
private RescueDamselQuest quest;
public DamselRescuingKnight() {
this.quest = new RescueDamselQuest(); // 紧耦合
}
public void embarkOnQuest() {
quest.embark();
}
}

如上面的代码,如果需要不同的quest的行为,则无法轻易实现,必须修改源代码,且无法复用。

通过依赖注入(DI),对象的依赖关系将有负责协调系统中对各个对象的第三方组件在创建对象时设定。对象无需自行创建或管理它们的依赖关系—也就是对象无需自己创建需要依赖的对象,而是创建时有第三方安排合适的需要依赖的对象给这个对象。

到底什么是依赖注入?让我们将这两个词分开来看一看。这里将依赖关系部分转化为两个类之间的关联。例如,类A依赖于类B。现在,让我们看一看第二部分,注入。所有这一切都意味着类B 将通过 IoC 被注入到类 A 中。

依赖注入可以以向构造函数传递参数的方式发生,或者通过使用 setter 方法 post-construction。

1
2
3
4
5
6
7
8
9
10
11
12
public class BraveKnight implements Knight {
private Quest quest;
public BraveKnight(Quest quest) {
this.quest = quest; // 构造器注入
}
public void embarkOnQuest() {
quest.embark();
}
public void setQuest(Quest quest) {
this.quest = quest; // setter注入
}
}

这样BraveKnigh没有与任何特定的Quest实现发生耦合。只需要直接使用依赖对象的方法,而该依赖对象会在运行时有第三方指定。这就是依赖注入最大的好处—松耦合。

现在BraveKnight类可以接受传递给它的任何一种Quest的实现,我们又该如何将特定的Query实现传递给它?

在Spring中,每个对象都是一个bean,由Spring容器管理。创建应用组件(对象)之间的协作关系的行为通常称为装配。Spring有多种装配Bean的方式,采用XML配置通常是最常见的装配方式,也就是,对象之间的协作关系都在XML中指定。下面是一个简单的Spring配置文件:knights.xml,该配置文件让BraveKnight接受了一个SlayDragonQuest探险任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="knight" class="com.springinaction.knights.BraveKnight">
<constructor-arg ref="quest" />
</bean>

<!-- Inject quest bean -->
<bean id="quest" class="com.springinaction.knights.SlayDragonQuest"> <constructor-arg value="#{T(System).out}" />
</bean>
</beans>

下一步需要做的是装载XML配置文件,并把应用启动起来。
Spring通过应用上下文(Application Context)装载Bean的定义并把他们组装起来。Spring应用上下文全权负责对象的创建和组装。Spring自带了几种应用上下文的实现,它们之间的主要区别是如何加载它们的配置。

因为knight.xml中的Bean是在XML文件中声明的,随意选择ClassPathXmlApplicaitonContext作为应用上下文是比较合适的,该类加载位于应用系统classpath下的一个或多个xml文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import org.springframework.context.support.
ClassPathXmlApplicationContext;
public class KnightMain {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context =
Load Spring context
Get knight bean
new ClassPathXmlApplicationContext(
"META-INF/spring/knight.xml");
Knight knight = context.getBean(Knight.class);
knight.embarkOnQuest();
context.close();
}
}

面向切面编程(AOP)

依赖注入让相互协作的软件组件保持松散耦合,而AOP编程允许把遍布应用的功能分离出来形成可重用的组件。

面向切面编程往往被定义为促使应用程序分离关注点的一项技术。系统通常由许多不同组件组成,每一个组件各负责一块特定功能。一些组件除了实现自身核心的功能之外,还经常承担额外的职责。诸如日志、事务管理和安全之类的系统服务经常融入到有自身核心业务逻辑的组件中去,这些系统业务通常称为横切关注点,因为它们总是跨越系统的多个组件。

如果将这些关注点,分散到多个组件中去,代码将出现双重复杂性:

  • 如果横切关注点实现代码将重复出现在多个组件(模块)中。如果要改变这些关注点的逻辑,则必须修改每个组件的相关代码。即是我们将其抽象成模块,其他模块只是调用其方法,但是方法的调用还是会重复出现在各个模块中。
  • 组件的代码可能会因为处理这些横切关注点的使用(也就是与自身核心业务无关的代码)变得混乱。一个像地址簿增加地址条目的方法应该只关注如何添加地址,而不是同时需要关注是不是安全或者是否需要支持事务。

下面图片展示了这种复杂性。左边的业务对象与系统级服务集合的过于紧密。每个对象不但需要知道它需要日志,安全控制,事务处理,而且还要亲自执行这些服务。

Spring_aop_example1

AOP使这些服务模块化,并以声明的方式讲它们应用到需要影响的组件中去。结果是功能组件具有更高内聚性以及更加关注自身业务,完全不需要了解系统服务的复杂性,从而保持POJO简单。

如下面图片所示,AOP可以让我们使用各种功能层去包裹核心业务。这些功能层也声明的方式应用到系统中,核心业务甚至根本不知道这些服务的存在。使得我们可以将横切关注点和业务逻辑模块分离。

Spring_aop_example2

假设knight在执行任务之前需要辅助者进行一些操作,最直接的方式是由knight自己直接调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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!");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.springinaction.knights;
public class BraveKnight implements Knight {
private Quest quest;
private Minstrel minstrel;
public BraveKnight(Quest quest, Minstrel minstrel) {
this.quest = quest;
this.minstrel = minstrel;
}
public void embarkOnQuest() throws QuestException {
minstrel.singBeforeQuest();
quest.embark();
minstrel.singAfterQuest();
}
}
//Should a knight manage his own minstrel?

这样做可以达到预期效果,但是knight引入不少与自身业务无关的代码,如果需要更多的战斗前准备,代码将会变得很混乱和难以修改。我们希望kinght只需关注自己的业务,Minstrel能自己判断并在knight战斗前后执行相应操作。

我们可以把Minstrel抽象为一个切面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="UTF-8"?>>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="knight" class="com.springinaction.knights.BraveKnight">
<constructor-arg ref="quest" />
</bean>
<bean id="quest" class="com.springinaction.knights.SlayDragonQuest"> <constructor-arg value="#{T(System).out}" />
</bean>
<bean id="minstrel" class="com.springinaction.knights.Minstrel">
<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="singAfterQuest"/>
</aop:aspect>
</aop:config>
</beans>

这里使用Spring的AOP配置的命名空间把Minstrel Bean声明为一个切面。首先把其声明一个bean,然后在<aop:aspect>元素中引用该Bean。更加具体的语法说明会在后面介绍。

使用模板消除样板式代码

容纳Bean

Spring容器

在基于Spring的应用中,应用对象生存于Spring容器中。Spring容器负责创建对象,装配对象,配置,管理对象的整个生命周期。

容器是Spring框架的核心。Spring容器使用依赖注入管理构成应用的组件,创建相互协作的组件之间的关联。毫无疑问,这些对象更加简单干净,容易理解,更加容易重用以及更易于单元测试。

Spring有两种类型的容器:Bean工厂(BeanFactory)是最简单的容器,提供了基本的DI支持。应用上下文基于BeanFactory之上构建,并提供面向应用的服务,例如从属性文件解析文本信息的能力,以及发布应用事件给感兴趣的时间监听者的能力。

在大部分情况下Bean工厂由于太简单无法满足需求,所以一般使用上下文。

应用上下文

Spring自带了几种类型的应用上下文:

  • AnnotationConfigApplicationContext—Loads a Spring application context from one or more Java-based configuration classes
  • AnnotationConfigWebApplicationContext—Loads a Spring web application context from one or more Java-based configuration classes
  • ClassPathXmlApplicationContext—Loads a context definition from one or more XML files located in the classpath, treating context-definition files as class- path resources
  • FileSystemXmlApplicationContext—Loads a context definition from one or more XML files in the filesystem
  • XmlWebApplicationContext—Loads context definitions from one or more XML files contained in a web application

Bean的生命周期

  1. Spring instantiates the bean.
  2. Spring injects values and bean references into the bean’s properties.
  3. If the bean implements BeanNameAware, Spring passes the bean’s ID to the setBeanName() method.
  4. If the bean implements BeanFactoryAware, Spring calls the setBeanFactory() method, passing in the bean factory itself.
  5. If the bean implements ApplicationContextAware, Spring calls the setApplicationContext() method, passing in a reference to the enclosing application context.
  6. If the bean implements the BeanPostProcessor interface, Spring calls its postProcessBeforeInitialization() method.
  7. If the bean implements the InitializingBean interface, Spring calls its afterPropertiesSet() method. Similarly, if the bean was declared with an initmethod, then the specified initialization method is called.
  8. If the bean implements BeanPostProcessor, Spring calls its postProcessAfterInitialization() method.
  9. At this point, the bean is ready to be used by the application and remains in the application context until the application context is destroyed.
  10. If the bean implements the DisposableBean interface, Spring calls its destroy() method. Likewise, if the bean was declared with a destroy method, the specified method is called.