Spring实战第一章学习笔记

Spring实战第一章学习笔记

Java开发的简化

为了降低Java开发的复杂性,Spring采取了以下四种策略:

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

1. 激发POJO的潜能

POJO简单的Java对象,实际上就是普通JavaBean。

Spring不会因为自身API而弄乱了你的应用代码,它不会让你实现Spring规范的接口或继承Spring规范的类,所以在Sora构建的类中,它的类没有使用Spring的痕迹。Spring的非侵入性编程意味着这个类在Spring应用和非Spring应用中都可以发挥同样的作用。虽然形式看起来很简单,Spring应用到POJO的方式之一就是通过依赖注入来装配它们。

2. 依赖注入(DI)

DI—Dependency Injection,即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

●谁依赖于谁:当然是应用程序依赖于IoC容器;

  ●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;

  ●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;

  ●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

那DI功能是如何实现的?一个有实际意义的应用通常都具有两个以上的类。按照传统做法每个对象管理与自己相互协作的对象的引用。这会导致各组件的高度耦合且代码难以测试以至于牵一发而动全身。


public class DamselRescuingKnight implements Knight { private RescueDamselQuest quest; public DamselRescuingKnight() {
this.quest = new RescueDamselQuest();
} public void embarkOnQuest() {
quest.embark();
} }

这时可以看到DamselRescuingKnight在构造函数中自行创建了RescueDamselQuest,使得无法保证embark方法被调用,所以DameRescuingKnight无法通过测试。这样创建出的骑士执行探险能力被限制了。所以这时我们要通过DI使得对象的依赖关系由第三方组件在创建对象时设定。这样对象无需自行创建或管理他们的依赖关系,而如下面的程序所示依赖关系将被自动注入到需要他们的对象中。

public class BraveKnight implements Knight {

  private Quest quest;

  public BraveKnight(Quest quest) {
this.quest = quest;
} public void embarkOnQuest() {
quest.embark();
} }

这段代码中将探险任务quest作为构造器参数传入这是依赖注入的方式之一,及构造器注入。更重要的是传递的参数类型是Quest,也就是所有的探险任务都必须实现的一个接口。所以BraveKnight可以响应任意的Quest实现。且重点是它没有和任一特定类型的Quest耦合。这是DI带来的最大收益——松耦合。如果对象只通过接口来表明依赖关系,那么这种依赖可以在对象本身毫不知情的情况下以各种实现来进行替换。对BraveKnight进行测试可以验证embark方法被调用了一次。

创建应用组件之间的协作的行为通常称为装配。Spring有多种装配bean的方法。而在下面提供的是基于Java的配置。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import sia.knights.BraveKnight;
import sia.knights.Knight;
import sia.knights.Quest;
import sia.knights.SlayDragonQuest; @Configuration
public class KnightConfig { @Bean
public Knight knight() {
return new BraveKnight(quest());
} @Bean
public Quest quest() {
return new SlayDragonQuest(System.out);
} }

Spring通过应用上下文(ApplicationContext)装载bean的定义并把它们组装起来。Spring应用上下文全权负责对象的创建和组装。Spring自带了多种引用上下文的实现,他们之间主要的区别在于如何加载配置。因为我是使用Java文件配置的所以选择AnnotationConfigApplicationContext作为应用上下文。

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class KnightMain {

  public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(
"KnightConfig.class");
Knight knight = context.getBean(Knight.class);
knight.embarkOnQuest();
context.close();
} }

这里main()方法基于KnightConfig.class文件创建了Spring应用上下文。当得到Knight对象的引用后,只需简单调用embarkOnQuest()方法就可以执行探险。且这个类完全不知道这是由BraveKnight来执行的,更不知道执行的是什么探险,只有KnightConfig。class文件才知道。

3. 应用切面

DI能够让相互协作的组件保持松散耦合,而面向切面编程(accept-oriented programming,AOP)允许你把遍布应用各处的功能组件分离出来形成可重用的组件。

AOP可以使得这些服务模块化,并以声明的方式将它们应用到相应的组件中去,这样,这些组件就具有更高内聚性以及更加关注自身业务,完全不需要了解可能涉及的系统服务的复杂性。总之,AOP确保POJO保持简单。

接下来我们回到骑士的例子,来示范如何在Spring中应用切面。每一个人都熟知骑士所做的事迹是因为吟游诗人用诗歌记载并进行传唱。所以需要一个吟游诗人这样的服务类Minstrel。

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!");
} }

如代码所示诗人会在骑士探险前和探险后被调用,所以骑士要调用诗人的方法来歌颂。可当我们将BraveKnight和Minstrel组合起来后发现BraveKnight代码复杂化了。我们发现如果这样实现首先骑士需要管理吟游诗人,同时骑士还要知道吟游诗人所以必须将吟游诗人注入到BraveKnight类中,同时我们还要考虑Minstrel是否会为空。所以这时我们利用AOP将Minstrel抽象为一个切面。

<?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/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="knight" class="com.spring.sample.knights.BraveKnight">
<constructor-arg ref="quest" />
</bean> <bean id="quest" class="com.spring.sample.knights.SlayDragonQuest">
<constructor-arg value="#{T(System).out}" />
</bean> <bean id="minstrel" class="com.spring.sample.knights.Minstrel">
<constructor-arg value="#{T(System).out}" />
</bean> <aop:config>
<aop:aspect ref="minstrel">
<aop:pointcut id="embark" expression="execution(* *.embarkOnQuest(..))"/>
<aop:before method="singBeforeQuest" pointcut-ref="embark" />
<aop:after method="singAfterQuest" pointcut-ref="embark" />
</aop:aspect>
</aop:config>
</beans>

在这个配置文件中增加了aop配置名字空间。首先定义Minstrel的bean,然后利用aop:config标签定义aop相关的配置;然后在aop:aspect节点中引用minstrel,定义方面;aspect负责将pointcut和要执行的函数(before、after或者around)连接在一起。在之后会详细介绍AOP的内容。

4. 使用模板消除样板式代码

在编写代码时我们经常会用大量重复无用的代码来实现简单的功能,最典型的就是JDBC的使用。

public Employee getEmployeeById(long id) {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
stmt = conn.prepareStatement("select id, name from employee where id=?");
stmt.setLong(1, id);
rs = stmt.executeQuery();
Employee employee = null;
if (rs.next()) {
employee = new Employee();
employee.setId(rs.getLong("id"));
employee.setName(rs.getString("name"));
}
return employee;
} catch (SQLException e) {
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
}
}
}
return null;
}

而通过Spring的模板封装可以消除样板式代码。

public Employee getEmployeeById(long id) {
return jdbcTemplate.queryForObject(
"select id, name from employee where id=?",
new RowMapper<Employee>() {
public Employee mapRow(ResultSet resultSet, int i) throws SQLException {
Employee employee = new Employee();
employee.setId(resultSet.getLong("id"));
employee.setName(resultSet.getString("name"));
return employee;
}
});
}

容纳你的Bean

在基于Spring的应用中,应用对象生存于Spring容器中,Spring容器负责创建对象,装配它们,配置它们并管理它们的整个生命周期。容器是Spring框架的核心。Spring容器使用DI管理构成应用的组件,它会创建相互协作的组件之间的关联。

而Spring容器的实现可分为两类:

  • Bean factories(由org.springframework.beans.factory.BeanFactory接口定义)是最简单的容器,只提供基本的依赖注入功能;
  • Application context(由org.springframework.context.ApplicationContext接口定义)在bean factory的基础上提供application-framework框架服务,例如可以从properties文件中解析配置信息、可以对外公布application events。

使用应用上下文

Spring自带多种类型的应用上下文。最常见的如下:

  • AnnotationConfigApplicationContext——从Java配置文件中加载应用上下文;
  • AnnotationConfigWebApplicationContext——从Java配置文件中加载Spring web应用上下文;
  • ClassPathXmlApplicationContext——从classpath(resources目录)下加载XML格式的应用上下文定义文件;
  • FileSystemXmlApplicationContext——从指定文件系统目录下加载XML格式的应用上下文定义文件;
  • XmlWebApplicationContext——从classpath(resources目录)下加载XML格式的Spring web应用上下文。

通过应用上下文实例,可以通过getBean()方法获得对应的bean。

bean的生命周期

传统的Java应用中bean的生命周期很简单。实例化一个bean后一旦不使用就由Java自动进行垃圾回收。

而在Spring中:

  1. Spring初始化bean;
  2. Spring将值和其他bean的引用注入(inject)到当前bean的对应属性中;
  3. 如果Bean实现了BeanNameAware接口,Spring会传入bean的ID来调用setBeanName方法;
  4. 如果Bean实现了BeanFactoryAware接口,Spring传入bean factory的引用来调用setBeanFactory方法;
  5. 如果Bean实现了ApplicationContextAware接口,Spring将传入应用上下文的引用来调用setApplicationContext方法;
  6. 如果Bean实现了BeanPostProcessor接口,则Spring调用postProcessBeforeInitialization方法,这个方法在初始化和属性注入之后调用,在任何初始化代码之前调用;
  7. 如果Bean实现了InitializingBean接口,则需要调用该接口的afterPropertiesSet方法;如果在bean定义的时候设置了init-method属性,则需要调用该属性指定的初始化方法;
  8. 如果Bean实现了BeanPostProcessor接口,则Spring调用postProcessAfterInitialization方法
  9. 在这个时候bean就可以用于在应用上下文中使用了,当上下文退出时bean也会被销毁;
  10. 如果Bean实现了DisposableBean接口,Spring会调用destroy()方法;如果在bean定义的时候设置了destroy-method, 则此时需要调用指定的方法。

总结

关于第一章就先总结这么多,前两节已经清晰地介绍了Spring的功能特性。其中依赖注入和AOP是Spring框架最核心的两部分,而第一章只是介绍了点皮毛,之后几章将深入探讨。

上一篇:Pro ASP.NET MVC –第二章 第一个MVC程序


下一篇:《Android开发艺术探索》读书笔记 (3) 第3章 View的事件体系