必会技能之Spring篇

spring是一个开源开发框架,用于简化java开发。

Spring中的组件称为bean,可以是任何形式的普通简单Java对象(Plain Ordinary Java Object,POJO)。

Spring框架的组成结构图如下所示:

Spring的核心机制

管理Bean

程序主要是通过Spring容器来访问容器中的Bean,ApplicationContext是Spring容器最常用的接口,该接口有如下两个实现类:

ClassPathXmlApplicationContext: 从类加载路径下搜索配置文件,并根据配置文件来创建Spring容器。

FileSystemXmlApplicationContext: 从文件系统的相对路径或绝对路径下去搜索配置文件,并根据配置文件来创建Spring容器。

依赖注入

Spring框架的核心功能有两个:

Spring容器作为超级大工厂,负责创建、管理所有的Java对象,这些Java对象被称为Bean。

Spring容器管理容器中Bean之间的依赖关系,Spring使用一种被称为"依赖注入"的方式来管理Bean之间的依赖关系。

使用依赖注入,不仅可以为Bean注入普通的属性值,还可以注入其他Bean的引用。依赖注入是一种优秀的解耦方式,其可以让Bean以配置文件组织在一起,而不是以硬编码的方式耦合在一起。

理解依赖注入

控制反转(Inverse of Control,IoC)又称为依赖注入(Dependency Injection),因此不管是依赖注入,还是控制反转,其含义完全相同。当某个Java对象(调用者)需要调用另一个Java对象(被依赖对象)的方法时,在传统模式下通常有两种做法:

原始做法: 调用者主动创建被依赖对象,然后再调用被依赖对象的方法。

简单工厂模式: 调用者先找到被依赖对象的工厂,然后主动通过工厂去获取被依赖对象,最后再调用被依赖对象的方法。

注意上面的主动二字,这必然会导致调用者与被依赖对象实现类的硬编码耦合,非常不利于项目升级的维护。使用Spring框架之后,调用者无需主动获取被依赖对象,调用者只要被动接受Spring容器为调用者的成员变量赋值即可,由此可见,使用Spring后,调用者获取被依赖对象的方式由原来的主动获取,变成了被动接受——所以称之为控制反转。

另外从Spring容器的角度来看,Spring容器负责将被依赖对象赋值给调用者的成员变量——相当于为调用者注入它依赖的实例,因此又称之为依赖注入。

设值注入

设值注入是指IoC容器通过成员变量的setter方法来注入被依赖对象。这种注入方式简单、直观,因而在Spring的依赖注入里大量使用。

构造注入

利用构造器来设置依赖关系的方式,被称为构造注入。通俗来说,就是驱动Spring在底层以反射方式执行带指定参数的构造器,当执行带参数的构造器时,就可利用构造器参数对成员变量执行初始化——这就是构造注入的本质。

两种注入方式的对比

设值注入有如下优点:

与传统的JavaBean的写法更相似,程序开发人员更容易理解、接受。通过setter方法设定依赖关系显得更加直观、自然。

对于复杂的依赖关系,如果采用构造注入,会导致构造器过于臃肿,难以阅读。Spring在创建Bean实例时,需要同时实例化其依赖的全部实例,因而导致性能下降。而使用设值注入,则能避免这些问题。

尤其在某些成员变量可选的情况下,多参数的构造器更加笨重。

构造注入优势如下:

构造注入可以在构造器中决定依赖关系的注入顺序,优先依赖的优先注入。

对于依赖关系无需变化的Bean,构造注入更有用处。因为没有setter方法,所有的依赖关系全部在构造器内设定,无须担心后续的代码对依赖关系产生破坏。

依赖关系只能在构造器中设定,则只有组件的创建者才能改变组件的依赖关系,对组件的调用者而言,组件内部的依赖关系完全透明,更符合高内聚的原则。

注意:

建议采用设值注入为主,构造注入为辅的注入策略。对于依赖关系无须变化的注入,尽量采用构造注入;而其他依赖关系的注入,则考虑采用设值注入。

Spring容器中的Bean

对于开发者来说,开发者使用Spring框架主要是做两件事:①开发Bean;②配置Bean。对于Spring框架来说,它要做的就是根据配置文件来创建Bean实例,并调用Bean实例的方法完成"依赖注入"——这就是所谓IoC的本质。

容器中Bean的作用域

当通过Spring容器创建一个Bean实例时,不仅可以完成Bean实例的实例化,还可以为Bean指定特定的作用域。Spring支持如下五种作用域:

singleton: 单例模式,在整个Spring IoC容器中,singleton作用域的Bean将只生成一个实例。

prototype: 每次通过容器的getBean()方法获取prototype作用域的Bean时,都将产生一个新的Bean实例。

request: 对于一次HTTP请求,request作用域的Bean将只生成一个实例,这意味着,在同一次HTTP请求内,程序每次请求该Bean,得到的总是同一个实例。只有在Web应用中使用Spring时,该作用域才真正有效。

对于一次HTTP会话,session作用域的Bean将只生成一个实例,这意味着,在同一次HTTP会话内,程序每次请求该Bean,得到的总是同一个实例。只有在Web应用中使用Spring时,该作用域才真正有效。

global session: 每个全局的HTTP Session对应一个Bean实例。在典型的情况下,仅在使用portlet context的时候有效,同样只在Web应用中有效。

如果不指定Bean的作用域,Spring默认使用singleton作用域。prototype作用域的Bean的创建、销毁代价比较大。而singleton作用域的Bean实例一旦创建成果,就可以重复使用。因此,应该尽量避免将Bean设置成prototype作用域。

使用自动装配注入合作者Bean

Spring能自动装配Bean与Bean之间的依赖关系,即无须使用ref显式指定依赖Bean,而是由Spring容器检查XML配置文件内容,根据某种规则,为调用者Bean注入被依赖的Bean。

Spring自动装配可通过元素的default-autowire属性指定,该属性对配置文件中所有的Bean起作用;也可通过对元素的autowire属性指定,该属性只对该Bean起作用。

autowire和default-autowire可以接受如下值:

no: 不使用自动装配。Bean依赖必须通过ref元素定义。这是默认配置,在较大的部署环境中不鼓励改变这个配置,显式配置合作者能够得到更清晰的依赖关系。

byName: 根据setter方法名进行自动装配。Spring容器查找容器中全部Bean,找出其id与setter方法名去掉set前缀,并小写首字母后同名的Bean来完成注入。如果没有找到匹配的Bean实例,则Spring不会进行任何注入。

byType: 根据setter方法的形参类型来自动装配。Spring容器查找容器中的全部Bean,如果正好有一个Bean类型与setter方法的形参类型匹配,就自动注入这个Bean;如果找到多个这样的Bean,就抛出一个异常;如果没有找到这样的Bean,则什么都不会发生,setter方法不会被调用。

constructor: 与byType类似,区别是用于自动匹配构造器的参数。如果容器不能恰好找到一个与构造器参数类型匹配的Bean,则会抛出一个异常。

autodetect: Spring容器根据Bean内部结构,自行决定使用constructor或byType策略。如果找到一个默认的构造函数,那么就会应用byType策略。

创建Bean的3种方式

使用构造器创建Bean实例

使用构造器来创建Bean实例是最常见的情况,如果不采用构造注入,Spring底层会调用Bean类的无参数构造器来创建实例,因此要求该Bean类提供无参数的构造器。

采用默认的构造器创建Bean实例,Spring对Bean实例的所有属性执行默认初始化,即所有的基本类型的值初始化为0或false;所有的引用类型的值初始化为null。

使用静态工厂方法创建Bean

使用静态工厂方法创建Bean实例时,class属性也必须指定,但此时class属性并不是指定Bean实例的实现类,而是静态工厂类,Spring通过该属性知道由哪个工厂类来创建Bean实例。

除此之外,还需要使用factory-method属性来指定静态工厂方法,Spring将调用静态工厂方法返回一个Bean实例,一旦获得了指定Bean实例,Spring后面的处理步骤与采用普通方法创建Bean实例完全一样。如果静态工厂方法需要参数,则使用元素指定静态工厂方法的参数。

调用实例工厂方法创建Bean

实例工厂方法与静态工厂方法只有一个不同:调用静态工厂方法只需使用工厂类即可,而调用实例工厂方法则需要工厂实例。使用实例工厂方法时,配置Bean实例的元素无须class属性,配置实例工厂方法使用factory-bean指定工厂实例。

采用实例工厂方法创建Bean的元素时需要指定如下两个属性:

factory-bean: 该属性的值为工厂Bean的id。

factory-method: 该属性指定实例工厂的工厂方法。

若调用实例工厂方法时需要传入参数,则使用元素确定参数值。

协调作用域不同步的Bean

当singleton作用域的Bean依赖于prototype作用域的Bean时,会产生不同步的现象,原因是因为当Spring容器初始化时,容器会预初始化容器中所有的singleton Bean,由于singleton Bean依赖于prototype Bean,因此Spring在初始化singleton Bean之前,会先创建prototypeBean——然后才创建singleton Bean,接下里将prototype Bean注入singleton Bean。

解决不同步的方法有两种:

放弃依赖注入: singleton作用域的Bean每次需要prototype作用域的Bean时,主动向容器请求新的Bean实例,即可保证每次注入的prototype Bean实例都是最新的实例。

利用方法注入: 方法注入通常使用lookup方法注入,使用lookup方法注入可以让Spring容器重写容器中Bean的抽象或具体方法,返回查找容器中其他Bean的结果,被查找的Bean通常是一个non-singleton Bean。Spring通过使用JDK动态代理或cglib库修改客户端的二进制码,从而实现上述要求。

建议采用第二种方法,使用方法注入。为了使用lookup方法注入,大致需要如下两步:

将调用者Bean的实现类定义为抽象类,并定义一个抽象方法来获取被依赖的Bean。

在元素中添加子元素让Spring为调用者Bean的实现类实现指定的抽象方法。

两种后处理器

Spring提供了两种常用的后处理器:

Bean后处理器: 这种后处理器会对容器中Bean进行后处理,对Bean进行额外加强。

容器后处理器: 这种后处理器会对IoC容器进行后处理,用于增强容器功能。

Spring的"零配置"支持

搜索Bean类

Spring提供如下几个Annotation来标注Spring Bean:

@Component: 标注一个普通的Spring Bean类

@Controller: 标注一个控制器组件类

@Service: 标注一个业务逻辑组件类

@Repository: 标注一个DAO组件类

在Spring配置文件中做如下配置,指定自动扫描的包:

Spring的AOP

使用AspectJ实现AOP

AspectJ是一个基于Java语言的AOP框架,提供了强大的AOP功能,其他很多AOP框架都借鉴或采纳其中的一些思想。其主要包括两个部分:一个部分定义了如何表达、定义AOP编程中的语法规范,通过这套语法规范,可以方便地用AOP来解决Java语言中存在的交叉关注点的问题;另一个部分是工具部分,包括编译、调试工具等。

AOP实现可分为两类:

静态AOP实现: AOP框架在编译阶段对程序进行修改,即实现对目标类的增强,生成静态的AOP代理类,以AspectJ为代表。

动态AOP实现: AOP框架在运行阶段动态生成AOP代理,以实现对目标对象的增强,以Spring AOP为代表。

一般来说,静态AOP实现具有较好的性能,但需要使用特殊的编译器。动态AOP实现是纯Java实现,因此无须特殊的编译器,但是通常性能略差。

AOP的基本概念

关于面向切面编程的一些术语:

切面(Aspect): 切面用于组织多个Advice,Advice放在切面中定义。

连接点(Joinpoint): 程序执行过程中明确的点,如方法的调用,或者异常的抛出。在Spring AOP中,连接点总是方法的调用。

增强处理(Advice): AOP框架在特定的切入点执行的增强处理。处理有"around"、"before"和"after"等类型

切入点(Pointcut): 可以插入增强处理的连接点。简而言之,当某个连接点满足指定要求时,该连接点将被添加增强处理,该连接点也就变成了切入点。

依赖注入(Dependency Injection,DI)

Spring框架重要的思想核心是 ,在代码编写时使用接口定义类和其他对象的依赖关系,而非直接引用类的具体对象,在运行时通过配置,将引用的接口的具体实现类注入其中。

这里通过一个例子解释什么叫代码间的耦合,以及如何通过依赖注入DI降低代码间的耦合度。

下面一个例子:我们需要创建一个类,它叫Worker,维护对象需要完成的工作(work),需要完成工作(执行doWork)。工作的类型有设计(Design),编码(Coding),测试(Test),由于直接引用类的对象,它们的完成实现是各自定义的。这些工作定义如下:

public class Design(){

    public String doDesign(){

return "Doing work design";

    }

}

public class Coding(){

    public String doCoding(){

return "Doing work coding";

    }

}

public class Test(){

    public String doTest(){

return "Doing work test";

    }

}

通过直接引用对象来编写Worker类,代码如下:

//通过new创建工作对象的Worker

public class Worker(){

    Design design = new Design();

    public doWork(){

  System.out.println(design.doDesign());

    }

}

//运行

public class WorkMain{

public static void main(String[] args) throws Exception{

        Worker worker = new Worker();

        worker.doWork();

        //显然不更改代码它一次只能完成一种工作,有点蠢

    }

}

上述代码有个明显的问题:如果下一次Worker要去做Coding工作,则必须把引用的类,以及doWork方法全部改写。这大大增加了代码维护量。

DI的思路是,定义接口,编写代码时引用接口,而非具体的实现类,在运行中将具体的实现注入其中。

刚才所述三种工作,我们可以抽象出一个统一的接口IWork,定义统一的完成工作方法doWork

//接口定义

public interface IWork{

    //定义统一的接口方法

    public String doWork();

}

三种对象改为接口的实现。各自提供的完成工作方法统一为接口定义的方法的实现。

public class Design() implements IWork{

  //@Override,重写接口方法注释

  @Override

  public String doWork(){

return "Doing work design";

    }

}

public class Coding() implements IWork{

    @Override

    public String doWork(){

return "Doing work coding";

    }

}

public class Test() implements IWork{

    @Override

    public String doWork(){

return "Doing work test";

    }

}

引用这些类的Worker,改为引用接口,不明确定义该类的实现。

public class Worker(){

    //引用接口定义,而非具体的类。

    private IWork work;

    //定义设置方法,提供注入入口

    public void setWork(IWork work){

      this.work = work;

    }

    public doWork(){

        //直接调用接口的方法

  System.out.println(work.doWork());

    }

}

Work类中IWork接口的对象,在代码运行时通过注入的方式定义。

//运行时注入实现

public class WorkMain{

public static void main(String[] args) throws Exception{

        Worker worker = new Worker();

        //在运行时候绑定实现的具体类,实际开发过程中,这些由Bean容器进行创建和托管

        woker.setWork(new Design());

        //完成design的工作

        worker.doWork();

        woker.setWork(new Coding());

        //完成coding的工作

        worker.doWork();

        woker.setWork(new Test());

        //完成test的工作

        worker.doWork();

    }

}

上面的例子是我们自己来注入对应的Bean。在Spring中框架中,提供了创建、维护,使用这些Bean的方法,我们可以使用Spring来配置运行时需要的类。下一节将说明如何在Spring中配置Bean。

Bean容器(container)管理及应用上下文(context),以及装配和使用

Spring框架中,Bean组件通过Spring容器进行管理。它管理所有用到依赖注入的组件。

容器中Bean的创建有两类创建方式:工厂模式创建,上下文创建。

工厂模式创建通过Baen工厂BeanFactory接口的实现进行创建。

在实际项目中,一般通过xml的形式注册bean。对于项目工程而言,bean的定义为applicationContext-*.xml,*为自定义字符。

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-2.0.xsd">

上下文模式使用ApplicationContext获取。

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

推荐阅读更多精彩内容