Spring核心

Spring的核心

为了下降 Java 开发的复杂性,Spring 采起了如下 4 种关键策略:spring

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

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

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)。this

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 经过它的配置,可以了解这些组成部分是如何装配起来的。这样的话,就能够在不改变所依赖的类的状况下,修改依赖关系。spa

启动程序:日志

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 展现了这种复杂性。左边的业务对象与系统级服务结合得过于紧密。每一个对象不但要知道它须要记日志、进行安全控制和参与事务,还要亲自执行这些服务。
image.png

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

image.png

好比看看《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 的方法。

<bean id="minstrel" class="sia.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>

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

这就是咱们须要作的全部的事情!经过少许的 XML 配置,就能够把 Minstrel 声明为一个 Spring 切面

相关文章
相关标签/搜索