服务器之家

服务器之家 > 正文

闲言碎语-逐步了解Spring

时间:2021-02-18 14:07     来源/作者:Wung

WHY

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

WHAT

Spring是一个开源框架,最早由RodJohnson创建,Spring是为了解决企业级应用开发的复杂性而创建的,使用Spring可以让简单的JavaBean实现之前只有EJB才能完成的事情。Spring不仅仅限于服务端的开发,任何Java应用都能在简单性、可测试性和松耦合等方面从Spring中获益。

如今Spring在移动开发、社交API集成、NoSQL数据库、云计算及大数据方面都在涉足。随着时间的推移EJB也采用了依赖注入(DependencyInjection,DI)和面向切面编程(Aspect-OrientedProgramming,AOP)的理念。总之,Spring最根本的使命就是简化Java开发

HOW

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

基于POJO的轻量级和最小侵入性编程通过依赖注入和面向接口实现松耦合

基于切面和惯例进行声明式编程通过切面和模板减少样式代码

POJO潜能

很多框架通过强迫应用继承它们的类或实现它们的接口从而导致应用与框架绑定在一起,这就是侵入性编程,导致无法复用代码块。Spring竭力避免因自身的API而弄乱你的应用代码,在基于Spring构建的应用程序中,他的类通常没有任何痕迹表明你使用了Spring

?
1
2
3
4
5
public class HelloWorldBean {
  public String sayHello(){
    return "Hello World";
  }
}

以上的实例代码表示一个很简单普通的Java类(POJO),你看不出来他是一个Spring组件,Spring的非侵入式编程体现在这个类在Spring应用和非Spring应用中都可以发挥作用。仅仅这么一段代码并没有能实际体现Spring功能,还需要后面的知识。
依赖注入(将自身依赖的类注入的自身)

依赖注入这个此在Spring中并不是这么高大上,尽管现在已经演变成一项复杂的编程技巧或者设计模式理念。在Spring中可以这么理解,注入依赖。一个具有实际意义的应用都需要多个类进行相互协作来完成特定的业务逻辑。传统的做法是每个对象负责管理与自己有关的对象(这个有关的对象就是Spring中表述的所依赖的对象)的引用,不过这将会导致高度耦合和代码难以测试

考虑如下代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**这个拗口的类名是作者为了拟合一个模拟的场景特地取名
 * 这个类表示营救少女的骑士
 * 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),对象之间的依赖关系由系统中负责协调各对象的第三方组件在创建对象的时候进行设定。也就是说对象只需要管理自己内部的属性而无需自行创建或者管理它们的依赖关系,依赖关系会被自动注入到需要它们的对象当中去。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * 这个骑士可以执行各种冒险任务而不在仅仅是之前那个营救少女的任务
 * 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的目的——松耦合

如果一个对象只通过接口来表明依赖关系,那么这种依赖关系就能够在对象本身毫不知情的情况下用不同的具体实现来进行替代。

那么现在来进行实际意义的注入

对于上面那块代码我们来将一个具有具体实现的冒险任务注入到勇敢骑士中去

?
1
2
3
4
5
6
7
8
9
10
11
12
/**屠龙的冒险任务(这个作者好中二阿)
 * 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注解的装配

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--
    这就是一个装配的过程,将SlayDragonQuest声明为一个bean,取名为"quest"
    因为是构造器注入所以使用constructor-arg属性 value表示注入的值
    那么这个过程解决了之前的问题,将PrintStream对象注入到SlayDragonQuest对象中
  -->
  <bean class="impl.SlayDragonQuest" id="quest">
    <constructor-arg value="#{T(System).out}">
  </constructor-arg></bean>
 
  <!--
    这个过程同上,BraveKnight声明为一个bean取名为"knight"(不一定使用到这个名字)
    然后BraveKnight的构造器参数要求为Quest类型此时传入了另外一个bean的引用
    就是上面这个名字"quest"的bean的引用,此时也完成了对BraveKnight的构造器注入
   -->
  <bean class="impl.BraveKnight" id="knight">
    <constructor-arg ref="quest">
  </constructor-arg></bean>

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**基于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有好多中实现他们之间的区别仅仅是加载配置的方式不同,下面来看一种加载配置的方式

?
1
2
3
4
5
6
7
8
9
10
11
12
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能够让相互协作的软件组件保持松耦合,而面向切面编程允许你把遍布应用各处的功能分离出来形成可重用的组件,更详细的说,它是促使软件系统实现关注点奋力的一项技术。什么是关注点呢,诸如日志、事务管理、安全管理这样的系统服务经常需要融入到其他自身具有业务逻辑的组件中去,那么这些系统服务通常就被称为横切关注点,因为它们会在多个地方被重用,跨越系统的多个组件。简单来说,就是你把需要重用的经常会服务与各种其他组件的组件抽离出来,但是抽离出来如何使用呢,其实就是在用的时候将方法插入到需要使用的地方。但是根据这个"切面"的术语,应该表述为将重用的组件抽离出来作为一个切面,在需要使用的时候将切面横切进组件。所以这样就做到了核心应用不需要知道这些切面的存在,切面也不会将业务逻辑融合在核心应用中。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**一个歌手类用来歌颂骑士也就是服务于骑士类
 * 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");
  }
 
}
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class BraveKnight implements Knight{
    private Quest quest;
    private Minstrel minstrel;
    /**
   * BraveKnight没有自行创建冒险类型,而是在构造的时候把冒险任务作为参数传入
   * 这是依赖注入的方式之一:构造器注入
   */
    //  public BraveKnight(Quest quest){
    //    this.quest = quest;
    //  }
    public BraveKnight(Quest quest, Minstrel minstrel){
        this.quest = quest;
        this.minstrel = minstrel;
    }
    public void embarkOnQuest(){
        minstrel.singBeforeQuest();
        quest.embark();
        minstrel.singAfterQuest();
    }
}

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

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

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

使用模板消除样式代码

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

容纳你的Bean

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

应用上下文也分为多种,显著的区别的是加载配置的方式不同

Spring的各种功能

闲言碎语-逐步了解Spring

总结

Spring是一个可以简化开发的框架技术,核心内容是DI和AOP。

以上就是本文关于闲言碎语-逐步了解Spring的全部内容,希望对大家有所帮助。如有不足之处,欢迎留言指出。

原文链接:https://www.2cto.com/kf/201608/542320.html

标签:

相关文章

热门资讯

2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全
2020微信伤感网名听哭了 让对方看到心疼的伤感网名大全 2019-12-26
yue是什么意思 网络流行语yue了是什么梗
yue是什么意思 网络流行语yue了是什么梗 2020-10-11
Intellij idea2020永久破解,亲测可用!!!
Intellij idea2020永久破解,亲测可用!!! 2020-07-29
背刺什么意思 网络词语背刺是什么梗
背刺什么意思 网络词语背刺是什么梗 2020-05-22
苹果12mini价格表官网报价 iPhone12mini全版本价格汇总
苹果12mini价格表官网报价 iPhone12mini全版本价格汇总 2020-11-13
返回顶部