Spring核心

Spring的核心

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

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

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

public class BraveKnight implements Knight {  
    private Quest quest;  
    public BraveKnight(Quest quest){  
        this.quest = quest;  
    }  
    public void embarkOnQuest(){  
        quest.embark();  
    }  
}

Spring 赋予 POJO 魔力的方式之一就是通过 DI 来装配它们。让我们看看 DI 是如何帮助应用对象彼此之间保持松散耦合的。

依赖注入(DI)

BraveKnight 足够灵活可以接受任何赋予他的探险任务:

public class BraveKnight implements Knight {  
    private Quest quest;  
    public BraveKnight(Quest quest){ //构造器参数注入
        this.quest = quest;  
    }  
    public void embarkOnQuest(){  
        quest.embark();  
    }  
}

这里的要点是 BraveKnight 没有与任何特定的 Quest 实现发生耦合。对它来说,被要求挑战的探险任务只要实现了 Quest 接口,那么具体是哪种类型的探险就无关紧要了。这就是 DI 所带来的最大收益 —— 松耦合。如果一个对象只通过接口(而不是具体实现或初始化过程)来表明依赖关系,那么这种依赖就能够在对象本身毫不知情的情况下,用不同的具体实现进行替换。

SlayDragonQuest 是要注入到 BraveKnight 中的 Quest 实现:

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

这里最大的问题在于,我们该如何将 SlayDragonQuest 交给 BraveKnight 呢?又如何将 PrintStream 交给 SlayDragonQuest 呢?
创建应用组件之间协作的行为通常称为装配(wiring)。

Spring 提供了基于 Java 的配置,可作为 XML 的替代方案:

@Configuration  //相当于spring的配置文件XML
public class KnightConfig {  

  @Bean  //声明为 Spring 中的 bean,bean 的各种名称……虽然 Spring 用 bean 或者 JavaBean 来表示应用组件,但并不意味着 Spring 组件必须要遵循 JavaBean 规范。一个 Spring 组件可以是任何形式的 POJO。
  public Knight knight(){  
        return new BraveKnight(quest());  
    }  
  
  @Bean  
  public Quest quest() {  
        return new SlayDragonQuest(System.out);  
    }  
  
}

尽管 BraveKnight 依赖于 Quest,但是它并不知道传递给它的是什么类型的 Quest,也不知道这个 Quest 来自哪里。与之类似,SlayDragonQuest 依赖于 PrintStream,但是在编码时它并不需要知道这个 PrintStream 是什么样子的。只有 Spring 通过它的配置,能够了解这些组成部分是如何装配起来的。这样的话,就可以在不改变所依赖的类的情况下,修改依赖关系。

启动程序:

public class KnightMain {  
    public static void main(String[] args) {  
       KnightConfig knightConfig = new KnightConfig();  
       Knight knight = knightConfig.knight();  
       knight.embarkOnQuest();  
    }  
}

得到 Knight 对象的引用后,只需简单调用 embarkOnQuest() 方法就可以执行所赋予的探险任务了。注意这个类完全不知道我们的英雄骑士接受哪种探险任务,而且完全没有意识到这是由 BraveKnight 来执行的。只有KnightConfig知道哪个骑士执行哪种探险任务。

应用切面

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

图 1.2 展示了这种复杂性。左边的业务对象与系统级服务结合得过于紧密。每个对象不但要知道它需要记日志、进行安全控制和参与事务,还要亲自执行这些服务。
Spring核心_第1张图片

AOP 能够使这些服务模块化,并以声明的方式将它们应用到它们需要影响的组件中去。

Spring核心_第2张图片

比如看看《Spring in Action》书中的一个例子:使用骑士的例子,这里我们为他添加一个切面,假设我们需要使用吟游诗人这个服务类来记载骑士的所有事迹。

//添加吟游诗人这个服务类
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!");  
    }  
}

我们可以通过DI构造函数来注入这个类。

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();  
        minstrl.singAfterQuest();  
    }  
  
}

但是管理他的吟游诗人并不是骑士职责范围内的工作。毕竟,用诗歌记载骑士的探险事迹,这是吟游诗人的职责。为什么骑士还需要提醒吟游诗人去做他份内的事情呢?

此外,因为骑士需要知道吟游诗人,所以就必须把吟游诗人注入到 BarveKnight 类中。这不仅使 BraveKnight 的代码复杂化了,而且还让我疑惑是否还需要一个不需要吟游诗人的骑士呢?如果 Minstrel 为 null 会发生什么呢?我是否应该引入一个空值校验逻辑来覆盖该场景?

简单的 BraveKnight 类开始变得复杂,如果你还需要应对没有吟游诗人时的场景,那代码会变得更复杂。但利用 AOP,你可以声明吟游诗人必须歌颂骑士的探险事迹,而骑士本身并不用直接访问 Minstrel 的方法。

  
      
  
  
  
      
          
  
          
  
          
      

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

这就是我们需要做的所有的事情!通过少量的 XML 配置,就可以把 Minstrel 声明为一个 Spring 切面

你可能感兴趣的:(spring)