第一章 Spring之旅

来源:互联网 发布:淘宝网mac版 编辑:程序博客网 时间:2024/05/16 00:38
缩写 全称 备注 POJO Plain Old Java Object 简单老式对象,普通Java类 DI Dependency Injection 依赖注入 AOP Aspect-Oriented Programming 面向切面编程 EJB Enterprise JavaBean 企业级JavaBean

1.1 简化Java开发


  • Spring可以让简单的JavaBean实现之前只有EJB才能完成的事情
  • Spring不仅仅局限于服务器开发,任何Java应用都可以在简单些、可测试性和松耦合等方面从Spring获益
  • Spring使用bean或JavaBean来表示应用组件,但是不意味着Spring组件必须遵从JavaBean规范
  • Spring组件可以是任何形式的POJO

为降低Java开发复杂性,Spring采用的四种策略

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

1.1.1 激发POJO的潜能


Spring不会像Struts、WebWork、Tapestry等规范或框架强迫应用继承它们的类或实现它们的接口从而导致应用与框架绑死。Spring竭力避免因自身的API而弄乱你的应用代码。在构建基于Spring的应用中,它的类通常没有你使用Spring的痕迹。

HelloWorldBean

public class HelloWorldBean {    public String sayHello() {        return "Hello World!";    }}

可以看到这是一个简单的Java类—POJO。没有任何地方表明它是一个Spring组件。Spring的非侵入编程模型意味着这个类在Spring应用和非Spring应用中都可以发挥同样的作用。

Spring赋予POJO魔力的方式之一就是通过DI来装配它们。


1.1.2 依赖


DI功能是如何实现的

任何一个有意义的应用都会由两个或更多的类组成,这些类相互之间进行协作完成特定的业务逻辑。按照传统的做法,每个类会负责管理与自己相互协作的类的对象的引用,这将会导致高度耦合和难以测试。
Knight

public interface Knight {    public void embarkQuest();}public interface Quest {    public void embark();}
//DamselRescuingKnight只能执行RescueDamselQuest探险任务public class DamselRescuingKnight implements Knight {    private RescueDamselQuest quest;    public DamselRescuingKnight() {        this.quest = new RescueDamselQuest();    }    @Override    public void embarkOnQuest() {        quest.embark();    }}

可以看到,DamselRescuingKnight在它的构造方法中自行创建了RescueDamselKnight。这导致二者紧密的耦合在了一起,因此极大的限制了这个骑士执行探险的能力。如果一个少女需要救援,这个骑士可以过来,可是要是杀死一条恶龙,那么这个骑士就爱莫能助了。
另外,测试DamselRescuingKnight时,当你需要保证embarkQuest()被调用的时候,quest.embark()也会被调用,但是没有一个简单的方法能够实现这一点,从而无法进行测试。
通过DI,对象的依赖关系将由系统中负责协调各对象的第三方组件在创建对象时决定。对象无需自行创建或管理它们的依赖关系,依赖关系将被自动注入到需要他们的对象中。

BraveKnight

//BraveKnight足够灵活可以接受任何赋予的探险任务public class BraveKnight implements Knight {    private Quest quest;    public BraveKnight(Quest quest) {        this.quest = quest;    }    @Override    public void embarkQuest() {        quest.embark();    }}

这是在构造时把探险任务作为构造器参数传入,是DI的一种方式,即构造器注入(Constructor injection)。

如果一个对象只通过接口而不是具体的实现或者初始化过程来表明依赖关系,那么这种依赖就能够在对象本身毫不知情的情况下,用不同的具体实现进行替换。

import java.io.PrintStream;public class SlayDragonQuest implements Quest {    private PrintStream stream;    public SlayDragonQuest(PrintStream stream) {        this.stream = stream;    }    @Override    public void embark() {        stream.println("Emabrking on quest to slay the dragon!");    }}

这时,我们又有一个新问题,如何把Quest交给BraveKnight呢?创建应用组件之间的协作行为通常称之为装配(wiring)。Spring有多种装配bean的方式,采用xml是一种简单的装配方式。

knight-config.xml

<?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="BraveKnight">        <constructor-arg ref="quest" />    </bean>    <bean id="quest" class="SlayDragonQuest">        <constructor-arg value="#{T(System).out}" />    </bean></beans>

Spring通过应用上下文(Application Context)装载bean的定义并把他们组装起来。由于knight-config.xml中的bean是使用XML装配的,所以选择ClassPathXmlApplicationContext作为应用上下文比较合适。

import org.springframework.context.support.ClassPathXmlApplicationContext;public class KnightMain {    public static void main(String[] args) {        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("knight-config.xml");        Knight knight = context.getBean(Knight.class);        knight.embarkQuest();        context.close();    }}

当然,Spring也支持Annotation进行配置:

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

启动方法变为:

import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class KnightMainByAnnotation {    public static void main(String[] args) {        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(KnightConfig.class);        Knight knight = context.getBean(Knight.class);        knight.embarkQuest();        context.close();    }}

1.1.3 应用切面


DI能够让相互协作的软件保持松散耦合,而面向切面编程允许你把遍布应用各处的功能分离出来形成可重用组件。
面向切面编程往往被定义为促使软件系统实现关注点分离的一项技术。系统由许多不同组件组成,每一个组件负责一块特定的功能。除了实现自身核心功能外,这些组件还经常承担着额外的责任,诸如日志、事务管理和安全这样的系统服务经常融入到自身具有核心业务逻辑的组件中去,这些系统服务通常被成为横切关注点,因为它们会跨越系统多个组件。
如果将这些关注点分散到多个组件中去,你的代码将会带来双重的复杂性:

  • 实现系统关注点的代码会重复出现在多个组件中。这意味着如果你要改变这些关注点的逻辑,必须修改各个模块中的相关实现。即使你把这些关注点抽象为一个独立的模块,其他模块只是调用它的方法,但方法的调用还是会重复出现在各个模块。
  • 组件会因为那些与自身核心代码业务无关的代码而变得混乱。一个向地址簿增加地址条目的方法应该只关注如何增添地址,而应该关注它是不是安全的或是否需要支持事物。

AOP能够使服务模块化,并以声明的方式将他们应用到他们需要影响的组件中去。从而使这些组件具有更高的内聚性并且会更加关注自身的业务,完全不要了解涉及系统服务所带来的复杂性。
AOP能够确保POJO的简单性。借助AOP,可以使用各种功能层去包裹核心业务层。这些层以声明的方式灵活的应用到系统中,你的核心应用甚至根本不知道它们的存在。可以将安全、事物和日志关注点与核心逻辑相分离。

AOP应用

吟游诗人会用诗歌记载骑士的事迹并传唱,假设我们需要使用吟游诗人这个服务来记载骑士的所有事迹。
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 knighr is so brave!");    }    public void singAfterQuest() {        stream.println("Tee hee hee, the brave knight did embark on a quest!");    }}

Minstrel是只有两个方法的简单类。在骑士执行每一个探险任务之前,singBeforeQuest()方法会被调用;在骑士完成探险任务之后,singAfterQuest()方法会被调用。在这两种情况下,Minstrel都会通过一个PrintStream类来歌颂骑士的事迹,这个类是通过构造器注入进来的。
然后更改Knight的代码:

public class BraveKnight implements Knight {    private Quest quest;    private Minstrel minstrel;    public BraveKnight(Quest quest, Minstrel minstrel) {        this.quest = quest;        this.minstrel = minstrel;    }    @Override    public void embarkQuest() {        minstrel.singBeforeQuest();        quest.embark();        minstrel.singAfterQuest();    }}

不过,当你去配置Knight时,你会感觉骑士管理吟游诗人太没有理由了,此外还会使代码复杂化(吟游诗人为null时怎么办?还要加空值检验?!)!
而利用AOP,你可以声明吟游诗人必须歌颂骑士的事迹,而骑士却不会直接访问Minstrel的方法。
更新后的xml文件如下,Knight代码恢复为以前代码:

<?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="BraveKnight">        <constructor-arg ref="quest" />    </bean>    <bean id="quest" class="SlayDragonQuest">        <constructor-arg value="#{T(System).out}" />    </bean>    <bean id="minstrel" class="Minstrel">        <constructor-arg value="#{T(System).out}"/>    </bean>    <aop:config>        <aop:aspect ref="minstrel">            <aop:pointcut id="embark" expression="execution(* *.embarkQuest(..))" />            <aop:before pointcut-ref="embark" method="singBeforeQuest" />            <aop:after pointcut-ref="embark" method="singAfterQuest" />        </aop:aspect>    </aop:config></beans>

这里使用了Spring的aop配置命名空间把Minstrel bean声明为一个切面。首先,需要把Minstrel声明为一个bean,然后在<aop:aspect>元素中引用该bean。为了进一步定义切面,声明(使用<aop:before>)在embarkQuest()方法执行前调用Minstrel的singBeforeQuest()方法。这种方式被称为前置通知(before advice)。同时声明(使用<aop:after>)在embarkQuest()方法执行后调用singAfterQuest()方法。这种方式被称为后置通知(after advice)。

在这两种方式中,pointcut-ref属性都引用了名字为embark的切入点。该切入点是在前边的<pointcut>元素中定义的,并配置expression属性来选择所应用的通知。表达式的语法采用的是AspectJ的切点表达式语言。现在,你无需担心不了解AspectJ或编写AspectJ切点表达式的细节,我们稍后会在第4章详细地探讨Spring AOP的内容。

首先,Minstrel仍然是一个POJO,没有任何代码表明它要被作为一个切面使用。当我们按照上面那样进行配置后,在Spring的上下文中,Minstrel实际上已经变成一个切面了。其次,也是最重要的,Minstrel可以被应用到BraveKnight中,而BraveKnight不需要显式地调用它。实际上,BraveKnight完全不知道Minstrel的存在。

必须还要指出的是,尽管我们使用Spring魔法把Minstrel转变为一个切面,但首先要把它声明为一个Spring bean。能够为其他Spring bean做到的事情都可以同样应用到Spring切面中,例如为它们注入依赖。


1.1.4 使用模版消除样板式代码


在进行JDBC访问数据库查询数据时,你会发现查询语句很繁琐,大多数时候你都是一遍遍的敲几乎一样的命令!然而JDBC并不是产生样板代码的唯一场景,在JMS、JNDI和使用REST服务也通常会设计大量的重复代码。

Spring旨在通过模版封装来消除样板代码,Spring的JdbcTemplate使得执行数据库操作时,避免传统的JDBC样板代码成为了可能。

例如:

public Employee getEmployeeById(long id) {        return jdbcTemplate.queryForObject(                "select id, firstname, lastname, salary "                + "from employee where id = ?",                new RawMapper<Employee>() {                    public Employee mapRow(ResultSet rs,                            int rowNum) throws SQLException {                        Employee employee = new Employee();                        employee.setId(rs.getLong("id"));                        employee.setFirstName(rs.getString("firstname"));                        employee.setLastName(rs.getString("lastname"));                        employee.setSalary(rs.getBigDecimal("salary"));                        return employee;                    }                },                id);    }

1.2 容纳你的Bean


在基于Spring的应用中,你的应用对象生存于Spring容器(container)中。Spring容器负责创建对象,装配它们,配置它们并管理它们的整个生命周期,从生存到死亡(在这里,可能就是newfinalize())。

容器是Spring框架的核心。Spring容器使用DI管理构成应用的组件,它会创建相互协作的组件之间的关联。毫无疑问,这些对象更简单干净,更易于理解,更易于重用并且更易于进行单元测试。

Spring容器并不是只有一个。Spring自带了多个容器实现,可以归为两种不同的类型。bean工厂(由org.springframework. beans.factory.eanFactory接口定义)是最简单的容器,提供基本的DI支持。应用上下文(由org.springframework.context.ApplicationContext接口定义)基于BeanFactory构建,并提供应用框架级别的服务,例如从属性文件解析文本信息以及发布应用事件给感兴趣的事件监听者。

bean工厂对大多数应用来说太低级,因此应用上下文要比bean工厂更受欢迎。


1.2.1 使用应用上下文


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

  • AnnotationConfigApplicationContext:从一个或多个基于Java的配置类中加载Spring上下文。
  • AnnotationConfigWebApplicationContext:从一个或多个基于Java的配置类中加载Spring Web应用上下文。
  • ClassPathXmlApplicationContext:从类路径下的一个或多个XML配置文件中加载上下文定义,把应用上下文的定义文件作为类资源。
  • FileSystemXmlapplicationcontext:从文件系统下的一个或多个XML配置文件中加载上下文定义。
  • XmlWebApplicationContext:从Web应用下的一个或多个XML配置文件中加载上下文定义。

无论是从文件系统中装载上下文,还是从类路径下装载应用上下文,将bean加载到bean工厂的过程都是相似的。如:

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("knight-config.xml");ClassPathXmlApplicationContext context = new FileSystemXmlApplicationContext("C:/knight-config.xml");

使用FileSystemXmlApplicationContext和使用ClassPathXmlApplicationContext的区别在于:FileSystemXmlApplicationContext在指定的文件系统路径下查找knight-config.xml文件;而ClassPathXmlApplicationContext是在所有的类路径(包含JAR文件)下查找 knight-config.xml文件。


1.2.2 bean的生命周期


传统Java程序中,bean的生命周期很简单,new的时候bean被实例化,然后该bean就可以被使用了,一旦bean不再使用,Java便会自动回收垃圾。

在Spring则复杂的多。

  1. Spring对bean进行实例化
  2. Spring将值和bean的引用注入到bean对应的属性中
  3. 如果bean实现了BeanNameAware接口,Spring将bean的ID传递给setBeanName()方法
  4. 如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入
  5. 如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引用传入进来
  6. 如果bean实现了BeanPostProcessor接口,Spring将调用它们的postProcessBeforeInitialization()方法
  7. 如果bean实现了InitializingBean接口,Spring将调用它们的afterPropertiesSet()方法。类似地,如果bean使用init-method声明了初始化方法,该方法也会被调用
  8. 如果bean实现了BeanPostProcessor接口,Spring将调用它们的postProcessAfterInitialization()方法
  9. 此时,bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁
  10. 如果bean实现了DisposableBean接口,Spring将调用它的destroy()接口方法。同样,如果bean使用destroy-method声明了销毁方法,该方法也会被调用

1.3 俯瞰Spring风景线


1.3.1 Spring模块


Spring的lib目录:

Spring的核心容器

容器是Spring框架最核心的部分,它管理着Spring应用中bean的创建、配置和管理。在该模块中,包括了Spring bean工厂,它为Spring提供了DI的功能。基于bean工厂,我们还会发现有多种Spring应用上下文的实现,每一种都提供了配置Spring的不同方式。

除了bean工厂和应用上下文,该模块也提供了许多企业服务,例如E-mail、JNDI访问、EJB集成和调度。所有的Spring模块都构建于核心容器之上。当你配置应用时,其实你隐式地使用了这些类。

Spring的AOP模块

在AOP模块中,Spring对面向切面编程提供了丰富的支持。这个模块是Spring应用系统中开发切面的基础。与DI一样,AOP可以帮助应用对象
解耦。借助于AOP,可以将遍布系统的关注点(例如事务和安全)从它们所应用的对象中解耦出来。

数据访问与集成

Spring的JDBC和DAO(Data Access Object)模块抽象了这些样板式代码,使我们的数据库代码变得简单明了,还可以避免因为关闭数据库资源失败而引发的问题。该模块在多种数据库服务的错误信息之上构建了一个语义丰富的异常层。

对于那些更喜欢ORM(Object-Relational Mapping)工具而不愿意直接使用JDBC的开发者,Spring提供了ORM模块。Spring的ORM模块建立在对DAO的支持之上,并为多个ORM框架提供了一种构建DAO的简便方式。Spring没有尝试去创建自己的ORM解决方案,而是对许多流行的ORM框架进行了集成,包括Hibernate、Java Persisternce API、Java Data Object和iBATIS SQL Maps。Spring的事务管理支持所有的ORM框架以及JDBC。

本模块同样包含了在JMS(Java Message Service)之上构建的Spring抽象层,它会使用消息以异步的方式与其他应用集成。从Spring 3.0开始,本模块还包含对象到XML映射的特性,它最初是Spring Web Service项目的一部分。除此之外,本模块会使用Spring AOP模块为Spring应用中的对象提供事务管理服务。

Web与远程调用

MVC(Model-View-Controller)模式是一种普遍被接受的构建Web应用的方法,它可以帮助用户将界面逻辑与应用逻辑分离。Java从来不缺少MVC框架,Apache的Struts、JSF、WebWork和Tapestry都是可选的最流行的MVC框架。

虽然Spring能够与多种流行的MVC框架进行集成,但它的Web和远程调用模块自带了一个强大的MVC框架,有助于在Web层提升应用的松耦合水平。

除了面向用户的Web应用,该模块还提供了多种构建与其他应用交互的远程调用方案。Spring远程调用功能集成了RMI(Remote Method Invocation)、Hessian、Burlap、JAX-WS,同时Spring还自带了一个远程调用框架:HTTP invoker。Spring还提供了暴露和使用REST API的良好支持。

Instrumentation

Spring的Instrumentation模块提供了为JVM添加代理(agent)的功能。具体来讲,它为Tomcat提供了一个织入代理,能够为Tomcat传递类文件,就像这些文件是被类加载器加载的一样。

测试

Spring为使用JNDI、Servlet和Portlet编写单元测试提供了一系列的mock对象实现。对于集成测试,该模块为加载Spring应用上下文中的bean集合以及与Spring上下文中的bean进行交互提供了支持。


1.3.2 Spring Protfolio


Spring Web Flow

Spring Web Flow建立于Spring MVC框架之上,它为基于流程的会话式Web应用(可以想一下购物车或者向导功能)提供了支持。你还可以访问Spring Web Flow的主页。

Spring Web Service

虽然核心的Spring框架提供了将Spring bean以声明的方式发布为Web Service的功能,但是这些服务是基于一个具有争议性的架构(拙劣的契约后置模型)之上而构建的。这些服务的契约由bean的接口来决定。 Spring Web Service提供了契约优先的Web Service模型,服务的实现都是为了满足服务的契约而编写的。

Spring Security

安全对于许多应用都是一个非常关键的切面。利用Spring AOP,Spring Security为Spring应用提供了声明式的安全机制。你可以在Spring文档上获得关于Spring Security的更多信息。

Spring Integration

许多企业级应用都需要与其他应用进行交互。Spring Integration提供了多种通用应用集成模式的Spring声明式风格实现。你可以访问Spring Integration的主页。

Spring Batch

当我们需要对数据进行大量操作时,没有任何技术可以比批处理更胜任这种场景。如果需要开发一个批处理应用,你可以通过Spring Batch,使用Spring强大的面向POJO的编程模型。你可以访问Spring Batch的主页。

Spring Data

Spring Data使得在Spring中使用任何数据库都变得非常容易。尽管关系型数据库统治企业级应用多年,但是现代化的应用正在认识到并不是所有的数据都适合放在一张表中的行和列中。一种新的数据库种类,通常被称之为NoSQL数据库,提供了使用数据的新方法,这些方法会比传统的关系型数据库更为合适。
不管你使用文档数据库,如MongoDB,图数据库,如Neo4j,还是传统的关系型数据库,Spring Data都为持久化提供了一种简单的编程模型。这包括为多种数据库类型提供了一种自动化的Repository机制,它负责为你创建Repository的实现。

Spring Social

这是Spring的一个社交网络扩展模块。不过,Spring Social并不仅仅是tweet和好友。尽管名字是这样,但Spring Social更多的是关注连接(connect),而不是社交(social)。它能够帮助你通过REST API连接Spring应用,其中有些Spring应用可能原本并没有任何社交方面的功能目标。可以查看网址accessing-facebook和accessing-twitter中的入门指南。

Spring Mobile

Spring Mobile是Spring MVC新的扩展模块,用于支持移动Web应用开发。

Spring For Android

这个新项目,旨在通过Spring框架为开发基于Android设备的本地应用提供某些简单的支持。最初,这个项目提供了Spring RestTemplate的一个可以用于Android应用之中的版本。它还能与Spring Social协作,使得原生应用可以通过REST API进行社交网络的连接。你可以通过spring-android了解更多内容。

Spring Boot

Spring极大地简化了众多的编程任务,减少甚至消除了很多样板式代码,如果没有Spring的话,在日常工作中你不得不编写这样的样板代码。
Spring Boot是一个崭新的令人兴奋的项目,它以Spring的视角,致力于简化Spring本身。Spring Boot大量依赖于自动配置技术,它能够消除大部分(在很多场景中,甚至是全部)Spring配置。它还提供了多个Starter项目,不管你使用Maven还是Gradle,这都能减少Spring工程构建文件的大小。

原创粉丝点击