Spring学习(1)------初识Spring

今天将开始了解学习Spring,尽管也曾使用过Spring,或者说看看几个简单的Demo也能马马虎虎用起来Spring,但是深深地感觉到一知半解地使用在很多地方都不能搞清楚,小项目就罢了,遇到稍微大一些的项目就显得力不从心。于是对于学习Spring的过程做一个记录,当然如果对大家有一丝丝帮助,笔者将会十分开心。笔者是就着Spring in Action这本书学习的,当然也学习了Spring boot的官方文档,大家可以参考。

======================================================================================================================================

首先我们要对Spring有一个基本的认识,Spring是一个轻量级的java开发框架,对于初学者来说,对于框架的概念可能很熟悉但却仅仅知其然不知其所以然,这里简单地描述一下框架,可以理解为是在某一特定范围内的“规范”,框架使用得广泛了,可能就会逐渐转化为规范,因此大家只要知道,Spring约定了某一范围内的编程规范即可。那么为什么要使用Spring呢,前面我们刚刚提到Spring是一个“轻量级”的开发框架,是为了代替原本的重量级企业java技术而产生的,其核心目的就是简化java的开发。为了降低java开发的复杂性,Spring采取了一下四种策略:

1、基于POJO(Plain Ordinary Java Object)的轻量级和最小侵入性编程;

2、通过依赖注入和面向接口编程实现松耦合;

3、基于切面和惯例进行声明式编程;

4、通过切面和模板减少样板式代码。

提到Spring的核心,有所了解的朋友可能会立刻想到IoC(CInversion of Control)即反转控制,以及AoP(Aspect-Oriented Programming)面向切面编程这两个概念,但是本篇将会不提IoC而是介绍DI(Dependency Injection)依赖注入的概念,这里我去查找了资料,关于IoC和DI的关系,请参考http://zhangjunhd.blog.51cto.com/113473/126530;简单来说DI可以实现IoC。

一、从DI入手初识Spring的魅力

首先我们来理解一下什么是非侵入性编程,所谓的侵入式编程就是强迫应用继承框架的类或实现其接口,从而导致应用与框架绑定,非侵入式则恰恰相反,Spring竭力避免因为自身的API而弄乱你的代码,我们举个例子说明:

package com.cambridge.spring

public class HelloWorldBean{
public String sayHello(){
        return "Hello World";
     }
}
可以看出来这是一个POJO,非侵入式编程的模式意味着这个类无论在Spring还是非Spring的应用中都能发挥同样的作用;那么如何使我们的POJO变得更加强大呢,Spring的方式之一就是通过DI来装配它们,DI可以有效地帮助对象之间保持松散的耦合状态。

一个具有实际意义的应用必然会有很多的类组成,这些类之间需要相互传递消息,相互协作才能够完成特定的功能,传统来看,每个对象负责管理与自己相互协作的对象(即 它所依赖的对象)的引用,这导致高度耦合和难以测试的代码。举个例子:

public class DamselRescuingKnight implements Knight{

   private RescueDamselQuest quest;

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

   public void embarkOnQuest(){
      quest.embark();
   }
}
可以看出来,这个拯救少女的骑士与拯救少女的任务紧密的耦合到了一起,因为DamselRescuingKnight在构造函数中自行创建了RescueDamselQuest,这极大地限制了骑士的能力,如果少女需要救援,骑士可以身先士卒,但如果有其他冲锋陷阵的事儿,这个骑士就帮不上忙了;这种紧耦合不仅使得类的功能受到限制,而且将会使得测试十分困难。但如果通过DI,对象之间的依赖关系将由系统中负责协调各对象的第三方组件在创建对象的时候进行设定,对象自身则无需自行创建管理它们的依赖关系,依赖关系,江北自动注入到需要它们的对象中去!还是上面的例子,我们来看一看另一个勇敢的骑士:

public class BraveKnight implements Knight{

   private Quest quest;

   public BraveKnight(Quest quest){                //Quest被注入进来
      this.quest = quest;
   }

   public void embarkOnQuest(){
      quest.embark();
   }
}
从这个例子中可以看出来,BraveKnight没有自行创建探险任务,而是在构造的时候把探险任务作为构造器参数传入,这就是DI的方式之一构造器注入,对比两种方式可以发现,前者DamselRescuingKnight的构造依赖于RescueDamselQuest的构造实现,而后者不关心Quest构造的具体实现,这就是两者最大的区别;并且,BraveKnight没有与任何特定的Quest实现发生耦合,因此只要需要挑战的任务实现了Quest接口,那么具体哪种类型的探险就无关紧要了,这就是松耦合。如果一个对象只通过接口来表明依赖关系,那么这种依赖可以在对象本身毫不知情的情况下,用不同的具体实现进行替换。

对依赖进行替换的最常用方法就是在测试时使用mock实现:

import static org.mockito.Mockito.*;
import org.junit.Test;

public class BraveKnightTest{
   @Test
   public void knightShouldEmbarkOnQuest(){
   Quest mockQuest = mock(Quest.class);     //创建mock Quest
   BraveKnight knight = new BraveKnight(mockQuest);          //注入mock Quest
   knight.embarkOnQuest();
   verify(mockQuest,times(1)).embark();
   }
}
现在,BraveKnight可以接受任意一种Quest实现,那么如何把某个Quest实现传给它呢?让我们假设要实现一个屠龙的Quest:

import java.io.PrintStream;
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实现了Quest接口,则可以注入到BraveKnight中去了。但是这里存在一个问题,如何将SlayDragonQuest交给BraveKnight又如何将PrintStream交给SlayDragonQuest呢?以上所有的行为,都是在创建应用组件之间的协作,这样的行为通常称为装配(wiring),Spring有多种装配bean的方式,很常见的方式是采用XML,下面我们来看看一如何将BraveKnight、SlayDragonQuest和PrintStream装配到一起:

<?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="BraveKnight">
<constructor-arg ref="quest"/>              <!--注入Quest bean-->
</bean>
<bean id="quest" class="SlayDragonQuest">      <!--创建SlayDragonQuest-->
<constructor-arg value="#(T(System).out)"/>
</bean>
</beans>
这是一个简单的Spring配置文件:knights.xml,其中BraveKnight和SlayDragonQuest被声明为Spring中的bean,就BraveK来讲,它在构造时传入了对SlayDragonQuest的引用,将其作为构造器参数,SlayDragonQuest的处理也是这样,将System.out传入到了SlayDragonQuest的构造器中。当然,Spring还支持使用Java来描述配置:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.springinaction.knights.BraveKnight;
import com.springinaction.knights.knight;
import com.springinaction.knights.Quest;
import com.springinaction.knights.SlayDragonQuest;

@Configuration
public knight knight(){
   return new BraveKnight(quest());
}

@Bean
public Quest quest(){
   return new SlayDragonQuest(System.out);
}
我们可以看到,尽管BraveKnight依赖于Quest,SlayDragonQuest依赖于PrintStream,但编码过程中,它们并不需要详细了解所依赖的类是什么样子的,只有Spring通过其配置,能够了解这些组成部分如何装配起来。对于Spring如何加载这些配置,我们在后面的篇章中再详细介绍。

二、认识Spring的核心——AOP

上面我们介绍了能够让组件保持松耦合的DI,下面我们来学习AOP,它允许把遍布应用各处的功能分离出来从而形成可以重用的组件。什么意思,我们的应用程序中的各式各样的组件,除了负责自己的核心功能外,常常也会承担着额外的内容(例如日志、安全等内容),但是这些内容又常常不仅仅牵涉到一个模块,它的改动可能导致大量其它核心模块的修改,但是这些核心模块的主要功能并非这些额外的内容,因此会导致代码的复杂和混乱。


而AOP的概念允许这些服务模块化,并以声明的方式将它们应用到它们影响的组件中去,可以把AOP想象为一个覆盖相同功能的外壳,这些功能层以声明的方式存在于应用中,而你的核心模块甚至无需知道这些功能层的存在,这样的理念,安全便捷地将繁琐的服务与核心业务逻辑相分离开来。

我们重新回到Knight的例子中来,我们为其添加一个切面。首先,我们假设有这样一个吟游诗人在传颂骑士的故事,因此我们需要一个吟游诗人这样的服务类来记载Knight的事迹:

import java.io.PrintStream;

public class Minstrel{
   private PrintStream stream;
   public Minstrel(PrintStream stream){
      this.stream = stream;
   }
   public void singBeforeQuest(){
      stream.println("The knightis so brave!");
   }
   public void singAfterQuest(){
      stream.println("The brave knight did embark on a quest!");
   }
}
下面我们来尝试让Minstrel和Knight互动起来,让我们对Knight的代码稍作修改:

public class BraveKnight implements Knight{

   private Quest quest;
   private Minstrel minstrel;

   public BraveKnight(Quest quest,Minstrel minstrel){                //Quest、Minstrel被注入进来
      this.quest = quest;
      this.minstrel = minstrel;
   }

   public void embarkOnQuest(){
      minstrel.singBeforeQuest();
      quest.embark();
      minstrel.singAfterQuest();
   }
}
那么问题来了,Knight是应该管理Minstrel的吗?管理吟游诗人好像并非是骑士的职责,是不是有骑士无需吟游诗人呢,那是不是要对Minstrel赋null或者进行逻辑校验呢?事情开始变得复杂,代码可能变得混乱,因为我们看到知道Minstrel并非是Knight的核心功能,那么当吟游诗人的歌颂方式改变了,我们就可能需要进入到Knight去修改代码。所以我们利用AOP,将Minstrel抽象为一个切面,声明吟游诗人必须歌颂骑士的事迹,但是骑士本身无需访问Minstrel;我们同样可以通过.xml配置中声明这些切面,具体内容我们后面再做详细介绍。

通过本篇,我们初步探索了DI和AOP两大特性,在后面的篇幅中我们会更加深入详细地探讨这两个核心特性。

猜你喜欢

转载自blog.csdn.net/cambridgewoo/article/details/59481335