《Spring实战》学习笔记-第一章:Spring之旅

简洁的Spring

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

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

激发POJO的潜能

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

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

依赖注入

任何一个有意义的应用一般都需要多个组件,这些组件之间必定需要进行相互协作才能完成特定的业务,从而导致组件之间的紧耦合,牵一发而动全身
代码示例:

package com.springinaction.knights;

public class DamselRescuingKnight implements Knight {

    private RescueDamselQuest quest;

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

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

}

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

另一方面,可以通过依赖注入的方式来完成对象之间的依赖关系,对象不再需要自行管理它们的依赖关系,而是通过依赖注入自动地注入到对象中去。

代码示例:

package com.springinaction.knights;

public class BraveKnight implements Knight {

    private Quest quest;

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

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

}

不同于之前的DamselRescuingKnight,BraveKnight没有自行创建探险任务,而是在构造器中把探险任务作为参数注入,这也是依赖注入的一种方式,即构造器注入

更为重要的是,BraveKnight中注入的探险类型是Quest,Quest只是一个探险任务所必须实现的接口。因此,BraveKnight能够响RescueDamselQuest、SlayDraonQuest等任意一种Quest实现,这正是多态的体现。

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

注入一个Quest到Knight

创建应用组件之间协作关系的行为称为装配,Spring有多种装配Bean的方式,其中最常用的就是通过XML配置文件的方式装配。
示例代码:使用Spring将SlayDragonQuest注入到BraveKnight中。

<?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"></constructor-arg>
    </bean>

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

</beans>

Spring是如何注入的?

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文件知道。

应用切面

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

对遍布系统的横切关注点的调用散布在各个组件里,而这些关注点并不是组件的核心业务
对遍布系统的横切关注点的调用散布在各个组件里,而这些关注点并不是组件的核心业务

AOP可以使得这些服务模块化,并以声明的方式将它们应用到相应的组件中去,这样,这些组件就具有更高内聚性以及更加关注自身业务,完全不需要了解可能涉及的系统服务的复杂性。总之,AOP确保POJO保持简单。

利用AOP,可以将横切关注点覆盖在所需的组件之上,而这些组件不再需要额外的关注这些非核心业务。
利用AOP,可以将横切关注点覆盖在所需的组件之上,而这些组件不再需要额外的关注这些非核心业务。

如图所示,我们可以把切面想象为覆盖在很多组件之上的一个外壳。利用AOP,你可以使用各种功能层去包裹核心业务层。这些层以声明的方式灵活应用到你的系统中,甚至你的核心应用根本不知道它们的存在。

AOP应用

接上面骑士的故事,现在需要一个诗人来歌颂骑士的勇敢事迹,代码如下「Minstrel是中世纪的音乐记录器」:

package com.springinaction.knights;

public class Minstrel {
    public void singBeforeQuest() { // 探险之前调用
        System.out.println("Fa la la; The knight is so brave!");
    }

    public void singAfterQuest() { // 探险之后调用
        System.out.println("Tee hee he; 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;
    }

    @Override
    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>
        <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的存在。

使用Spring模版

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

容纳Bean

在Spring中,应用对象生存于Spring容器中,如图所示,Spring容器可以创建、装载、配置这些Bean,并且可以管理它们的生命周期。

在Spring中,对象由Spring容器创建、装配、管理
在Spring中,对象由Spring容器创建、装配、管理

Spring的容器实现

  • Bean工厂(org.springframework.beans.factory.BeanFactory):最简单的容器,提供基本的DI支持;
  • 应用上下文(org.springframework.context.ApplicationContext):基于BeanFactory之上构建,提供面向应用的服务。

常用的几种应用上下文

  • ClassPathXmlApplicationContext:从类路径中的XML配置文件加载上下文,会在所有的类路径(包括jar文件)下查找;
  • FileSystemXmlApplicationContext:从文件系统中读取XML配置文件并加载上下文,在指定的文件系统路径下查找;
  • XmlWebApplicationContext:读取Web应用下的XML配置文件并加载上下文;

Bean的生命周期

Spring中Bean的生命周期
Spring中Bean的生命周期
  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()接口方法,将应用上下文的引用传入;
  6. 如果Bean实现了BeanPostProcessor接口,Spring将调用postProcessBeforeInitialization()接口方法;
  7. 如果Bean实现了InitializationBean接口,Spring将调用afterPropertiesSet()方法。类似的如果Bean使用了init-method声明了初始化方法,该方法也会被调用;
  8. 如果Bean实现了BeanPostProcessor接口,Spring将调用ProcessAfterInitialization()方法;
  9. 此时此刻,Bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁;
  10. 如果Bean实现了DisposableBean接口,Spring将调用destory()方法,同样的,如果Bean中使用了destroy-method声明了销毁方法,也会调用该方法;

纵观Spring

Spring模块

Spring中的6个重要模块
Spring中的6个重要模块

核心Spring容器

容器是Spring框架最核心的部分,它负责Spring应用中Bean的创建、配置和管理。Spring模块都构建与核心容器之上,当配置应用时,其实都隐式地使用了相关的核心容器类。另外,该模块还提供了许多企业级服务,如邮件、JNDI访问、EJB集成和调度等。

AOP

AOP是Spring应用系统开发切面的基础,与依赖注入一样,可以帮助应用对象解耦。借助于AOP,可以将遍布于应用的关注点(如事务和安全等)从所应用的对象中解耦出来。

数据访问与集成

Spring的JDBC和DAO模块封装了大量的样板代码,这样可以使得在数据库代码变得简洁,也可以更专注于我们的业务,还可以避免数据库资源释放失败而引发的问题。另外,Spring AOP为数据访问提供了事务管理服务。同时,Spring还与流程的ORM(Object-Relational Mapping)进行了集成,如Hibernate、MyBatis等。

Web和远程调用

Spring提供了两种Web层框架:面向传统Web应用的基于Servlet的框架和面向使用Java Portlet API的基于Portlet的应用。Spring远程调用服务集成了RMI、Hessian、Burlap、JAX-WS等。

测试

Spring提供了测试模块来测试Spring应用。


如果觉得有用,欢迎关注我的微信,有问题可以直接交流:

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 依赖注入 按照传统的做法,每个对象负责管理与自己相互协作的对象的引用,这将会导致高度耦合和难以测试的代码。例如: ...
    谢随安阅读 487评论 0 0
  • 纵览Spring , 读者会发现Spring 可以做非常多的事情。 但归根结底, 支撑Spring的仅仅是少许的基...
    六尺帐篷阅读 1,589评论 0 12
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,367评论 6 343
  • 父亲的财富 微风如裙纱一般 温柔拂过青涩的苗圃 一双可人的 兴艳的眸子 笑意里是黄土地 给予父亲的希望 童年的时候...
    零温度阅读 231评论 0 3