Spring In Action 00 ---Spring 之旅

原创 2016年08月25日 22:11:10

WHY

在诞生之初,创建Spring的主要目的是用来替代更加重量级的企业级Java技术,尤其是 EJB 。相对于EJB来说,Spring提供了更加轻量级和简单的编程模型。

WHAT

Spring是一个开源框架,最早由Rod Johnson创建,Spring是为了解决企业级应用开发的复杂性而创建的,使用Spring可以让简单的JavaBean实现之前只有EJB才能完成的事情。Spring不仅仅限于服务端的开发,任何Java应用都能在简单性、可测试性和松耦合等方面从Spring中获益。
如今Spring在移动开发、社交API集成、NoSQL数据库、云计算及大数据方面都在涉足。随着时间的推移EJB也采用了依赖注入(Dependency Injection,DI)和面向切面编程(Aspect-Oriented Programming,AOP)的理念。总之,Spring最根本的使命就是简化Java开发

HOW

为了降低Java开发的复杂性,Spring采取了4钟关键策略
  • 基于POJO的轻量级和最小侵入性编程
  • 通过依赖注入和面向接口实现松耦合
  • 基于切面和惯例进行声明式编程
  • 通过切面和模板减少样式代码

POJO潜能

很多框架通过强迫应用继承它们的类或实现它们的接口从而导致应用与框架绑定在一起,这就是侵入性编程,导致无法复用代码块。Spring竭力避免因自身的API而弄乱你的应用代码,在基于Spring构建的应用程序中,他的类通常没有任何痕迹表明你使用了Spring
public class HelloWorldBean {
    public String sayHello(){
        return "Hello World";
    }
}
以上的实例代码表示一个很简单普通的Java类(POJO),你看不出来他是一个Spring组件,Spring的非侵入式编程体现在这个类在Spring应用和非Spring应用中都可以发挥作用。仅仅这么一段代码并没有能实际体现Spring功能,还需要后面的知识。

依赖注入(将自身依赖的类注入到自身)

依赖注入这个词在Spring中并不是这么高大上,尽管现在已经演变成一项复杂的编程技巧或者设计模式理念。在Spring中可以这么理解,注入依赖。一个具有实际意义的应用都需要多个类进行相互协作来完成特定的业务逻辑。传统的做法是每个对象负责管理与自己有关的对象(这个有关的对象就是Spring中表述的所依赖的对象)的引用,不过这将会导致高度耦合和代码难以测试
考虑如下代码
/**这个拗口的类名是作者为了拟合一个模拟的场景特地取名
 * 这个类表示营救少女的骑士
 * Created by Wung on 2016/8/25.
 */
public class DamselRescuingKnight implements Knight{

    private RescueDamselQuest quest;

    /**
     * 在它的构造函数中自行创建了RescueDamselQuest
     * 这使得DamselRescuingKnight和在它的构造函数中自行创建了RescueDamselQuest耦合在了一起
     */
    public DamselRescuingKnight(){
        this.quest = new RescueDamselQuest();
    }

    public void embarkOnQuest(){
        quest.embark();
    }

}

耦合具有两面性,一方面,紧密耦合的代码难以测试难以复用难以理解,并且会出现"打地鼠"式的BUG。另一方面,一定程度的耦合又是必要的,不同的类必须以适当的方式进行交互。
问题出现了,那么Spring是如何解决的呢
在Spring中,通过依赖注入(DI),对象之间的依赖关系由系统中负责协调各对象的第三方组件在创建对象的时候进行设定。也就是说对象只需要管理自己内部的属性而无需自行创建或者管理它们的依赖关系,依赖关系会被自动注入到需要它们的对象当中去。
/**
 * 这个骑士可以执行各种冒险任务而不在仅仅是之前那个营救少女的任务
 * Created by Wung on 2016/8/25.
 */
public class BraveKnight implements Knight{

    private Quest quest;

    /**
     * BraveKnight没有自行创建冒险类型,而是在构造的时候把冒险任务作为参数传入
     * 这是依赖注入的方式之一:构造器注入
     */
    public BraveKnight(Quest quest){
        this.quest = quest;
    }

    public void embarkOnQuest(){
        quest.embark();
    }

}

BraveKnight没有与任何特定的Quest实现发生耦合,只要某个任务实现了Quest接口,那么具体是哪种类型的冒险就无所谓了,这就达到了DI的目的——松耦合
如果一个对象只通过接口来表明依赖关系,那么这种依赖关系就能够在对象本身毫不知情的情况下用不同的具体实现来进行替代。
那么现在来进行实际意义的注入
对于上面那块代码我们来将一个具有具体实现的冒险任务注入到勇敢骑士中去
/**屠龙的冒险任务(这个作者好中二阿)
 * Created by Wung on 2016/8/25.
 */
public class SlayDragonQuest implements Quest{

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

    public void embark() {
        stream.print("slay the dragon");
    }
}

那么现在问题来了,在SlayDragonQuest中如何注入它所依赖的PrintStream对象,在BraveKnight中如何注入它所依赖的Quest对象。前面提到的Spring会将这些依赖进行集中管理,创建应用组件之间协作的行为通常称为装配(wiring)也就是注入。Spring提供了多种装配的方式在后面会更加详细的介绍,此处简单介绍基于XML的装配和基于Java注解的装配
<!--
        这就是一个装配的过程,将SlayDragonQuest声明为一个bean,取名为"quest"
        因为是构造器注入所以使用constructor-arg属性 value表示注入的值
        那么这个过程解决了之前的问题,将PrintStream对象注入到SlayDragonQuest对象中
    -->
    <bean id="quest" class="impl.SlayDragonQuest">
        <constructor-arg value="#{T(System).out}"/>
    </bean>

    <!--
        这个过程同上,BraveKnight声明为一个bean取名为"knight"(不一定使用到这个名字)
        然后BraveKnight的构造器参数要求为Quest类型此时传入了另外一个bean的引用
        就是上面这个名字"quest"的bean的引用,此时也完成了对BraveKnight的构造器注入
     -->
    <bean id="knight" class="impl.BraveKnight">
        <constructor-arg ref="quest"/>
    </bean>
Spring提供了基于Java的配置,可作为XML的替代方案
/**基于Java的配置文件实现对象的装配
 * Created by Wung on 2016/8/26.
 */
@Configuration
public class KnightConfig {
    
    @Bean
    public Knight knight(){
        return new BraveKnight(quest());
    }

    @Bean
    public Quest quest(){
        return new SlayDragonQuest(System.out);
    }
}
效果是相同的,具体的解释在下一章中详细描述。现在再来回顾一下,一直说Spring会自动管理对象之间的依赖关系,那么这种管理者是什么。答案是Application Context(应用上下文),它是Spring的一种容器,可以装载bean的定义并将它们组装起来。Spring应用上下文全权负责对象的创建和组装。实际上这个Context有好多中实现他们之间的区别仅仅是加载配置的方式不同,下面来看一种加载配置的方式
public class KnightMain {

    public static void main(String[] args){
        AnnotationConfigApplicationContext context  = new
                AnnotationConfigApplicationContext(KnightConfig.class);
        //从配置文件中就可以获取到bean的定义了
        Knight knight = context.getBean(Knight.class);
        knight.embarkOnQuest();
        context.close();
    }

}

应用切面

DI能够让相互协作的软件组件保持松耦合,而面向切面编程允许你把遍布应用各处的功能分离出来形成可重用的组件,更详细的说,它是促使软件系统实现关注点分离的一项技术。什么是关注点呢,诸如日志、事务管理、安全管理这样的系统服务经常需要融入到其他自身具有业务逻辑的组件中去,那么这些系统服务通常就被称为横切关注点,因为它们会在多个地方被重用,跨越系统的多个组件。简单来说,就是你把需要重用的经常会服务于各种其他组件的组件抽离出来,但是抽离出来如何使用呢,其实就是在用的时候将方法插入到需要使用的地方。但是根据这个"切面"的术语,应该表述为将重用的组件抽离出来作为一个切面,在需要使用的时候将切面横切进组件。所以这样就做到了核心应用不需要知道这些切面的存在,切面也不会将业务逻辑融合在核心应用中。
/**一个歌手类用来歌颂骑士也就是服务于骑士类
 * Created by Wung on 2016/8/26.
 */
public class Minstrel {

    private PrintStream stream;

    public Minstrel(PrintStream stream){
        this.stream = stream;
    }
    //在冒险之前执行
    public void singBeforeQuest(){
        stream.print("Begin");
    }
    //在冒险之后执行
    public void singAfterQuest(){
        stream.print("End");
    }

}

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(){
        minstrel.singBeforeQuest();
        quest.embark();
        minstrel.singAfterQuest();
    }

}
这个时候这个勇敢的骑士开始执行,但是他发现在他的职责中不仅仅是冒险了,现在竟然还要管理这个歌手要为他歌颂,然而这本身并不应该属于这个类应该管理的。所以应用切面的思想,我们需要将这个歌手的歌颂行为抽离出来成为一个切面,在骑士冒险之前这个切面将会横切进去执行singBeforeQuest方法在冒险之后执行singAfterQuest方法。那么这样是不是就实现了骑士中不需要歌颂的代码,歌手也不存在与骑士对象中,他将不仅仅赞颂骑士,也可以赞颂任何人只要别人使用这个切面切入即可
<bean id="minstrel" class="impl.Minstrel">
        <constructor-arg value="#{T(System).out}"/>
</bean>

    <aop:config>
        <!-- 意思就是将上述id为minstrel的bean配置为切面实际上就是把歌手配置为切面 -->
        <aop:aspect ref="minstrel">
            <!--
                定义切入点,也就是在哪里会使用切面
                expression="execution(* *.embarkOnQuest(..))
                是一种AspectJ的切点表达式语言后面会深入
                此处的意思是会在embarkOnQuest()方法处切入
                下面那个分别为前置通知和后置通知在切入点之前和之后执行
            -->
            <aop:pointcut id="embark" expression="execution(* *.embarkOnQuest(..))"/>
            <aop:before method="singBeforeQuest"
                        pointcut-ref="embark"/>
            <aop:after method="singAfterQuest"
                       pointcut-ref="embark"/>
        </aop:aspect>
    </aop:config>


现在的情况是Minstrel仍然是独立的一个POJO,Spring的上下文已经将他变成了一个切面了。最重要的是此时骑士完全不知道这个切面的存在,这只是一个小小的栗子,实际上可以做很多重要的事情。


使用模板消除样式代码

有这样一种情况,当我们使用JDBC访问数据库查询数据的时候,完整的流程需要建立连接、创建语句对象、处理结果集、查询、关闭各种连接,此外还需要各种异常的捕获,然后对于各种场景的查询都需要如此费心费力的重复。JDBC还不仅仅是唯一这样会有大量的样式代码的情况,Spring旨在通过模板封装来消除样式代码,比如Spring的jdbcTemplate。

容纳你的Bean

在基于Spring的应用中,你的应用对象生存于Spring容器,容器负责创建对象装配对象并且管理他们的生命周期。那什么是Spring的容器呢,容器并不是只有一种,Spring自带了多个容器实现。分为两类:一个是bean工厂,是最简单的容器提供基本的DI支持。另一个是应用上下文,比较高级提供应用框架级别的服务。大多数情况下应用上下文更加受欢迎。
应用上下文也分为多种,显著的区别的是加载配置的方式不同
  • AnnotationConfigApplicationContext
  • AnnotationConfigWebApplicationContext
  • ClassPathXmlApplicationContext
  • FileSystemXmlApplicationContext
  • XmlWebApplicationContext

Spring的各种功能


结束

Spring是一个可以简化开发的框架技术,核心内容是DI和AOP。
版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

Spring in action 00

从今天开始再学习一遍Spring,这是一个强大的开源框架,最早由Rod Johnson 创建,我也不认识他。每个学习Java 的童鞋都用过,但是你真的很懂它么,反正我是不太懂,所以从今儿起,我要开始一...
  • stanxl
  • stanxl
  • 2016年05月26日 00:04
  • 425

Spring in Action(第一章 Spring之旅)

0.核心特性 支持依赖注入(DI)和面向切面编程(AOP) 1.耦合具有两面性 a.紧密耦合的代码难以测试,难以复用,难以理解 b.完全没有耦合的代码什么也做不了,不同的类必须以适当的...

《Spring in action》——学习总结(三)

这周开始着手的AOP,因为同时在弄数学建模和计算机网络的复习,所以跟着书和视频只学了一点点。面向切面的Spring(AOP)概念: AOP:Aspect Oriented Programming的缩...

Spring in Action 第一部分学习笔记

Spring in Action是一本讲解Spring的经典书籍,全书分为三个部分,第一部分介绍Spring框架的核心知识。第二部分在此基础上深入介绍Spring应用程序的常用元素,最后一个部分展示S...
  • dtlscsl
  • dtlscsl
  • 2015年09月25日 15:00
  • 1163

Spring In Action(一):基于注解装配bean

@Component注解,将一个类作为组件 1.创建一个Spring项目,修改pom文件,添加Spring单元测试所需依赖 ...

《Spring In Action》 读书笔记(3) -- factory-method的应用

因为单例类的特殊性,spring对于单例类的注入提供了factory-method属性。

xml形式装配bean——spring in action chapter 2

1、声明bean 利用beans命名空间: 当spring容器加载该bean时,spring将调用默认的构造器实例化beanClass,相当于 new beanClass()...

Spring Integration in action 2 - 消息传递(Messaging)

3 消息(Messages)和管道(channels) 3.1 介绍Spring Integration Messages 3.1.1 4 消息端点(Message Endpoints) 5 ...

Spring in action 01 -- 装配 Bean(@Autowired)

昨天走马观花,看一遍Spring,当然也比较的片面,今天就从第一个知识点详细些再学习。温故而知新,可以为师也,如此而而一个概念 装配(wiring) : 创建应用对象之间协作关系的行为通常称为装配。它...
  • stanxl
  • stanxl
  • 2016年05月29日 18:40
  • 416

Spring In Action 05 ---渲染Web视图

视图解析器 将控制器中请求处理的逻辑和视图中的渲染实现解耦是Spring MVC的一个重要特性,如果在控制器的方法中直接产生HTML的话,就很难在不影响请求处理逻辑的前提下维护和更新视图。控制器方法和...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Spring In Action 00 ---Spring 之旅
举报原因:
原因补充:

(最多只允许输入30个字)