Spring实战笔记(Ch01-Spring之旅)

来源:互联网 发布:免费下载音乐软件 编辑:程序博客网 时间:2024/06/06 00:43

1.1 简化Java开发

Spring是一个开源框架,是为了解决企业级应用开发的复杂性而创建的,使用Spring可以让简单的JavaBean实现之前只有EJB才能完成的事情。但Spring不仅仅局限于服务器端开发,任何Java应用都能在简单性、可测试性和松耦合等方面从Spring中获益。

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

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

1.1.1 激发POJO的潜能

相对于EJB的臃肿,Spring尽量避免因自身的api而弄乱用户的应用代码,Spring不会强迫用户实现Spring规范的接口或继承Spring规范的类,相反,在基于Spring构建的应用中,它的类通常没有任何痕迹表明你使用了Spring。最坏的场景是,一个类或许会使用Spring注解,但它依旧是POJO。

//一个简单普通的Java类——POJO//Spring的非侵入编程模型意味着这个类在Spring应用和非Spring应用中都可以发挥同样的作用public class HelloWorldBean{    public String SayHello(){        return "Hello World";    }}

Spring赋予POJO魔力的方式之一就是通过依赖注入DI来装载它们。

1.1.2 依赖注入

DI的实现

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

package com.sia.knights;public class DamselRescuingKnight implements Knight {    private RescueDamselQuest quest;    public DamselRescuingKnight() {        this.quest = new RescueDamselQuest();   //与RescueDamselQuest紧耦合    }    public void embarkOnQuest() {        quest.embark();    }}

可以看到,DamselRescuingKnight在它的构造函数中自行创建了Rescue DamselQuest。这使得DamselRescuingKnight紧密地和RescueDamselQuest耦合到了一起,因此极大地限制了这个骑士执行探险的能力。如果一个少女需要救援,这个骑士能够召之即来。但是如果一条恶龙需要杀掉,那么这个骑士就爱莫能助了。

通过DI,对象的依赖关系将由系统中负责协调各对象的第三方组件在创建对象的时候进行设定。,对象无需自行创建或管理它们的依赖关系。

package com.sia.knights;public class BraveKnight implements Knight {  private Quest quest;  public BraveKnight(Quest quest) { //Quest被注入    this.quest = quest;  }  public void embarkOnQuest() {    quest.embark();  }}

不同于之前的DamselRescuingKnight, BraveKnight没有自行创建探险任务, 而是在构造的时候把探险任务作为构造器参数传入。这是依赖注入的方式之一,即构造器注入(constructor injection)

更重要的是,传入的探险类型是Quest,也就是所有探险任务都必须实现的一个接口。所以,BraveKnight能够响应RescueDamselQuest、SlayDragonQuest、MakeRound TableRounderQuest等任意的Quest实现,这正是多态的体现

这里的要点是BraveKnight没有与任何特定的Quest实现发生耦合。对它来说,被要求挑战的探险任务只要实现了Quest接口,那么具体是哪种类型的探险就无关紧要了。这就是DI所带来的最大收益——松耦合

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

将Quest注入到knight中

//SlayDragonQuest 是要注入到BraveKnight中的Quest实现package com.sia.knights;import java.io.PrintStream;public class SlayDragonQuest implements Quest {  private PrintStream stream;  public SlayDragonQuest(PrintStream stream) {    this.stream = stream;  }  public void embark() {    stream.println("Embarking on quest to slay the dragon!");  }}

创建应用组件之间协作的行为通常称为装配(wiring)
Spring有多种装配bean的方式:
1.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 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">    <bean id="knight" class="com.sia.knights.BraveKnight">        <constructor-arg ref="quest"/>    </bean>    <bean id="quest" class="com.sia.knights.SlayDragonQuest">        <constructor-arg value="#{T(System).out}"/>    </bean></beans>

2.Java配置

package com.sia.knights.config;import com.sia.knights.BraveKnight;import com.sia.knights.Knight;import com.sia.knights.Quest;import com.sia.knights.SlayDragonQuest;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);    }}

3.观察工作

Spring通过应用上下文(Application Context) 装载bean的定义并把它们组装起来。Spring应用上下文全权负责对象的创建和组装。

package com.sia.knights;import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.AnnotationConfigApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class KnightMain {    public static void main(String[] args) throws Exception {        ClassPathXmlApplicationContext context =                new ClassPathXmlApplicationContext("META-INF/spring/knight.xml");   //加载Spring上下文        Knight knight = context.getBean(Knight.class);  //获取knight bean        knight.embarkOnQuest(); //使用knight        context.close();// 基于Java配置加载应用上下文// ApplicationContext context1 = new AnnotationConfigApplicationContext(com.sia.knights.config.KnightConfig.class);    }}

1.1.3 应用切面

面向切面编程(aspect-oriented programming, AOP) 允许把遍布应用各处的功能分离出来形成可重用的组件。

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

AOP应用

package com.sia.knights;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!");    }}

如果把吟游诗人minstrel注入到BraveKnight中,会使简单的BraveKnight类变得复杂。
但利用AOP,你可以声明吟游诗人必须歌颂骑士的探险事迹,而骑士本身并不用直接访问Minstrel的方法。

要将Minstrel抽象为一个切面,只需要把在knights.xml中配置。

……<bean id="minstrel" class="com.sia.knights.Minstrel">    <constructor-arg value="#{T(System).out}"/></bean><aop:config>    <aop:aspect ref="minstrel">        <aop:pointcut id="embark" expression="execution(* *.embarkOnQuest(..))"></aop:pointcut>        <aop:before pointcut-ref="embark" method="singBeforeQuest"></aop:before>        <aop:after pointcut-ref="embark" method="singAfterQuest"></aop:after>    </aop:aspect></aop:config>

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

在这两种方式中,pointcut-ref属性都引用了名字为embank的切入点。该切入点是在前边的<pointcut>元素中定义的,并配置expression属性来选择所应用的通知。表达式的语法采用的是AspectJ的切点表达式语言

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

样板式的代码(boilerplate code),通常为了实现通用的和简单的任务,你不得不一遍遍地重复编写这样的代码。

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

1.2 容纳的你bean

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

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

1.2.1 使用应用上下文

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

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

1.2.2 bean的声明周期

bean在Spring容器中从创建到销毁经历了若干阶段,每一阶段都可以针对Spring如何管理bean进行个性化定制。
Markdown
详细描述:
1. Spring对bean进行实例化;
2. Spring将值和bean的引用注入到bean对应的属性中;
3. 如果bean实现了BeanNameAware接口,Spring将bean的ID传递给setBean-Name()方法;
4. 如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入;
5. 如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引用传入进来;
6. 如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessBeforeInitialization()方法;
7. 如果bean实现了InitializingBean接口,Spring将调用它们的after-PropertiesSet()方法。类似地,如果bean使用initmethod声明了初始化方法,该方法也会被调用;
8. 如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessAfterInitialization()方法;
9. 此时,bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁;
10. 如果bean实现了DisposableBean接口,Spring将调用它的destroy()接口方法。同样,如果bean使用destroy-method声明了销毁方法,该方法也会被调用。

1.3 Spring风景线

1.3.1 Spring模块

Spring由6个定义良好的模块分类组成
Markdown

1.3.2 Spring Portfolio

事实上,Spring远不是Spring框架所下载的那些。整个Spring Portfolio包括多个构建于核心Spring框架之上的框架和类库。概括地讲,整个SpringPortfolio几乎为每一个领域的Java开发都提供了Spring编程模型。
具体详情参阅Spring官网

原创粉丝点击