Spring之旅:Spring是如何简化Java开发的?

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

  1. 基于POJO的轻量级和最小侵入性编程;
  2. 通过依赖注入(DI)和面向接口实现松耦合;
  3. 基于切面和惯例进行切面式编程;
  4. 通过切面和模板减少样本式代码;
    下面对这几种策略进行通俗的讲解,以便能够快速入门。

1.基于POJO的轻量级和最小侵入性编程

最小侵入性编程体现在,Spring竭力避免因自身的API而弄乱你的应用代码。Spring不会强迫你使用Spring的接口或是继承Spring的类,相反,在基于Spring构建的应用中,它的类通常没有任何痕迹表明你使用了Spring。最坏的场景是,一个类可能会使用Spring注解,但它依旧是POJO(Plain Old Java Object, 即普通Java对象)。
Spring赋予POJO魔力的方式之一就是通过DI装配它们,DI是如何帮助应用对象之间保持松散耦合的?此时依赖注入(DI)登场了。

2. 通过依赖注入(DI)和面向接口实现松耦合

2.1DI功能如何实现的?

任何有实际意义的应用都会由两个或者更多的类组成,它们之间相互协作来完成特定的业务逻辑。传统的做法是每个对象管理与自己交互协作的对象(即它所以来的对象)的引用,这样会导致高度耦合和难以测试的代码,首先看一个例子:

package com.springinaction.knights;

public class DamselRescuingKnight implements Knight {

    private RescueDamselQuest quest;

    public DamselRescuingKnight() {
        this.quest = new RescueDamselQuest();     // 与RescueDamselQuest紧耦合
    }

    public void embarhOnQuest() throws QuestException {
        quest.embark();
    }

}

这里的DamselRescuingKnight在构造函数中自行创建了RescueDamselQuest,使得两者高度耦合,这样极大限制了骑士执行探险的能力:只能救援少女,若要杀掉恶龙就爱莫能助了。而且,为DamselRescuingKnight编写单元测试极其困难。
通过DI,对象的依赖关系将由系统中负责协调对象的第三方组件在创建对象的时候进行设定,对象无需自行创建或管理它们之间的依赖关系。下面的代码是一个勇敢骑士类,可以挑战任何探险:

package com.springinaction.knights;

public class BraveKnight implements Knight {

    private Quest quest;

    public BraveKnight(Quest quest) {                // Quest被注入到对象中
        this.quest = quest;           
    }

    
    public void embarhOnQuest() throws QuestException {
        quest.embark();
    }

}

与之前不同的是,BraveKnight没有自行创建探险任务,而是在构造的时候把探险任务作为构造器参数传入。(这是依赖注入的方式之一:构造器注入)更重要的是,传入的探险类型是Quest接口,BraveKnight并没有与任何特定的Quest发生耦合。因此骑士挑战的探险任务只要实现Quest接口就可以了,具体是什么类型的探险任务就无关紧要了,实现了骑士与探险任务之间的松耦合。
这就是DI带来的最大利益——松耦合。如果一个对象只通过接口(而不是具体实现或初始化的过程)来表明依赖关系,那么这种依赖就能够在对象本身毫不知情的情况下,用不同的具体实现进行替换。那如何将特定的Quest实现类SlayDragonQuest传给BraveKnight类呢?
SlayDragonQuest实现如下:

package com.springinaction.knights;

import java.io.PrintStream;

public class SlayDragonQuest implements Quest {   //实现Quest接口

    private PrintStream stream;

    public SlayDragonQuest(PrintStream stream) {
        this.stream = stream;
    }

    public void embark() {
        stream.println("Embarking on quest to slay the dragon!");
    }
}

创建了这个类之后,剩下的就是将这个类交给BraveKnight。这就是创建应用组件之间的写作的行为,被称为装配(wiring)。Spring有多种装配Bean的方式,其中最常用的就是通过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="com.springinaction.knights.BraveKnight">
        <constructor-arg ref="quest"/>   <!--注入Quest bean-->
    </bean>

    <bean id="quest" class="com.springinaction.knights.SlayDragonQuest"><!--创建SlayDragonQuest-->
        <constructor-arg value="#{T(System).out}" />
    </bean>

</beans>

以上配置代码实现了:

  1. 将BraveKnight和SlayDragonQuest声明为Spring中的bean。
  2. 在构造BraveKnight bean的时候传入SlayDragonQuest bean的应用,作为构造器的参数。
  3. 在构造SlayDragonQuest bean的时候将System.out传入到构造器中

2.2我们如何使用?

Spring通过应用上下文(ApplicationContext)来装载Bean,ApplicationContext全权负责对象的创建和组装。
Spring自带了多种ApplicationContext来加载配置,比如,Spring可以使用ClassPathXmlApplicationContext来装载XML文件中的Bean对象。

package com.springinaction.knights;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class KnightMain {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("knights.xml");// 加载Spring上下文
        Knight knight = (Knight) context.getBean("knight");// 获取knight bean
        knight.embarhOnQuest();// 使用knight
    }
}

这个示例代码中,Spring上下文加载了knights.xml文件,随后获取了一个ID为knight的Bean的实例,得到该对象实例后,就可以进行正常的使用了。需要注意的是,这个类中完全不知道是由哪个Knight来执行何种Quest任务,只有knights.xml文件知道。

3. 基于切面和惯例进行切面式编程

通常情况下,系统由许多不同组件组成,其中的每一个组件分别负责一块特定功能。除了实现自身核心的功能之外,这些组件还经常承担着额外的职责,诸如日志、事务管理和安全等,此类的系统服务经常融入到有自身核心业务逻辑的组件中去,这些系统服务通常被称为横切关注点,因为它们总是跨越系统的多个组件。如下图:



AOP可以使得这些服务模块化,如下图。借助AOP,可以使用各种功能层去包裹核心业务层,并以声明的方式将它们应用到相应的组件中去,核心应用根本不知道他们的存在,只需关注自身业务,完全不需要了解系统服务的复杂性。



我们把AOP应用在上面的例子中,人们大多是通过说书人描写叙述的骑士的事迹来了解骑士的。如果你想使用说书人这个服务来记录BraveKnight的一些事迹。以下的代码清单列出了你可能会用到的说书人(Minstrel)类。
package com.springinaction.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!");
    }

}

如代码中所示,诗人会在骑士每次执行探险前和结束时被调用,完成骑士事迹的歌颂。骑士必须调用诗人的方法完成歌颂:

package com.springinaction.knights;

public class BraveKnight implements Knight {

    private Quest quest;
    private Minstrel minstrel;

    public BraveKnight(Quest quest) {
        this.quest = quest;// quest被注入到对象中
    }
    
    public BraveKnight(Quest quest, Minstrel minstrel) {
        this.quest = quest;// quest被注入到对象中
        this.minstrel = minstrel;
    }

    public void embarhOnQuest() throws QuestException {
        minstrel.singAfterQuest();
        quest.embark();
        minstrel.singAfterQuest();
    }

}

但是,感觉是骑士在路边抓了一个诗人为自己「歌功颂德」,而不是诗人主动地为其传扬事迹,简单的BraveKnight类开始变得复杂。但是有了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.springinaction.knights.BraveKnight">
        <constructor-arg ref="quest"></constructor-arg>
    </bean>

    <bean id="quest" class="com.springinaction.knights.SlayDragonQuest"></bean>

    <!-- 声明诗人Minstrel -->
    <bean id="minstrel" class="com.springinaction.knights.Minstrel"></bean>

    <aop:config>
        <!-- 将Minstrel类设置为切面 -->
        <aop:aspect ref="minstrel">
            <!-- 定义切点,即定义从哪里切入 -->
            <aop:pointcut expression="execution(* *.embarkOnQuest(..))"
                id="embark" />
            <!-- 声明前置通知-->
            <aop:before method="singBeforeQuest" pointcut-ref="embark" />
            <!-- 声明后置通知 -->
            <aop:after method="singAfterQuest" pointcut-ref="embark" />
        </aop:aspect>
    </aop:config>

</beans>

通过运行结果可以发现,在没有改动BraveKnight的代码的情况下,就完成了Minstrel对其的歌颂,而且BraveKnight并不知道Minstrel的存在。

4. 通过切面和模板减少样本式代码

使用Spring模版可以消除很多样板式代码,比如JDBC、JMS、JNDI、REST等。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 227,488评论 6 531
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 98,034评论 3 414
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 175,327评论 0 373
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,554评论 1 307
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,337评论 6 404
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 54,883评论 1 321
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 42,975评论 3 439
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,114评论 0 286
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,625评论 1 332
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,555评论 3 354
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,737评论 1 369
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,244评论 5 355
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 43,973评论 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,362评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,615评论 1 280
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,343评论 3 390
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,699评论 2 370

推荐阅读更多精彩内容